함수형 프로그래밍의 특징
1. 순수함수
- 함수에 동일한 인자를 패스했을 때 항상 같은 값을 반환하는 함수이다.
- 함수는 외부의 어떤 값도 수정할 수 없어야 한다.
side effect(부수 효과)가 없는 함수
const array = [1, 2, 3]
function mutateArray(arr) {
arr.pop()
}
mutateArray(array);
- 외부의 변수를 함수 안에서 조작하는 것을 side effect라 한다
- mutateArray함수는 전역 변수에 등록된 array를 조작한다.
- mutateArray는 언제든 호출할 수 있기 때문에 원본 데이터가 예상치 못하게 바뀔 수 있다.
- 위 코드는 다음과 같이 수정할 수 있다.
const array = [1, 2, 3]
function mutateArray(arr) {
const newArray = [].concat(arr);
newArray.pop();
return newArray;
}
console.log(mutateArray(array));
// [1, 2]
console.log(array)
// [1, 2, 3]
- 함수 내부에서 인자로 들어온 arr를 복사해서 새로운 배열로 만들고 복사된 배열을 조작하여 반환하면 원본 데이터값을 보존하면서 데이터를 조작할 수 있다.
2. 불변성(Immutability)
- state가 바뀌지 않아야 한다.
- state의 불변성을 지키기 위해 state를 복사해서 새로운 state를 만들고 조작한 반환한다.
const obj = {name: 'Andrei'}
function clone(obj) {
return {...obj}
}
function updateName(obj) {
const obj2 = clone(obj);
obj2.name = 'NaNa';
return obj2
}
updatName(obj);
console.log(obj);
//{name: 'Andrei'}
3. 멱등성(Idempotent)
function notGood() {
return Math.random()
}
notGood()
- 함수를 여러번 호출하더라도 항상 연산 결과는 같아야 한다. 즉 예상한 대로 결과값을 반환해야한다,
Math.abs(Math.abs(-50))
// 50
4. 명령형 vs 선언형
명령형 코드(imperative code)
- 기계에서 어떻게 수행해야(how to do) 할지 알려주는 코드이다.
- 기계어는 명령형이다.
for(let i = 0; i < 1000; i++) {
console.log(i)
}
- for반복문은 초기식, 조건식, 증강식을 직접 명시해야 하므로 명령형 코드에 가깝다.
선언형 코드(declative code)
- 기계에게 무엇을 수행해야(what to do) 할지 알려주는 코드이다.
- high level language는 명령형에 가깝다.
[1, 2, 3].forEach(item => console.log(item))
- forEach문은 반복을 돌면서 무엇을 수행해야하는지 알려주는 코드로 선언형 코드에 가깝다.
함수형 프로그래밍과 선언형 코드
- 함수형 프로그래밍의 코드는 선언형 코드에 가깝다. 함수들을 서로 조합하고 구성하면서 프로그램에게 어떻게 수행하는지를 알려주는 것 보다 무엇을 수행해야하는지 알려준다.
- 선언형 코드는 결국 기계어처럼 명령형 코드로 컴파일 된다.
5. 고차 함수(HOF) 와 클로저
- 고차 함수: 함수를 인자로 받거나 함수를 반환하는 함수이다.
함수를 반환하는 함수
const hof = () => () => 5;
함수를 인자로 받는 함수
const hof = (fn) => fn(5);
hof(function a(x){return x})
- 클로저: 반환된 내부함수가 자신이 선어됐을 때의 환경인 스코프를 기억하여 자신이 선언됐을 때의 환경 밖에서 호출되어도 그 환경에 접근할 수 있다.
const closure = function () {
let count = 0;
return function increment(){
count++;
return count;
}
}
const incrementFn = closure();
- closure함수는 종료되었지만 클로저로 인해 clousre의 내부 변수인 count를 increment 함수에서 쓸 수 있다.
- increment함수는 자신의 내부 state 혹은 데이터가 아닌 count를 조작할 수 있으므로 side-effect를 발생시킨다.
- 클로저는 매우 유용하게 쓰일 수 있는데 아래와 같이 side-effect없이 클로저 특성을 사용할 수 있다.
const closure = function () {
let count = 30;
return function getCounter(){
// 외부 함수의 변수인 count를 조작하지 않고 단지 반환만한다.
return count;
}
}
const getCounter = closure();
getCounter();
- 위 코드의 경우 클로저로 인해 count 변수를 privacy하게 숨길 수 있다.
- 내부 함수에서 외부 데이터나 state의 조작없다면 side-effect발생시키지 않고 클로저의 장점을 활용하여 함수형 프로그래밍을 할 수 있다.
커링(currying)
- 커링은 인자를 여러개 받는 함수를 분리하여, 인자를 하나씩 받는 함수들의 체인으로 바꾸는 방법이다.
const multiply = (a, b) => a*b;
multiply(3, 4)
const multiply = (a) => (b) => a*b;
mutiply(3)(4);
partial application
- 여러개의 인자를 받는 함수를 부분적으로 쪼깰 수 있다.
- 커링(currying)은 하나의 인자를 받는 함수로 쪼갠것이다. partial application의 경우 함수가 받는 인자는 하나 이상일 수 있다.
- partial application을 구현하기위새 bind를 쓸 수 있다.
const multiply = (a, b, c) => a*b*c;
const curriedMultiply = (a) => (b) => (c) => a*b;
curriedMultiply(3)(4)(5)
- a 인자는 한번만 호출해서 저장하고(5) 나머지 b, c를 여러 번 값을 다르게 해서 호출할 수 있다.
const multiply = (a, b, c) => a*b*c;
const partialMutiplyBy5 = multiply.bind(null, 5)
partialMutiplyBy5(4, 10)
//200
partialMutiplyBy5(2, 11)
// 110