
함수형 자바스크립트: 모던 웹 개발에 충실한 실전 함수형 프로그래밍 안내서
[함수형 프로그래밍과 ES6+ | 네이버 테크톡]](https://tv.naver.com/v/5328303)
외부에서 관찰 가능한 부수 효과가 제거된 불변 프로그램을 작성하기 위해 순수함수를 선언적으로 평가하는 것
함수형 프로그래밍은 선언적 프로그램 패러다임에 속한다. 컴퓨터에게 원하는 작업을 어떻게 수행하는지를 상세히 이르기보다는, 내부적인 제어 흐름이나 상태 변화를 밝히지 않으면서 로직을 표현식(expression)으로 나타낸다.
var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// 명령형, 절차적 프로그래밍
for(let i = 0; i < array.length; i++) {
array[i] = array[i] * 2;
}
// 함수형 프로그래밍
array.map(function(value) {
return value * 2;
});
// ES6부터 등장한 화살표 함수
array.map((value) => value * 2);
+) 함수를 인수로 전달하거나 반환하는 함수를 고차 함수(high-order function)이라고 한다.
함수형 프로그래밍은 순수 함수로 구성된다.
함수형 프로그래밍에서 한 번 선언한 데이터는 불변해야 한다. 원본 변수의 값을 수정하거나 내부 속성을 변경하는 것은 허용되지 않는다.
Javascript에서는 불변 데이터를 흉내내기 위해 const를 쓰거나, 클로저 패턴, Object.writable이나 Object.freeze와 같은 메타 속성을 제어하는 방식을 사용할 수 있다.
애플리케이션이 정답을 도출하는 데까지 거치는 경로를 제어 흐름(flow control)이라고 한다.
명령형 프로그래밍은 if문을 활용한 분기와 for, while등의 루프로 제어하는 반면, 함수형 프로그래밍은 연속된 블랙박스 연산을 제어하는 방식을 쓴다.
객체에 속한 여러 메서드를 단일 구문으로 연쇄 호출하는 객체 지향 패턴을 체이닝(chaining)이라고 한다.
단순 중첩 함수로만 작성된 코드는 가장 안쪽에 감싸진 함수부터 한꺼풀씩 벗겨내는 방식으로 읽어야 해서 가독성이 훨씬 떨어진다.
// 스트링 객체에 속한 메서드 체이닝
'Functional Programming'.toUpperCase().split('').reverse().join('');
// 만약 그냥 함수형로 썼다면 이렇게 됐을 걸...
join(reverse(split(toUpperCase('Functional Programming'), '')), '');
체이닝된 메소드를 수행할 때마다 반환값으로 결과물이 담긴 새 객체를 반환하기 때문에 이러한 연속 호출이 가능하다.
체이닝 기법은 구조적으로 짜임새가 있고 가독성이 좋아진다. 하지만 객체에 값이 얽매여있기 때문에, 체이닝에서 실행 가능한 메서드 종류에 한계가 존재하기 때문에 코드 표현성이 줄어든다는 한계점도 존재한다.
파이프라인(pipeline)은 함수를 연결하는 또 다른 기법으로, 한 함수의 출력이 다른 함수의 입력이 되게끔 느슨하게 배열한 방향성이 있는 함수 순차열을 말한다.
각 단계를 이루는 함수를 일련의 순서로 합성하는 것이 파이프라인의 기본이다.
// compose는 오른쪽 인자부터 출발하여 차곡차곡 결과값을 쌓아갑니다.
function compose(...functions) {
return function(arg) {
return functions.reduceRight((composed, f) => f(composed), arg);
};
}
// pipe는 왼쪽 인자부터 출발하여 차곡차곡 결과값을 쌓아갑니다.
function pipe(...functions) {
return function(arg) {
return functions.reduce((composed, f) => f(composed), arg);
};
}
compose(
// ...
reverse,
toUpperCase,
)('Functional Programming');
커링(currying)은 다변수 함수가 인수를 받을 때까지 실행을 보류 또는 지연시켜 단계별로 나뉜 단항함수의 순차열로 전환하는 기법이다.
Javascript에서는 커링 구현을 클로저 패턴을 위해 사용한다.
const add = (a, b) => a + b;
// 일반적인 커링
function do(func) {
return function (x) {
return function (y) {
return func(x, y);
}
};
}
// 화살표 함수를 쓰면 이렇게도 가능합니다.
const do = (func) => (x) => (y) => func(x, y);
do(add)(1)(2)
커링으로 함수를 만들게되면 모자란 인수가 채워지기를 기다리는 새로운 함수가 반환된다. 마지막 인수가 들어오기까지 전체 값을 구하지는 않기 때문에 부분적으로 느긋한 계산(lazy evaluation)이 가능하다.
필요에 따라서는 커링에 필요한 인수 일부를 미리 채울 수도 있는데, 이는 부분 적용 함수(partial applied function)라고 부른다. 이를 이용해 함수의 인수를 일부만 미리 평가할 수 있을 뿐만 아니라 함수 팩토리를 모방할 수도 있다.
함수자는 어떤 불완전한 값을 감싼느 컨테이너 역할을 하는 자료 구조이다. 함수자 안에 값을 집어넣는다는 것을 리프팅이라고 부른다.
함수자는 외부 함수를 인자로 받아 현재 리프트된 값을 적용한 결과값을 다시 새 함수자로 반환하는 매핑 기능을 제공한다.
함수자가 이런 식으로 값을 감싸는 이유는 부수 효과가 있는 모든 함수를 안전하게 순수 함수로 만들기 위한 목적이다.
모나드는 다양한 함수자가 중첩되는 경우를 깔끔하게 연결하기 위해 고안되었다. 즉, 함수자를 연결할 수 있게 만든 함수형 프로그래밍의 디자인 패턴이다.
함수 실행 도중에 예외가 발생한다고 해도, 해당 예외는 함수자라는 컨테이너에 싸여 있기 때문에 예외를 따르면서 남은 함수들을 실행시킨다. 이 모나드 덕에 함수 간 데이터가 안전하게 흘러가도록 조정하고 역할 자원을 추상화할 수 있다.
안전한 함수 합성을 위해서 사용한다.
배열도 모나드, Promise도 모나드
const g = a => a + 1;
const f = a => a * a;
console.log(f(g(1));
[1].map(g).map(f).forEach(a => console.log(a));
Promise.resolve(1).then(g).then(f).then(a => console.log(a));
여기서 [1]와 Promise가 모나드. 함수를 안전하게 합성하기 위한 목적을 가지고 있는데, Promise는 비동기적으로 언제 일이 끝날지 모르는 시점을 기다렸다가 합성하려는 성질.
const g = JSON.parse;
const f = ({k}) => k;
const fg = x => Promise.resolve(x)
.then(g)
.then(f);
fg('{"K": 10}').then(log);
fg('{"K: 10}').catch(_ => '미안...').then(log);
Promise는 값으로 다루기 위해서 사용하는 것이다.
어떤 일이 일어날지모르는 효과를 감싸놓고, 나는 이런 형태의 값이야라고 말을 해놓고, Promise는 비동기적인 상황과 성공과 실패를 값으로 다루는 형태의 모나드이다.
값이다.
// delay
const delay = (time, a) => new Promise(resolve =>
setTimeOut(() => resolve(a), time));
delay(100, 5).then(log);
// go1
const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);
const a = 10;
const b = delay(1000, 5);
go1(a, log);
go1(b, log);
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
//myFunc변수에 displayName을 리턴함
//유효범위의 어휘적 환경을 유지
myFunc();
//리턴된 displayName 함수를 실행(name 변수에 접근)
function makeAdder(x) {
var y = 1;
return function(z) {
y = 100;
return x + y + z;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
//클로저에 x와 y의 환경이 저장됨
console.log(add5(2)); // 107 (x:5 + y:100 + z:2)
console.log(add10(2)); // 112 (x:10 + y:100 + z:2)
//함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산

객체 지향 프로그래밍은 객체를 중심으로 사고하고 프로그램을 작성하는 것이다.
함수형 프로그래밍은 데이터를 함수로 연결하는 것을 중심으로 사고하고 프로그래밍을 하는 것이다.
함수형 프로그래밍은 객체 지향 프로그래밍을 더 단순하게 그리고 간결하게 보도록 도와준다. 하지만 함수형 프로그래밍이 객체지향보다 반드시 더 나은 것은 아니다.
자바스크립트는 함수형 프로그래밍 기반 위에 객체 지향 언어의 껍데기를 씌운 언어이다. 이렇게 다소 실험적으로 탄생한 이 언어로 인해 개발자들은 '객체지향에 함수형 프로그래밍을 적당히 섞으면 훨씬 더 좋다'라는 사실을 알게 되었다.
좋은 글 잘 읽었습니다, 감사합니다.