[TIL] 20210810 - 함수형 프로그래밍2

younoah·2021년 8월 10일
1

[TIL in 웹데코]

목록 보기
4/8

🚀 intro

최근에 고질병이 하나 생겼다. 🤯

웹데코와 관련된 모든것들에 대해 아침 눈뜨고부터 잠들기전까지 계속 집중을 넘어선 집착을 하게 된다.

아.. 내가 무언가 놓친게 있지 않나?? 혹시 부족한 부분이 있지 않나?? 더 꼼꼼히 봐야되는것 아닌가?? 등등

이런 현상은 내가 현재 웹데코 과정에 푹 빠젔다는 증거이기도 하기도 하다. 😝

하지만 반대로 나만의 시간이 없어지고 생활 패턴이 무너지는 단점이 드러났다.

데드라인 없이 계속해서 컴퓨터를 붙잡고 있다보니 새벽시간을 넘기기 일쑤였고 그러다 보니 늦게 자고 늦게 일어나게 되었다.

늦게 일어나다 보니 눈뜨고 밥먹으면 바로 교육을 시작해야 됐다.

즉! 그냥 하루가 웹데코로 시작해서 웹데코로 끝났다. 🥲

그러다 문득 든 생각은 이렇게 가다보면 오래 못달리거나 번아웃이 올수도 있을것 같단 생각이 들었다.

그래서 앞으로 1가지 규칙을 세우고자 한다.

밤 11시가 되면 컴퓨터는 꺼비린다. 🔥

이렇게 하면 하루를 마무리할 수 있는 여유시간을 확보함과 동시에 일찍 잠들고 일찍 일어날수 있는 장치가 될 것이라고 생각한다.

운동을 하거나 여가시간을 보냄을 통해 체력관리, 멘탈케어 등의 부수효과를 기대할 수 있지 않을까?? 😀

📘 오늘 학습한 내용

오늘은 함수형 프로그래밍 학습의 연장선이다.

본격적으로 이터러블 데이터를 가공하여 함수형 프로그래밍에 적용할 수 있는 다양한 함수들을 구현해본다.

부수 효과와 순수 함수

부수효과 : 외부의 상태를 변경하는 것, 또는 함수로 들어온 인자의 상태를 직접 변경하는 것

순수함수 : 부수효과가 없는 함수 즉, 어떤 함수에 동일한 인자를 주었을 때 항상 같은 값을 리턴한 하는 함수

// 순수함수
function add1(a, b) {
  return a + b;
}
console.log(add1(2, 3)); // 5

// 비순수함수
let c = 1; // 변수 c에 의해 함수의 결과값이 달라진다.
function add2(a, b) {
  return a + b + c;
}
console.log(add2(2, 3)); // 6
c = 5;
console.log(add2(2, 3)); // 10

전까지 구현한 함수 (리마인드)

go : 값을 여러개의 함수들에 순차적으로 적용한 후 결과를 반환하는 함수

pipe : 여러개의 함수들을 하나로 압축하는 함수

curry : 인자가 원하는 갯수만큼 들어왔을 때 받아온 함수를 실행하게 도와주는 함수

map : 이터러블까지 사용가능한 map 함수 (curry 활용)

filter : 이터러블까지 사용가능한 filter 함수 (curry 활용)

reduce : 이터러블까지 사용가능한 reduce 함수 (curry 활용)

오늘 구현한 함수

아래에서 (지연평가) 표시

인자로 이터러블을 받는다. 제너레이터이므로 결과는 이터러블 객체 값이 아니다.

순회가 시작되면 비로소 값이 조회가 된다. 순회 이전까지는 suspended상태이다.

따라서 일반적인 이터러블처럼 값으로써 활용하기 보다

함수형 프로그래밍에서 지연평가하는 함수들의 조합을 위해서 사용한다.

마지막에 take함수를 사용하면 이터러블 객체 값으로써 사용이 가능하다.

혹은 [...iter] 의 형태처럼 스프레드 문법으로 값을 배열로 뽑아올수도 있다.

const range = L.range(10)
console.log(range) // suspended... , 값으로써 활욜은 불가

go(L.range(10),
    L.map(n => n + 10),
    L.filter(n => n % 2),
    take(2),
    console.log
); // [11, 13, 15]
// 지연평가하는 함수들의 조합으로 사용
// 최종적으로 take함수를 사용하면 비로소 값으로써 사용가능.

take : 제너레이터 혹은 이터러블에서 이터러블 객체로 값을 원하는 갯수로 뽑아오기 위해 사용하는 함수

