기능적 프로그래밍

이 주제에서 우리는 파이썬이 어떻게 또 다른 프로그래밍 스타일을 지원할수 있는지 살펴본다: 기능적 프로그래밍(Functional Programming(FP)). 되부름에서와 마찬가지로 이것은 진짜로 진보된 주제이며 우리는 지금현재로는 그것을 무시하고 싶을 정도이다. 기능적 테크닉은 매일매일의 프로그래밍에서 사용하고 있는 것이며 FP의 지지자들은 이것이 소프트웨어를 개발하기 위한 근본적으로 더 좋은 방법이라고 믿고 있다.

기능적 프로그래밍이란 무엇인가?

기능적 프로그래밍은 명령적(혹은 절차적) 프로그래밍과 혼동되어서는 안된다. 객체지향형 프로그래밍과도 같지 않다. 그것은 다른 어떤 것이다. 그렇게 급진적이지는 않다, 왜냐하면 우리가 탐험하려는 그 개념들은 친숙한 프로그래밍 개념이다. 단지 다른 방식으로 표현되었을 뿐이다. 문제를 푸는데 이러한 개념들이 적용되는 방식뒤에 숨겨진 철학은 또한 약간 다르다.

기능적 프로그래밍은 표현식expressions에 관한 것이 모두이다. 사실 FP를 기술하는 또 다른 방법은 그것을 표현 지향적 프로그래밍expression oriented programming이라고 이름지을수도 있다. 왜냐하면 FP에서는 모든것이 표현으로 수렴한다. 여러분은 표현식이 변수와 처리방법들의 집합이라는것을 기억해야 하며 그 집합은 단 하나의 값을 결과로 한다는 것을 기억해야 한다. 그런식으로 x == 5 는 불리언 표현식이다. 5 + (7-Y) 는 수학적 표현식이다. 그리고 string.search("Hello world", "Hell") 는 문자열 표현식이다. 마지막의 것은 또한 string 모듈안에 있는 함수 호출이다 그리고, 우리가 앞으로 보게 되듯이, 함수는 FP에서 대단히 중요하다 (여러분은 이미 그 이름으로부터 예측했을 것이다!).

함수는 FP에서 객체로 사용된다. 다시 말하면 함수들은 때때로 프로그램안에서 다른 변수들과 똑 같은 방식으로 넘겨진다. 우리는 우리의 구이(GUI) 프로그램에서 이러한 예를 보았다. 거기에서 우리는 함수의 이름을 버튼 콘트롤의 명령어(command) 속성으로 할당했다. 우리는 사건 처리자 함수를 객체로 취급하고 그 함수의 참조점을 그 버튼에 할당했다. 우리의 프로그램에서 함수를 넘겨준다는 이러한 개념은 FP로 가는 열쇠이다.

기능적 프로그램은 또한 대단히 리스트 지향적인 경향이 있다.

마지막으로 FP는 문제 풀기의 방법보다는 문제 풀기의 정의에 집중한다. 다시 말하면 기능적 프로그램은 해법의 매카니즘에 집중하기 보다는 문제가 해결되도록 기술해야만 한다. 이런식으로 작동하는 것을 목표로 하는 여러 프로그래밍 언어들이 있는데, 가장 널리 쓰이는 것 중의 하나는 Haskell 이다. Haskell의 웹사이트(www.haskell.org)에는 하스켈 언어뿐만 아니라 FP의 철학을 기술하는 수많은 논문들이 있다. (나의 개인적 견해로 보면 이 목표는 FP의 옹호자들에 의해 약간은 과장된 감이 있다. 그렇지만 칭송할 만하다.)

순수한 기능적 프로그램은 프로그램의 의도를 요약하는 표현식을 정의함으로써 만들어진다. 표현식의 각 단어들은 이번에는 문제의 특징을 기술하는 (아마도 다른 표현으로 캡슐화된) 서술문이다.그리고 이러한 용어들의 각각을 평가함으로써 결과적으로 해법을 산출한다.

자, 이것이 그 이론이다. 작동하는가? 그렇다, 때로는 잘 작동한다. 어떤 종류의 문제에는 그것이 자연스럽고 강력한 테크닉이다. 불행하게도 많은 다른 문제에는 그것은 수학적 원리에 크게 영향을 받는, 아주 추상적인 사고의 스타일을 요구한다. 결과적으로 생기는 코드는 가끔 평범한 프로그래머로서는 도저히 읽기가 어렵다. (그러나) 그 코드는 또한 동등한 명령적 코드보다 아주 대단히 더 짧으며 더 신뢰할 수 있다. 전통적인 많은 명령적 프로그래머 혹은 객체 지향적 프로그래머들이 FP를 연구하도록 한 원동력은 바로 이러한 후자의 성질 때문이다. 진심으로 모든 사람이 동의하지는 않겠지만 모든 사람들이 사용할 수 있는 강력한 도구들이 있다.

