Search Results for '헤스켈'

6 POSTS

  1. 2008/04/30 Crossing borders (2)
  2. 2008/03/28 디버거 없이 개발하기 (2)
  3. 2008/03/26 헤스켈과 병렬 프로그래밍
  4. 2007/05/20 Haskell 독서중. (2)
  5. 2007/05/18 Haskell에 대한 오해?
  6. 2006/10/08 Dynamic Typing과 Test Driven Development (6)

Crossing borders

Posted 2008/04/30 16:47
IBM dW의 연재 중에 Crossing borders는 프로그래밍 언어와 기술 간의 경계를 뛰어넘어 새로운 세계를 맛 보자는 뜻으로, 다양한 함수 언어를 소개하고 있습니다.

헤스켈 소개

Crossing borders: Explore functional programming with Haskell


얼랑 소개

Crossing borders: Concurrent programming with Erlang


비함수형 개발자에게 함수 언어의 개념을 소개하는 것은 비교적 쉽습니다만, 그 다음 단계인 함수 언어로 실제 어플리케이션을 작성하는 일과는 상당한 괴리가 있는 것으로 보입니다. 저 역시 함수 언어에 관심 갖고 공부해온 지가 꽤 오래되었지만 비교적 최근에서야 업무를 비롯해서 시험적으로 조금씩 써보는 정도거든요.

특히, 함수 언어를 실제 어플리케이션 작성에 쓰기 위해 반드시 풀어야할 문제는 IO, 외부 함수 호출, 동시성(concurrency) 등인 것 같습니다. Real World Haskell은 Haskell로 연습 문제만 풀지 말고 실제 어플리케이션을 작성할 수 있도록 가이드하자는 취지해서 작성되고 있는 책인데, 얼마나 성공적일지 지켜봐야겠습니다.


디버거 없이 개발하기

Posted 2008/03/28 20:09
전 개발할 때 디버거를 자주 사용하지 않는 편입니다. C/C++, 자바, 파이썬, 헤스켈 등 개발 언어를 가리지 않고 디버거는 거의 사용하지 않습니다. 물론 디버거를 안 사용하는 게 무슨 개발 철학 같은 건 아닙니다. 직관적으로 디버거를 사용하는 편이 좋겠다는 생각이 들 때도 있고 프로그램이 C 코드 안에서 죽을 때는 디버거를 실행시켜서 메모리와 변수 값을 보기도 합니다.

하지만 디버거로 절대 잡기 힘든 멀티쓰레드 버그가 난무하고 메모리 보호도 없는 임베디드 시스템 개발로 개발을 시작하다보니 버그가 생겨도 디버거를 써야지라는 생각을 잘 안 하게 된 것 같습니다. 대신 저는 디버깅에 크게 2가지 방법을 사용합니다. (다른 개발자도 마찬가지겠지만요)

1) 콘솔 출력

언어마다 다르지만 결국 중요한 지점 몇 군데가 변수 값을 찍어보는 걸로 디버깅이 시작됩니다. printf, System.out.println, print가 디버깅 도구인 셈이죠. 간단한 버그들은 보통 원인이 짐작이 되기 때문에 중요 변수를 콘솔로 몇 번 출력해 보면 문제가 파악됩니다.


2) 코드 검토

멀티쓰레드 버그에 대해서는 가장 강력한 디버깅 방법이 머리 속으로 시뮬레이션하는 것입니다. 증상을 놓고 왜 이런 문제가 생겼을지 의심가는 여러 쓰레드를 놓고 생각해 보는 것이죠. 하루종일 책상에 앉아서 왜 이런 현상이 생길지 생각만 하다가 집에 간 날도 있습니다.


버그를 찾아내면 기쁘긴 하지만 디버깅 자체는 괴롭습니다. 처음부터 버그를 안 만드는 게 최선이겠죠.  하지만 나름대로 열심히 짠다고 해도 버그 없는 프로그램을 만들기란 너무 힘듭니다. 코드를 작성했는데 한 번에 컴파일되고 돌아가면 스스로 의심합니다. "그럴리가 없는데." 어릴 때는 그래도 머리가 잘 돌아가서 가끔 실행은 제대로 안 되더라도 컴파일 에러 없는 코드 정도는 만들어내곤 했는데 요즘은 그나마도 힘들어 그냥 포기하고 무슨 마약처럼 컴파일러에 의존(주기적으로 컴파일을 해보지 않으면 불안함)해 살고 있습니다.

