함수형 프로그래머가 되고 싶다고? (Part 3)

Hun-yong Song edited this page Oct 28, 2016 · 2 revisions

이 문서는 https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-3-1b0fd14eb1a7#.ii6um685m 를 번역한 내용입니다.

functinoal

함수형 프로그래밍 개념을 처음으로 접해본다는 것 자체가 중요하다. 그리고 이 단계가 가장 어렵기도 하다. 하지만, 올바른 관점으로 접근한다면 그렇게 어렵진 않다.

지난 글: 파트1, 파트2

합성 함수

lego

프로그래머들은 게으르다. 개발하고 테스트하고 배포하는 과정을 매번 반복하는 것을 원치 않는다.

우리는 항상 일을 한 번에 끝낼 수 있는 방법을 찾고, 한 번 만든 걸 어떻게하면 코드를 재활용할 수 있을지에 대해 생각한다.

코드 재활용은 보기에는 좋아 보이지만, 달성하기 어려운 개념이다. 코드를 작성할 때 너무 구체화하면 재활용할 수 없다. 그렇다고 너무 느슨한 코드로 바꾸면 처음에 적용한 곳에서 그 코드를 사용하기 어려워진다.

코드를 작고 재활용이 가능한 조각으로 쪼개면 그 코드는 더 복잡한 기능의 조각이 될 수 있다. 마치 건물의 벽돌처럼 말이다. 그렇기 때문에 위의 경우를 포함한 둘 사이의 균형이 필요하다.

함수형 프로그래밍에서 함수는 벽돌과 같은 개념이다. 우리는 매우 구체적인 기능을 하기위해 함수를 작성한다. 그리고 그 함수들을 마치 레고 블럭처럼 조합한다.

이런 걸 합성 함수라고 부른다.

그래서 합성 함수는 어떻게 동작할까? 아래 두 개의 자바스크립트 함수로부터 시작해보자.

var add10 = function(value) {
    return value + 10;
};
var mult5 = function(value) {
    return value * 5;
};

이 코드는 너무 장황하니까 화살표 함수로 다시 작성해보자.

var add10 = value => value + 10;
var mult5 = value => value * 5;

괜찮아졌다. 이제 우리는 한 개의 값을 입력받고, 그 값에 10을 더하고, 5와 곱한 값을 반환하는 새로운 함수가 필요하다고 가정해보자.

var mult5AfterAdd10 = value => 5 * (value + 10)

굉장히 간단한 예제 임에도 불구하고, 우리는 아무런 사전 지식 없이 이 함수를 작성하는 것을 원치 않는다. 첫째로 우리는 괄호를 빼먹는 것과 같은 실수를 할 수 있다.

둘째로 우리는 이미 10을 더해주는 함수를 가지고 있다. 그리고 5를 곱해주는 함수도 가지고 있다. 우리는 이미 쓰여진 코드를 작성했다.

그래서 add10과 mult5를 사용해서 새로운 함수를 만들어 보자.

var mult5AfterAdd10 = value => mult5(add10(value));

우리는 기존 함수를 사용해서 mult5AfterAdd10라는 함수를 만들었다. 하지만 이거보다 더 나은 방법이 있다.

수학에서 f ∘ g 는 합성 함수다. 그리고 이 개념은 "g와 합성된 f"로 읽을 수 있다. 그래서 (f ∘ g)(x)는 x 값과 함수 g가 호출된 다음 함수 f를 호출한 것과 동일하다. 혹은 간단하게 f(g(x))와 같다.

우리의 예제에서, 우리는 mult5 ∘ add10를 가졌다. 따라서 새로운 함수의 이름을 mult5AfterAdd10로 지었다.

그리고 이게 바로 우리가 한 것이다. 입력값과 add10을 호출한 후에 mult5를 호출했다. 혹은 간단하게 mult5(add10(value))와 동일하다.

네이티브 자바스크립트는 합성 함수를 지원하지 않기 때문에 Elm을 보자.

add10 value =
    value + 10
mult5 value =
    value * 5
mult5AfterAdd10 value =
    (mult5 << add10) value

Elm에서는 << 라는 이항 연산자로 함수끼리 합성한다. 이 연산자는 데이터가 어떻게 흘러가는지 시각적으로 알려준다. 첫째로 입력값이 add10에게 전달되고, 결과값이 mult5에 전달된다.

mult5AfterAdd10의 괄호 부분을 살펴보자. 즉 (mult5 << add10). 입력값을 적용하기 이전에 먼저 함수끼리 합성이 된다는 것을 확신하기 위해 괄호가 존재한다.

