[FP 용어] 함수형 프로그래밍의 조건

트릴로니·2022년 9월 1일
0

함수형 프로그래밍의 특징

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

0개의 댓글