- 자바스크립트에서의 함수형 프로그래밍을 위한 타입과 값
- JSON 데이터 타입
- undefined
- 열거 가능한 값, 컬렉션
- 컬렉션 순회
- 불변성
- 함수와 화살표 함수
- Promise
자바스크립트에서 함수형 프로그래밍을 하기 위해서는 타입과 값에 대한 추상을 정확히 해둘 필요가 있습니다. 또한 어떠한 전략을 취할 것인지를 정의해야합니다. 이것을 통해 다양한 함수 합성을 더 안전하게 할 수 있다는 믿음을 가질 수 있게 되며, 이것은 복잡성을 정복하고 안정성을 얻기 위한 중요한 기초가 됩니다.
좋은 전략을 정하기 위해서는 먼저 자바스크립트의 내장 타입과 값들의 특징에 대해 정확히 이해하는 것이 필요하고, 그 위에 함수형 프로그래밍 언어들이 가진 전략들을 잘 취사하여 올려야 합니다. 자바스크립트는 멀티패러다임 언어이며, 다른 함수형 언어들이 갖는 뼈대 전략이 없는 편입니다. 그렇지만 걱정하지는 않아도 됩니다. 자바스크립트는 높은 수준의 함수형 코드를 작성할 수 있게 하는 많은 기능을 지원합니다.
함수형 프로그래밍에서 데이터 타입에 대한 컨셉은 크게 두 가지 계열이 있고, 하스켈과 클로저를 통해 생각해볼 수 있습니다. 두 언어 모두 데이터 타입에 대한 명확한 철학을 지니며, 그 뼈대 위에 안전하게 합성하기, 모듈화 수준 높이기, 부수 효과를 없애거나 구분하기, 불변성 이루기, 동시성/병렬성 잘 다루기 등의 목적을 이루고 있습니다.
하스켈은 ADT와 타입 클래스를 통해 개발자가 새로운 타입이나 함수를 만들 때에도 안전한 합성을 가능하게 하며, 모나드를 통해 부수 효과를 철저히 관리하고, 강력한 타입 시스템을 통해 견고한 프로그램을 작성할 수 있게 합니다. 클로저는 적은 종류의 데이터 구조와 추상, 그리고 영속성(Persistent data structure)을 통해 높은 다형성/불변성을 지원하여 문구(form)/함수 조합을 극대화 할 수 있게 합니다.
그렇다면 자바스크립트에서의 함수형 프로그래밍에서는 어떤 전략을 취할 수 있을까요. 결론은 하스켈이나 클로저 스타일을 자바스크립트 위에 적용하는 것이겠지만 ADT, 지연성, 불변성, head, tail 등을 엄격하게 구현할수록 불필요한 자원 사용이 많아져 성능이 떨어지게 되기도 하고, 자바스크립트만이 가진 일부 장점이 사라지게도 되고, 다른 자바스크립트 세상과의 조합성이 낮아지게 되기도 하니, 균형을 잘 찾는 것이 필요합니다.
JSON으로 표현 가능한 값인 string, number, object, array, true, false, null은 자바스크립트에서 다루기 매우 좋고 빠릅니다. 이 값들은 JSON.strigify()
를 하기 위해 myObj.toJSON()
등의 작업을 할 필요가 없어, 중첩된 데이터를 다루거나 다른 환경으로 전송하거나 저장하고 꺼내쓸 때 매우 쉽습니다. 자바스크립트는 JSON을 만들기 위해 별도의 작업을 할 필요가 없다는 특별한 장점을 가지고 있습니다.
JSON을 중심으로 프로그래밍을 할 경우 적은 종류의 데이터 구조와 추상 전략을 취할 수 있습니다. 이는 조합성을 높이고, 함수의 재사용성을 높일 수 있게 합니다. 만일 백엔드 프로그래밍을 NodeJS로 하고 있다면, 프론트엔드와 백엔드에서 공유할 코드도 얻을 수 있습니다.
const json = {
string: "text",
number: 1,
object: {
key1: 'val1',
key2: 'val2'
},
array: [1, 2, 3],
others: [true, false, null]
}
undefined
는 정의되지 않은 변수나, 리턴 값이 없는 함수, 객체에 정의되지 않은 key 접근을 통해 얻게 됩니다. undefined
는 DB에 저장할 수도 없고, JSON으로 전달할 수도 없습니다. 서버 API를 통해 undefined
받게될 가능성도, undefined
를 다른 컴퓨터로 전달할 방법도 없습니다. 때문에 자바스크립트 외부 세상으로 부터 전달 받을 수 있는 값이 아닙니다.
런타임에서 직접 undefined
를 할당하지만 않는다면 undefined
를 '함수를 실행해서 아무것도 찾지 못했거나, 아무것도 하지 못했을 때'를 나타내는 구분자로 사용하기에 적합합니다. 실제로 이렇게 쓰이는 내장 함수나 라이브러리들이 많으며, 나머지 매개 변수의 구분자도 undefined
입니다.
[1, 2, 3].find(a => a > 3);
// undefined
const sub = (a = 0, b = 0) => a - b;
console.log(sub(10, 5)); // 5
console.log(sub(10)); // 10
console.log(sub(10, undefined)); // 10
Maybe, Just, Nothing
의 모든 가치를 담아올 수는 없지만, 자바스크립트의 값을 undefined
와 아닌 것으로 바라볼 수 있기 때문에 Nothing
의 역할을 대신할 수 있습니다. 대표적으로 find
같은 함수가 Maybe
를 리턴하는데, 자바스크립트에서 find
와 같은 함수를 구현할 경우, 아무것도 찾지 못했을 때 undefined
를 리턴하는 식으로 약속할 수 있습니다.
이 글에서 작성하는 함수형 코드에서는 '아무것도 찾지 못했거나, 아무일도 하지 못했을 때'를 구분하기 위한 값으로 undefined
를 사용하고자 합니다.
함수형 프로그래밍에서는 컬렉션을 많이 다룹니다. 특히 실무적으로는 더욱 그렇습니다. 컬렉션을 다루는 대표적인 함수인 map, filter, reduce, find
에서 사용할 수 있는 값들을 아래와 같이 정할 수 있습니다. iterator 혹은 Generator를 실행한 결과 값을 이용하면 무한 수열도 표현할 수 있습니다.
- JSON 데이터 타입 내의 object
- Array, Map, Set ...
- 그 외
[Symbol.iterator]()
가 구현된 모든 iterable과 iterator - Generator를 실행한 결과 값
for, while, i++, j++, length
등을 이용한 루프는 명령적이고 코드가 복잡합니다. i++
등을 직접 다루거나 순회하는 객체의 값을 변경하면 영향을 받기도 합니다. 스코프와 실행 컨텍스트와 관련된 실수도 일어나게도 됩니다. 게다가 비동기 상황 등을 고려하면 파편화가 심해집니다. 그렇다고 head, tail
로 추상화하기에는 성능 이슈가 만만치 않습니다.
ES6의 for...of
를 사용하면 이런 문제를 최소화할 수 있고 for...of
문은 상당히 선언적입니다. for...of
문은 Iterable/Iterator 프로토콜을 따르기 때문에 다형성이 높고, 비동기 상황에서의 재귀등의 복잡한 로직을 파편화 없이 구현할 수 있습니다. 따라서 자바스크립트에서 순회를 해야할 때는 for...of
를 사용하면 좋습니다. 그렇게하면 상대적으로 안전한 명령형 프로그래밍을 할 수 있게 되고, 함수형 프로그래밍에 사용될 함수들을 간결한 코드로 구현할 수 있습니다.
function map(f, coll) {
const res = [];
for (const val of coll) res.push(f(val));
return res;
}
console.log(
map(a => a + 10, [1, 2])
);
함수형 프로그래밍에서 불변성은 매우 중요한 키워드입니다. 자바스크립트에는 불변성을 지원하는 값이 존재하지 않습니다. Object.freeze
는 객체를 불변하도록 만들지만, 객체의 값이 변하지 않는 것은 불변성의 일부일 뿐입니다. 불변성에서 더 중요한 내용이자 목적은 불변적으로 데이터를 '변형'하는 것입니다. 이것은 한 번 정의한 값을 직접 변경하지 않고, 변형된 새로운 값으로 만드는 것을 말합니다. 자바스크립트에서 이것을 달성하기 위해 잘 숙지해야 할 것은, 값이 전달/할당될 때 원시 값의 경우는 복사를, 객체의 경우는 레퍼런스 사본을 생성한다는 규칙입니다. 이 규칙들을 각기 다르게 다루는 함수와 연산자 등을 잘 조합하여 불변성을 지켜야 합니다.
const list = [1, 2];
console.log([...list, 3]);
// [1, 2, 3]
console.log(list);
// [1, 2]
const object = { a: 1, b: 2 };
const object2 = { ...object, ...{ b: 3 } };
console.log(object2, object2.b); // {a: 1, b: 3} 3
console.log(object, object.b); // {a: 1, b: 2} 2
const object3 = Object.assign({}, object, { a: 2 });
console.log(object3, object3.a); // {a: 2, b: 2} 2
console.log(object, object.a); // {a: 1, b: 2} 1
자바스크립트의 함수는 매우 강력하고, 화살표 함수는 간결합니다. 일급 함수, 클로저, 전개 연산자, 기본 매개 변수, 나머지 매개 변수, 구조 분해 등의 기능으로 무장한 자바스크립트의 함수는 높은 수준의 함수형 프로그래밍을 가능하게 하고, 멋진 표현력을 갖고 있습니다.
const curry2 = f => (..._) => _.length < 2 ? (..._2) => f(..._, ..._2) : f(..._);
const add = curry2((a = 0, b = 0) => a + b);
const add10 = add(10);
console.log(add10(5)); // 15
console.log(add10(10)); // 20
console.log(add10(undefined)); // 10
Promise는 보통 소개된 것보다 훨씬 많은 가능성을 지닌 값이며, 자바스크립트에서의 동시성/비동기 프로그래밍을 지탱하는 기반입니다. ES6+에서는 */yield, async/await와 함께 사용될 수 있습니다.
자바스크립트에서 비동기 프로그래밍은 매우 중요하며, bluebird, js-csp, co, RxJS 등의 다양한 비동기 해법들이 제시되고 있습니다. 이 글에서는 일급 객체로서의 Promise 활용을 통해 다양한 함수의 인자와 결과 값으로 전달하면서 높은 수준의 동시성 프로그래밍을 구현하는 사례들을 다룰 것입니다.