함수형 패러다임의 꽃: 함수 합성(composition)
함수형 패러다임에서 최우선 설계 원칙으로 삼아진다고 하는 함수들의 합성에 대해서 설명한다.
합성
이전에 설명했던 compose
함수를 말한다.
1 | const compose = (...fns) => |
합성은 왜 하는걸까?
프로그램을 간결하고 실용적으로 작성할 수 있게 한다. 합성이 되므로 함수를 부담 없이 나눌 수 있게 되어 더 작고 의미있는 단위의 함수를 더 편하게 작성할 수 있다. 이렇게 합성된 함수는 가독성이 좋다. 아무래도 객체지향 패러다임을 강하게 지원하는 언어들에선 함수 합성이 쉽지 않다. 애초에 순수 함수를 작성하기도 쉽지 않다. public static
으로 도배할 순 없기 때문이다.
합성함수의 결합법칙
함수 합성은 수학에서의 합성함수
와 같이 결합법칙이 성립한다. compose(f, compose(g, h)) === compose(compose(f, g), h)
가 성립한다. Javascript 상에서 생성되는 함수가 동일하다는 것이 아니라, 그 실행 결과가 언제나 같다는 뜻이다.
결합법칙이 무슨 소용일까
합성한 함수들을 재귀적으로 합성한 경우, 결합법칙
을 적용하면 결과 예측과 리팩토링 시에 유용하다.
그 예로, 아래 세가지 loudLastUpper
함수는 동일하다. 더 작고 더 의미있는 함수로 정의할수록 재사용성과 가독성은 높아진다.
버전 1
1 | const loudLastUpper = compose( |
버전 2 (리팩토링)
1 | const last = compose(head, reverse); |
버전 3 (리팩토링)
1 | const last = compose(head, reverse); |
쓸모있고 재미있는 디버깅 방법
합성 함수를 디버깅하는 재밌는 방법이 있다. 흔히 trace
라 부르는 유명한 함수인데, 항등함수(const pass = x => x;
)에 console.log
만 추가한 함수이다.
1 | const trace = (tag) => (x) => { |
당연하게도 trace
함수는 순수하지 않다. console
를 사용하기 때문이다.
간단한 함수 합성 예제
이하의 예제 코드는 아래의 cars
객체를 대상으로 한다.
1 | [ |
예제 1
각 함수들의 정의는 이 문서를 참고하라. 이 문서는 ramdajs documentation과도 호환된다.
1 | const isLastInStock = (cars) => { |
예제 2
1 | const average = (xs) => |
예제 3
1 | const fastestCar = (cars) => { |
함수 합성 예제 프로그램
스펙
검색어에 대응하는 URL을 생성한다.
flicker API를 호출한다.
결과 JSON에서 이미지 링크를 추출한다.
이미지를 HTML에 표시한다.
구현 코드
예제의 스펙에서 보았듯, 2단계 API 호출과 4단계 이미지 표시는 순수하지 않다. 일단 순수하지 않은 함수를 같이 사용하면서 예제를 구현한다.
1 | // 유틸 함수 선언 |
compose와 map 리팩토링
아주 간단한 리팩토링이다. 같은 배열에 대해 map
을 여러 번 실행하기보다, 순서를 유지한 채로 매 원소에 대해 map
할 함수를 합성해서 한 번에 실행하게 되면 반복 횟수를 줄일 수 있다.
1 | // from |