[함수형 프로그래밍과 es6+] 6. 지연성

fano·2021년 1월 29일
0

range

숫자하나를 받고 그 숫자 이전까지 0부터 숫자가 늘어나는 수열의 배열을 리턴하는 함수

const range = l => {
  let i = -1;
  const result = [];
  while (++i < l) 
    res.push(i);
  return result;
}

range(4)를 호출하면
와 같은 값이 나온다

lazy range

const L = {};
L.range = function* (l) {
  let i = -1;
  while (++i < l) 
    yield i;
}

L.range(4)를 호출하면
위와 같은 값이 나온다.
이는 generator를 생성한 iterator. 이전에 만든 map, filter, reduce는 이터러블 프로토콜을 따르기에 iterator를 인자로 주어도 정상적으로 작동한다.
일반 range()와 L.range()의 차이점은 range()는 호출하면 모든 값이 배열로 담겨 반환되지만, L.range()는 호출하여도 동작하지 않고 next메서드를 호출하는 그 시점 yield keyword가 있는 코드라인까지만 함수가 실행되고 다시 중단된다.
range()를 굳이 쓸 필요가 없는게 배열을 반환해도 순회하는 요소 한 개만 필요하지 그 배열 자체로 사용을 요하지 않는다. 그래서 L.range가 더 효율적인 형태인 것.

take

iterable의 총 length를 임의의 수만큼 잘라서 배열로 반환한다

const take = (l, iter) => {
  const result = [];
  for (const a of iter) {
    result.push(a);
    if (result.length === l ) return result;
  };
}

이를 curry함수로 감싸주게 되면

go(
  L.range(100),
  take(3),
  reduce((a, b) => a + b)
)

위와 같이 함수형 프로그래밍의 함수로 활용할 수 있게 된다.

lazy map

L.map = function* (f, iter) {
 for (const el of iter) yield f(el);
};

역시 평가하는 값만큼만 계산을 진행하게 됨

lazy filter

L.filter = function* (f, iter) {
 for (const el of iter) 
   if (f(a)) yield el;
};

역시 평가하는 값만큼만 계산을 진행하게 됨

lazy 함수는 어떻게 동작하는가

go(L.range(10), // 1. 일시중단 7. ~~ 
   L.map(n => n + 10), //2. 일시중단 6. ~~
   L.filter(n => n % 2), //3. 일시중단 5. next()까지가서 일시중단
   take(2),//4. next()까지가서 일시중단
   log);

디버거를 돌려보면 take부터 breakpoint가 잡힌다. 이유는 앞선 메서드들이 모두 제너레이터여서 모두 일시중단된 것. 제일 마지막에 next메서드를 사용하는 take의 중단점부터 역순으로 함수가 재개된다.

일반 함수가 함수마다 전체순회를 한 번 씩하는데 반해 수직적으로 순회가 일어나는 것.

map, filter 계열 함수들이 가지는 결합 법칙

  • 사용하는 데이터가 무엇이든
  • 사용하는 보조 함수가 순수 함수이기만하면
  • 아래와 같이 결합한다면 일반 함수이든, lazy함수이든 결과가 같다
[[mapping, mapping], [filtering, filtering], [mapping, mapping]]
===
[[mapping, filtering, mapping],[mapping, filtering, mapping]]

join

이터러블의 요소들을 하나의 문자열 값으로 반환한다.

const join = (seperator, iter) => 
	reduce((a, b) => `${a}${seperator}${b}`);

lazy entries

Object.entries 메서드에 지연성을 더한 함수 [key, value] 형태의 요소를 가진 배열을 리턴한다.

L.entries = function* (obj) {
  for (const key in obj)
    yield [key, obj[key]];
}

find

조건에 일치하는 맨 첫 번째 값 한 개를 리턴하는 함수

const find = (f, iter) => go(
	iter,
  L.filter(f),
  take(1),
  ([el]) => el
);

L.map으로 map 만들기

const map = curry(pipe(L.map, take(Infinity)));

L.filter로 filter 만들기

const filter = curry(pipe(L.filter, take(Infinity)));

lazy flatten

중첩된 iterable들을 모두 1개 뎁스의 Array로 리턴한다.

const isIterable = a => a && a[Symbol.iterator];

L.flatten = function* (iter) {
	for (const a of iter) {
    	if (isIterable(a)) for (const b of a) yield b;
      else yield a;
    }
}

yield *

yield *을 활용하면 위코드를 더 줄일 수 있다.
yield *iterablefor (const val of iterable) yield val; 과 같다.

lazy deepFlat

3중, 4중의 iterable도 펼칠 수 있는 함수

L.deepFlat = function *f(iter) {
	for (const of iter) {
      if( isIterable(a)) yield *f(a);
      else yield a;
	}
}

flatMap

flatten 후에 map 진행. map과 flatten을 따로따로 진행하지 않아서 더 효율적이다

const L.flatMap = pipe(L.map, L.flatten);

위에 나열된 함수들을 조합해 목표하는 데이터를 도출해내는 프로그래밍, 함수형 프로그래밍

profile
서비스를 생각하는 개발

0개의 댓글

관련 채용 정보