하스켈 나라 탐험기 (2)

요즘은 다른 언어에서도 원래 하스켈에서만 볼 수 있던 기능을 종종 발견할 수 있습니다. 하지만 타 언어는 하스켈과는 다른 목표를 염두에 두고 설계된 만큼 그런 언어에 도입된 하스켈의 기능은 원래 언어와 뭔가 따로 노는 느낌을 줍니다. 반면 하스켈은 함수형 프로그래밍 요소를 최우선적으로 고려합니다. 저는 하스켈을 특별하게 만들어주는 것은 그 기능보다는 함수형 프로그래밍에 순수하게 집중하는 점이라고 봅니다. 그래서 하스켈과 타 언어에서 이론적으로는 동일한 기능을 제공한다고 해도, 해당 기능을 사용하는 경험은 완전히 다르게 느껴집니다. 제가 다음과 같은 것을 배울 수 있었던 것은 아마 하스켈만이 제공할 수 있는 특수한 환경 덕분이었으리라고 생각합니다.

객체 클래스는 타입이 아니다

저는 루비를 사용해서 소프트웨어 개발을 배웠습니다. 루비에서는 모든 것이 객체죠. 정수는 Integer 클래스의 인스턴스고, 문자열은 String 클래스의 인스턴스입니다. 그리고 저는 이런 관점을 마음 깊이 받아들였습니다. 하지만 그런 접근법의 부작용도 있었는데, 저는 객체의 클래스와 객체의 타입이 동일한 개념이라고 잘못 이해하게 되었습니다. 사실 루비에서는 두 개념 사이에 실질적인 구분이 없거든요.

하스켈 덕분에 그 오해를 완전히 풀 수 있었습니다. 이제는 객체라는 것이 특별한 종류의 데이터 타입이라는 것을 알게 되었습니다. 데이터 타입 수준의 추상화가 아니라 객체 수준의 추상화를 기본 단위로 삼고 동작하는 스몰토크 스타일 언어인 루비가 독특했던 것이란 것도 알게 되었습니다.

자바를 잠깐 다뤘을 때 경험으로 저는 원시 데이터 타입은 제대로 된 객체 타입보다 기능적으로 뒤떨어진다는 오해를 하고 있었습니다. 그러나 하스켈을 배우면서 ‘원시적’ 데이터 타입을 바탕으로도 대수적 데이터 타입(algebraic data type)이나 타입 클래스(typeclass) 같은 강력한 추상화를 만들어낼 수 있다는 것을 알게 되었습니다. 덕분에 완전히 새로운 관점으로 프로그램을 바라볼 수 있게 되었습니다.

완벽주의자 컴파일러

제가 컴파일러를 처음 접한 것은 자바였는데, 정말정말 싫어했습니다. 당시 제가 보기에 컴파일러는 불필요한 코드를 추가하도록 강제하고 간단한 실수를 쓸데 없이 까칠하게 지적하는 불한당이었습니다. 스위프트 컴파일러는 훨씬 나았습니다. 타입 추론이 있어서 변수마다 타입을 지정하지 않아도 되었으니까요. 제가 루비에서 익숙해진 puts 위주의 디버깅이 아니라 다른 방식의 디버깅을 사용하도록 하기도 했지만, 그 방식의 장점도 분명히 있어서 괜찮았습니다.

하지만 하스켈 컴파일러는 완전히 다른 느낌이었는데, 제가 그 동안 사용해 본 다른 컴파일러보다도 훨씬 높은 수준의 정확성(correctness)을 컴파일 타임에 요구했습니다. 하스켈 프로그램이 타 언어로 작성된 프로그램보다 더 정확하다는 말은 아닙니다. 어차피 모든 프로그램은 정확해야만 제대로 작동하니까요. 하지만 루비나 자바스크립트 같은 인터프리티드 언어는 코드의 정확성을 대부분 런타임에 확인합니다. 자바나 스위프트 등의 컴파일되는 언어도 정확성의 상당 부분을 런타임에 확인합니다. 하스켈은 컴파일 타임에 매우 엄정한 정확성을 요구한다는 면에서 타 언어와 정말 달랐습니다.

그런 하스켈의 특성 때문에 프로그래밍에 대한 접근법을 새로 배울 수 있었습니다. 어차피 하스켈 컴파일러의 엄격한 기준을 통과해야하기 때문에 저도 코드를 짜기 전에 최대한 그 기준에 맞출 수 있도록 제가 작성하려는 프로그램에 대해서 더 꼼꼼하게 생각해보게 되었습니다. 물론 하스켈 컴파일러가 요구하는 수준의 정확도를 제가 충족할 수 있게 된 것은 아닙니다. 하지만 하스켈 언어로 계속 프로그램을 작성하면 제 추상적 멘탈 모델링 능력을 타 언어를 사용할 때보다 훨씬 더 높은 수준까지 끌어올릴 수 있겠다는 확신이 들었습니다.

