함수형 자바스크립트의 기초

2017년 03월 15일


함수형 자바스크립트 라이브러리

자바스크립트의 기본 기능만으로도 충분히 함수형 프로그래밍의 기본 요소들을 구현할 수 있었습니다.

이렇게 하나하나 만들어 보는것도 함수형 프로그래밍을 배울때 도움이 되겠지만, 중요한건 구현이 아닌 추상화입니다.

다행히도 앞으로는 라이브러리를 사용할 것이기 때문에 지금과 같이 어려운 코드를 직접 구현하는 일이 그렇게 많지 않습니다.

Underscore

언더스코어는 굉장히 오래된 라이브러리입니다. 함수형 프로그래밍과 기본 자료형을 위한 다양한 함수를 제공합니다.

이 강좌에서는 사용하지 않습니다.

Lodash

아마 jQuery 다음으로 많이 쓰는 라이브러리가 아닐까 싶습니다.

기본적으로 함수형 프로그래밍을 위한 라이브러리는 아니지만 기능의 일부로 함수형 프로그래밍을 지원합니다.

굉장히 많은 기능을 제공하는 훌륭한 라이브러리지만 여기서는 사용하지 않습니다.

Ramda

앞으로 강좌에서 사용할 라이브러리입니다.

앞서 알아본 라이브러리에 비해 상대적으로 최근에 나왔고, 함수형 프로그래밍을 제외한 기능은 거의 제공하지 않습니다.

Ramda는 fantasy-land라는 스펙을 따릅니다. fantasy-land를 구현한 다른 라이브러리들과 호환되는 특징이 있습니다.

Sanctuary

또 다른 함수형 프로그래밍 라이브러리입니다. Ramda에 비해 타입이 엄격하고 함수의 타입체계가 잘 잡혀있습니다.

이 라이브러리도 fantasy-land와 호환되기 때문에 Ramda와 함께 사용하는 것이 가능합니다.

설치 및 임포트

Ramda는 패키지 매니저를 이용해 설치할 수 있습니다

yarn add ramda

실수로 rambda를 받지 않도록 조심합니다. 설치가 완료됐다면 다음과 같이 임포트 해서 사용할 수 있습니다.

import * as R from 'ramda';
import { compose } from 'ramda';

jQuery가 모듈 이름으로 $를 쓰는 것 처럼 Ramda는 R을 사용합니다.

default 객체로 익스포트하고 있지 않기 때문에 * as R로 임포트하거나, 필요한 함수만 임포트해서 사용합니다.

예시

비교

R.equals 함수를 통해 두 값을 비교할 수 있습니다. 항상 값을 통해 비교하기 때문에 기존의 ==나 ===와 동작이 다릅니다.

==나 ===보다 사람의 생각과 비슷하게 동작합니다. 배열이나 객체를 비교할때 참조를 비교하는 것이 아닌 값을 비교합니다.

import * as R from 'ramda';

1 ==  '1'; // true
1 === '1'; // false
R.equals(1, '1'); // false
R.equals(1, 1);   // true

const xs = [1, 2];
const ys = [1, 2];
const zs = xs;

xs == ys; // false
xs == zs; // true
ys == zs; // false

xs === ys; // false
xs === zs; // true
ys === zs; // false

R.equals(xs, ys); // true
R.equals(xs, zs); // true
R.equals(ys, zs); // true

const ww = { b: 20, a: 10 };
const xx = { a: 10, b: 20 };
const yy = { a: 10, b: 20 };
const zz = xx;

ww == xx; // false
ww == yy; // false
ww == zz; // false
xx == yy; // false
xx == zz; // true
yy == zz; // false

ww === xx; // false
ww === yy; // flase
ww === zz; // flase
xx === yy; // false
xx === zz; // true
yy === zz; // false

R.equals(ww, xx); // true
R.equals(ww, yy); // true
R.equals(ww, zz); // true
R.equals(xx, yy); // true
R.equals(xx, zz); // true
R.equals(yy, zz); // true

객체는 key의 순서에 상관없이 비교하는 특징이 있습니다

함수 합성

직접 만들었던 compose는 이미 Ramda에서 제공하고 있습니다.

왼쪽이 먼저 적용되도록 compose와 반대로 합성하는 R.pipe도 제공합니다.

R.pipe는 R.compose보다 직관적이기 때문에 이후 강좌에서는 함수의 합성이 필요할때 대부분 R.pipe를 이용합니다.

import * as R from 'ramda';

const inc = (x) => x + 1;
const double = (x) => x * 2;
const square = (x) => x * x;

const f = R.compose(inc, double, square);
const g = R.pipe(inc, double, square);

f(1); // inc(double(square(1))) -> 3
g(1); // square(double(inc(1))) -> 16

커링

일반 함수를 커링된 함수로 바꿔주는 함수는 Ramda에서 이미 제공하고 있습니다.

이전에 만든 curry와 비슷한 R.curry를 사용합니다.

import * as R from 'ramda';

const add = (x, y) => x + y;
const foo = R.curry(add);
const bar = foo(1);

foo(1, 2); // 3
bar(2);    // 3

Ramda는 가변인자를 받는 함수를 제외한 모든 함수가 커링돼있습니다. 덕분에 다음과 같이 함수를 고차함수로 사용하기 편합니다.

import * as R from 'ramda';

const x = 0;
const y = 0;
const isZero = R.equals(0);

R.equals(x, y); // true
isZero(x);      // true
isZero(y);      // true