버그는 사람이 만들지만 버그를 유도하는 것은 개발 환경입니다. 특히 프로그래밍 언어는 개발자가 얼마나 많은 버그를 만드는지를 결정하는 중요한 요소입니다. 함수 언어를 사용하면서 느낀 점 중에 하나는 적당히 컴파일되게만 짜면 다른 언어에 비해 버그가 현저히 적다는 사실입니다. 물론 강력한 정적 타입 시스템에 도움이 큰 셈이죠. 여기에 덧붙여 버그를 줄이는 프로그래밍 언어의 특징으로는 변조불가능(Immutability), 순수(Purity), 참조 투명성(Referential Transparency) 등이 있습니다. 세 가지 단어는 서로 다 관련이 있습니다.

헤스켈을 비롯한 함수 언어 개발자들이 다른 개발자들과 차이를 보이는 점 중에 하나가 디버거를 거의 사용하지 않는다는 사실입니다. 물론 GHCi에 보면 디버거가 없는 건 아닙니다. 앞서 언급한 언어적 특성 때문에 헤스켈 디버깅은 디버거를 통해 변수에 잘못된 값이 들어가 있는지 확인하는 일이 필요가 없습니다. (사실 변수라고 부를만한 것도 별로 없습니다.) 그저 함수 명세에 맞춰서 코드가 제대로 작성되었는지 테스트 값 몇 개만 넣어보면 됩니다.

특히 헤스켈의 중요 특징의 하나인 참조 투명성은 테스트 혹은 디버깅에서 강력한 힘을 발휘합니다. 참조 투명성이란 헤스켈의 함수가 수학적인 함수와 동일하게 같은 입력(인자)에 대해서는 언제나 같은 값을 리턴함을 의미합니다. 헤스켈의 순수 함수는 전역 변수를 참조하거나 IO에 영향을 받지 않기 때문에 테스트나 디버깅을 위해 가짜 오브젝트(Mock Object) 등을 만들어내는 수고를 할 필요가 없습니다. 그저 함수의 명세(specification)만 테스트하면 됩니다. 이런 특성 때문에 헤스켈의 테스트프레임워크 중 하나인 QuickCheck은 명세 기반 테스트라는 용어를 사용하고 있습니다.

나는 연장질을 잘 못한다고 생각하기 보다는 내 연장이 나쁜게 아닐까라는 생각의 전환이 필요합니다.

헤스켈과 병렬 프로그래밍

Posted 2008/03/26 02:25
제가 함수형 언어에 직접적인 관심을 가지게 된 계기는 함수형 언어가 멀티코어 프로그래밍(병렬 프로그래밍)에 대한 가장 활발한 연구 결과를 내어놓고 있기 때문입니다. 부작용(side-effect)이 없고 연산(evaluation) 순서에 제약이 덜하다는 함수 언어 자체의 특징이 병렬화와 잘 어울리는 부분도 있습니다만, 언어 특징과 별개로 병렬 프로그래밍에 대한 최신 생각들이 함수 언어에 가장 먼저 적용되는 부분도 무시할 수 없습니다.

병렬 프로그래밍하면 주로 얼랑(Erlang)이 많이 주목받고 있습니다. 함수 언어 중 거의 유일하게 상업적인 용도로 이용된 적이 있다는 사실 때문에 근래에 관심을 가지게 되신 분이 많이 있는것 같습니다. 하지만 경량 프로세스(lightweight process)와 메시지 패싱 방식은 순차적 프로그래밍에 익숙한 기존 개발자들이 쉽게 적응할 수 있는 모델이 아니라 굉장히 큰 진입 장벽이 존재합니다. 자바 개발자가 파이썬으로 코딩해도 여전히 자바 스타일인 것처럼 락 기반 멀티쓰레드 프로그래밍에 익숙한 개발자가 태스크를 잘게 쪼개고 수많은 경량 프로세스를 통해 메시지를 주고 받는 모델로 개발하기란 쉽지 않습니다.

