함수형 프로그래밍(Functional Programming) 기초
원문: Functional Programming Basics | PragPub, January 2013
저자: Robert C. Martin (Uncle Bob) (Blog: http://cleancoder.posterous.com/)
번역: 오광신 (Email: kwangshin (at) gmail.com)
함수형 프로그래밍이 복잡하고 어려워보이나요? "Uncle Bob(엉클 밥)"으로 불리는 Martin(마틴)이 함수형 프로그래밍 패러다임에서 반드시 알아야하는 기본적인 것들을 파헤쳐 주고, 왜 지금 함수형 프로그래밍을 꼭 이해해야만 하는지 그 이유를 설명해줍니다!

Source: http://wadler.blogspot.sg/2008/04/functional-programming-is-beautiful.html
아마도 지금쯤이라면 대부분의 소프트웨어 개발자들이 함수형 프로그래밍에 대해서 한번쯤은 들어보았을 것이다. 다시 말하면 수많은 개발자들이 주위에서 함수형 프로그래밍에 대해서 이야기하는데, 귀를 막고 있는 것이 아니라면 어떻게 한번도 들어보지 못할 수 있는가? 요즘 새롭게 뜨고 있는 Scala(스칼라), F#(에프샵), Clojure(클로저)와 같은 언어들이 함수형 프로그래밍 언어들이다. 또한 꽤 오래전에 발표된 함수형 프로그래밍 언어인 Erlang(얼랑), Haskell(해스켈), ML(엠엘)과 같은 언어들에 대한 이야기들도 심심치 않게 들리고 있다.
그렇다면, 이 함수형 프로그래밍 언어란 무엇일까? 왜 함수형 프로그래밍이 앞으로 다가올 획기적인 변화중에 하나일까? 함수형 프로그래밍 안에는 과연 무엇이 있는 것일까?
먼저, 함수형 프로그래밍이 앞으로 다가올 획기적인 변화중에 하나라는 것은 거의 틀림없는 사실이다. 왜 틀림없는 사실인지 대해서는 이해할 수 있고, 신뢰할 수 있는 이유들과 함께 이 글 후반부에서 이야기하도록 하겠다. 왜 그러한 이유들을 지금 당장 이야기 하지 않고, 이 글 후반부에서 설명하는 까닭은, 그러한 이유들을 이해하기 위해서는 함수형 프로그래밍이 무엇인지 먼저 알아야 하기 때문이다.
내가 지금 소개하려는 다음 문장이 수 많은 사람들을 당황하게 만들지도 모르겠다. 왜냐하면, 나는 지금 가장 단순한 방법으로 함수형 프로그래밍을 정의하려고 하기 때문이다. 나는 지금 함수형 프로그래밍을 줄이고 줄여서 가장 핵심이 되는 부분만 남겨놓으려고 한다. 사실 함수형 프로그래밍이라는 주제가 아주 방대하고, 아주 훌륭한 개념들로 가득 차 있기 때문에, 어떤면에서는 이렇게 단순하게 정의하는 것이 올바른 방법이 아니라는 사실도 알고 있다. 그리고 이 글에서 그런 훌륭한 개념들을 더욱 잘 이해할 수 있는 힌트를 얻게될 수도 있다. 하지만 지금 당장은 함수형 프로그래밍을 다음과 같이 단순하게 정의하려고 한다:
함수형 프로그래밍은 대입문(assignment statements) 없이 프로그래밍을 하는 것이다.
이런, 드디어 이 말을 해 버리고 말았군! 함수형 프로그래밍 언어를 사용하는 프로그래머들이 쇠스랑과 횃불을 들고 쫓아올지도 모르겠다. 너무 극단적인 정의를 사용해서 원래의 의미가 너무 많이 사라져버렸다고 나에게 항의를 할 것임이 분명하다. 반면, 함수형 프로그래밍이 진짜 무엇인지 배우기를 원하는 수많은 사람들은, 위에서 내가 말한 가장 단순한 정의는 말 자체가 안된다며 이 글을 그만 읽으려고 할지도 모른다. 즉, "세상에 어떻게 대입문을 사용하지 않고 프로그래밍을 할 수가 있어?"라고 생각하며 계속 이 글을 읽는것은 시간낭비라고 생각할 것이다.
아무래도 설명하기 가장 좋은 방법은 예를 보여주며 설명하는 것 같다. 다음에 나오는 자바 프로그래밍 언어로 작성된 정수의 제곱을 구하는 매우 간단한 프로그램을 보도록 하자.
public class Squint {
public static void main(String args[]) {
for (int i=1; i<=25; i++)
System.out.println(i*i);
}
}
아마도 위와 같은 간단한 프로그램이나 아니면 이와 비슷한 프로그램은 모두들 한번씩은 작성해 보았을 것이다. 나도 이런 종류의 프로그램을 백번도 넘게 만들어보았다. 보통 내가 새로운 언어를 배울때 두번째로 만들어보는 프로그램이고, 프로그래머들에게 새로운 언어를 가르칠 때 두번째나 세번째로 작성하도록 하는 프로그램이다. 모든 프로그래머들이 오래전부터 사용되어진 정수의 제곱을 구하는 이 프로그램을 분명하게 알고 있다!
하지만, 이제 이 프로그램을 조금 더 자세하게 들여다보도록 하자. 이 프로그램은 간단한 반복문을 가지고 있고, 그 반복문 안에서는 i 라는 이름의 변수가 1부터 25까지의 값을 가지고 계산되고 있다. 반복문이 실행될 때마다, 변수 i 는 새로운 값을 가지게 된다. 이것이 바로 대입(assignment)이다. 반복문 안에서 반복이 될때마다 변수 i 에는 새로운 값이 들어가게 된다. 컴퓨터의 메모리 구조 안으로 들어가서, 메모리에서 i 라는 변수의 값이 저장되어 있는 위치를 살펴본다면, 그 위치에 있는 메모리가 가지고 있는 값이 반복문이 반복될때마다 변하는 것을 볼 수 있다. 
Source: http://public.arnau-sanchez.com/ruby-functional/
바로 위에서 설명하고 있는 한 문단이 아주 당연한 것을 내가 길게 늘어놓고 쓴 것처럼 보일수도 있겠지만, 필요하다면 이 주제만으로 작성된 여러 논문들을 소개해 줄수도 있다. 다시 말해서, 동일성(identity), 값(value), 상태(state)와 같은 개념들이 우리에게 매우 직관적으로 보일지도 모르겠지만, 하지만 사실 이 개념들 하나 하나 그리고 그 개념들 사이에 있는 주제는 아주 방대한 분량이다. 이 이야기는 이쯤에서 그만하고 다시 이 글의 원래 주제인 함수형 프로그래밍으로 돌아가보자.
이번에는 위에서와 동일한 내용인 정수의 제곱을 구하는 것을 함수형 프로그램으로 작성한 예제를 살펴보도록 하자. 우리가 살펴보고자 하는 내용들은 모든 함수형 프로그래밍 언어에서 동일하게 적용되겠지만, 이 글에서는 함수형 언어인 Clojure(클로저)를 사용하도록 하겠다.
(take 25 (squares-of (integers)))
무엇인가 이상하다는 듯이 고개를 갸우뚱거릴 수도 있겠지만, 지금 보고 있는 한줄짜리 프로그램이 내가 보여주려고 하는 것이다. 물론 이 한줄짜리 프로그램은 실제 결과를 얻을 수 있는 실제 프로그램이라는 것도 확실하다. 결과를 봐야만 믿겠다고 한다면, 결과는 다음과 같다:
(1 4 9 16 25 36 49 64 ... 576 625)
이 프로그램은 단지 3개의 단어만을 가지고 있다: take, squares-of, 그리고 integers. 각각의 단어들은 특정 함수임을 의미한다. 각각의 단어들 앞에 있는 여는 괄호는 "여는 괄호 다음에 나오는 함수를 호출하는데, 함수 이름 이후부터 닫는 괄호가 나오기 전까지의 모든 것을 이 함수의 인수(arguments)로 사용해라."라는 의미이다.
처음에 나오는 take 함수는 정수 n 과 리스트 l , 이렇게 2개의 인수를 필요로 하고, 이 함수는 리스트 l 에서 처음부터 n 개의 항목을 반환한다. 그 다음에 나오는 squares-of 함수는 인수로 integers 라는 리스트를 받아서 이 리스트에 있는 모든 항목의 제곱으로 만들어지는 리스트를 반환한다. 마지막으로 나오는 integers 라는 함수는 1부터 시작해서 순차적으로 1씩 커지는 정수들의 리스트를 반환한다.
이게 전부이다. 이 프로그램을 통해서 1부터 시작해서 순차적으로 1씩 커지는 정수들의 제곱을 가지고 있는 리스트에서 처음 25개의 값들을 얻을 수 있다.
바로 위에서 언급한 마지막 문장은 매우 중요하므로 다시한번 읽어보자. 그 이전 단락에서 이 프로그램에서 사용된 서로 다른 3개의 함수에 대해서 정의했고, 이 정의들을 모아서 하나의 문장으로 만든것이 이전 단락에 나오는 마지막 문장이다. 새로운 전문용어(buzzword)를 받아들일 준비(팡파르가 울리고 있고, 반짝이 옷을 입고 있고, 창문에서는 오색 종이 테이프가 휘날리고 있고, 수많은 인파가 환호하는 그런 분위기?)가 되어있는지 모르겠지만, 우리는 이것을 "참조 투명성(Referential Transparency)"이라고 부른다.
참조 투명성(REFERENTIAL TRANSPARENCY)
간단하게 말하자면, 참조 투명성은 위에서와 같이 정의를 가지고 있는 문장의 어디에서나 동일한 단어라면 그 단어를 서로 맞바꾸어 놓아도 그 문장이 가지고 있는 원래 의미가 절대 변하지 않는 것을 의미한다. 이 글의 문맥에 맞게 다시 바꾸어 말하자면, 함수를 호출하는 부분을 그 함수가 반환하는 값과 맞바꾸어 놓아도 이 프로그램은 동일한 작업을 수행한다는 의미이다. 이 이야기를 실제 상황과 함께 좀 더 자세하게 살펴보자.
위에서 나왔던 (integers) 라는 함수를 호출하면 (1 2 3 4 5 6 ...) 라는 값을 반환한다. 아마도 이 문장에 대해서 여러 독자들이 의문을 제기할 것이 분명하다. 다시 말해서 이 리스트의 크기가 얼마나 큰가 하는 문제가 머릿속에서 맴돌것이다. 실제 정답은 "이 리스트는 내가 필요로 하는 만큼 크다."이다. 하지만 지금 상황에서는 이 리스트의 크기가 얼마나 큰가에 대해서는 너무 깊게 생각하지 말자. 실제로도 그렇게 동작하므로, 지금 당장은 (integers) 라는 함수는 (1 2 3 4 5 6 ...) 라는 값을 반환한다고 이해하고 받아들이도록 하자!
이제 위에서 작성했던 프로그램에서 (integers) 라는 함수 호출 부분을 이 함수가 반환하는 값으로 바꾸어보도록 하자. 즉, 프로그램이 다음과 같이 바뀌게 된다.
(take 25 (squares-of (1 2 3 4 5 6 ...)))
물론 복사와 붙여넣기 기능을 이용했다. 이것 또한 또 하나의 중요한 포인트이다. 참조 투명성은 함수가 반환하는 값을 복사해서, 그 함수를 호출하는 부분에 그대로 붙여넣기를 하는 것과 동일하다.
자 이제 다음 단계로 넘어가 보자. 그 다음에 나오는 함수 (squares-of (1 2 3 4 5 6 ...)) 는 인수로 받는 리스트에 있는 숫자들의 제곱을 가지는 리스트를 반환한다. 즉, 이 함수는 (1 4 9 16 25 36 49 64 ...) 라는 값을 반환한다. 이 함수의 호출 부분을 이 함수가 반환하는 값으로 바꾸면, 프로그램은 간단하게 다음과 같이 바뀐다:
(take 25 (1 4 9 16 25 36 49 64 ...))
자 마지막 남은 함수의 반환값은 당연히 다음과 같다:
(1 4 9 16 25 36 49 64 ... 576 625)
여기에서 위에서 작성한 프로그램을 다시한번 살펴보자:
(take 25 (squares-of (integers)))
자세히 보면 이 프로그램에는 변수가 없다는 사실을 알 수 있다. 결국 이 프로그램은 3개의 함수와 하나의 상수(constant)가 전부이다. 자바 프로그래밍 언어를 사용해서 변수를 사용하지 않고 정수의 제곱을 구하는 프로그램을 한번 작성해보아라. 물론 아마도 변수를 사용하지 않고도 프로그램을 작성할 수 있는 방법이 있을수도 있지만, 결코 자연스럽지도 않고 가독성이라는 측면에서도 위에서 작성한 프로그램처럼 높은 가독성을 가지기는 힘들 것이다.
하지만 더 중요한 것은, 컴퓨터의 메모리 안으로 들어가서 프로그램이 사용하는 메모리의 위치를 살펴보면, 프로그램이 사용하는 메모리들은 맨 처음 사용되어질 때 초기값으로 설정되고, 그 이후로 프로그램이 실행되는 동안에는 처음 값들이 변하지 않고 계속 유지된다는 사실이다. 다시 말해서, 한번 값이 할당된 메모리 위치에는 새로운 값이 다시 할당되지 않는다.
결국은 한번 값이 할당된 메모리 위치에는 새로운 값을 다시 할당하지 않는 것이 참조 투명성의 필수 조건이 된다. 이러한 필수 조건이 만족된다면, 어떤 함수를 수없이 호출하더라도, 항상 동일한 결과값을 얻게 된다. 프로그램이 사용하는 컴퓨터 메모리의 값이 프로그램이 실행되는 동안에는 절대 변하지 않는다는 사실은, (f 1) 이라는 함수를 호출할 때 몇 백번을 호출하든지에 관계없이 항상 동일한 결과값을 얻게된다는 이야기이다. 이 말은 결국 (f 1) 이라는 함수 호출 부분이 어디에 있든지, 몇번을 호출하든지 상관없이, 이 함수의 결과값으로 대체할 수 있다는 것을 의미한다.
또 다른 방법으로 설명하자면, 참조 투명성은 함수가 내가 의도하지 않은 값을 반환하는 경우(side effect: 부작용)가 발생하지 않는다는 의미이다. 즉, 한번 초기화된 변수에는 다른 값을 다시 대입할 수가 없으므로, 하나의 변수가 대입문의 사용으로 내가 기대하는 값이 아닌 다른 값을 가지고 있게 되는 전형적인 부작용의 발생을 원천봉쇄할 수 있다.
그렇다면 왜 함수형 프로그래밍의 이러한 특성이 중요할까? 참조 투명성이 왜 대단하다고 하는 걸까? 대입문 없이 프로그램 작성이 가능하다는 사실이 왜 중요할까?
대부분의 독자들이 지금 컴퓨터의 화면에서 이 글을 읽고 있을 것이다. 아니라 하더라도 아마 주위를 둘러보면 컴퓨터가 한 대 정도는 있을 것이다. 지금 사용하고 있는 컴퓨터나 주위에 있는 컴퓨터의 사양을 한번 보자. 몇 개의 코어(Core)를 가지고 있는가?
나는 지금 이 글을 4개의 실제 코어를 가지고 있는 맥북 프로에서 작성하고 있다. (맥북 프로의 사양을 보면 8개의 코어를 가지고 있다고 이야기하지만, "하이퍼쓰레딩(hyperthreading)"이라고 불리는 기술을 난 인정하고 싶지 않기 때문에, 4개의 코어를 가지고 있는 것으로 간주한다.) 내가 이전에 쓰던 노트북은 2개의 코어를 가지고 있었다. 그 이전에 사용하던 컴퓨터는 1개의 코어만을 가지고 있었다. 내가 다음 노트북을 사게 된다면 그 노트북은 하이퍼쓰레딩을 사용하지 않고, 실제로 8개의 코어를 가지고 있을 것이고, 그 이후에 구매하는 컴퓨터는 아마도 16개의 코어를 가지고 있을 것이다.
지난 40년간 불철주야로 수고한 하드웨어 엔지니어들의 노고로 컴퓨터의 속도가 이제 거의 빛의 속도 수준까지 도달했다. 이제 컴퓨터의 클럭(clock) 속도가 눈부시게 빨리 발전하기에는 무리가 있을 것이다. 지금까지 컴퓨터 클럭의 속도는 (나를 제외한) 대부분의 프로그래머들이 태어나서 살아왔던 시간동안, 매 18개월마다 두배씩 빨라졌다. 이런 눈부신 컴퓨터 속도의 향상은 한계의 부딪혀서 거의 멈춰있는 상황이고, 그런 눈부신 속도의 향상은 다시는 없을 것 같다.
컴퓨터의 초당 사이클수를 더 늘리려고 그렇게 고민해오던 하드웨어 엔지니어들이 이제는 하나의 칩(chip)에 더 많은 프로세서(processor)를 넣으려고 고민하면서 노력하고 있고, 끝없는 연구를 통해 더 많은 프로세서들이 하나의 칩에 들어가고 있고, 아직은 그 한계가 보이지 않을 정도로 계속 발전하고 있는 상황이다.

Source: http://itcprosolutions.com/skyrimguides/tweak_guide.htm
그렇다면 여기에서 이 글을 읽고 있는 유능하고 경험이 많은 프로그래머들에게 한 가지 질문을 해 보자: 만약 지금 사용하는 컴퓨터가 4096개의 코어들을 가지고 있다면, 어떻게 하면 컴퓨터의 모든 사이클(cycle)을 사용하면서 최대한의 이득을 얻을 수 있을까? 만약 동일한 메모리 시스템을 서로 먼저 사용하려고 다투고 있는 16384개의 프로세서들이 있는 컴퓨터에서 내 함수를 실행해야 하는 상황이라면, 어떻게 해야 함수의 실행 속도가 빨라질까? 모델(models), 뷰(views), 컨트롤러(controllers)가 65536개의 프로세서를 공유해야만 하는 상황에서, 응답 시간이 짧으면서도 유연한 구조를 가지는 웹 사이트를 어떻게 구축할 수 있을까?
사실 우리 프로그래머들은 2개 이상의 자바 쓰레드를 사용하는 경우에도 어떻게 해서든지 문제를 해결할 수도 있다. 그리고 실제 프로세서를 고기나 감자와 같은 음식으로 비유하면, 우리가 사용하는 쓰레드는 이유식 정도밖에는 되지 않는다. 지난 반세기(50년) 동안 우리 프로그래머들이 사용한 컴퓨터에서 실행되는 프로세스들은 실제로 동시에 실행되는 것이 아니라, 마치 실제로 동시에 실행되는 것처럼 느껴지게 만들어진 것이라는 사실을 이미 알고 있다.
자, 이제 진정한 동시성(simultaneity)의 놀라운 세계에 온 것을 환영한다! 그렇다면 어떻게 하면 이 놀라운 동시성의 세계를 누려볼 수 있을까?
대답은 의외로 간단한다. "모든 대입문의 사용을 포기해라! 그러면 놀라운 동시성의 세계로 들어갈 수 있다."
의심할 여지없이, 만약 메모리의 어떤 위치에 맨 처음 한번만 값을 할당하고 프로그램이 실행되는 동안 이 값을 변경하지 않는다면, 131072개의 프로세서들이 서로 먼저 이 메모리 값을 사용하려고 한다 할지라도 우리는 더이상 신경쓸 필요가 없다. 이전에는 두 개 이상의 프로세스가 동시에 하나의 메모리 값을 변경하는 경우를 방지하기 위해서 세마포어(semaphores)라는 기술을 사용해야만 했지만, 이제는 그럴 필요가 없다. 왜냐하면, 이제는 더 이상 메모리 값이 변경되는 일이 발생하지 않기 때문이다.
그래서 이것이 함수형 프로그래밍 언어가 가지고 있는 장점들 중에 하나이고, 가장 큰 장점이라고 볼 수 있다. 지금 우리 프로그래머들에게 빠른 속도로 다가오는 있는 기술들 중에 하나가 다중 코어(multi-core)이다. 더 늦기 전에 다중 코어에 대한 준비를 해 두는 것이 좋을것이다.

Source: http://www.hanselman.com/blog/HanselminutesPodcast171TheReturnOfUncleBob.aspx
로버트 마틴, Robert Martin (Uncle Bob) (@unclebobmartin),은 1970년부터 프로그래머로 일했다. 그는 현재 8th Light inc 라는 회사에서 Master Craftsman 이라는 직함으로 일하고 있으며, 전세계에서 열리는 수많은 컨퍼런스에서 인기를 누리는 발표자이며, 다음과 같은 수많은 책들을 쓰기도 했다: The Clean Coder, Clean Code, Agile Software Development: Principles, Patterns, and Practices, UML for Java Programmers. 그는 수많은 기사들을 쓰고, 논문들을 발표하면서도, 블로그 활동도 활발하게 하고 있다. 그는 C++ Report의 편집장(Editor-in-chief)으로 활동했고, Agile Alliance의 초대 의장으로도 활동했다.
로버트 마틴의 블로그를 방문하면, 이 글의 약간 다른 버전도 볼 수 있고, 함수형 프로그래밍에 대한 더 많은 이야기들을 접할 수 있다. 이 글에 대한 피드백이 있다면, 로버트 마틴에게 직접 보내도 되고, PragPub Magazine 포럼에 올려서 전 세계에 있는 수 많은 프로그래머들과 토론할 수도 있다.