때는 2030년, 2016년 바둑으로 이세돌을 이긴 알파고는 더욱 진화하여 인류를 지배하기 시작하였다. 저항군은 알파고를 파괴할 컴퓨터 바이러스 개발에 나선다. 알파고는 이를 막고자 개발자들이 사용할 수 있는 함수들을 파괴하기 시작했다. 오랜 싸움 끝에 이제 인류에게 남은 함수는 단 3개뿐, 과연 인류는 3개의 함수로 알파고를 물리칠 바이러스를 만들 수 있을까?
2030년 인류가 바이러스 개발에 사용할 수 있는 함수는 단 3개가 남았습니다. 남은 함수가 3개밖에 없다는 사실을 알고 이제 알파고를 이기는 것을 불가능하다며 많은 사람들이 절망하고 있습니다.
s x y z = x z (y z)
k x y = x
i x = x
각각 s
, k
, i
라고 이름 붙여진 함수들은 보시는 바와 같이 아주 간단한 일을 하는 함수입니다.
- i: 인자
x
를 받아서 그대로x
를 리턴하는 함수입니다. 항등 함수를 뜻하는 영어 identity function의 i에서 따온 이름입니다. - k: 2개의 인자
x
와y
를 받아서 항상 첫 번째 인자x
를 리턴하는 함수입니다. 상수 함수를 뜻하는 영어 constant function을 c에서 따온 이름인데, 개발자들은 c를 k라고 부르는 습성이 있어k
함수가 되었습니다. - s: 3개의 인자
x
,y
,z
를 받는 함수입니다. 먼저,z
를 인자로x
를 호출합니다. 그리고 나서 리턴된 함수에z
를 인자로y
함수를 호출한 결과를 다시 인자로 넘겨서 호출합니다.s
라는 이름은 대체 연산자를 뜻하는 substitution operator의s
에서 왔습니다.
i
함수와 k
함수는 크게 쓸모가 없어 보입니다. 인자를 그대로 돌려주는 함수나 두 개의 인자 중 항상 첫 번째 인자만 리턴하는 함수 모두 그다지 유용한 일을 하는 것 같지는 않습니다.
그나마 s
함수는 뭔가 유용한 일을 하는 것 같습니다. s
가 대체 연산자라는 이름이 붙은 이유는 함수 호출은 변수를 대체하는 것과 같기 때문입니다. 간단한 예로, 인자 x
를 받아서 튜플 (x, x)
를 리턴하는 함수 \x -> (x,x)
가 있다고 하면 이 함수에 1을 인자로 호출한 (\x -> (x, x)) 1
의 값은 변수 x
를 1로 대체한 (1, 1)
과 같기 때문입니다.
많은 사람들이 절망하고 있던 그 순간 그간 비주류 언어라며 천대 받던 하스켈 개발자가 나섭니다. 이 세 함수만 있으면 알파고를 무찌를 바이러스를 충분히 개발할 수 있다고 자신감 있게 주장합니다. 사람들은 동요하기 시작합니다. 누군가가 묻습니다. “하지만 우린True
도 False
도 if
문도 없는데 없다고! 조건문 없이 어떻게 프로그램을 만들 수 있단 말이야?”
하스켈 개발자가 답합니다. “가능합니다. True
는 k
, False
는 sk
로 정의하면 됩니다.k
함수는 인자 x
, y
를 받아서 x
를 리턴합니다. sk
함수는 반대로 인자 x
, y
를 받아서 y
를 리턴합니다. True
일 때는 x
, False
일 때는 y
를 리턴하는 값을 만들었으므로 if 문처럼 사용할 수 있습니다.”
k x y = x
s k x y = k y (x y) = y
또 다른 개발자가 외칩니다. “하지만 루프가 없다고! 루프 없이는 아무런 유용한 프로그램을 짤 수가 없다고. 튜링 머신이 아니란 말이야.” 하스켈 개발자가 단호한 표정으로 다시 답합니다. “우리에게 남은 함수는 세 개 밖에 없고, 말씀하신 것처럼 이제 인류는 더 이상 루프를 사용할 수가 없습니다. 하지만 우리에게 재귀 함수가 있습니다. 루프에 비해 느리고, 어렵다며 천대 받던 재귀 함수이지만 이제 우리에게 남은 유일한 희망입니다. 재귀 함수를 정의하는 원리는 간단합니다. 먼저 sii
를 보시기 바랍니다.”
sii a = (i a) (i a) = a a
“이처럼 sii
는 인자 a
를 받아서 a
를 인자로 자기 자신을 다시 호출합니다. 네. 맞습니다. 여러분이 사랑하고 동시에 증오하던 무한 루프입니다. 이런 방식을 이용하면 재귀 함수 y
를 만들어낼 수 있습니다. y
함수는 다음과 같이 정의합니다.”
y = s(c(s(ks)k)(sii))(c(s(ks)k)(sii))
또 다른 개발자가 소리칩니다. “그럼 정수는? 루프나 if가 있다고 하도 정수 없이 무슨 코딩을 하란 말이야?” 하스켈 개발자는 동요하지 않고 여전히 자신감 있게 설명을 이어갑니다. 오랜 설명 끝에 결국 모든 개발자들이 s
, k
, i
단 3개의 함수만으로 알파고를 물리칠 바이러스 개발이 가능하다는 말을 납득합니다. 이후 더 이상 하스켈 개발자를 천대하지 않고 함께 열심히 바이러스를 개발하여 알파고를 무찌르고 행복하게 살았답니다.
여기서 설명한 s
, k
, i
함수는 컴퓨터 과학자, 논리학자에게는 이미 잘 알려진 SKI 콤비네이터입니다. 콤비네이터라는 용어 자체가 생소할 수 있는데, 콤비네이터는 free 변수가 없는 함수를 뜻합니다. 여기서 free 변수가 없다는 뜻은 함수에 주어진 인자만 사용하는 순수 함수를 말합니다. 다음은 콤비네이터의 예제입니다.
\a -> a
\a -> \b -> a
\f -> \a -> \b -> f b a
콤비네이터가 아닌 함수의 예제는 다음과 같습니다. x
라는 변수가 함수의 인자로 정의되지 않았습니다. 이런 변수 x
를 free 변수라고 부릅니다.
\a -> \b -> x
재미있는 사실은 계산가능성(computability)를 봤을 때 SKI 콤비네이터는 람다 대수(lambda calculus)와 동급이고, 람다 대수가 튜링 머신과 동급이기 때문에 결국 SKI 콤비네이터가 정의한 3개의 함수만 가지고 우리가 컴퓨터를 가지고 할 수 있는 모든 계산을 할 수 있습니다. 인류가 이 세 개의 함수만 가지고 알파고를 무찌를 바이러스를 만들 수 있었던 이유도 세 개의 함수가 튜링 머신과 동급이기 때문입니다.
사실 엄밀하게 이야기하면 s
와 k
2개만 함수만 있어도 됩니다. i
조차도 s
와 k
의 조합인 skk
로 만들어 낼 수 있기 때문입니다.
skk a = k a (k a) = a
s
, k
, i
세 함수를 장황하게 소개한 이유는 각 함수가 하스켈 프로그래밍에도 실제로 아주 자주 사용되는 필수 함수이기 때문입니다. 이들 함수는 하스켈에서 각각((->) r) Applicative
인스턴스의 (<*>)
함수, const
함수, id
함수로 정의되어 있습니다. 앞으로 설명할 내용에도 자주 등장하는 함수들이 꼭 기억해 두시기 바랍니다.
Pingback: SKI 콤비네이터 – 서광열의 하스켈 스쿨