제너릭 프로그래밍의 본모습

제너릭 프로그래밍을 처음 접한 것은 자바에서였는데, 사실 처음에는 잘 이해하지 못 했습니다. 그냥 엄청나게 불편하고 복잡하다는 첫인상을 받았죠. 그리고 나서 스위프트에서 다시 한 번 제너릭 프로그래밍을 접할 수 있었습니다. 그 사이에 프로그래밍에 대해서 훨씬 더 많이 배웠기 때문에 그 때쯤에는 제너릭 프로그래밍의 유용함을 알아볼 수는 있었습니다. 하지만 스위프트에서도 제너릭 프로그래밍은 언어의 중심부와 조금 따로 노는 느낌이었고 여전히 사용하기 너무 번거로웠습니다.

하스켈에서는 제너릭 프로그래밍을 파라메트릭 다형성(parametric polymorphism)이라 부르는데, 하스켈 덕분에 그 온전한 능력을 알게 되었습니다. 제 생각에 여러 언어에서 제너릭 프로그래밍의 기능적 차이는 적은 것 같습니다. 하지만 하스켈은 파라메트릭 다형성을 기반에 두고 설계된 반면, 자바와 스위프트는 기존 언어에 해당 기능을 추가했다는 차이점이 있습니다. 이 때문에 실제로 제너릭 프로그래밍을 사용하는 경험이 엄청나게 다릅니다. 하스켈 덕분에 제너릭 프로그래밍의 진면목을 보게 되어서 다행이라고 생각합니다.

게으른 사고방식

하스켈은 게으른 평가(lazy evaluation) 방식을 사용합니다. 게으른 평가는 엘릭서에서도 지원되기 때문에 어느 정도 익숙했습니다. 하지만 쓰고 싶을 때 선택적으로 게으른 평가를 사용하는 것과, 항상 그걸 사용해야만 하는 것은 완전히 다른 경험이었습니다. 엄격한 평가(strict evaluation) 방식을 사용하는 언어를 다룰 때는 프로그램을 작고 완결된 단계들로 나누어서 생각했었습니다.

하지만 하스켈 프로그램은 게으르기 때문에 그런 방식으로 생각해서는 안 됩니다. 프로그램을 작은 단계로 쪼개서 생각하는 것은 가능하지만, 각 단계를 완결된 형태로 생각할 수는 없습니다. 게으른 평가 방식으로는 제 프로그램 전체가 작동하던가 아니면 하나도 작동하지 않던가 둘 중 하나의 결과만 나왔기 때문에, 프로그램의 전체 흐름을 언제나 제 시야 속에 담아두고 있어야 했습니다. 하스켈에는 엄격한 컴파일러 뿐 아니라 이런 특성도 있었기 때문에 프로그램의 전체를 조망하는 자세를 항상 견지하도록 훈련받는 느낌이었습니다.

모나드, 모나드, 모나드

모나드는 하스켈의 독특한 추상화 중 가장 흔히 알려진 녀석입니다. 저도 모나드라는 단어만 들으면 뭔가 미지의 것에 대한 두려움이 들었죠. 그런데 모나드가 뭔지 배우고 나니 그렇게까지 두려워할 것이 아니라는 사실을 알게 되었습니다. 프로그램을 어느 정도 해보았다면 모나드라고 불리는 추상적 개념을 이미 많이 접해봤을 확률이 높습니다. 그저 그게 함수형 프로그래밍에서 모나드라고 불린다는 것만 몰랐던 거죠.

모나드에 대해서 알게 되니 오히려 객체지향 프로그래밍의 디자인 패턴이 연상되었습니다. 왜냐면 객체지향에서도 Gang of Four 책을 통해 디자인 패턴이라는 개념이 널리 퍼지기 전에도 경험 있는 개발자들은 이미 디자인 패턴을 사용해 왔거든요. 그저 해당 패턴들에 대해서 서로 다른 이름을 붙여서 사용하고 있었을 뿐이죠. 마찬가지로 여러분도 모나드라고 불리는 추상화를 이미 이해하고 있을 것입니다. 마음속에서 해당 추상화를 모나드라고 명명해서 나중에 보았을 때 인식할 수 있도록 하는 과정만 거치면 될 것입니다.

결론

이상 하스켈 나라를 잠깐 탐험한 감상이었습니다. 하스켈로 뭔가 제대로 된 프로그램을 작성한 적이 없으니 하스켈에 대한 제 첫인상도 그다지 정확하거나 깊이 있지는 않을 것입니다. 하지만 그렇게 잠깐 발만 담구었는데도 여러가지 새로운 관점을 열어주었다는 것이 정말 놀랍습니다. 앞으로도 하스켈로 프로그램을 더 작성해 보고 싶습니다.