함수형 프로그래밍을 잘 못 이해했을 수 있다. 솔직히 어렵지만, 내 나름대로 이해한 내용을 정리해봤다. 나중에 다시 보고 어떤 부분을 잘 못 알고 있었는지 알 수 있겠지
적용해보려니까 정말 힘들던데, 개념을 대충 알았으니 적용해보고 사고해보려고 노력해야할 것이다.
소프트웨어의 크기가 커짐에 따라 복잡하게 엉킨 코드들은 유지보수가 너무 힘들다.
함수형 프로그래밍은 작은 문제를 해결하기 위한 함수를 작성, 사용하기에 가독성이 높고 유지보수가 용이하다고 한다. 왜 그럴까..?
외부에서 관찰 가능한 부수 효과가 제거된 불변
프로그램을 작성하기 위해 순수함수
를 선언적으로 평가
하는 것
함수형 프로그래밍은 다음과 같은 키워드로 설명 가능하다.
프로그램 패러다임은 크게 아래와 같이 구분 가능하다.
어떻게
할 것인지 설명무엇
을 할 것인지 설명솔직히 처음보고 이게 대체 무슨 뜻인가 싶었다. 무엇과 어떻게를 어디에 대입해야할지 상상하기 힘들었다. 좋은 예시가 있더라
// 명령형, 절차적, ...
for(let i = 0; i < array.length; i++){
array[i] = array[i] * 2
}
// 선언형, 함수형, ...
array.map(function(val) {
return value * 2;
});
이해가 쏙쏙 되는 예시가 여기도 있다. https://velog.io/@nakta/자바스크립트로-접해보는-함수형-프로그래밍-
순수함수
: 입력 값에 대해서만 특정 절차를 거치고, 값을 반환하는 형식의 함수. 함수형 프로그래밍에서 함수들은 순수함수여야한다.
참조 투명성
side effect
가 없다. 따라서 순수함수는 불변성
이라는 특성을 지닌다.얘도 무슨말인가 싶었다. 아래를 쓱 훑어보자. 이 글을 쓰는 당시 내가 이해한 것을 내 나름의 방식으로 표현해놓은 것도 있다.
side effect
는 다음을 일컫는다
불변성
: 함수형 프로그래밍에서 함수의 유일한 결과는 함수가 뱉어내는 값일 뿐이다. 다른 부분에는 절대 영향을 미치지도, 받지도 않는다.
const person = {name : "me", age : 25};
function increaseAge(person){
return {...person, age: person.age+1};
}
참조 투명성
: 함수형 프로그램에서 함수의 결과는 파라미터에만 의존적이다. 함수 외부의 영향을 절대 받지 않는다.
아래 코드는 외부 변수를 참조한다. 참조에 투명하지 않다.
const name = "me";
function hi(){
console.log(`hey ${name}`);
}
아래와 같이 변경 가능하다.
function hi(name){
return `hey ${name}`;
}
function main() {
const h = hi('me');
console.log(h);
}
이게 대체 무슨 소리일까??
다 읽고 봤을 때, 현실 세계에서 우리가 초중고대학교 생활 내내 접했던, 프로그래밍 세계보다는 우리가 현실 세계에서 많이 접했던 ‘함수’에 가깝다는 느낌이 상당히 강했다. (deterministic한 것만 가정.. 확률적인 요소가 들어가면 참조 투명성에 위배될 것 같다.)
맞는 비유일지는 모르겠지만.. 이해해본 것을 토대로 기술해보겠다.
참조 투명성
부터 보자.
불변성
순수함수를 설명하며 불변성이 튀어나와 같이 설명했다.
명령형 프로그램은 if, for, while문으로 흐름을 제어하지만, 함수형 프로그램은 여러 함수를 이어 흐름을 제어한다. 제일 위에서 예시로 보여준 map처럼 개발자는 map이 무엇을 할지를 지정해주거나, map이 어떤 결과를 내어주는 친구인지는 알지만, 내부 동작 원리에 대해 알필요는 없다.
First-class
: first-class 함수들은 first-class 변수들 처럼 취급된다. first-class 변수들은 함수에 넘겨질 수 있는 파라미터들이다. 즉, first-class 함수들은 파라미터처럼 취급될 수 있는 함수들을 말한다.
Higher-Order
: Higher order 함수는 함수를 파라미터로 받는 함수를 의미한다.
뜬금없이 위 단어가 왜 나왔나 싶겠다. JS에서 함수는 first-class이면서 higher-order이다. 함수는 변수처럼 취급될 수 있고, 또 함수의 파라미터로 넘겨질 수 있다.
이 단어 자체가 중요하다기보다는, JS에서 함수를 저렇게 취급하기에 함수형 프로그래밍을 더욱 고차원적으로 하는게 가능해지는 것을 알아두고 넘어가자는 의미에서 소개했다.
thisIsObject.removeField('field1').doSth1().doSthElse();
doSthElse(doSth1(removeField(thisIsObject, 'field1')))
위 예시만 봐도 체이닝이라는 단어가 와닿지 싶다.
체이닝은 객체 내에 속한 메서드들을 체인마냥 이어 계속해서 호출해나가는 방법이다. 체이닝을 사용하지 않으면, 아래와 같이 반환 값을 인자로 또 받고, 또 받고, 또 받또받또받고..를 반복해야겠지만, 체이닝을 사용하니 가독성이 좋다.
다만, 객체 내부에 메소드가 정의되어있지 않다면 체이닝으로 사용할 수 없을 것
→ 함수들끼리 강하게 결합되어있는 상태라고 봐도 될 것
lodash 라이브러리에서 체이닝을 지원한다고 한다.
pipe(fn1, fn2, fn3, doSth, ...)(target)
pipe에 함수들을 넣어 파이프라인을 타 내려가듯이 처리해나가는 방법이다. pipe에 아무 함수나 전달해줄 수 있기 때문에 함수들이 느슨하게 결합되어있는 상태라고 봐도 될 것이다
아래와 같은 방법으로 나뉜다. compose는 우측부터, pipe은 좌측부터 시작해 결과를 내뱉는다.
function compose(...functions) {
return function(arg) {
return functions.reduceRight((composed, f) => f(composed), arg);
};
}
function pipe(...functions) {
return function(arg) {
return functions.reduce((composed, f) => f(composed), arg);
};
}
이건 진짜 처음 보고 무슨 소리인가 했다. 코드 돌려보고 너무 신기하더라. 어떻게 적용할지는… 아직 모르겠다.
커링은 처리를 유보하는 친구이다. 인자들을 모두 받기 전까지는 함수로 남아있다가, 인자들이 모두 들어오면 결과를 내뱉는다. 이런 아이디어를 누가 내는거지
코드를 보자. 그게 이해가 빠르다
function do(func) {
return function (x) {
return function (y) {
return func(x, y);
}
};
}
일단 아이디어 자체만 보고 마구잡이로 구현해보면 위와 같이 구현 가능하다. 그런데 파라미터가 100개이면? ㅋㅋ ㅋ ㅋㅋ.
일반화해서 커링을 사용하는 방법은 구글링하면 나온다. 나도 봤지만 여기에 적어두진 않겠다.
JS에서 함수형 프로그래밍과 관련될만한 기타 개념들을 정리하고 마무리하겠다.
사실 위 커링 예시는 클로저를 사용한다.
`클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.`
→ 무슨 소리일까? :’)
클로저는 함수가 선언될 때, 외부 변수의 상태 참조하는 경우 그 상태를 그대로 봉합(close)해버린다. 따라서 어떤 일반 함수 내부에서 클로저가 선언되고, 그 일반 함수가 종료되더라도, 일반 함수 내부의 어떤 변수 등을 클로저가 참조했었다면, 계속해서 접근할 수 있게 되는 것이다. 아래 설명이 진짜 짱이다. MDN 짱
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
//myFunc변수에 displayName을 리턴함
//유효범위의 어휘적 환경을 유지
myFunc();
//리턴된 displayName 함수를 실행(name 변수에 접근)
//"Mozilla" 출력
function makeAdder(x) {
var y = 1;
return function(z) {
y = 100;
return x + y + z;
};
}
var add5 = makeAdder(5); // add5, add10은 모두 클로저이다.
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값에 접근하여 값을 계산
array 자료형의 map, filter 메소드는 다음과 같다.
이러한 고차함수는 재귀적으로 구현된다. 물론 반복문을 통해서도 원하는 결과를 도출할 수 있지만, 함수형 프로그래밍에서 지향하는 바를 철저하게 지킨다면 재귀적으로 구현해야할 것이다.
참조 투명성, 불변성을 지킨다면 아래 코드와 같이 만들 수 있겠다. 해당 map method는 array내부 메소드가 아닌, 일반 함수라고 가정하겠다. 따라서 array자체도 변수로 받는다.
map(callback, array){
if(array.length === 0) return [];
const [firstElement, ...rest] = array;
return [...map(callback(firstElement), ...rest)];
}
**읽어본 것, 읽어볼 것
https://medium.com/humanscape-tech/함수형-프로그래밍에-관해-7f6172599fc → 간략하고 쉬운 설명
https://www.quora.com/Why-dont-pure-functional-programming-languages-provide-a-loop-construct → 함수형 프로그래밍에서 왜 반복문을 쓰지 않는가?
https://velog.io/@teo/functional-programming → 다시 쓰는 함수형 프로그래밍
https://dev.to/alexmercedcoder/javascript-writing-map-as-a-recursive-function-2854
https://frontsom.tistory.com/10#:~:text=앞에서 말했듯이 커링의,보수 할 때도 유용하다.&text=보통 일반 함수로는,높여보면 다음과 같다 → 커링
https://www.youtube.com/watch?v=cXi_CmZuBgg →functional reactive programming?!
https://www.youtube.com/watch?v=4sO0aWTd3yc → naver d2
**참고한 링크
https://www.geeksforgeeks.org/functional-programming-paradigm/
https://jongminfire.dev/함수형-프로그래밍이란
https://mangkyu.tistory.com/111
https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures 클로저
https://wormwlrm.github.io/2022/03/08/Functional-Programming-in-JavaScript.html, https://velog.io/@nakta/FP-in-JS-자바스크립트로-접해보는-함수형-프로그래밍-함수-컴포지션-커링-s7k2z039vb JS로 접해보는 함수형 프로그래밍을 정리해 놓은 블로그