2. Properties of Functional Programming

선언형 언어

함수형 언어는 선언적입니다. 이는 우리가 흔히 아는 명령형 언어와 대비됩니다. 명령형 언어에서는 어떠한 계산을 수행하기 위하여 저수준 동작을 명시해야 합니다. 다음 예시를 보죠.

def sum55():
  s = 0
  for i in range(1,11):
    s += i
  return s

이미 프로그래밍을 경험해 본 사람들이라면 위의 코드는 너무나 당연하게 보입니다. 보통은 명령형 프로그래밍으로 시작을 하기 마련이니까요. 위의 코드를 뜯어보면 s라는 문자가 보입니다. 이는 for 문으로 i를 순회하면서 그 값을 담기 위해 도입한 것인데 값을 담아야 하는 s와 직접 순회명령을 짰기에 이는 저수준 동작을 명시한 코드입니다. 흔히 쓰이는 명령형 언어의 과정은 다음과 같습니다.

  • 값을 어디에 보관할지 선언합니다. (값의 대입)
  • 어디로부터 값을 가져올지 명시합니다. (참조)
  • 다음에 어떤 절차로 진행할 것인지를 명시합니다. (절차의 호출)

하지만 이미 C언어가 만들어진지 40년이 지난 지금, 우리가 저수준의 코드를 명시해야할 이유는 딱히 없습니다.

함수형 언어인 Haskell은 위의 코드를 다음과 같이 적습니다.

sum55 = foldr (+) 0 [1..10]

위의 코드엔 값을 보관할 변수도 필요없고 순회를 하라고 방법을 명시할 필요도 없습니다. 그저 필요한 규칙만 선언해주는 것으로 모든 결과가 도출됩니다.

좀 더 재미있는 예시를 보죠. 다음은 4색 정리를 Prolog라는 선언형 언어로 푼 예시입니다.

Coloring by Prolog

여기서 특히 재미있는 부분은 다음의 코드입니다.

coloring(A,B,C,D,E,F) :-
  different(A,B),
  different(A,C),
  different(A,D),
  different(A,F),
  different(B,C),
  different(B,E),
  different(C,D),
  different(C,E),
  different(D,E),
  different(E,F).

이 코드는 아무런 해결방법을 제시하지 않고 그저 무엇이 다른지 선언만 해줍니다. 여기에 색깔의 다름을 지정하는 코드만 넣고 코드를 실행하게 되면 놀랍게도 답이 나옵니다. 이처럼 선언형 언어는 컴퓨터의 동작방법을 지정하지 않고 최소한의 규칙만 선언해주는 방식으로 코드를 실행하게 됩니다.

사기 아닌가요?

언뜻보면 사기처럼 보이지만 함수형 프로그래밍의 역사를 보면 충분히 가능하다는 것을 짐작할 수 있습니다. 함수형 언어의 목적 중 가장 중요한 것은 컴퓨터의 구현 방법에 구애받지 않고 문제를 충분히 추상화시켜 그 알고리즘에만 집중하게 하는 것입니다. 이를 위해서 함수형 언어들은 인류가 만든 가장 강력한 추상화 방법인 수학을 사용합니다. 역사에 대해서는 다음에 다시 다루겠습니다.

불변성

앞의 장에서 언급했다시피 함수형 언어는 수학에서와 동등한 순수 함수를 사용합니다. 이러한 순수 함수에서는 그 어떤한 부작용도 용납되지 않는데 대표적인 것이 값의 변경입니다. 위에서도 적어 놓은 다음 코드를 봅시다.

# python3
def sum55():
  s = 0
  for i in range(1,11):
    s += i
  return s

위의 코드는 i가 1 부터 10까지 순회하면서 더한 값을 s에 저장하는데, 문제는 s와 i가 변경된다는 것입니다. 수학에서 사용하는 순수한 함수는 한 번 정의해놓은 값은 영원히 그 값이어야 합니다. 물론 여기서 사용되는 s와 i는 그 변수의 변경이 함수의 결과에 영향을 주진 않습니다. 함수를 여러 번 호출해도 결과는 항상 같죠. 하지만 이는 저수준 동작을 명시한 것이고 함수형 프로그래밍의 지향점은 추상화에 있기 때문에 이러한 동작은 권장되지 않습니다. 재미있게도 함수형 언어별로 이러한 동작에 대해 취하는 행동이 다른데, 순수 함수형 언어들은 이를 절대 허용치 않는 반면, 다중 패러다임 언어는 함수형을 메인으로 내세울지라도 대개 이를 허용해줍니다. 이에 대해서는 함수형 언어의 종류를 다룰 때 다시 언급하겠습니다.

자, 일단 우리는 함수형 프로그래밍을 공부하는 중이니 순수 함수로 생각하고 접근하겠습니다. 따라서 그 어떤 값의 변경도 배제할텐데, 처음에는 매우 불편할 수 있습니다. 예를 들어 과학계산에서 자주 사용되는 다음 코드를 보죠.

# Julia
a = []
for i = 1:100
  if iseven(i)
    append!(a, i)
  end
end

빈 벡터 a를 만들고 1부터 100까지 순회하면서 짝수만 담는 코드입니다. 값의 변경이 불가능하다 했으니 당연하게도 벡터의 크기를 변경하는 것 역시 허용되지 않습니다. 따라서 위의 코드는 함수형 프로그래밍에 해당되지 않습니다.

만일 최적화에 관심이 있는 사람이라면 위의 코드를 다음과 같이 고칠 것입니다.

# python3
import numpy as np
a = np.zeros(50)
for i in range(1,101):
  if i%2 == 0:
    a[i/2] = i

벡터의 크기를 동적으로 놓는 것보다 크기를 미리 정해놓고 대입하는 것이 훨씬 빠르죠. 하지만 이 코드 역시 i의 변경은 차치하더라도 0으로 초기화한 벡터 a를 다른 값으로 변경하고 있습니다. 이 역시 함수형에서는 허용되지 않습니다. 즉 성분이 0인 벡터나 행렬은 영원히 0으로 남습니다.

이쯤되면 함수형 언어에서는 도대체 어떻게 코딩을 한다는 건지 감이 오지 않을 겁니다. 아직 보여준 것은 아무것도 없는데 제약만 엄청 쌓여가고 있으니까요. 일단, 위의 코드를 함수형으로 짠다면 다음과 같습니다.

-- Haskell
a = filter even [1..100]

혹은 Julia로 짜면 다음과 같습니다.

# Julia
a = filter(iseven, 1:100)

Python 코드는 다음과 같구요.

# python3
a = list(filter(lambda x: x%2==0, range(1,101)))

공통점은 모두 한 줄의 간결한 코드이며 그 어떤 값의 변경도 전제하지 않았다는 것입니다. 이 코드들을 아직은 이해 못하는 것이 정상입니다. 뒤에서 모두 다룰테니 천천히 읽으시면 어느 순간 이해가 될 것입니다. (A-ha moment)

일단 가장 중요한 것은 사실 전달보다 동기부여니 다음 장에서는 "왜 함수형 프로그래밍을 사용해야 하는가"에 대해 다루겠습니다.

results matching ""

    No results matching ""