어떻게 파이썬은 그렇게 하는가?

파이썬은 프로그래밍에 기능적으로 접근할수 있도록 해주는 약간의 함수들을 제공한다. 그것들이 파이썬으로 아주 쉽게 작성될 수 있다는 점에서 이러한 함수들은 정말로 편리한 사양이다. 그렇지만 더 중요한 것은 그것들이 제공된 묵시적인 의도 이다. 다시말해서 원하기만 한다면 파이썬 프로그래머가 FP 방식으로 작업할 수 있도록 해주는 것이다.

우리는 제공된 함수들 약간을 살펴볼 것이다. 그리고 우리가 다음과 같이 정의한 샘플 데이타 구조에서 그것들이 어떻게 작동하는지 살펴볼 것이다:

spam = ['pork','ham','spices']
numbers = [1,2,3,4,5]

def eggs(item):
    return item

map(aFunction, aSequence)

이 함수는 파이썬의 함수, aFunctionaSequence의 각 구성원에 적용한다.
다음의 표현식은 :

L = map(eggs, spam)
print L
이 표현식은 L 에 반환되는, (이경우에는 spam과 동일한) 새로운 리스트를 결과로 낸다.

우리는 다음과 같이 작성하여 같은 일을 할수 있다:

for i in spam:
   L.append(i)
print L

그렇지만, map 함수로 우리는 내포된 코드 블록의 필요성을 제거할 수 있다. 한가지 관점에서 본다면 그것은 프로그램의 복잡성을 한 수준 감소시킨다. 우리는 그것을 FP의 되부름의 주제로서 살펴볼 것이다. FP함수에 이것을 사용하면 코드의 복잡성이 블록을 제거함으로써 상대적으로 감소한다.

filter(aFunction, aSequence)

이름이 암시하는 바와 같이 filter는 그 함수가 참값으로 반환하는 연속열에서 각 요소를 추출한다 우리의 숫자의 목록을 생각해보자. 만약 우리가 오직 홀수만 가지는 새로운 리스트를 만들고자 한다면 우리는 그것을 다음과 같이 함으로써 산출할 수 있다:

def isOdd(n): return (n%2 != 0) # use mod operator
L = filter(isOdd, numbers)
print L

다른 식으로 우리는 작성할수 있다:

def isOdd(n): return (n%2 != 0)
for i in numbers:
   if isOdd(i):
      l.append(i)
print L

다시 또 주목할 것은 관례적인 코드는 두 레벨의 들여쓰기를 요구하여 같은 결과를 달성한다는 것이다. 또 증가하는 들여쓰기는 코드의 복잡성이 증가한다는 표시이다.

reduce(aFunction, aSequence)

reduce 함수는 그 의도가 약간은 불분명하다. 제공되어진 함수를 통하여 요소들을 결합함으로써 이 함수는 리스트를 한개의 값으로 줄여준다 예를 들어 우리는 리스트의 값들을 합할 수 있고 그 합계를 다음과 같이 반환할 수 있다:

def add(i,j): return i+j
print reduce(add, numbers)

전과 같이 우리는 이것을 더욱 전통적인 방식으로 다음과 같이 할 수 있다.:

L = [] # empty list
res = 0
for i in range(len(numbers)): # use indexing
    res = res + numbers[i]
print res

이것은 이경우에는 같은 결과를 산출하기는 하지만 그렇게 항상 간단하지는 않다. reduce실제적으로 하는 일은 그 연속열의 첫 번째 두 구성원을 넘겨 주고 주어진 함수를 호출하는 것이며 두 번째 항목을 그 결과로 바꾸는 것이다. 다른 말로 하면 더욱 줄어진 정확한 표현은 다음과 같다:

L = numbers[:] # make a copy of original
while len(L) >= 2:
   i,j = L[0],L[1] # use tuple assignment
   L = [i+j] + L[2:]
print L[0]

한번 더 우리는 들여쓰기된 코드의 블록의 필요성을 피함으로써 코드의 복잡성을 감소시키는 FP의 테크닉을 살펴본다.

