1. What is Functional Programming?

함수형 프로그래밍이 뭐죠?

함수형 프로그래밍이란, 이름 그대로 함수 위주의 프로그래밍 패러다임을 의미합니다. 조금 더 덧붙이자면, 프로그램 자체를 함수로 보는 관점이라 할 수 있습니다.

프로그래밍 패러다임에는 크게 3가지가 있습니다.

  • 명령형 프로그래밍
  • 객체지향 프로그래밍
  • 함수형 프로그래밍

이들은 프로그램이나 어떠한 문제 자체를 볼 때, 어떤 관점을 취할 것인가로 나누어집니다. 명령형은 프로그램을 "컴퓨터가 수행해야 할 명령의 나열"로 보고, 객체지향은 프로그램을 "객체와 그것들과의 메시징"으로 봅니다. 마찬가지로 함수형은 프로그램을 "함수"로 취급하는 패러다임입니다.

음? 원래 프로그래밍은 함수로 구성되지 않나요?

맞습니다. 다만, 함수형 프로그래밍은 일반적인 프로그래밍에서의 함수가 아닙니다.

조금 다른 함수

함수형 프로그래밍의 함수는 기존 프로그래밍에서 일반적으로 사용하는 함수와는 조금 다릅니다. 함수형에서의 함수는 오히려 수학에서 쓰이는 함수와 같습니다. 두 개가 어떻게 다르냐구요? 우리는 다음과 같은 함수는 사용하지만

다음과 같은 함수는 사용하지 않죠.

이를 코드로 예를 들어 봅시다.

# python3
def double(x):
  return 2 * x

위의 파이썬 함수는 당연하게도 수학에서의 함수와 같습니다. 단순히 x를 넣으면 2x가 나오는 함수니까요.

# python3
def doubleWithHello(x):
  print("Hello, World!")
  return 2 * x

위의 함수는 수학에서의 함수로 나타낼 수 없습니다. x를 넣으면 결국 2x가 나오긴 하지만, 중간에 "안녕, 세계!"를 외쳐야만 하죠. 수학은 마법이 아니기에 주문 영창은 없으니 이는 수학에서의 함수와 다릅니다.

이번엔 또 다른 예시를 봅시다.

# python3
import random

def randVal():
  return random.random()

여기서는 아무런 주문 영창이 없습니다만, 문제가 하나 있습니다. 함수를 호출할 때마다 값이 달라진다는 것이죠. 우리가 배운 수학에서의 함수의 정의는 같은 정의역의 원소에 대해서는 항상 같은 값이 나와야 하니 이것 역시 수학에서의 함수와 다른 경우입니다.

자, 그럼 우리는 과학자들답게 함수를 효과적으로 구분할 수 있는 정의를 도입해야 합니다.

참조 투명성(Referentially Transparent)

A mode of containment φ is referentially transparent if, whenever an occurrence of a singular term t is purely referential in a term or sentence ψ(t), it is purely referential also in the containing term or sentence φ(ψ(t)).1

정의(참조투명성) 모든 프로그램에 대해 어떤 표현식 e를 모두 그 표현식의 결과로 치환해도 프로그램에 아무 영향이 없다면, 그 표현식 e는 참조에 투명하다.

간단히 예를 들면, 1+2+5에서, 1+2를 결과값인 3으로 치환해도 식에는 아무런 영향이 없으므로 더하기 함수는 참조투명합니다.

역시나 코드로 예를 들어봅시다.

// C++
#include <iostream>

using namespace std;

int multiply(int a, int b) {
  return a * b;
}

int main() {
  cout << multiply(3, 4) + multiply(1, 2) << endl;

  return 0;
}

위의 코드에서 multiply에 집중해봅시다. multiply(3, 4)의 결과가 12라는 것은 쉽게 알 수 있는 결과인데, main에서의 multiply(3, 4)를 12로 바꾼다고 해도 프로그램의 출력값은 달라지지 않습니다. 따라서 이 함수는 참조투명한 함수입니다.

이번엔 다음 예시를 봅시다.

# Julia
mutable struct Circle
  radius :: Float64
end

function extend(C::Circle)
  C.radius += 1
end

extend 함수는 일단, return값이 없으므로 수학에서의 함수와는 궤를 달리한다는 것을 쉽게 간파할 수 있습니다. 또한, 실행시점에서의 C값에 따라 결과가 변하므로 정적인 값으로 치환할 수 조차 없죠. 따라서 참조투명하지 않은 함수입니다.

순수(Pure)

참조 투명성은 뭔가가 모자라 함수의 순수성을 정의하기에 이르렀습니다. 정의는 간단합니다.

정의(순수) 어떤 함수 f(x)가 모든 입력값 x에 대하여 참조 투명하면 함수 f는 순수하다.

따라서 위의 multiply함수는 순수한 함수이며 extend함수는 순수하지 않은 함수입니다.

부작용(Side Effect)

위에서 몇몇 예시를 본 결과, 순수한 함수와 순수하지 않은 함수는 대개 콘솔에서의 입출력, 파일 입출력, 상태 변화 등 현실과 맞닿은 기능이라는 것을 알 수 있습니다. 이러한 입출력 및 상태 조작들을 일컬어 부작용(Side Effect)라고 부릅니다. 정의에 따라 조금씩 다르지만 현재는 부작용을 없애거나 거의 없앤 프로그래밍 언어들을 함수형 언어라 말합니다.


함수형 언어는 무엇인가요?

이제 함수형 프로그래밍 패러다임에 대해서는 어느 정도 익혔으니 본격적으로 함수형 언어가 무엇인지에 대해 알아봅시다. 일반적으로 함수형 언어란, 함수가 1급 시민(first-class citizen)으로 다뤄지는 언어를 의미합니다. 이때, 일급 시민이란 아래의 특징들을 가진다는 뜻입니다.

  • 리터럴(Literal)2이 있다.
  • 실행시간에 생성할 수 있다.
  • 변수에 넣어서 취급할 수 있다.
  • 절차나 함수에 인수로서 제공할 수 있다.
  • 절차나 함수의 결과로서 반환할 수 있다.

보다 쉽게 말하자면, 함수를 값과 동일하게 취급할 수 있는 언어를 함수형 언어라 말합니다.

간단히 예를 들어봅시다.

-- Haskell
-- String을 받아서 String에서 String으로 가는 함수를 반환함
hello :: String -> (String -> String) 
hello text = (text ++)

main :: IO ()
main = do
  -- hello가 String을 받아서 함수를 반환하므로 test는 함수!
  let test = hello "Hello, "
  print $ test "World!" -- "Hello, World!"
# Julia
function hello(text::String)
  return (text2::String) -> text * text2 # 람다식 (아직 이해할 필요는 없습니다)
end

test = hello("Hello, ") # hello는 함수를 반환!
println(test("World!")) # "Hello, World!"
# Python3
def hello(text):
  return lambda text2: text + text2 # Lambda Expression!

test = hello("Hello, ")
print(test("World!")) # "Hello, World!"

이제 대부분의 언어들은 업데이트를 통해서 위와 같은 기능들을 갖고 있습니다. 하지만 그들을 모두 함수형 언어라고 부르지 않는데, 이는 함수형 언어를 분류하는 기준이 또 존재하기 때문입니다. 이에 대해서는 뒤에서 다루도록 하겠습니다.

1. Word and Object(1960)
2. 리터럴이란, 소스 코드의 고정된 값을 대표하는 용어입니다. 1은 int의 리터럴이고 "hi"는 문자열의 리터럴이죠. 눈치 채셨겠지만 함수의 리터럴은 람다식입니다.

results matching ""

    No results matching ""