데브코스 TIL-day8-es6의 지연성

조주영·2021년 8월 11일
1

데브코스-TIL

목록 보기
10/34

range와 느긋한 L.range

느긋한 계산법?

지연 평가

컴퓨터 프로그래밍에서 느긋한 계산법(Lazy evaluation)은,
계산의 결과 값이 필요할 때까지 계산을 늦추는 기법이다.
지연 평가를 하면 필요 할 때까지 계산을 늦추면서 불필요한 계산을 줄일 수 있다

장점

  • 불필요한 계산을 하지 않으므로 빠른 계산이 가능하다.-> 자료가 무한이어도 필요한 것만 쓰기 때문에 계산이 가능.

  • 복잡한 수식 안에서 오류를 피할 수 있다.

일반적인 range

어떤 함수에 list를 전달할때, list라는 변수에 담긴 값이 이미 배열인 상태

코드:

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

console.log(range(5));
// [0, 1, 2, 3, 4]

느긋한 L.range

L.range에서의 list의 값은 이터레이터이다.

구현:

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


let list = L.range(4);
console.log(list); //이터레이터가 출력된다
console.log(reduce(add, list));

//일반 array는 어레이를만들고 이터레이터를 만들고 next()하면서순회
//실행됫을때 이터레이터를 만들고 자기자신을 그냥 리턴하는 이터러블이고, 
//해당함수를 실행하면
//이미 만들어진 함수를 리턴만하고 순회를 한다.->효율적

range & L.range동작 비교

range

  1. array 미리 만든다.
  2. 이터레이터를 만든다.
  3. next를 만들면서 순회한다.

L.range

  1. array를 만들지 않고 실행될때 이터레이터를 만든다.
  2. 그 이터레이터가 자기 자신을 그냥 리턴하는 이터러블이 된다.
  3. 해당 함수를 실행하면 이미 만들어진 함수(이터레이터)를 그냥 리턴만하고 순회=>효율적

효율

