[Haskell] 03. 커링 (Currying)

이제 함수를 표현하는 방법에 익숙해졌을 것으로 생각한다. 입력이 2개인 함수를 생각해보자.

\begin{equation}
f:(x,y) \mapsto z
\end{equation}

2개의 입력값 \(x, y\)를 받아서 \(z\)값을 출력하는 함수이다. 좀 더 간단하게 필요 없는 내용은 없애고 다음과 같은 예를 생각하자.

\begin{equation}
\text{add}: (x,y) \mapsto x+y
\end{equation}

이를 Haskell에서 구현하면,

Prelude> let add x y = x+y

와 같다. 여기서는 interactive command line에서 함수를 정의하기 때문에 let 키워드를 사용하였는데, 일반적으로 .hs 파일과 같은 source code를 작성하는 경우에는 add x y = x+y만으로 충분하다.

Prelude> add 3 4
7
Prelude> add 5 7
12

실행결과이다.


여기까지 쉽게 왔을지도 모른다. 그런데 여기에 약간의 반전이 있다.
Haskell은 2개의 입력값을 받는 함수가 없다. 그렇게 만들 수도 없다. Haskell의 함수는 오직 1개의 입력만 받을 수 있고, 1개의 출력만을 내보낸다. 뭐지? 방금 add x y라는 함수를 만들어서 테스트도 했는데. Haskell은 항상 simple한 것을 좋아하기 때문에 함수의 입력값과 출력값을 오직 1개만 가질 수 있게 제한해 놓았다.

그럼 방금 우리가 했던 것이 어떻게 동작할 수 있었는지 알아보자. 2개 이상의 입력값을 받는 함수는 모두 1개의 입력값을 받는 함수”들”의 조합으로 항상 변환할 수 있다.

\begin{equation}
\text{add}: (x,y) \mapsto x+y
\end{equation}

add라는 함수를 변환해 보자.

\begin{equation}
f_A: x \mapsto f_B \\
f_B:y \mapsto x+y
\end{equation}

여기서 함수 \(f_A\)는 입력값 \(x\)를 받아서 함수 \(f_B\)를 반환한다. 여기서 반환값이 함수 그 자체라는 것이 중요하다. 이 함수 \(f_B\)는 어떤 함수이냐면, 입력값 \(y\)를 받아서 \(x+y\)를 출력값으로 반환하는 함수이다.
위에서 계산하였던 add 3 4를 예로 계산해보자. 이것은 ((add 3) 4)와 같은 순서로 계산된다. add 3이 먼저 계산이 되는데, 이 계산은 함수 \(f_B:y \mapsto 3+y\)를 반환한다. 이 함수는 어떤 입력값 1개를 받아서 3을 더하는 함수이다. 그러면 ((add 3) 4)fB 4가 되고, 4에 3을 더한 값인 7이 반환되게 된다. 이 과정을 내부적으로 Haskell이 처리해 준다. 입력값이 3개 혹은 그 이상이더라도 비슷한 과정이 반복되는 것이다. 이러한 과정을 커링(Currying)이라고 부른다.
왜 Currying이라고 불렀을까? Curry는 Haskell의 last name이다. 언어 Haskell은 “Haskell B. Curry”라는 수학자의 이름을 딴 것인데, 이런 함수의 수학적 변환을 많이 연구했나보다.

정리하면,
첫 번째 입력값을 받아 함수를 반환하고, 이 반환된 함수에 두 번째 입력값을 넣으면 또 함수가 반환되고, … , 마지막 입력값을 넣으면, 최종 결과를 반환해 준다. 다시 말해 여러 개의 입력값을 받는 함수는 1개의 입력값을 받는 함수들의 조합(sequence of functions with one argument)들로 변환할 수 있고, Haskell은 이러한 변환으로 모든 함수를 처리한다. 좀 더 수학적인 과정을 보고 싶은 분들은 wiki page를 참고.

Reply