안녕하세요, 저는 Franklin Frisby 교수입니다. 만나서 반갑습니다. 당신과 어울리면서 함수형 프로그래밍에대해 조금 알려드리겠습니다. 그런데 당신은 어떤 사람입니까? 저는 당신이 최소한 자바스크립트에 대해 친숙하고 객체지향 프로그래밍 경혐이 조금 있기를 바랍니다. 그리고 당신 자신을 프로그래머라고 생각하고 있기를요. 곤충학 박사가 아니어도 되지만 곤충(bug)를 찾고 잡는 법을 알고 있어야 합니다.
저는 당신이 함수형 프로그래밍을 알고 있다고 생각하지는 않겠습니다. 그렇게 생각하면 어떻게 될지 우리 모두 알고 있잖아요. 하지만 변하는 상태(mutable state)와 제한되지 않은 부수효과(side effects), 규칙 없는 디자인이 만드는 불쾌한 상황을 겪어봤길 기대합니다. 이제 서로 충분히 알게 된 것 같군요. 이제 시작합니다.
저는 이 장에서 앞으로 우리가 무엇을 하게 될지 대략적으로 말해주려고 합니다. 무엇이 함수형 프로그램을 만드는지에 대해 어느정도 알고있어야지 이후의 내용을 이해할 수 있을 겁니다. 그렇지 않으면 우리는 목적을 잃고 헤메고 책을 읽기 위한 노력이 무용지물이 될 거에요. 우리는 코드를 볼 초롱초롱한 눈과 바다가 거칠어 졌을 때를 위한 천체 나침반이 필요합니다.
개발할 때 막막한 밤을 안내해주는 몇 가지 프로그래밍 법칙들이 있어요. DRY(반복하지 마라, don't repeat yourself), YAGNI(필요한 작업만 해라, ya ain't gonna need it), 낮은 결합도 높은 응집도, 최소한으로 놀라게 하라, 단일 책임 등등...
저는 제가 몇 년 동안 들어온 모든 조언을 말하면서 당신을 지루하게 하지는 않을거에요. 중요한 것은 이 격언들이 우리의 궁극적인 목표에는 조금밖에 맞닿아 있지 않지만 함수형 프로그래밍에서도 유효하다는 것이에요. 앞으로 더 나아가기 전에 우리가 키보드를 칠 때의 의도를 당신이 느꼈으면 느껴봤으면 좋겠어요. 우리의 함수형 프로그래밍의 무릉도원을요.
여기 갈매기 프로그램이 있어요. 약간 이상하게 보일 수도 있어요. 무리들이 결합(cojoin)하면 더 큰 무리가 되고 새끼를 치면 다른 무리의 수만큼 곱해집니다. 좋은 객체지향 코드가 아니지만 할당에 기반한 현대적인 접근 방식이 위험하다는 것을 강조하기 위한 코드입니다. 한번 보세요.
class Flock {
constructor(n) {
this.seagulls = n;
}
conjoin(other) {
this.seagulls += other.seagulls;
return this;
}
breed(other) {
this.seagulls = this.seagulls * other.seagulls;
return this;
}
}
const flockA = new Flock(4);
const flockB = new Flock(2);
const flockC = new Flock(0);
const result = flockA
.conjoin(flockC)
.breed(flockB)
.conjoin(flockA.breed(flockB)).seagulls;
// 32
세상의 어떤 사람이 이런 이상한 것을 만들겠어요? 내부 상태 변화를 추적하기가 너무 힘들군요. 그리고 세상에, 심지어 답도 틀렸어요! 16
이 되어야하지만 flockA
가 중간에 바뀌어 값이 달라졌습니다. 불쌍한 flockA
... 이것이 I.T.의 난세입니다! 이것이 야생의 수학입니다!
이 프로그램을 이해하지 못해도 괜찮아요. 저도 모르겠거든요. 기억해야할 점은 이렇게 작은 예제에서도 상태와 변하는 값(mutable values)은 추적하기 힘들다는 것입니다.
이번에는 함수형 프로그래밍의 접근방법을 사용해서 한번 더 시도해볼까요?
const conjoin = (flockX, flockY) => flockX + flockY;
const breed = (flockX, flockY) => flockX * flockY;
const flockA = 4;
const flockB = 2;
const flockC = 0;
const result = conjoin(
breed(flockB, conjoin(flockA, flockC)),
breed(flockA, flockB)
);
// 16
와, 이번에는 올바른 답을 얻었네요. 그것도 좀 더 적은 코드로요. 함수 중첩은 약간 헷갈리긴 하네요... (5장에서 이 문제를 해결할 거예요). 좀 나아졌지만 조금 더 깊게 가볼까요. "ㄱ"을 "기역"이라고 부르는 장점이 있습니다. 한번 자세히 본다면 그저 덧셈(cojoin
)과 곱셈(breed
)를 하고 있다는 것을 알 수 있을 거에요.
이 두 함수에는 이름 말고 다른 특별한 점은 하나도 없어요. 우리의 함수들을 multiply
와 add
라고 다시 이름지어서 이들의 정체을 드러내봅시다.
const add = (x, y) => x + y;
const multiply = (x, y) => x * y;
const flockA = 4;
const flockB = 2;
const flockC = 0;
const result = add(
multiply(flockB, add(flockA, flockC)),
multiply(flockA, flockB)
);
// 16
그리고 그것을 통해 우리는 오래전부터 알고 있던 지식을 얻을 수 있어요.
// 결합법칙(associative)
add(add(x, y), z) === add(x, add(y, z));
// 교환법칙(commutative)
add(x, y) === add(y, x);
// 항등원(identity)
add(x, 0) === x;
// 분배법칙(distributive)
multiply(x, add(y, z)) === add(multiply(x, y), multiply(x, z));
이 오래되고 믿음직한 수학 성질들은 곧 손에 익게 될 거에요. 하지만 지금 당장 이해하지 못해도 걱정하지 마세요. 우리 중 대부분은 이 산수 법직들을 배운지 오랜 시간이 지났으니깐요. 이 법칙들을 이용해 우리의 작은 갈매기 프로그램을 간단하게 만들 수 있는지 한 번 볼까요?
// 기존 코드
add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));
// 필요없는 덧셈을 줄이기 위해 항등원 규칙 적용하기
// (add(flockA, flockC) == flockA)
add(multiply(flockB, flockA), multiply(flockA, flockB));
// 분배법칙 적용하기
multiply(flockB, add(flockA, flockA));
와!! 우리는 함수 호출하는 것 이외의 코드를 쓸 필요가 없었군요. 지금은 완전성을 위해 add
와 multiply
의 정의를 적었지만 굳이 사용할 필요는 없습니다. add
와 multiply
를 제공하는 라이브러리가 있을테니까요.
아마 당신은 "이런 수학적인 예제는 허수아비의 오류가 아닌가요" 내지는 "실제 프로그램은 이렇게 단순하지 않아서 이런 식으로 생각할 수 없어요"라고 말할지도 몰라요. 저는 우리 대부분이 덧셈과 곱셈에 대해 알고 있고 또 이 예제를 통해 수학이 얼마나 유용한지 보여주기 쉽기 때문에 이 예제를 선택했어요.
하지만 실망하지 마세요. 이 책을 읽는 동안 우리는 약간의 범주론(category theory)과 집합론, 람다 대수(lambda calculus)을 탐험할 것입니다. 그리고 실제 세상의 문제를 갈매기 프로그램 예제처럼 간단하지만 우아하게 풀어볼 것입니다. 당신이 수학자일 필요는 없어요. 당신은 함수형 프로그램이 "보통의" 프레임워크나 API를 사용하는 것처럼 쉽고 자연스럽고 느껴질 것이에요.
아마 모든 어플리케이션을 위와 같은 함수형 코드로 짤 수 있다는 것을 알면 당신은 놀라겠지요. 견실한 성질을 가지고 적은 코드를 쓰지만 이해하기 쉬운 프로그램을요. 또 매번 바퀴를 발명하지 않는 프로그램을요. 당신이 범죄자라면 무법지대를 좋아하겠지만 이 책에서는 견고한 수학의 법칙을 인정하고 따르고 싶어 질 것이에요.
우리는 모든 조각이 서로 딱 들어맞는 이론을 원하게 될 것이에요. 또 우리는 특정한 문제를 일반적이고 조합 가능한 조각으로 표현하고 우리 자신의 문제를 위해 이용하고 싶어질 거에요. 명령형 프로그램의 "모든지 가능한" 접근방식 보다는 좀 더 규율적인 접근방식을 택할 것이에요(나중에 "명령형"의 정확한 정의를 다루겠지만 지금은 함수형 프로그래밍 이외의 모든 것을 의미한다고 생각해주세요). 원리가 있는 수학적 프레임워크 안에서 일하는 것을 당신을 진실로 놀라게 할 것이에요.
우리는 함수형 세계의 북극성이 반짝이는 것을 잠깐 봤지만 본격적으로 우리의 여정을 시작하기 전에 알아야하는 구체적인 개념이 몇 가지 있습니다.