5. 순서쌍

 
일, 2006-07-16 18:38 — 귤
평면 위에 있는 점의 위치를 가리킬 때 가로 좌표와 세로 좌표를 묶어 (x, y)라고 말한다. 이렇게 여러 개의 값을 하나의 단위로 나타내는 것을 순서쌍(tuple)이라고 부른다. Haskell에서 순서쌍을 다루는 방법은 별로 어려울 것이 없다.
move (x, y) (a, b) = (x+a, y+b) move (1, 3) (2, 2) ⇒ (3, 5)
순서쌍과 리스트는 여러 개의 값이 들어가기 때문에 비슷해 보이지만, 단지 여러 개의 값이 모여있는 리스트와 달리 순서쌍은 좌표처럼 그 자체로 의미있는 값이다. 따라서 순서쌍에서 값의 개수는 고정되어 있다.
순서쌍은 보통 두 개의 값을 묶어 쓰는 경우가 많기 때문에 그런 경우에 대응하는 기본 함수들이 여럿 있다. 가장 많이 쓰이는 함수는 fst와 snd이다. fst는 순서쌍의 첫 원소, snd는 두 번 째 원소를 돌려준다.
fst (1, 'a') ⇒ 1 snd (1, 'a') ⇒ 'a'

커링

수학의 함수나 다른 프로그래밍 언어는 f(x,y)꼴로 매개 변수를 순서쌍의 형태로 묶어주는 반면에 함수형 언어들은 대체로 f x y 같은 표현을 사용한다. 이는 함수를 바라보는 관점의 차이에서 비롯된다. 보통 학교에서 배우는 수학에서는 f(x,y)에서 f(1,) 같은 방식으로 새로운 함수를 정의할 수 없지만 함수형 언어에서는 f x y에서 f 1로 새로운 함수를 정의할 수 있다.
직관적으로 볼 때는 함수형 언어의 방식이 더 간단하다. 예를 들어 add 1은 "1을 더하라"라는 뜻이니까 이것이 새로운 함수라는 사실은 쉽게 이해가 된다. 그럼 어떤 원리로 가능한 것일까? f(x,y) 같은 표기법은 함수 f가 x와 y의 값을 '한 번에' 받는다는 의미가 있다. 반면 f x y는 f를 x에 적용하여 새로운 함수를 만든 다음 그것을 y에 적용한다는 의미다. add 1 2가 있으면 1과 2를 한 번에 더해서 3을 만드는 것이 아니라 add 1에서 새로운 "더하기 1하는 함수"를 만든 다음에 이 함수를 2에 적용해서 3을 만드는 것이다. 어차피 결과야 둘 다 똑같지만 뒤의 방법이 함수 자체를 다루기는 더 쉽다. 이 방법은 수학자 Haskell B. Curry가 제안한 것인데 그의 이름을 따서 '커링 (currying)'이라고 부른다.
순서쌍을 이용하면 Haskell의 함수 형태를 우리가 익숙한 함수 형태로 바꿀 수 있다. 이렇게 하는 것을 커링의 반대라고 해서 '언커링 (uncurrying)'이라고 한다. 다음의 예를 보자.
add' = uncurry add add' (1,2) → add 1 2 add'' = curry add' add'' 1 2 ⇒ add' (1,2)
uncurry는 함수를 언커링해주고, curry는 커링해준다. curry는 쓸 일이 별로 없지만 uncurry는 종종 쓸 경우가 생긴다. 다음 경우를 보자.
divMod n d = (div n d, mod n d) addDM = uncurry add . divMod addDM 5 3 ⇒ uncurry add (divMod 5 3) ⇒ uncurry add (div 5 3, mod 5 3) ⇒ uncurry add (1,2) ⇒ add 1 2
divMod는 n을 d로 나눈 몫과 나머지를 돌려주는 함수다. Haskell에 기본으로 들어있으니 따로 만들어줄 필요는 없다. add를 (1,2)에 적용시키려면 (\ (x,y) -> add x y) (1,2) 같은 방식으로 써야하는 데 번잡하기 이를 데 없다. 이때 uncurry를 add에 적용시키면 똑같은 결과를 얻을 수 있다.
fst나 snd와 마찬가지로 curry와 uncurry도 두 개의 값이 묶여있는 순서쌍에만 사용한다. 세 개나 네 개의 값이 묶여 있는 경우는 어떻게 할까? Haskell로 프로그래밍을 해보면 알겠지만 그럴 경우는 거의 없다. 매우 드물게 그런 경우가 있으면 람다 함수를 이용해서 순서쌍을 풀어주면 된다.
※ 눈치챘겠지만 Haskell 언어의 이름은 Haskell B. Curry에서 따온 것이다.