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

2017년 03월 15일


유용한 문법 알아보기

자바스크립트의 수 많은 기능 중, 상대적으로 새롭고 함수형 프로그래밍에 유용한 기능 몇 가지를 살펴봅니다.

상수

자바스크립트는 명령형을 뼈대로 함수형의 기능을 추가했기 때문에 기본적으로 사이드 이펙트를 허용합니다.

이미 선언된 값을 변경하거나, 콘솔에 문자열을 출력하는 등 다양한 사이드 이펙트가 자연스럽게 발생합니다.

상수에 대한 기능을 사용하면 값의 변경에서 발생하는 사이드 이펙트를 어느정도 방지할 수 있습니다.

const

가장 자주 사용하는 기능입니다. const 키워드를 통해 통해 변수가 참조하는 객체를 고정할 수 있습니다.

하지만 참조하고 있는 객체의 값을 변경하는 것을 방지하지 않습니다.

const a = { b: 20 };

a = 30;   // Error
a.b = 30; // Ok

문법에서 강제하는 사항은 아니지만 강좌에선 필드만 변경 가능한 객체를 let으로 표현합니다. 한 눈에 가변성을 파악할 수 있습니다.

freeze

참조하고 있는 객체의 필드를 바꾸는 것은 Object.freeze 함수를 통해 방지할 수 있습니다.

이 함수는 참조하고 값의 모든 필드를 바꿀 수 없도록 불변성을 추가합니다.

사용법은 다음과 같습니다. 호출한 인자를 불변으로 만들고 다시 반환합니다.

const x = { b: 20, c: { d: 40 } };
Object.freeze(x); // x에 불변성 추가

const y = Object.freeze({ b: 10, c: { d: 30 } }); // 불변객체 y 생성

x = 30;     // const  Error
x.b = 30;   // freeze Error
y.b = 30;   // freeze Error
x.c.d = 30; // Ok

freeze는 얕게 동작합니다. 중첩된 필드는 여전히 가변입니다. 때문에 모든 필드를 불변으로 만들기 위해선 재귀적으로 freeze를 호출해야합니다.

다음 예시는 재귀적으로 freeze를 호출하는 deepFreeze입니다. Mozilla MDN의 구현을 빌려왔습니다.

function deepFreeze(obj) {
  var propNames = Object.getOwnPropertyNames(obj);

  propNames.forEach(function(name) {
    var prop = obj[name];
    if (typeof prop == 'object' && prop !== null)
      deepFreeze(prop);
  });

  return Object.freeze(obj);
}

freeze를 호출하는 것은 번거롭기 때문에 자주 사용하지는 않습니다.

대부분의 경우에 const를 사용하는 선에서 끝내고, 값을 변경하지 않도록 주의하면서 코드를 작성합니다.

함수

ES6에선 함수의 생성과 인자를 다루는 문법을 제공합니다.

함수형 프로그래밍에서는 작고 조합가능한 함수를 만드는 일이 잦기 때문에, 간결한 함수 문법을 사용하면 코드가 훨씬 읽기 편해집니다.

화살표 함수

앞으로 가장 많이 사용할 기능입니다. function 키워드와 비슷하게 함수를 생성하는 문법입니다.

화살표 함수는 인자와 함수 몸통을 화살표(=>)로 이어서 표현합니다.

const addA = function (x, y) {
  return x + y;
};

const addB = (x, y) => {
  return x + y;
};

const addC = (x, y) => x + y;

화살표 함수의 특징으로 하나의 표현식으로 끝나는 경우에는 중괄호와 return을 생략할 수 있다는 것이 있습니다.

짧은 함수를 만드는 경우 function 키워드를 사용하는 방법에 비해 가독성이 뛰어납니다.

화살표 함수는 function 키워드와 다르게 함수가 선언된 위치를 기준으로 this가 바인딩됩니다.
때문에 this에 의존하는 메서드를 만들거나, 콜백으로 메서드를 전달할때 기존의 function 키워드 방식과 다르게 동작합니다.
하지만 앞으로 다룰 함수형 프로그래밍에서는 this에 접근할 일이 별로 없기 때문에 여기서는 자세히 다루지 않습니다.

화살표 함수가 갖는 또 다른 특징으로 우측 연관이 있습니다. 대입연산자와 비슷하게 연쇄적으로 표현할 수 있습니다.

이러한 특징에 괄호와 return을 생략할 수 있다는 특징을 조합하면, 다음과 같이 함수를 반환하는 함수의 연속을 간결하게 표현할 수 있습니다.

let a, b, c;

a = b = c = 4;     // 4
a = (b = (c = 2)); // 2

const f = (x) => (y) => (z) => x + y + z;
const g = (x) => ((y) => ((z) => x + y + z));

f(1)(2)(3); // 6
g(1)(2)(3); // 6

전개 연산자

전개 연산자는 배열과 값을 넘나듭니다. 특정한 타입에 대해 직접 동작을 정의할 수 있는 특징이 있습니다.

이 전개 연산자는 다양한 사용법이 있습니다. 이중 가장 기본적인 사용법은 가변인자를 표현하는 것입니다.

함수를 선언할 때 인자의 이름앞에 ...를 붙이면, 호출할 때 사용한 인자들을 배열로 전환합니다.

기존의 arguments를 이용해서 흉내낼 수 있지만, 전개연산자를 사용하면 용도가 명확하게 보이기 때문에 더 낫습니다.

const foo = (...args) => args;

foo(0, 1, 2, 3);     // [0, 1, 2, 3]
foo([0, 1], [2, 3]); // [[0, 1], [2, 3]]

const bar = (...args) => args.length;
bar(0, 1, 2, 3);     // 4
bar([0, 1], [2, 3]); // 2

인자를 배열로 돌리는 것과 반대로, 배열을 인자로 펼칠 수 있습니다.

문법은 똑같이 변수의 이름 앞에 ...를 붙이지만, 함수의 정의가 아닌 호출에서 사용합니다.

Function.apply를 사용한 것과 비슷합니다.

const xs = [1, 2, 3];

const sum3 = (x, y, z) => x + y + z;
sum3(1, 2, 3); // 6
sum3(...xs);   // 6
sum3.apply(null, xs); // 6

이 밖에도 Symbol.iterator로 동작을 정의하거나, 해제 할당을 할 수 있는 등 다양한 기능이 있습니다.