더불어 얼랑은 함수 언어 연구와 깊은 상관관계를 맺고 있는 정적 타입 시스템의 장점을 전혀 살리지 못하는 동적인 언어라는 사실도 약점입니다. 얼랑이 주로 통신 장비 등 상당한 안정성이 요구되는 분야에 사용된 언어임에도 불구하고, 버그를 일찍 쉽게 발견할 수 있게 도와주는 정적 타입 시스템의 도움을 전혀 못 받고 있다는 사실을 조금 의외입니다. 런타임에 쉽게 오류에서 회복되는 것도 중요하지만 애당초 버그를 줄일 수 있었다는 더 좋았을 텐데 말이죠.

헤스켈은 병렬 프로그래밍과 관련해서는 가장 많은 옵션을 제공하는 언어입니다. 일단 가장 유명한 세 가지는 다음과 같습니다.


1) Concurrent Haskell

forkIO 등의 함수를 이용해 쓰레드를 생성하고 죽일 수 있으며 MVar 등 공유 메모리를 이용해 동기화하는 모델입니다.

2) Parallel Haskell

Paralle Haskell은 `par`와 `seq` 등을 이용해 순차적인 프로그램에 병렬화할 수 있는 단서를 달아주는 방법입니다. 어떤 부분이 동시에 수행될 수 있는지만 알려주면 헤스켈이 알아서 쓰레드를 생성하고 동기화를 해줍니다. 아래 글을 참고하세요.

3) Software Transaction Memory

1) 번 모델을 발전시킨 것으로 데이터베이스의 트랜잭션처럼 특정 코드를 모 아니면 도(all or nothing)로 실행시켜주는 방법입니다. 직접 메모리에 쓰는 대신에 트랜잭션 로그를 만들고 한 번에 커밋하는 방식입니다. 도중에 누가 방해하면 처음부터 다시 실행하면 됩니다. retry를 이용해 컨디션 변수를 대신해 블로킹을 할 수도 있습니다.

락 기반 멀티쓰레드 프로그래밍의 문제점인 데드락, 레이스컨디션 등이 불가능하고, 규모 가변성(scalability)이 뛰어난 편이라 병렬 프로그래밍 계의 총아로 떠오르고 있는 분야입니다.

Haskell 독서중.

Posted 2007/05/20 06:46
주말 동안에 Haskell, The Craft of Functional ProgrammingThe Haskell School of Expression을 열심히 읽고 있습니다. 2001년도에 프로그래밍 언어(PL) 수업 들으면서 한참 함수형 언어에 빠져들어서 샀던 책인데, 최근에 다시 한 번 읽어보는 중입니다.

최근 프로젝트에서 파이썬을 주 언어로 사용하고 있는데, 제대로 된 타입 시스템이 없는 파이썬 코드에 영 적응이 안 되던 차에 강력한 타입 시스템을 제공하는 헤스켈에 대한 향수가 생겼달까요.

그때만 해도 컨셉은 좋지만 실용적으로 쓰기에는 무리라고 판단을 내렸었는데, 다시 보는 헤스켈이 생각보다 성능이 좋군요. 헤스켈을 바이너리로 컴파일해 주는 GHC(Glasgow Haskell Compiler)의 성능도 상당히 향상된 것으로 보이고요.

심볼 연산(symbolic computation)이 주가 되는 실무에 일부 활용해 보려고 고민 중인데, 관련 소식을 업데이트 하겠습니다.

Haskell에 대한 오해?

Posted 2007/05/18 04:57
현대 함수형 언어의 대표격인 Haskell은 이제 대부분 개발자가 이름 정도는 들어본 언어가 되었죠?
그리고 Haskell의 강력함을 소개하는 예제로 빠지지 않고 등장하는 것이 바로 C 언어로 정확하게 구현하려면, 머리 잡아 뜯으며 디버깅해야 한다는 퀵소트(QuickSort)를 다음과 같이 매우 직관적이고 손쉽게 할 수 있다는 것이죠.

사용자 삽입 이미지

위 간단한 예제는 Haskell의 List Comprehension이나 Generic Recursion 같은 함수 언어 특유의 여러 특징을 잘 보여줍니다.

그런데 요즘 주변 친구들에게 Haskell 언어 아냐고 물어보면 이런 대답이 돌아옵니다. 그거 퀵소트 잘하는 언어 아냐? 그냥 퀵소트 잘하는 언어. 아무 짝에도 쓸모 없는 퀵소트만 잘하는 언어...

프로그래밍 언어도 보다 적극적인 마케팅이 필요한 시대입니다.