이 방법으로 많은 함수들을 합성할 수 있다.

f x =
   (g << h << s << r << t) x

x는 t 함수로 전달된 뒤, 결과값이 r로 전달된 후, 그 결과값이 s로 전달된다. (나머지 생략) 만약 이와 비슷한 행위를 자바스크립트에서 했다면, 대략 g(h(s(r(t(x))))) 이런 식으로 생겼을 것이다.

Point-Free Notation

free

함수를 작성할 때 매개변수를 정의하지 않는 Point-Free 표기법이라고 불리는 방식이 있다. 처음에 이 방식은 이상하게 보이겠지만, 계속 사용하다보면 이 방식의 간결함에 감사하게 될 것이다.

mult5AfterAdd10를 보면 value가 2번 명시된 것을 알 수 있다. 매개변수 리스트에서 한 번, 그리고 사용이 되는 부분에서 한 번 말이다.

-- 1개의 매개변수를 기대하는 함수
mult5AfterAdd10 value =
    (mult5 << add10) value

하지만 이 매개변수는 필요하지 않는다. add10이 같은 매개변수를 기대하기 때문이다. 아래의 point-free 방식은 동일한 기능을 한다.

-- 마찬가지로 1개의 매개변수를 기대하는 함수
mult5AfterAdd10 =
    (mult5 << add10)

point-free 방식에는 많은 장점이 있다.

첫째로 불필요한 매개변수를 사용하지 않아도 된다. 그렇기 때문에 그것들에 대한 이름을 생각하지 않아도 된다.

둘째로, 훨씬 간결해지기 때문에 읽기에도 편하다. 이 예제는 간단하지만, 매개변수가 엄청 많은 함수를 상상해보자.

Trouble in Paradise

sea

지금까지 합성 함수가 어떻게 동작하는지, 그리고 간결함과 유연성을 위해 Point-Free 표기법으로 어떻게 함수를 정의해야하는지에 대해 살펴봤다.

이제 약간 다른 시나리오에서 이 아이디어들을 사용해보자 그리고 어떻게 되는지 살펴보자. add10을 add로 치환했다고 가정하자.

add x y =
    x + y
mult5 value =
    value * 5

이 2개의 함수로 어떻게 mult5After10을 작성할까?

계속 읽어나가기 전에 해결 방법에 대해 생각해보자. 너무 깊히는 말고 간단하게 생각해보자.

음 만약 실제로 생각해봤다면 아래와 같은 해결책을 생각해낼지도 모른다.

-- This is wrong !!!!
mult5AfterAdd10 =
    (mult5 << add) 10 

하지만 정상동작하지 않을 것이다. 왜그럴까? add 함수는 2개의 매개변수를 받기 때문이다.

만약 이게 Elm이라서 명확하지 않다면, 자바스크립트에서 작성해보자.

var mult5AfterAdd10 = mult5(add(10)); // this doesn't work

이 코드는 잘못됐다. 왜 그럴까?

왜냐하면 add 함수는 2개의 매개변수를 받게 되어있는데, 1개만 전달되니까 부정확한 결과가 mult5에 전달되었다. 그 결과 잘못된 결과가 산출되었다.

사실 Elm에서 잘못된 코드는 이미 컴파일 단계에서 알려준다. (Elm의 가장 큰 장점 중 하나)

다시 시도해보자.

var mult5AfterAdd10 = y => mult5(add(10, y)); // not point-free

이건 point-free 방식이 아니지만, 사실 이 방식으로도 가능은 하다. 하지만 더이상 함수를 결합하지 않을 것이다. 새로운 함수를 작성할 것이다. 또한, 함수가 점점 더 복잡해지면 (예를 들어 mult5AfterAdd10를 또다른 함수와 조합할 때) 진짜 문제에 처하게 될 것이다.

그래서 함수 합성의 장점은 한계가 있으므로 이 두 개의 함수를 결합(marry)할 수 없게 되었다. 강력한 기능이지만, 너무 아쉽다.

어떻게 이걸 해결할까? 이 문제를 해결하기 위해서 어떤 것이 필요할까?

음. 만약 add 함수에 1개의 매개변수만 보내고, mult5AfterAdd10 호출될 때 두 번째 매개변수를 전달한는 방법이 있다면 정말 훌륭할 것 같다.

커링이라는 방법으로 해결할 수 있다.

아이고 머리야!!!

d

오늘은 이걸로 충분하다.

다음 문서부터는 커링, 함수형 함수(map, filter, fold 등), 참조 투명성 등에 대해 이야기 할 예정이다.

위키

기술 문서
Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.