람다 lambda

지금까지 여러분이 이 예제들에서 깨닫았을 지도 모르는 하나의 사양은 FP 함수들로 넘겨지는 그 함수들이 대단히 짧은 경향이 있으며, 때로는 겨우 한 줄의 코드라는 것이다. 수 많은 아주 작은 함수들을 정의하는 수고를 덜기 위하여 파이썬은 FP에 또 다른 도움을 제공한다 - lambda

람다는 익명함수(이름없는 함수 anonymous function)라고 지칭되곤 하는 용어로서, 익명함수는 이름없이도 마치 하나의 함수인것 처럼 실행될 수 있는 코드 블록이다. 람다는 적법한 파이썬 표현식이 나타날 수 있는 곳이면 프로그램안의 어디에나 정의될 수 있으며, 그것이 뜻하는 것은 우리가 그것들을 우리의 FP 함수안에서 사용할 수 있다는 것을 의미한다.

람다는 다음과 같은 것이다:

lambda <aParameterList> : <a block of code using the parameters>

그런식으로 위의 add 함수는 다음과 같이 다시 작성될 수 있다.:

add = lambda i,j: i+j
그리고 우리는 정의를 하는 라인을 reduce함수에 대한 호출안에 람다를 만들어서, 완전하게 피할 수 있다. 다음과 같이:
print reduce(lambda i,j:i+j, numbers)

비슷하게 우리는 우리의 mapfilter의 예제를 다음과 같이 다시 작성할 수 있다:

L = map(lambda i: i, spam)
print L
L = filter(lambda i: (i%2 != 0), numbers)
print L

다른 구조들

물론 이러한 함수들이 자신만의 목적을 위하여는 유용한 반면에 파이썬 안에서 완전한 FP 스타일을 허용하기에는 그것들은 충분하지 않다. FP 접근방식으로 언어의 제어구조가 또한 변경될 필요가 있으며 혹은 적어도 대치될 필요가 있다. 이것을 달성하는 한 가지 방법으로는 파이썬이 불리언 표현식을 평가하는 방식에서 오는 부가효과를 적용하는 것이다.

단축 회로 평가

파이썬은 불리언 표현식에 대하여 단축 회로 평가 short circuit evaluation를 사용하기 때문에 이러한 표현식의 어떤 속성들이 이용될 수 있다. 단축-회로 평가에 관하여 요약하면 : 불리언 표현식이 평가될 때 그 평가는 왼쪽의 표현식에서 시작하여 오른쪽으로 처리하다가, 마지막 출력결과를 결정하기 위하여 더 이상 평가할 필요가 없으면 평가를 중지한다.

약간의 특별한 예제를 택하여 어떻게 단축 회로 평가가 작동하는지 살펴보자:

>>> def TRUE:
...   print 'TRUE'
...   return 1 # boolean TRUE
...
>>>def FALSE:
...   print 'FALSE'
...   return 0 # boolean FALSE
...

먼저 우리는 그것들이 언제 실행될지 우리에게 알려주는 함수 그리고 그들의 이름값을 반환하는 함수, 두 개의 함수를 정의한다. 이제 우리는 이것을 사용하여 어떻게 불리언 표현식이 평가되는 지를 탐험할 수 있다:

>>>print TRUE() and FALSE()
TRUE
FALSE
0
>>>print TRUE() and TRUE()
TRUE
TRUE
1
>>>print FALSE() and TRUE()
FALSE
0
>>>print TRUE() or FALSE()
TRUE
1
>>>print FALSE() or TRUE()
FALSE
TRUE
1
>>>print FALSE() or FALSE()
FALSE
FALSE
0

주목하라. AND 표현식의 첫 번째 부분이 참일 때만 그럴 때만 두 번째 부분이 평가된다. 만약 첫 번째 부분이 거짓이라면 그러면 두 번재 부분은 평가되지 않는다. 왜냐하면 그 표현식 전체가 참일수는 없기 때문이다.

마찬가지로 OR 를 기초로한 표현식에서도 만약 첫 번째 부분이 참이라면 두 번째 부분은 평가될 필요가 없다. 왜냐하면 전체는 반드시 참이기 때문이다.

우리는 이러한 속성들을 사용하여 마치 행위(behaviour)와 같이 분기를 다시 만들 수 있다.
예를 들어 우리가 다음과 같은 코드 조각을 가지고 있다고 하자:

if TRUE(): print "It is True"
else: print "It is False"