Dynamic Typing과 관련된 글을 블로그에 Static Type Checking이란 제목으로 쓴 적이 있는데 여기서 다시 한 번 정리해 보고자 한다.

블로그를 돌아다니다보면 많은 사람들이 TDD가 Dynamic Typing하는 언어의 특징이라고 생각하고 있음을 알 수 있다. 특히 일부 파이썬/루비 팬들이 운영하는 블로그에서 TDD가 언급되면 어김없이 동적 타이핑 언어의 테스트 편리성(혹은 크게는 생산성)에 대한 이야기가 뒤따라 온다.

하지만 C/C++처럼 수십 년도 더 된 구세대 언어와, 소프트웨어 개발 방법론과 테스팅에 대한 어느정도 이해가 쌓인 후에 나온 파이썬/루비와 같은 언어를 정적/동적 타이핑 면에서만 놓고 비교하는 것은 공정하지 못한다. 사실 너무 불공평하다.

파이썬/루비와 정반대 선상에서 정적 타입핑의 극상을 달리는 친구들은 ML과 Haskell 같은 함수형 언어(functional)들인데, 이 놈들은 C/C++ 보다 더 엄격하게 컴파일 타임에 타입 검사를 한다. 하지만 이쪽 언어 사용자들의 주장에 따르면 ML과 Haskell은 절대 테스트하기 불편한 언어도 아니고, 코드 길이가 길거나 생산성이 떨어지는 언어도 아니다.

순수하게 타이핑 측면에서만 바라본다면, 동적 타이핑은 컴파일 타임에 해주는 검사가 적은만큼(상당히 귀납적이다) 테스트를 통해 더 많은 버그를 찾아내야 하므로, 테스트를 훨씬 더 철저하게 많이 해야 한다. 잘못된 타입 연산 등의 간단한 버그 조차도 테스트를 통해서만 발견할 수 있다는 것은 재앙이다(타입 검사는 연역적이다)

테스트의 부담이 더 큰 파이썬/루비 같은 동적 타이핑 언어가 테스트를 강조하는 것은 당연한 일이다. 같은 기능을 하는 다른 프로그램 보다 더 많은 테스트를 해야 프로그램이 제대로 동작하기 때문이다. 즉 원래 언어적인 측면에서 태생적인 약점이 있으므로, 후천적인 노력으로 개선을 한 셈이다.

파이썬과 루비 같은 동적 타이핑하는 스크립트 언어의 테스트 편리성은 동적 타이핑이라는 속성에 기인한 것이 아니다. 이는 언어의 추상화(abstraction) 수준과 관계가 있다. 추상화 수준이 높을수록 코드는 사람이 생각한 바(스펙)에 가까워지고, 알아보기 쉬워지며, 금방 만들 수 있다.

C 언어와 파이썬/루비의 추상화 수준은 어떻게 다를까? 간단한 통계로 C 언어 construct 하나가 보통 1-10개의 머신 인스트럭션으로 컴파일 되는 반면에, 파이썬과 루비의 construct 하나는 100-1000개 이상의 머신 인스트럭션으로 실행된다.

즉, 파이썬이나 루비 개발자가 그토록 강조하는 "가독성", "짧은 코드"는 절대로 동적 타이핑(흔히 스크립트 언어)의 특성이 아닌 셈이다. 강력한 정적 타이핑 언어인 헤스켈(Haskell)의 퀵 소트 코드를 본 적이 있다면, 타이핑은 생산성이나 테스트 편의와는 직접적인 상관 관계가 없다는 데 동의할 것이다.

qsort [] = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)

위 Haskell 코드는 polymorphic하다. qsort에 넘길 수 있는 타입은 >= 연산자가 정의된 list이기만 하면 된다. Haskell이 이처럼 간결한 코드를 보여줄 수 있는 이유는 파이썬/루비와 같은 수준의 추상화 레벨이면서, type inference를 통해 불필요한 타입 어노테이션을 필요를 상당히 줄였기 때문이다.

이처럼 같은 추상화 수준이라면 당연히 동적 타이핑보다는 정적 타이핑이 유리하다. 비유하자면, 타입 이론은 '사람은 모두 죽는다' '그러므로 소크라테스도 죽는다'라고 말하는 것이고, 테스트는 'A도 죽었고, B도 죽었고, C도 죽었으니 아마 소크라테스도 죽을 것이다'라고 말하는 셈이다.