[functional JS ES6+] 총정리
작성:    
업데이트:
카테고리: Functional JS
태그: FE Language, Functional JS, JS
본 포스트는 인프런의 함수형 프로그래밍과 JavaScript ES6+ 강의(링크)를 듣고 정리한 내용입니다.
기본 용어
평가(excuation)
코드 → 계산 → 값
일급함수
- 함수를 값, 인자, 결과로 취급
- 유연한 조합과 추상화 가능
고차함수
- 함수를 값으로 취급하는 함수
- 함수를 인자로 받는 경우
- 함수를 만들어 반환하는 경우
iteration
Symbol.iterator
- idx를 통해 개별 원소에 접근을 가능케 함
- next 메서드를 반환
iterable
- iterator를 return하는 Symbol.iterator 메서드를 가지는 값
- iterable protocol을 따름
iterator
- next 메서드를 가진 값
- next 메서드: { value, done } 객체를 return
iterable protocol
- iterable을 iterable처럼 쓸 수 있게 하는 규약
- for of문, 전개 연산자 등
well-formed iterator
- Symbol.iterator를 출력하면 자기 자신이 나오는 iterator
const arr = [1, 2, 3];
let iter = arr[Symbol.iterator]();
log(iter[Symbol.iterator]() == iter); // true
- iterator이자, iterable을 생성(return)하는 함수
function *gen() {
yield 1;
yield 2;
return 100;
}
let iter = gen();
log(iter.next()); // {value: 1, done: false}
log(iter.next()); // {value: 2, done: false}
log(iter.next()); // {value: 100, done: true}
- 일반 함수 앞에 ‘*’ 표시
- yield로 각 단계 표시
- generator 내부에서 로직을 통해 선택적으로 yield 처리 가능
- return 값은 done: true에 나오는 값
코드를 값으로 취급 : go, pipe, curry
go
- 함수 중첩에서 가독성을 위해 사용
- 인자에 코드 사용(값으로 취급)
- 이전 인자의 결과를 다음 함수의 인자로 사용
- 값-함수 묶음
// args의 첫 요소는 항상 시작'값'이므로 args는 시작값
const go = (...args) => reduce((a, f) => f(a), args);
go(
0,
a => a + 1,
a => a + 10,
a => a + 100,
log); // 111
pipe
- 내부에서 go를 실행하는 함수
- 완전한 함수 묶음
- 값은 외부에서 함수의 인자로 받음
// 함수들 목록을 인자로 받는데, 초기값을 나중에 받고, 이를 go 함수에 넣어 실행
const pipe = (...fs) => (a) => {go(a, ...fs)};
const f = pipe(
a => a + 1,
a => a + 10,
a => a + 100);
log(f(0));
curry
- 값을 받아 내가 원하는 시점에 평가
2개 이상의 인자를 받았을 때 함수를 실행하는 curry 함수 코드
const curry = f =>
(a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
const multi = curry((a, b) => a * b);
log(multi(3)); // (..._) => f(a, ..._)
log(multi(3)(2)); // 6
지연성
- 어떠한 iterable을 여러 함수 중첩으로 처리할 때 모든 배열을 매번 평가하지 않는 것
- 필요할 때마다 하나씩 평가해서 처리
- 게으르다는 의미의 Lazy 평가
즉시 평가
매 함수 단계마다 모든 배열을 평가
지연 평가
- 함수 중첩의 모든 지연성을 확인
- 다음 함수에 연산을 넘기게 됨
- 뒤의 조건을 충족한다면 불필요한 평가 회피 가능
비동기/동시성
비동기
- 자바스크립트는 동기적
- 시간 지연 등 시간이 필요한 작업은 따로 분류해 나중에 처리
- 이를 기다렸다가 결과를 받도록 하는 것이 비동기 처리
- 기존에는 callback 함수를 통해 평가된 값을 받을 때까지 기다려서 처리
function add10(a, callback) {
setTimeout(() => callback(a + 10), 100);
}
add10(5, res => {
log(res); // 15
});
add10(5, res => {
add10(res, res => {
add10(res, res => {
log(res); // 35
});
});
});
Promise
- 시간이 소요되는 작업의 비동기를 보장
function add20(a) {
return new Promise(resolve => setTimeout(() => resolve(a + 20), 100));
}
add20(5)
.then(log); // 25
add20(5)
.then(add20)
.then(add20)
.then(log); // 65
- callback 함수에 비해 깊이가 깊어지지 않음
- 비동기 상황을 값으로 취급
- Promise instance 반환
- 코드 진행에 대한 상황 파악 가능
Monad
함수 합성에서의 안전성 보장
- Promise: 비동기 상황에서 사용하는 모나드
- 기존 monad는 map chain, Promise는 then chain
- 함수 합성에서 안전성 보장이지만, 인자가 없는 경우가 아닌 대기 상황(비동기 상황)에서의 안전성 보장
Kleisli Composition
오류가 있을 수 있는 함수 합성에서의 안정성을 보장하는 하나의 규칙
지연된 함수열의 병렬적 평가
- 자바스크립트는 싱글스레드이기 때문에 동시에 여러 작업은 불가능하고 비동기 IO로 처리
- 하나의 스레드에서도 CPU를 점유하는 작업을 보다 효율적으로 처리
- 한 node 환경이 아니라, 네트워크나 브라우저, 기타 IO 등 외부 환경으로 작업을 보내 한 번에 처리한 뒤, 이 결과를 받음
C : Concurrency 병행성
const C = {};
C.reduce = curry((f, acc, iter) => iter ?
reduce(f, acc, [...iter]) :
reduce(f, [...acc]));
- iterator가 있는 경우 […iter]를, 없는 경우 …acc를 reduce에 인자로 전달
- 비동기성을 고려하지 않고, 모든 배열을 평가해 reduce로 넘긴다는 것
console.time('');
go([1, 2, 3, 4, 5],
L.map(a => delay1000(a * a)),
L.filter(a => a % 2),
C.reduce(add),
log,
_ => console.timeEnd('')); // 1005.9492...ms
- L.map과 L.filter를 사용하므로 go함수 내부에서 세로(함수별로 하나씩) 방향으로 작업을 처리
- 때문에 기존 reduce를 사용하면 매번 delay1000 함수 실행
- C.reduce를 활용하면 배열 내 요소 각각마다 delay1000 함수를 실행하는 것이 아니라, 배열을 펼쳐 한 번에 평가 후 다음 함수로 전달
async-await
Promise를 return하는 함수에서 보다 동기적으로 코드를 작성하는 방법
function delay(a) {
return new Promise(resolve => setTimeout(() => resolve(a), 500));
}
async function f1() {
const a = await delay(10);
const b = await delay(5);
log(a); // 10
}
f1();
- async로 표시한 함수 내부에서 지연되는 함수 또는 작업의 앞에 await를 붙임
- 쉽게 말하면 await는 Promise 객체를 가능한 순간에 값으로 평가
- 해당 Promise를 반환하는 statement는 비동기적으로 값으로 평가될 때까지 지연
- 위의 코드에서 await가 없다면 Promise 객체가 출력, await가 있다면 계산된 10이 출력
주의사항 : async 함수는 Promise 객체를 return ⭐
- 때문에 async 함수 내부에서 처리된 값을 함수 내부에서만 사용한다면 무관
- 하지만 async 함수의 결과값(return값)을 외부에서 사용하게 된다면 .then을 통해 외부에서 return값을 처리
댓글남기기