《하스켈로 배우는 프로그래밍》(2009, 대림) 옮긴이의 말

박준규 @curry@hackers.pub
이 글은 2009년에 대림출판사에서 낸 책 《하스켈로 배우는 프로그래밍》 옮긴이의 말을 안기영 교수님의 허락을 받아 공유하는 것입니다.
옮긴이
이미 하스켈에 관심을 갖고 이 책을 선택한 분들에게 이 옮긴이의 말은 그저 지은이 머리말과 책 1장 내용의 장황한 사족에 지나지 않을 것입니다. 특히 프로그래밍을 처음 배울 목적으로 이 책을 선택하신 분들은 이 장황한 글을 건너뛰어도 좋습니다. 그럼에도 굳이 옮긴이로서 머리말을 쓰는 까닭은, 현업 IT 전문가의 입장에서 하스켈이라는 프로그래밍 언어가 어떤 의미로 다가올 수 있을지 좀 더 구구절절한 이야기를 풀어놓고 싶기 때문입니다.
하스켈은 원래 더 나은 프로그래밍 언어를 고안하려는 프로그래밍 언어 연구자들이 느긋한 계산법을 따르는 순수 함수형 언어를 표준화함으로써, 연구자들끼리 아이디어를 더욱 효과적으로 교류하려는 연구 목적으로 만들어지기 시작한 언어입니다.[1] 그래서 하스켈은 자연히 프로그래밍 언어 분야의 연구 성과를 충실하게 반영하도록 설계되었고, Hugs나 GHC와 같은 하스켈 구현에 연구자들이 꾸준히 참여하여 하스켈 98 표준 이외에도 최근의 프로그래밍 언어 연구 결과를 적용한 여러 가지 확장 기능을 추가로 제공하고 있습니다. 이러한 설계와 구현은 언어 자체의 학술적 심미성으로만 그치지 않았으며, 안목 있는 IT 전문가들의 눈에 하스켈은 빠르고 정확하게 소프트웨어를 개발할 가능성을 열어주는 프로그래밍 언어로 돋보이게 했습니다. 이런 IT 전문가들이 업무에 하스켈을 도입했고 그 과정에서 축적된 경험의 일부를 오픈 소스로 공개하여 활발한 하스켈 개발자 공동체를 만들기에 이르렀습니다. 그리하여 하스켈은 오늘날 연구자들에게 각광받는 학술적 프로그래밍 언어인 동시에 범용적인 프로그래밍을 빠르고 정확하게 작성할 수 있는 실무적 프로그래밍 언어로 발전하였습니다.
실무적인 범용 언어로서의 하스켈
하스켈은 군용 보안 소프트웨어 작성(예: Galois), 금융상품 분석(예: Credit Suisse), 하드웨어 설계(예: Bluespec), DNA 및 고분자 화합물을 이용한 신약 개발(예: Amgen) 등 다양한 업계에서 이미 많은 이윤을 창출하는 상용 소프트웨어의 핵심 기술 구현에 요긴하게 쓰이고 있습니다. 예로 든 대표적인 기업 외에도 매년 함수형 프로그래밍 국제 학회(The ACM SIGPLAN International Confrerence on Functional Programming)와 함께 열리는 함수형 프로그래밍 사용 기업(Commercial Users of Functional Programming) 모임이 최근 몇 년간 성황을 이루며 하스켈을 비롯한 함수형 언어를 업무에 성공적으로 도입한 기업들의 사례 보고가 잇따르고 있는데, 그 중 하스켈을 업무에 쓰는 기업들의 목록[2]이 하스켈 홈페이지에 정리되어 있습니다. 그리고 하스켈을 업무에 도입하려는 기업에 자문을 제공하는 컨설턴트들[3]도 생겨나고 있습니다.
하스켈 오픈 소스 소프트웨어 공동체의 활동 또한 요즘 들어 더욱 활기를 띠고 있습니다. 널리 쓰이는 스크립트 언어인 펄의 차세대 판 Perl 6 구현 Pugs[4], 패치 이론 바탕의 분산 버전 관리 시스템 Darcs[5], 그리고 하스켈 개발에 가장 많이 쓰이는 글래스고우 하스켈 컴파일러[6](Glasgow Haskell Compiler, GHC) 또한 하스켈로 개발이 진행되는 오픈 소스 프로젝트입니다. 이런 굵직한 프로젝트 외에도 각종 하스켈 라이브러리 및 응용 프로그램, 그리고 C 등 다른 언어로 작성된 기존 라이브러리와의 연동 등 요긴한 중소 규모 프로젝트가 활발히 진행되고 있습니다. 아치 리눅스는 현재 500개 이상의 하스켈 관련 배포판 패키지를 제공하고 있으며[7], 데비안이나 우분투 리눅스도 약 250개 가량의 배포판 패키지를 제공하고 있습니다. 이렇게 하스켈 오픈 소스 개발 활동이 활기를 띠는 것은 최근 몇 년간에 걸쳐 BSD의 port 패키지 관리 시스템과 유사한 하스켈 패키지 관리 시스템인 Cabal[8]이 하스켈 라이브러리와 프로그램을 배포하는 사실상의 표준으로 자리잡도록 했으며, 펄의 CPAN과 같은 Hackage[9]라는 하스켈 패키지 저장소를 중심으로 하스켈 개발자들이 만든 오픈소스 패키지들을 한데 모아 배포하는 등 하스켈 개발자 공동체의 노력이 모인 결과입니다.
참고로 이 책은 처음 프로그래밍을 배우는 분들도 읽을 수 있도록 구성된 프로그래밍 입문서이므로 하스켈을 실무에 응용하는 보기를 다루지는 않습니다. 이 책을 읽고 하스켈을 실무에 응용하는데 관심을 갖게 될 IT 전문가에게는 최근 출간된 『Real World Haskell』[10]을 이 책 다음으로 살펴볼 것을 추천합니다. 그 책은 실제로 하스켈을 업무에 쓰는 개발자들이 자신들의 경험을 정리해 놓은 책으로 데이터베이스, 웹, 시스템, GUI, 네트워크, 병렬 프로그래밍 등을 아우르는 실무적인 예제를 포함합니다.
프로그래밍 언어 연구 성과를 충실히 반영한 하스켈의 우수성
그렇다면 여러 분야에서 업계를 선도하는 기업의 IT 전문가들이 더 잘 알려진 다른 프로그래밍 언어들을 두고 왜 굳이 하스켈을 업무에 도입했을까요? 업계마다 기업마다 업무가 판이하게 다르기 때문에 정확히 어떤 이유라고 억측할 수는 없습니다. 하지만 우리가 하스켈을 써 보면서 발견한 것과 마찬가지로 그들도 하스켈을 쓰면서 다른 프로그래밍 언어와 대조되는 장점을 발견하여 하스켈에 매료되었음이 틀림없습니다. 옮긴이들의 제한된 경험으로 하스켈의 면면을 모두 비출 수는 없겠지만, 우리가 하스켈을 직접 쓰면서 그리고 하스켈을 도입하여 성공한 프로젝트들을 접하면서 알게 된 바를 간단히 정리해 보았습니다.
효율성 높은 컴파일 언어이면서도 스크립트 언어처럼 잽싼 하스켈
하스켈은 스크립트 언어의 장점과 컴파일 언어의 장점을 동시에 갖는 언어입니다. 스크립트 언어는 개발을 신속하게 진행할 수 있다는 장점을 내세우고 있습니다. 이는 스크립트 언어가 대개 고급 언어를 직접 실행하는 해석기(Interpreter)를 바탕으로 하기 때문인데, 프로그램을 수정한 후 바로 해석기에 불러들여 그 수정한 부분만 따로 검사하기에 매우 편리합니다. 하지만 해석기를 바탕으로 한 언어는 번역기(Compiler)로 미리 저급 언어로 번역한 다음 실행하는 컴파일 언어보다 효율이 확연히 떨어지기 때문에 반복적인 수치연산 등 계산량이 많은 코드를 직접 작성하기에 알맞지 않다는 단점이 있습니다. 반면 컴파일 언어는 스크립트 해석기로 돌리는 것보다 훨씬 효율이 뛰어난 실행 파일을 생성할 수 있다는 것이 장점입니다. 그러나 C와 같은 컴파일 언어는 작은 부분을 고치더라도 전체 프로그램을 실행 파일로 다시 연결한 다음 돌려보아야 하며, 프로그램 시작 지점부터 항상 실행을 시작하기 때문에 수정한 부분만 따로 떼어 검사하기 번거롭다는 단점이 있습니다. 하스켈과 같은 함수형 언어의 경우, 프로그래밍 언어 연구자들이 이미 수십 년 전부터 확립한 해석기와 번역기의 경계를 넘나드는 기술을 구현하고 있었습니다. 그렇기 때문에 마치 스크립트 언어처럼 신속한 개발도 가능하고 저급 언어로 번역된 효율성 높은 실행 파일도 얻을 수 있습니다. 최근에는 고급 언어와 그를 번역한 저급 언어를 연동하는 기술이 함수형 언어 구현 뿐 아니라 가상머신 기반의 언어나 스크립트 언어가 가지는 효율상의 한계를 극복하기 위한 JIT 번역(Just-In-Time compile) 등에도 응용되고 있습니다.
많은 설명보다 간단한 보기로 대표적인 스크립트 언어인 파이썬과 하스켈을 비교해 보면 하스켈 역시 신속한 개발을 지원하는 언어라는 사실이 와닿을 것입니다. 예컨대, 파이썬 스크립트 test.py
에 제곱을 구하는 함수를 아래와 같이 잘못 정의한 다음
def square(x): return x + x
해석기에서 불러들여 함수를 시험해 보면 잘못된 결과를 얻습니다.
>>> import test
>>> test.square(3)
6
이제 test.py
의 square
함수 정의를 다음과 같이 바르게 고친 다음
def square(x): return x * x
해석기에서 다시 불러들이면 바뀐 함수 정의대로 올바른 결과를 얻습니다.
>>> reload(test)
<module 'test' from 'test.py'>
>>> test.square(3)
9
이렇듯 신속하게 오류를 수정하고 스크립트를 다시 불러들여 바로 시험해 볼 수 있다는 장점이야말로 스크립트 언어가 각광받게 된 가장 큰 이유라고 생각합니다. 하스켈과 같은 함수형 언어의 대화식 환경[11]에서도 이와 마찬가지로 개발을 신속하게 진행할 수 있습니다. 하스켈 스크립트 Main.hs
에 제곱을 구하는 함수를 다음과 같이 잘못 정의한 다음
square x = x + x
GHC의 대화식 환경인 ghci에서 불러들여 함수를 시험해 보면 잘못된 결과를 얻습니다.
Prelude> :load Main
Main> square 3
6
이제 Main.hs
의 square
함수 정의를 다음과 같이 바르게 고친 다음
square x = x * x
다시 불러들이면 바뀐 함수 정의대로 올바른 결과를 얻습니다.
Main> :reload
Main> square 3
9
하지만 GHC에는 대화식 환경 뿐만 아니라 ghc 번역기가 있으므로 완전히 기계어로 번역된 실행 파일을 얻을 수도 있다는 것이 일반적인 스크립트 언어와 차별화되는 점입니다. 다음과 같이 Main.hs
에 프로그램 시작을 나타내는 간단한 main
함수를 추가한 후
square x = x * x
main = print (square 3)
GHC 번역기로 실행 파일을 만들어 돌려볼 수 있습니다.
$ ghc Main.hs
$ ./a.out
9
안전한 정적 타입 언어이면서도 동적 타입 언어처럼 유연한 하스켈
하스켈은 안전한 정적 타입 시스템을 바탕으로 하고 있으면서도 동적 타입 언어에 가까운 유연성을 갖는 언어입니다. 그 이유를 전문 용어를 동원해 설명하자면 하스켈은 Hindley-Milner 타입 시스템을 바탕으로 할 뿐 아니라, 타입 클래스라는 독특한 기능을 갖고 있기 때문이라 할 수 있습니다. Hindley-Milner 타입 시스템을 바탕으로 하는 언어는 타입 유추(Type Inference)를 바탕으로 인자 여러모양새(Parametric Polymorphism)를 자연스럽게 구사함으로써 동적 타입 언어처럼 유연하게 포괄적인 코드 작성이 가능합니다. 타입 클래스는 여러 의미(Overloaded) 함수를 Hindley-Milner 타입 시스템과 잘 어우러지면서도 깔끔하게 정의하기 위해 하스켈에서 도입한 독특한 기능으로, 물건 중심 언어의 인터페이스(Interface)와 비슷하지만 그보다 더 유연하고 확장성이 있습니다.
여러모양새(Polymorphism)란 정적 타입 언어에서 타입에 따른 코드 중복을 줄이고자 함수를 한 번만 정의하여 여러 모양으로 쓸 수 있도록 하는 기능을 일컫는데, 그 방법에 따라 여러 종류가 있습니다. 함수형 언어에서는 대개 인자 여러모양새를 구사하며 물건 중심(Object Oriented) 언어에서는 대개 하위 타입 여러모양새(Subtype Polymorphism)를 구사합니다. 대표적인 물건 중심 언어인 자바(Java)에서 아래와 같이 입력 스트림으로부터 내용을 읽어들이는 getContents
[12] 함수를 한 번만 정의하면
String readContents(InputStream is) { /* ... */ }
인자 타입으로 선언한 InputStream
타입의 물건(Object)인 System.in
은 물론 그 하위 타입인 FileInputStream
이나 DataInputStream
타입의 물건인 fis
나 dis
에도 적용할 수 있는데
FileInputStream fis;
DataInputStream dis;
// ...
String s1 = readContents(System.in); // InputStream
String s2 = readContents(fis); // FileInputStream
String s3 = readContents(dis); // DataInputStream
이러한 기능을 바로 하위 타입 여러모양새라고 합니다. 한편, 현대적인 타입 시스템을 갖춘 함수형 언어인 하스켈에서는 아래와 같이 length
함수를 한 번만 정의하면
length [] = 0
length (x:xs) = 1 + length xs
정수, 글자 등 아무 타입 원소를 갖는 리스트에 모두 적용할 수 있는데
Main> length [2,3,5,7] -- [Int] (정수 리스트)
4
Main> length ['a','b','c'] -- [Char] (글자 리스트)
3
Main> length [[],[0]] -- [[Int]] (정수 리스트의 리스트)
2
이러한 기능을 바로 인자 여러모양새라고 합니다.
과거에 인자 여러모양새를 직접 구현할 수 없었던 정적 타입 물건 중심 언어들이 다른 방법으로 에둘러 인자 여러모양새를 시늉내느라 큰 불편을 겪다 결국 포괄적 프로그래밍(Generic Programming)을 지원하는 언어 기능을 덧붙일 수밖에 없었습니다. 초기 C++의 경우, 인자 여러모양새를 흉내내기 위해 C에서처럼 조악한 전처리 매크로나 타입 안정성이 전혀 보장되지 않는 void *
를 거치는 임의적 형변환 방식에 의존해야 했습니다. 이후 C++ 표준 라이브러리를 제대로 설계하면서 템플릿을 언어에 추가하여 템플릿 메타프로그래밍으로 인자 여러모양새를 직접 표현할 수 있게 됩니다. Java의 경우 모든 물건들의 최상위 타입인 Object
에 대한 하위 타입 여러모양새를 이용하되 필요에 따라 동적으로 Object
타입으로부터 원래의 상위 타입으로 강제 변환을 하는 방식을 쓸 수 밖에 없었습니다. C의 void *
보다야 낫지만 동적인 하위 타입으로의 강제 변환 역시 타입 안정성을 보장하지 못하므로 정적 타입 언어의 장점을 거스릅니다. 결국 Java도 뒤늦게 제너릭을 추가면서[13] 타입을 검사하고 나중에 지우는 방식으로 인자 여러모양새를 직접 표현할 수 있게 됩니다. 이러한 역사적 사실을 종합해 보면 C++ 템플릿이나 Java 제너릭과 같은 포괄적 프로그래밍의 유래가 바로 탄탄한 이론을 바탕으로 설계된 정적 타입 함수형 언어에서 일상적으로 사용하던 인자 여러모양새라는 것을 알 수 있습니다.
함수형 언어의 인자 여러모양새와 오브젝트 중심 언어의 포괄적 프로그래밍이 다른 점은 함수형 언어에서는 인자 여러모양새가 걸음마 프로그래머도 구사하는 기본 초식인 반면 오브젝트 중심 언어에서는 포괄적 프로그래밍을 상대적으로 많은 노력을 요하는 고급 기술로 여긴다는 것입니다.
오브젝트 중심 언어에서 포괄적 함수나 클래스를 작성하려면 보통 함수나 클래스를 작성할 때와 달리 포괄적 인자를 별도로 표시하는 등의 노력이 추가로 필요합니다. 그렇기에 라이브러리 설계자라면 C++나 Java와 같은 언어로는 여유를 두고 포괄적 프로그래밍을 잘 지원하는 라이브러리를 만들 수 있을 것입니다. 하지만 상대적으로 신속한 개발을 요하는 응용 프로그램 개발자라면 이미 만들어진 포괄적 라이브러리를 즐겨 쓸지는 몰라도 포괄적 프로그래밍의 장점을 살리는 코드 작성을 선뜻 내켜하지 않을 가능성이 높습니다. C++나 Java를 다루는 책들도 대개 포괄적 프로그래밍을 고급 기법으로 분류하곤 합니다. 정적 타입 오브젝트 중심 언어에만 익숙했던 개발자들이 파이썬과 같은 동적 타입 스크립트 언어에 열광한 또 하나의 이유가 바로 이것이라 생각합니다. 동적 타입 스크립트 언어는 타입을 무시하고 상대적으로 적은 노력으로 포괄적 프로그래밍을 할 수 있습니다. 그래서 이런 스크립트 언어가 유행한 후 동적 타입 언어만이 유연하며 정적 타입 언어는 유연하지 않다는 생각이 퍼지게 되었습니다.
그러나 현대적 타입 시스템을 갖춘 함수형 언어를 접해 보았다면 정적 타입 언어가 유연할 수 없다는 것은 섣부른 편견이 아닌가 의문을 제기할 수밖에 없을 것입니다. 왜냐하면 앞서 살펴본 length
함수처럼 하스켈에서는 타입 정보를 전혀 표시하지 않고도 여러모양 함수를 작성할 수 있으며, 타입 시스템이 자동으로 그 타입을 유추해 주기 때문입니다. 함수형 언어를 전혀 접해 보지 않은 개발자가 length
의 정의를 보면 하스켈을 동적 타입 스크립트 언어로 오해할 정도입니다. 이렇듯 하스켈로 프로그래밍을 배우기 시작하면 인자 여러모양새를 자신도 모르게 구사하며 유연한 포괄적 프로그래밍을 하게 된다는 것을 이 책을 통해 발견하게 될 것입니다. 그리고 비록 당장의 업무에 하스켈을 도입하지 않더라도 하스켈을 익히면서 오브젝트 중심 언어에서 고급 기법으로 여기는 포괄적 프로그래밍을 자신있게 구사할 내공을 쌓는 재미 또한 쏠쏠할 것입니다.
타입 클래스(Type Class)란 말 그대로 타입(Type)의 분류(Class)로써 덧셈이나 곱셈과 같이 모든 타입에 다 적용 가능한 여러모양 함수는 아니지만 공통점이 있는 많은 타입에 적용 가능한 함수의 타입을 딱 떨어지게 정의하기 위해 하스켈에서 도입한 독특한 기능입니다. 오브젝트 중심 언어의 인터페이스와 유사한 개념이지만 그보다 더 확장성이 있다는 점에서 뛰어나다는 것을 하스켈의 타입 클래스와 자바의 인터페이스를 비교함으로써 알아보기로 합시다. 하스켈의 타입 클래스와 Java의 인터페이스를 비교하기 전에 한 가지 짚고 넘어갈 것은, 오브젝트 중심 언어에서 클래스라 불리는 기능은 같은 이름이지만 완전히 다른 개념이라는 것입니다. Java와 같은 오브젝트 중심 언어에서는 클래스 정의가 곧 하나의 타입에 대응되지만 하스켈에서 타입 클래스는 타입을 모아 놓은 집합을 대표한다고 생각하면 이해하기 좋습니다.
하스켈의 타입 클래스는 오브젝트 중심 언어에서 주로 쓰는 인터페이스라는 기능과 비슷합니다. 예를 들면 Java에는 java.lang.Comparable<T>
라는 인터페이스가 있는데 이것이 바로 하스켈의 Ord
클래스에 해당한다고 볼 수 있습니다. 예컨대, Java에서는 MyType
이라는 새로운 타입을 정의하면서 다음과 같이 인터페이스를 상속받아 Compare
메서드를 구현합니다.
// Java 인터페이스 정의
interface Comparable<T> {
int compareTo(T x);
// ...
}
// 타입을 정의하며 인터페이스 상속
public class MyType implements Comparable<T> {
public int compareTo(MyType x) {
// ... 메서드 구현 ...
}
// ...
}
하스켈도 이와 비슷하게 새로 정의한 MyType
타입을 Ord
클래스의 인스턴스로 선언하고 메서드를 구현합니다.
-- 하스켈 타입 클래스 정의
class Ord a where
compare :: a -> a -> Ordering
(<), (<=), (>=), (>) :: a -> a -> Bool
max, min :: a -> a -> a
-- 타입 정의
data MyType = -- ...
-- 타입 정의와는 별도로 인스턴스 선언
instance Ord MyType where
compare x y = -- ... 메서드 구현 ...
여기서 눈여겨 볼 점은 Java 인터페이스와 하스켈 타입 클래스의 차이점입니다. Java에서는 인터페이스 상속이 타입 정의의 일부이지만 하스켈에서는 타입을 정의한 다음 별도로 인스턴스 선언을 합니다. 작은 차이 같지만 타입 정의에 종속된 Java 인터페이스에 비해 타입 정의로부터 자유로운 하스켈 타입 클래스는 월등한 확장성을 갖습니다. 하스켈 타입 클래스는 타입을 정의한 후 언제라도 인스턴스 선언을 추가할 수 있습니다. 다른 모듈에 타입이 정의되어 있고 심지어 그 소스를 수정할 수 없는 라이브러리에 정의된 타입이라도 클래스 인스턴스로 선언하는 데는 아무 문제가 없습니다. 아직 하스켈에서 타입 정의와 타입 클래스가 느슨한 결합을 함으로써 갖는 장점이 실무에서 어떤 의미가 있을까 갸우뚱하는 분들을 위해 좀 더 피부에 와닿는 구체적인 상황을 상상해 보겠습니다.
OO기업 홍보부 지원 담당 데이터베이스 프로그래머 김대리는 Java에 능숙합니다. 홍보부에서 데이터베이스 검색은 웹 인터페이스로 자동화되어 있지만 검색한 고객 정보로 통계를 내기 위해 검색 결과를 매번 스프레드시트로 일일이 옮겨적어야 하는 애로사항이 있었습니다. 김대리는 이를 자동화하는 프로젝트를 Java로 시작하여
Xmlizable
이라는 인터페이스를 정의하고 데이터베이스 엔트리 등 각종 데이터 구조를Xmlizable
인터페이스를 상속받아 엑셀에서 읽어들일 수 있는 XML로 변환하도록 깔끔하게 처리했습니다. 이후 추가되는 테이블 엔트리도Xmlizable
인터페이스를 상속해 XML 변환 메서드만 정의하면 되는 확장성 있는 시스템을 구축했다는 생각에 김대리는 행복합니다. 이 일로 김대리는 홍보부에서 신임을 받게 되었고 부장님은 더 정확한 통계 분석을 위해 이전 텍스트 기반 시스템에 남아있는 데이터도 스프레드시트로 불러올 수 있게 통합하는 프로젝트를 김대리에게 맡깁니다. 이전 시스템을 관리하던 개발자는 한참 전에 그만뒀지만 이전 시스템도 Java로 작성되어 있고 소스도 남아있다니 다행이었습니다. 업무가 갑자기 늘어나긴 했지만 김대리에게 큰 문제는 아닙니다. 기존 시스템에서 사용하던 소스코드에서 필요한 타입들이Xmlizable
인터페이스를 상속하도록 고쳐 XML 변환 메소드만 구현해 주면 되므로 늘 하던 작업과 별다를 것이 없었습니다.문제는 OO기업이 XX기업을 인수합병하면서부터 시작됩니다. XX기업 홍보부에서 쓰던 시스템을 통합하는 일이 김대리에게 떨어졌고 부장님은 당연히 김대리가 이번에도 금방 처리해 주겠거니 기대합니다. 김대리도 XX기업 역시 Java를 썼다는 보고에 따라 예전처럼 일정을 잡습니다. 그런데 XX기업의 홍보부는 효율을 매우 중시해서 비공개 상용 라이브러리를 구입해 쓰고 있었으며 데이터도 일반 DBMS가 아닌 Berkeley DB에 들어 있었던 것입니다. 이전처럼 인터페이스를 상속해 XML 변환 메소드만 추가하면 될 줄 알았는데, 소스도 없는 라이브러리라 인터페이스 추가는 언감생심입니다. 김대리는 임베디드 파일 DB란 게 있다는 말만 들었지 한번도 다뤄 본 일이 없어 DBMS 수준의 데이터 통합을 하려 해도 일정을 맞출 자신이 없습니다. 인수합병 과정의 칼바람같은 구조조정으로 XX기업 홍보부 전산팀은 온데간데 없고 회사 분위기도 워낙 어수선해져 어디 하소연할 데도 없습니다. 김대리는 분명 오브젝트 중심 디자인 원칙을 충실히 따랐는데 뭐가 잘못됐는지 영문을 알 수 없습니다. 내키지는 않지만 밤샘을 해서라도 그 상용 라이브러리에서 필요한 모든 타입을 한 겹씩 감싸안은 데이터 타입을 한 벌 새로 정의하는 방법밖에 없는 듯 합니다. 이런 걸 디자인 패턴에서 데코레이터(decorator) 패턴이라고도 한다니 그래도 아주 잘못된 방법은 아닐 것이라 애써 스스로를 위안해 봅니다만 뭔가 찜찜하고 억울하다는 느낌은 지울 수 없습니다.
만일 OO기업과 XX기업이 하스켈을 쓰고 있었고 김대리가 Xmlizable
이라는 타입 클래스를 쓰고 있었다면 어땠을까요? 김대리는 고민할 필요도 없이 예전에 하던 대로 XX기업의 데이터 타입들을 Xmlizable
클래스의 인스턴스로 선언하여 XML 변환 메서드만 추가로 구현하기만 하면 될 것입니다. 왜냐하면 하스켈 타입 클래스는 타입 정의에 묶여 있지 않기 때문에 이전의 소스코드를 건드릴 필요가 없기 때문입니다.
마지막으로 하스켈의 타입 클래스와 Java의 인터페이스를 활용하는 보기를 통해 하스켈 타입 클래스가 오브젝트 중심 언어의 진화에 미친 영향을 짚어 보겠습니다. 하스켈에서 특정 클래스와 관련된 타입에만 제한적으로 적용 가능한 함수를 여러 의미 함수(Overloaded Function)라 합니다. 크기 비교가 가능한 타입으로 이루어진 리스트를 정렬해 주는 라이브러리 함수 sort
가 바로 대표적인 여러 의미 함수의 예입니다. ghci 대화식 환경에서 sort의 타입을 알아보면 다음과 같습니다.
List> :t sort
sort :: Ord a => [a] -> [a]
Ord a =>
라는 클래스 제약이 있기 때문에 아무 리스트 [a]
에 다 적용 가능한 것이 아니라 타입 인자 a
가 Ord
클래스의 인스턴스일 때만 함수 적용이 가능합니다. Java에서도 하스켈의 sort
에 상응하는 sort
함수를 다음과 같이 자바 제너릭 인자 T
가 만족해야 할 인터페이스 제약 extends Comparable<T>
를 표시하여 정의할 수 있습니다.
public static <T extends Comparable<T>>
List<T> sort(List<T> list) {
// ... 아무 정렬 알고리즘으로 list를 정렬 ...
return list;
}
이와 같이 제너릭과 인터페이스 제약을 함께 사용해 하스켈의 여러 의미 함수에 대응하는 자바 함수를 정의할 수 있는 것은 결코 우연이 아닙니다. 하스켈 타입 클래스 디자인에 핵심적인 역할을 한 필립 와들러(Philip Wadler)라는 유명한 프로그래밍 언어 연구자가 이후 자바 제너릭을 디자인하는 데도 핵심적인 역할을 했기 때문입니다. 또한 참고로 현재 C++ 표준안(C++ 98)에는 Java의 인터페이스 제약 표시처럼 하스켈의 클래스 제약에 해당하는 개념을 직접 표현할 방법이 없었는데, 최근에 디자인된 컨셉(Concept)이라는 기능이 새 C++ 표준의 초안(C++ 0x draft)에 들어갔다고 합니다. 따라서 다음 C++ 표준부터는 어떤 의미에서는 Java보다 하스켈 타입 클래스와 같은 개념을 더 잘 표현할 수 있는 강력한 기능이 추가될 것으로 기대하고 있습니다. 이와 관련해 더 자세히 알아보려면 필립 와들러 본인이 하스켈 타입 클래스와 자바 제너릭에 대해 구글에서 강연한 동영상[14]을 추천합니다.
http://research.microsoft.com/~simonpj/papers/history-of-haskell/ ↩︎
대화식 환경을 해석기라고 부르지 않는 이유는 대화식 환경도 엄밀히 말하자면 번역기이기 때문입니다. 고급 언어를 직접 실행하는 해석기와는 달리 대화식 환경을 통해 불러들인 스크립트는 하스켈과 기계어의 중간 단계인 바이트코드로 번역됩니다. ↩︎
박준규: 이어지는 예제에서
getContents
가 아니라readContents
를 사용하므로 이 부분은 옮긴이의 실수인 것 같다. ↩︎박준규: '추가하면서'의 오기이다. ↩︎
동영상 http://video.google.com/videoplay?docid=-4167170843018186532 슬라이드 http://homepages.inf.ed.ac.uk/wadler/papers/oopsla/oopsla.pdf ↩︎