Currying 과 Pipeline operator

썰풀기전에

ES9에 추가될지도 모르는 Pipeline operator를 nodejs에서 사용해보고 싶어서 이것저것 만져보며 느낀 점을 끄적여 본 포스팅이다. 프론트엔드 개발자라면 친숙한, 아니 친숙해야 하지만 결코 사랑할 순 없는 Webpack v4와, ES6 이후의 ECMAScript 명세들을 개발자들이 편하게 사용할 수 있게 해주는 Babel v7을 사용했으나 해당 부분에 대해서는 그리 거창한 이야기는 없다.

Currying

함수형 프로그래밍 기법 중에 커링(Currying)이라는 것이 있다.
매개변수가 둘 이상 정의된 함수를 다룰 때, 함수 호출 시점에서 인자를 하나만 보내면 이후에 나머지 인자들을 보내는 시점까지 함수 호출을 완료하지 않도록 지연 평가(Lazy Evaluation)하는 것을 말한다.
function add(a, b) { return a + b; }
위의 함수를 다음처럼 만드는 것이 커링이다.
const add = (a, b) => a + b; const curriedAdd = R.curry(add); const f = curriedAdd(1); f(2); //=> 3
자바스크립트에서는 add(1)로 호출하면 두번째 인자를 undefined로 전달하였다고 바로 평가하고 1+undefined를 return하기 때문에 NaN값이 나오는데,
Ramda의 R.curry 메소드로 add 함수에 커링을 적용한 curriedAddcurriedAdd(1)로 호출하면 두번째 인자를 받을 때까지 함수 호출의 평가를 미룬다.
따라서 f(2)는 다음과 같은 의미이기도 하다. curriedAdd(1)(2)
여기서 세번째 줄을 f라고 명명한 이유는, 커링이라는 개념에 좀 더 집중하게 만들기 위해서인데, 사실 const addOne = curriedAdd(1) 라고 정의할 수도 있다. 그럼 addOne(2)3 을 반환한다는 것이 좀 더 직관적으로 다가오지만 전체 코드가 아닌 해당 라인만 놓고 보면 이건 partial application이라고 해서 커링과 관련성도 높고 생김새도 비슷하지만 엄밀히 다른 무언가이기 때문에 위의 전체 코드(라봐야 네줄이지만)에 비중을 둘 수 있도록 의도한 함수명이다.
굳이 이렇게까지 해서 커링을 써야하나 의문이 들 수도 있는데, 함수형 언어에서는 본디 데이터를 불변(immutable) 상태로 유지하고 함수들의 조합을 통해 데이터를 제어하기 때문에 커링을 일종의 인터페이스로 여기고 함수들을 조합할 수 있는 상태로 만드는 작업이라고 생각하면 된다.
다음 코드는 커링을 왜 쓰냐는 질문에서 엄청 자주 볼 수 있는 답변. 하스켈 코드 혹은 하스켈 코드와 유사한 의사 코드로 생각된다.
map (add 2) [1, 2, 3]
이걸 ES6 코드로 바꾸면
const add = (x, y) => x + y; const curriedAdd = R.curry(add); const addTwo = curriedAdd(2); const array = [1, 2, 3].map(x => addTwo(x));
아무래도 하스켈에서는 위와 같은 장황한 단계를 거치지 않고 바로 map (add 2) [1, 2, 3] 이처럼 구현이 되니 커링의 효과가 극대화 되겠지만 자바스크립트에서도 그럭저럭 선언형 프로그래밍의 모양새를 갖추기 때문에 가독성은 올라간다고 생각한다.

Pipeline operator

커링 등으로 함수를 매개변수를 하나만 가지는 함수들로 나누어도 자바스크립트에서는 이것들을 조합해서 사용할 수 있는 방법들이 없다. 결국 f(g(z(value))) 식으로 사용하거나 Ramda의 pipe같은 메소드를 이용해서 조합해야 하는데, 마치 es6의 array 메소드로 추가된 map, filter, reduce를 체이닝하듯 함수들을 파이프라인으로 잇는 것이 es9에 추가될지도 모르는 |> ( Pipeline operator ) 로 가능하다.
더 자세한 내용은 해당 github 리포지토리를 참조하길. 아래 코드도 여기서 가져왔다.
그래서 이 코드를 돌려보기 위해 babel v7과 webpack v4를 사용했다.

Webpack 설정

npm run dev 로 실행면 dist 디렉토리에 app으로 시작하는 js 파일이 생긴다. 이미 bundlingtranspiling이 완료된 파일이기 때문에 node 로 실행시켜주면 된다.
package.json을 보면 알겠지만 babel-loader를 제외한 babel 관련 모듈은 모두 @babel 하위 모듈이다. 이거 때문에 엄청 삽질했다(...)

babel 만으로 실행

만약 웹팩을 쓰지 않고 babeltranspile만 해서 실행시키고 싶다면 package.json을 다음과 같이 하거나, root 디렉토리.babelrc 파일을 생성하고 package.json키 babel의 value들을 넣어주어야 한다.
원래 babel-node 커맨드를 사용하려면 babel-cli를 설치해주면 되었는데 babel v7부터 명세가 바뀐건지 @babel/cli@babel/node는 원래 분리되서 업데이트되는 건지는 모르겠는데 @babel/node 를 설치해주어야 "dev": "babel-node index.js" 스크립트를 실행할 수 있다.