함수형 프로그래밍을 공부하기 시작하니 시작부터 생소한 개념이 튀어나왔습니다.
go
라는 것인데, 공부해보니 뭔가 특별한 의미는 가진 것은 아니고 다음과 같은 일을 하는 함수라고 생각하면 될 것 같습니다.
첫 번째 인자를 초깃값으로 하여, 이 초깃값을 이후의 인자들(함수들)에 순차적으로 통과시켜 결과값을 내는 함수
조금 난해해 보이니 바로 코드로 구현해가며 차근차근 이해해보도록 하겠습니다.
우선 go
는 함수니까 다음과 같이 정의해두고 시작하도록 하겠습니다.
const go = () => {}
그 다음 인자를 받도록 해줄건데, 첫 번째 인자는 초깃값이고 나머지 인자들은 전부 함수입니다. 이 때 초깃값을 제외한 나머지 인자들은 ...
연산자를 사용해서 묶어주겠습니다.
const go = (initialValue, ...fns) => {}
이제 함수 내부 구현을 할 차례인데요 초깃값을 fns
에 담긴 함수들에 하나씩 차례로 적용시키며 값을 누적시켜 나가는 일을 해야 합니다. 이 역할에 딱 맞는 함수가 있죠? 바로 reduce
입니다. 이전에 작성해 두었던 포스트에서 reduce
코드를 좀 가져오겠습니다.
function reduce(predicate, iter, acc) {
if (acc === undefined) {
// 3번째 인자인 acc 인자가 주어지지 않은 경우
// 2번째 인자인 iter의 첫 번째 값을 초기 값으로 세팅한다.
const iterator = iter[Symbol.iterator]()
acc = iterator.next().value
}
// iter 를 순회하면서 각 item 과 acc 값에 대한 연산 결과를 acc 에 누적시켜 나간다.
for (const item of iter) {
acc = predicate(item, acc)
}
return acc
}
const go = (initialValue, ...fns) => {}
이제 이 reduce
를 가지고 go
함수를 구현해보겠습니다. go
함수 구현 부분에 집중해주세요.
const go = (initialValue, ...fns) => reduce((fn, acc) => fn(acc), fns, initialValue)
fns
에는 함수들이 배열 형태로 전달이 됩니다.
실행 흐름을 간단히 표현해보면 다음과 같겠네요.
fns[last]( ... ( fns[1]( fns[0](initialValue) ) ) )
자, 이제 구현한 go
함수를 직접 사용해 보겠습니다.
const result = go(
0,
a => a + 1,
a => a * 10,
a => a * 100
)
console.log(result) // 1000
위와 같이 깔끔하게 여러 함수의 연산결과를 쭉 이어서 작성 할 수 있습니다.
함수를 값으로 다루기 때문에 처음에는 많이 어색할 수 있습니다!
여러번 곱씹어 보면서 찬찬히 실행 흐름을 따라가 보세요.
pipe
는 go
의 개념과 한 부분에서만 다릅니다. 바로 인자 부분인데요, 다음과 같은 일을 하는 함수 입니다.
모든 인자가 함수이며, 이 인자들(함수들)을 마치 하나의 관처럼 연결하여 주는 새로운 함수를 반환한다.
말로 풀어 설명하자니 역시 조금 난해하네요..
이 개념 또한 코드로 구현해가며 천천히 이해해보도록 하겠습니다.
pipe
는 함수를 반환하는 함수라고 하였으니 다음과 같이 뼈대를 잡아줍니다.
const pipe = () => () => {}
인자는 전부 함수이고 여러개를 받을 수 있으므로 다음과 같이 작성해 줍시다.
const pipe = (...fns) => (...args) => {}
이제 반환되는 함수쪽을 구현해야 합니다.
반환되는 함수는 인자를 여러개 받을 수 있고, 받은 인자들을 fns
에 속한 함수들을 차례로 통과시킨(연산시킨) 값을 반환해야 합니다.
값을 받아서 해당 값을 차례로 통과시킨다.. 이건 위에서 구현한 go
함수를 사용하면 되겠죠?
const pipe = (...fns) => (...args) => go(...args, ...fns)
const pipedFunction = pipe(
a => a + 1,
a => a * 10,
a => a * 100
)
console.log(pipedFunction(0)) // 1000
go
와의 차이점은 뭘까요?
바로 "계산의 지연성" 입니다.
go
함수는 그 즉시 초깃값에 대한 연산을 수행하지만,
pipe
를 통해서는 일단 함수만 받아놓고 해당 함수를 통해 나중에 연산을 수행할 수 있습니다.