지연성 함수가 제너레이터로 구현되다보니 값으로써 사용이 불가능한데 take 함수로 이터러블 값으로 뽑아올수 있다.

takeAll : take(Infinity)

L.range : 제너레이터 방식으로 지정한 범위만큼 값을 하나씩 생성하는 함수 (지연평가)

L.map : 제너레이터 방식으로 조건을 적용하여 값을 하나씩 생성하는 함수 (지연평가)

L.filter : 제너레이터 방식으로 조건에 맞는 값을 하나씩 생성하는 함수 (지연평가)

join : 이터러블까지 사용 가능한 join 함수 (reduce 활용)

find : 이터러블까지 사용 가능한 find 함수 (take 활용)

L.map + takemap 만들기

L.filter + takefilter 만들기

L.flatten : 제너레이터 방식으로 입력받은 2차원 배열의 모든 요소를 하나씩 생성하는 함수 (지연평가)

L.deepFlat : 제너레이터 방식으로 입력받은 다차원 배열의 모든 요소를 하나씩 생성하는 함수 (지연평가)

L.flatMap : 제너레이터 방식으로 입력받은 2차원 배열을 L.map 하고 L.flatten 하여 값을 하나씩 생성하는 함수 (지연평가)

flatMap : 즉시 평가하는 flatMap 함수

모든 구현 코드

// go 함수
const go = (...args) => reduce((a, f) => f(a), args);

// pipe 함수
const pipe =
  (f, ...fs) =>
  (...as) =>
    go(f(...as), ...fs);

// curry
const curry =
  f =>
  (a, ...agrs) =>
    agrs.length ? f(a, ...agrs) : (...agrs) => f(a, ...agrs);

// map + curry;
const map = curry((f, iter) => {
  const res = [];
  for (const el of iter) {
    res.push(f(el));
  }
  return res;
});

// filter + curry;
const filter = curry((f, iter) => {
  let res = [];
  for (const a of iter) {
    if (f(a)) res.push(a);
  }
  return res;
});

// reduce + curry
const reduce = curry((f, acc, iter) => {
  if (!iter) {
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for (const a of iter) {
    acc = f(acc, a);
  }
  return acc;
});

// take, takeAll
const take = curry((limit, iter) => {
  let res = [];
  for (const a of iter) {
    res.push(a);
    if (res.length == limit) return res;
  }
  return res;
});

const takeAll = take(Infinity)

// 지연평가 L
const L = {};

// L.lange
L.range = function* (length) {
  let i = -1;
  while (++i < length) {
    yield i;
  }
};

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

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

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

// join
const join = curry((sep = ', ', iter) =>
  reduce((a, b) => `${a}${sep}${b}`, iter)
);

// find
const find = curry((f, iter) =>
  go(
    iter,
    filter(a => (console.log(a), f(a))),
    take(1),
    ([a]) => a
  )
);

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

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

// L.flatten
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;
    // === yield*
    else yield a;
  }
};
// flatten
const flatten = pipe(L.flatten, take(Infinity));

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

// L.flatMap
L.flatMap = curry(pipe(L.map, L.flatten));

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


// 테스트
const users = [
   {
      name: 'a', age: 21, family: [
         {name: 'a1', age: 53}, {name: 'a2', age: 47},
         {name: 'a3', age: 16}, {name: 'a4', age: 15}
      ]
   },
   {
      name: 'b', age: 24, family: [
         {name: 'b1', age: 58}, {name: 'b2', age: 51},
         {name: 'b3', age: 19}, {name: 'b4', age: 22}
      ]
   },
   {
      name: 'c', age: 31, family: [
         {name: 'c1', age: 64}, {name: 'c2', age: 62}
      ]
   },
   {
      name: 'd', age: 20, family: [
         {name: 'd1', age: 42}, {name: 'd2', age: 42},
         {name: 'd3', age: 11}, {name: 'd4', age: 7}
      ]
   }
];

go(users,
   L.map(u => u.family),
   L.flatten, // 2차원 배열 펼치기
   L.filter(u => u.age < 20), // 20세 이하 필터
   L.map(u => u.age), // 나이만
   take(3), // 3개만
   console.log); // 16, 15, 19

💬 comment

생각보다 많은 함수들을 구현하였다.

구현을 하면서 이해는 했지만 양이 많다보니 헷갈릴수 있을것 같아서 최대한 한 눈에 볼 수 있는 치트시트 형태로 압축해봤다.

틈틈이 치트시트를 보면서 복습을 해야할 것 같다.😎

profile
console.log(noah(🍕 , 🍺)); // true

0개의 댓글