L.range가 시간 효율이 높음을 확인할 수 있다.(필요 할 때 필요한 것만 가져다가 쓰기 때문이다.

take

값을 받아서, 원하는 크기 만큼 잘라주는 함수

const take = curry((l, iter) => {
    let res = [];
    for (const a of iter) { 
        res.push(a); 
        if (res.length == l) return res;
        //결과의 length와 리미트가 같아지면 순회종료    }
    return res;
});

//지연성을 가지는 값을 이터레이터를 만들게 되면
//이터러블 프로토콜을 따른다면 조합을 할 수 있다.
log(take(5,range(100)));
log(take(5,L.range(100)));
//지연평가? 
//=>영리하다고 볼수 있다 제때 계산법이라고 표현되기도 한다 가장 필요할때 
//해당하는 코드를 쓴다.
//값을 만드는 것을 최소화하고, 연산을 만드는것 줄인다.

L.map

L.map은 새로운 Array를 만들지 않고, 이터러블을 순회하면서,
각 요소에 대해 함수를 적용한 값을 yield를 통해 평가를 미루는 순서를 가지고 평가순서를 달리하는 이터레이터를 반환하는 제너레이터 함수를 수행.

코드:

L.map = function *(f, iter) {
    for (const a of iter) yield f(a); // f(a)
};
var it = L.map(a => a + 10, [1, 2, 3]);
console.log(it.next());
console.log(it.next());
console.log(it.next());
//11 12 13

L.filter

L.filter는 이터러블을 순회하면서, 결과 값이 참인 경우에만 값을 yield를 통해 전달

L.filter = function *(f, iter) {
for (const a of iter) if (f(a)) yield a; // a
};
var it = L.filter(a => a % 2, [1, 2, 3, 4]);
console.log(it.next());
console.log(it.next());
console.log(it.next());

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

사용하는 데이터가 무엇이든지
사용하는 보조 함수가 순수 함수라면,
아래와 같이 결합한다면 둘 다 결과가 같다.

[[mapping, mapping], [filtering, filtering], [mapping, mapping]]
=
[[mapping, filtering, mapping], [mapping, filtering, mapping]]

결과를 만드는 함수 reduce, take

reduce와 take는 map, filter와 같이 지연 평가를 진행한 값을 실제 연산하는 함수다.

reduce

reduce를 통해 객체로 부터 url의 queryStr을 얻는 방법

코드:

const queryStr = obj => go(
    obj,
    Object.entries,
    map(([k, y]) => `${k}=${v}`),
    reduce((a, b) => `${a}&${b}`)
);
    
console.log(queryStr({limit: 10, offset: 10, type: 'notice'}));

pipe를 이용해 지연 map을 쓰면,
코드:

const queryStr = pipe(
    L.entries,
    L.map(([k, v]) => `${k}=${v}`),
    join('&'));

console.log(queryStr({limit: 10, offset: 10, type: 'notice'}));

Array.prototype.join 보다 다형성이 높은 join 함수

join

코드:

const join = curry((sep = ',', iter) =>
    reduce((a, b) => `${a}${sep}${b}`, iter));
    //얘가 있어서 배열이 아니더라도 받을수 있다
    
function *a() {
    yield 10;
    yield 11;
    yield 12;
    yield 13;
}

log(join(' - ', a()));
const sharpJoin = join("#");
const obj = {limit : 10 , offset : 10 , type : "notice"};

for (const a in obj) {
  log(sharpJoin([a , obj[a]]));
}
//  limit#10
//  offset#10
//  type#notice

지연성 take, find

take를 통해 find 함수를 만들 수 있다.
(find는 take를 통해서 이터러블 값을 받는 함수)
find는 특정값 하나를 뽑는 함수이고,
find로 가기 전에 어떤 함수들을 필터 조건으로 합성해놓고 사용할 수 있다.

const users = [
    {age: 32},
    {age: 31},
    {age: 37},
    {age: 28},
    {age: 25},
    {age: 32},
    {age: 31},
    {age: 37}
];

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

console.log(find(u => u.age < 30)(users));
//나이가 30이하인 사람한명 뽑기

console.log(
go(users,
    L.map(u => u.age),
    find(n => n < 30),));

L.map, L.filter로 map과 filter 만들기

L.map + take 로 map

code:

const takeAll=take(Infinity);
const map = curry((f, iter) => go(
    L.map(f, iter),
    take(Infinity)
));

는 이렇게 간편히 바뀐다.

const map = curry(pipe(L.map, takeAll));
console.log(map(a=>a+10,L.range(4));
//10 11 12 13

L.filter + take로 filter

const takeAll=take(Infinity);
const filter = curry((f, iter) => {
    let res = [];
    iter = iter[Symbol.iterator]();
    let cur;
    while (!(cur = iter.next()).done) {
        const a = cur.value;
        if (f(a)) res.push(a);
    }
    return res;
});

심플하게

const filter = curry(pipe(L.filter, takeAll));
log(filter(a=>a%2, range(4)));
//1,3

L.flatten, flatten

값을 펼쳐서 하나의 배열로 만들게 하는 함수

const isIterable = a => a && a[Symbol.iterator];
//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;
    }
};

//출력
var it = L.flatten([[1, 2], 3, 4, [5, 6], [7, 8, 9]]);
    log(it.next());
    log(it.next());
    log(it.next());
    log(it.next());



log(take(6, L.flatten([[1, 2], 3, 4, [5, 6], [7, 8, 9]])));



const flatten = pipe(L.flatten, takeAll);
log(flatten([[1, 2], 3, 4, [5, 6], [7, 8, 9]]));

yield *, L.deepFlat

yield *

yield * 이터러블은
for (const val of iterable) yield val; 과 같다.

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

L.deepFlat

깊은 Iterable을 모두 펼치고 싶다면
L.deepFlat 사용하면 된다.
L.deepFlat 은 깊은 Iterable을 펼쳐준다.

L.deepFlat = function* f(iter) {
    for (const a of iter) {
        if (isIterable(a)) yield* f(a);
        else yield a;
    }
};
console.log([...L.deepFlat([1, [2, [3, 4], [[5]]]])]);
// [1, 2, 3, 4, 5];

L.flatMap, flatMap

flatMap : map과 flatten을 동시에 하는 함수

log([[1, 2], [3, 4], [5, 6, 7]].flatMap(a => a)); //자바스크립트 문법
log([[1, 2], [3, 4], [5, 6, 7]].flatMap(a => a.map(a => a * a)));
log(flatten([[1, 2], [3, 4], [5, 6, 7]].map(a => a.map(a => a * a))));

map과 flatten이 따로 동작하면 비효율적으로 동작하기 때문에 사용한다.

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

// var it = L.flatMap(map(a => a * a), [[1, 2], [3, 4], [5, 6, 7]]);
// 는 아래와 동일한 코드이다.
var it = L.flatMap(a => a, [[1, 2], [3, 4], [5, 6, 7]]);
console.log([...it]);

console.log(flatMap(a => a, [[1, 2], [3, 4], [5, 6, 7]]));

console.log(flatMap(L.range, map(a => a + 1, [1, 2, 3])));
var it = L.flatMap(L.range, map(a => a + 1, [1, 2, 3]));
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());


console.log(take(3, L.flatMap(L.range, map(a => a + 1, [1, 2, 3]))));

2차원 배열 다루기

const arr = [
    [1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [9, 10]
];

console.log(go(arr,
    L.flatten,
    L.filter(a => a % 2), // 홀수만 남기기
    L.map(a => a * a), // 제곱
    take(4), // 앞에서부터 4개만 뽑고
    reduce(add), // 합산
    ));

실무적코딩

var 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}
        ]
    }
];
console.log(go(users,
    L.map(u => u.family),//가족을뽑고
    L.flatten,모두표시
    takeAll));



console.log(go(users,
    L.map(u => u.family),//가족이다.
    L.flatten,//펼치기
    L.filter(u => u.age < 20),//성인이고
    L.map(u => u.name),//조건에 부합하는 그들의이름
    take(4)));//4명뽑을건데


console.log(go(users,
    L.flatMap(u => u.family),
    L.filter(u => u.age > 20),
    L.map(u => u.age),
    take(4),
    reduce(add)));

느낀점

오늘 지연성을 배우면서, 신기했지만 너무 복잡했다. til을 쓰며 복습해도, 지연성이라는 개념에 대해서도 와닿지가 않아서, 다시금 반복학습을 하려 한다.
여담으로, 2주차 과제를 하려다 시간관리가 안되어 못하게 되었다.
그런김에 1주차 과제의 멘토님과 팀원들의 피드백을 보고 개선하는데,
내가 그동안 짜왔던 코드는 규칙도 없고 안좋은 습관이 굉장히 많았다.... 변수명부터, 자료형선언, 코드의 간결성 모두 다시봐도 별로였다.
아직 한참 부족하고 갈 길이 멀다. 더욱더 분발해야겠다! 부족하다는 것은 더 성장할 수 있는 기회니까...? 라고 정신승리를하며 -끝

profile
꾸준히 성장하기

0개의 댓글