우리는 그것을 FP 스타일 구조로 바꿀 수 있다:

V =  (TRUE() and (print "It is True")) or \
     (print "It is False")

이 예제들을 시험해 보고 그리고나서 TRUE() 에 대한 호출을 FALSE()에 대한 호출로 대치하여 보라. 그런식으로 불리언 표현식에 단축 회로 평가를 사용함으로써 우리는 전통적인 if/else 서술문을 우리의 프로그램에서 제거하는 길을 발견한다. 여러분은 기억할지도 모르겠다. 되부름을 주제로 한 장에서 우리는 되부름이 회돌이 구조를 대체하는데 사용되어질 수 있다는 것을 관찰했었다. 그런식으로 이것들을 결합하여 작동시키면 우리의 프로그램에서 모든 전통적인 제어 구조를 제거할 수 있으며, 그것들을 순수한 표현식으로 대체할 수 있다. 이것이 순수한 FP 스타일의 해법을 향한 큰 발걸음이다.

결 론

이 시점에서 여러분은 무엇이 정확히 이 모든 것의 요점인가 궁금할지도 모르겠다? 여러분은 혼자가 아니다. FP가 많은 컴퓨터 과학 학계에 (특히 수학자들에게) 호소하는 바가 있지만 대부분의 현업에 종사하는 프로그래머들은 FP 테크닉을 잘 사용하지 않는 것 같으며 일종의 혼합적인 방식으로 그것을 더 전통적인 명령적 스타일과 혼합하여 사용하는 것 같다. 그것을 프로그래머들은 적절하다고 느낀다.

여러분이 리스트에 있는 요소들에 대하여 처리를 해야 한다면 'map, reduce 혹은 filter'와 같은 함수들은 해법을 표현하는 자연스러운 방법인 것처럼 보인다. 그것들을 어쨋든간 사용하라. 심지어는 자주 여러분은 되부름이 관례적인 회돌이 보다는 더 적절하다는 것을 발견한다. 심지어는 더욱 자주 여러분은 관례적인 if/else 보다는 단축 회로 평가를 사용하게 될 것이다 - 특히나 표현식안에서 요구된다면. 어떠한 프로그래밍 도구를 사용함에 있어서, 철학을 동반하지 말아라, 그보다는 어떤 도구라도 그 작업에 바로 쓸수 있는 가장 적당한 것을 사용하라. 적어도 여러분은 대안들이 존재한다는 것을 알지 않는가!

람다lambda를 설명하기 위한 마지막 요점이 있다. FP 영역의 바깥쪽에 있는 한 지역에서 람다가 실제적 사용처를 찾는다. 람다는 구이(GUI)프로그래밍에서 사건 처리자를 정의 하는데에 사용된다. 사건 처리자는 자주 대단히 짧은 함수이거나, 혹은 아마도 단순히 몇개의 단단히 엮인 인수 값들을 가지고 어떤 큰 함수를 호출하거나 한다. 어느 경우에나 람다 함수는 사건 처리자로 사용될 수 있어서 많은 수의 작은 개별적 함수들을 정의할 필요가 없으며 이름영역을 겨우 단 한 번만 사용되는 이름들로 채울 필요가 없다. 람다 서술문은 함수 객체를 반환한다는 것을 기억하라. 이 함수 객체는 위젯에 보내어지는 객체이며 사건이 발생하는 순간에 호출된다. 만약 여러분이 우리가 어떻게 Tkinter에서 버튼위젯을 선언하는지를 기억한다면, 그러면 람다는 다음과 같이 보일 것이다:

def write(s): print s
b = Button(parent, text="Press Me",
           command = lambda : write("I got pressed!"))
b.pack()

혹은 묶기 테크닉을 사용하면, 그것은 사건 객체를 인수로 보낸다:

def write(s): print s
b = Button(parent, text="Press me")
b.bind(, lambda ev : write("Pressed"))

허, 이것이 바로 기능적 프로그래밍 그것을 위한 것이다. 여러분이 더 깊이 그것을 들여다 보고 싶으면 많은 다른 자원들이 있다, 몇몇은 아래에 나열 한다.

또다른 자원들

만약 누구라도 좋은 참조점이 있다면 나에게 전자메일 한 통을 아래 링크로 보내라.


Previous Next Contents
 

여러분이 이 페이지에 대하여 질문 혹은 제안사항이 있으면 다음 주소를 전자메일을 보내라: agauld@crosswinds.net