728x90

Math.sqrt() 함수는 숫자의 제곱근을 반환합니다.

사용방법

Math.sqrt(x);

매개변수

x: number

return 

주어진 숫자의 제곱근을 반환하며, 만약 x 가 음수라면 NaN 을 반환합니다.

예제

console.log(Math.sqrt(9));
// > 3

console.log(Math.sqrt(4));
// > 2

console.log(Math.sqrt(1));
// > 1

console.log(Math.sqrt(0));
// > 0

console.log(Math.sqrt(-1));
// > NaN
728x90
728x90

지연성을 가진 L.map 을 구현해봅니다.

앞서 만든 map 을 지연성을 가진 map 으로 만들되 generator / iterator 프로토콜을 기반으로 구현해 봅니다.

이 L.map 은 평가를 지연시키는 성질을 가지고 평가 순서를 다르게 조작할 준비가 되어있는 iterator 를 반환하는 generator 함수라고 볼 수 있습니다.

L.map 함수는 아래와 같이 작성 가능합니다.

    /**
     * 제너레이터 / 이터러블 프로토콜 기반
     * 지연성을 가진 L.map
     * 이터레이터를 반환하는 제너레이터 함수
     */
    const L = {};
    L.map = function *(f, iter) {   // 함수와 이터러블을 받아서 이터레이터를 반환하는 제너레이터 함수 L.map 정의 (지연성을 가진 L.map)
        for (const a of iter) { // 이터러블을 순회하면서 함수를 적용
            yield f(a); // 함수를 적용한 값을 반환 (지연성) 
        }
    };
    console.log("L.map");
    const it = L.map(a => a + 10, [1, 2, 3]);
    
    console.log(it.next());
    console.log(it.next());
    console.log(it.next());

 

해당 코드의 특징으로는 선언하는 것만 가지고는 아무것도 진행이 되지 않고,  it.next() 를 통해서 내가 평가하는 만큼의 값만 출력할 수 있습니다.

그래서 이 해당하는 iterator 를 통해 여러가지 방법으로 평가가 가능합니다. 예를 들어 spread 연산자를 통해 평가하게 되면 관련 배열을 리턴할 수 있기도 합니다.

    console.log("L.map");
    console.log(...L.map(a => a + 10, [1, 2, 3]));
    // > [11, 12, 13]

L.map 자체에서는 새로운 배열을 만들지 않고 값 하나하나마다 순회하며 yield 를 통해 함수가 적용된 값을 iterator 에대한 .next() 를 실행할때마다 하나씩 전달하게 되고, 그러한 준비가 된 iterator 객체를 내가 원하는 방법으로 평가하기 위한 준비를 할 수 있도록 L.map 을 구현할 수 있습니다. 

 

프로그래머스 - 강사 유인동/함수형 프로그래밍과 ES6
728x90
728x90

take란 함수를 만들어 봅니다.

/**
 * range
 */
const range = (l) => {
    let i = -1;
    let res = [];
    while (++i < l) {
        res.push(i);
    }
    return res;
};
/**
 * take
 */
const take = (count, iter) => { // 카운트와, 이터러블 2개의 인자를 받습니다.
    let res = []; // 리턴할 결과를 만듭니다.
    for (const a of iter) {	// 이터러블을 순회합니다.
        res.push(a);	// 리턴할 결과값에 push를 합니다.
        if (res.length === count) return res;	// 의도한 카운트에 도달하면 결과를 리턴합니다.
    }
    return res; // 카운트가 없다면 전체 이터러블을 값으로 리턴합니다.
};

console.log(range(100));	// range 100을 선언하여 100개의 배열을 출력합니다.
// > [0, 1, 2, 3, ... 99]

console.log(take(5, range(100));	// take 함수를 통하여 선언한 100개의 배열을 5개의 결과를 값으로 리턴합니다.
// > [0, 1, 2, 3, 4]

take 함수는 이터러블 프로토콜을 따르고 있으며, 카운트에 따라 push를 수행하는 아주 단순한 함수입니다.

이러한 함수를 통하여 이전에 만들었던 L.range 를 통하여 동일한 결과값을 출력 할 수 있습니다.

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

console.log(L.range(100));
// > [0, 1, 2, 3, 4, ... 99]

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

 L.range와 같이 지연성을 가지는 값을 이터레이터로 만들게 되면 전혀 다른 함수여도 이터러블 프로토콜만 따르게 되면 모두 조합을 할 수가 있기 때문에, 자바스크립트 고유의 프로토콜을 통하여 작성이 가능하므로 조합성이 높게 구성이 가능합니다.

이 코드 역시 효율성 측면에서 조금 더 나은 모습을 보여주게 됩니다.

코드는 아래와 같은 차이점이 있습니다.

console.log(take(5, range(10000)));
// 1. range 함수를 통해 10000개의 배열을 생성합니다.
// 2. 그중 5개만큼의 배열을 take 함수를 통해 반환합니다.

console.log(take(5, L.range(10000)));
// 1. L.range 를 통해 최대 10000개의 배열을 생성할 것임을 구성 후 연산은 하지 않습니다.
// 2. 그중 take 를 통해 최대 5개의 배열을 함수를 통해 반환합니다.
// 3. 5번 이후로는 배열을 생성하지 않습니다.

모두 생성하고 그중 뽑아 내는것과, 제너레이터를 통해 해당하는 값일 경우가 될때까지 지연시켜 놓은 후 원하는 값일 경우 출력하는 것의 차이가 있습니다.

아래와 같이 무한 수열의 경우 range는 error 가 나지만 L.range 의 경우 원하는 take의 값만큼 결과를 뽑아낸 후 더이상을 작동을 하지 않으므로, 원하는 결과값을 뽑아낼 수 있게 됩니다.

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

이를 go, curry 함수를 통해 조금 더 가독성 좋게 작성이 가능합니다.

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

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

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

/**
 * reduce
 */
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;
});

go(
    range(100),
    take(5),
    console.log
);

go(
    L.range(100),
    take(5),
    console.log
);

말씀드린 것처럼, 이러한 지연성을 지닌 함수는 take 나 reduce 같은 함수를 만날때 연산이 시작되게 됩니다.

제너레이터로 이터레이터를 리턴하는 함수를 실행했을경우에는 해당하는 연산이 안에서 이루어지지 않으며, 안쪽에 있는 배열의 값을 통해 연산을 필요로 하는 함수나, 몇개의 length가 될지 모르는 배열([0, 1, 2, 3, ...])에서 앞에서 몇개의 결과만 꺼내서 결과를 만들려고 하는 값이 필요하기 전까지 연산을 미루다가 해당 함수를 만났을때 연산 처리를 하는 기법입니다.

 

프로그래머스 - 강사 유인동/함수형 프로그래밍과 ES6
728x90
728x90

repeat() 메서드는 문자열을 주어진 매개변수의 횟수만큼 반복하여 붙인 새로운 문자열을 반환합니다.

구문

str.repeat(count);

매개변수

count - 문자열을 반복할 횟수. 0과 양의 무한대 사이의 정수 ([0, +∞))

리턴값

현재 문자열을 주어진 매개변수의 횟수만큼 반복해 붙인 새로운 문자열.

예제

const str = '안녕하세요';

str.repeat(-1);
// RangeError

str.repeat(0);
// ''

str.repeat(1);
// 안녕하세요

str.repeat(2);
// 안녕하세요안녕하세요

str.repeat(3.5);
// 안녕하세요안녕하세요 (카운트는 정수형으로 변환되어 적용)

str.repeat(1/0); 
// RangeError (infinity)

repeat은 ECMAScript 2015 명세에 추가 되었으므로, 어떠한 표준 구현체에서는 사용할 수 없을 수 있습니다.

아래 코드를 포함하면 미지원 플랫폼에서도 repeat을 사용할 수 있습니다.

if (!String.prototype.repeat) {
  String.prototype.repeat = function(count) {
    'use strict';
    if (this == null) {
      throw new TypeError('can\'t convert ' + this + ' to object');
    }
    var str = '' + this;
    count = +count;
    if (count != count) {
      count = 0;
    }
    if (count < 0) {
      throw new RangeError('repeat count must be non-negative');
    }
    if (count == Infinity) {
      throw new RangeError('repeat count must be less than infinity');
    }
    count = Math.floor(count);
    if (str.length == 0 || count == 0) {
      return '';
    }
    // Ensuring count is a 31-bit integer allows us to heavily optimize the
    // main part. But anyway, most current (August 2014) browsers can't handle
    // strings 1 << 28 chars or longer, so:
    if (str.length * count >= 1 << 28) {
      throw new RangeError('repeat count must not overflow maximum string size');
    }
    var maxCount = str.length * count;
    count = Math.floor(Math.log(count) / Math.log(2));
    while (count) {
       str += str;
       count--;
    }
    str += str.substring(0, maxCount - str.length);
    return str;
  }
}

 

출처: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/repeat

728x90
728x90

range 함수를 만들어 보겠습니다.

우선 range 함수에 대한 내용을 이해하는데 필요한 add 함수와 reduce 함수를 먼저 선언해 줍니다.

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

range 함수는 숫자 하나를 받고 해당 숫자의 크기만한 배열을 리턴하는 함수입니다. 해당 함수를 작성해봅니다.

인자로 받은 숫자만큼 순회하며 배열에 넣고 리턴하는 간단한 함수입니다.

구현한 range 를 통하여 받은 배열의 값들을 모두 더해 봅니다.

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,]

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

const list = range(5);
console.log(list);
// [0, 1, 2, 3, 4,]
console.log(reduce(add, list));
// 10

 

이번엔 느긋한(Lazy) range 함수를 작성해봅니다.

구현한 L.range 를 통하여 받은 배열의 값들을 모두 더해 봅니다.

// 느긋한 L.range
const L = {};
L.range = function *(l) {
    let i = -1;
    while (++i < l) {
        console.log(i, "L.range");
        yield i;
    }
};

console.log(L.range(5));
// [object Generator]

const list2 = L.range(5);
console.log(list2);
// [object Generator]
console.log(reduce(add, L.range(5)));
// 10

L.range 를 본다면, 이터레이터를 만드는 제너레이터 함수를 이용하여 L.range 함수를 선언 하였습니다.

위처럼 제너레이터 함수를 선언하여도 동일한 값이 출력 되는것을 확인 할 수 있습니다.

이 두가지 range 코드의 차이점을 확인해보면 console.log(list) 와 console.log(list2) 의 결과값이 차이가 있는것을 볼 수 있습니다.

list 는 배열이 출력되며, list2 는 이터레이터가 로그로 출력이 됩니다.

두개의 값이 동일한 결과가 나오게 되는 이유는 reduce라는 함수가 이터러블을 받아서 처리하기 때문입니다. 

그말은 list 와 list2 가 모두 이터러블이라는 뜻이며, 해당 이터러블을 이터레이블로 만든 후 순회하여 결과를 만들어 내어 출력하게 됩니다.

조금 더 list 와 list2 에 대한 차이점을 이야기 해보면, 아래와 같이 선언했을 경우의 로그값을 통하여 확인이 가능합니다.

 

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

const list = range(5);
// 0 "range"
// 1 "range"
// 2 "range"
// 3 "range"
// 4 "range"

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

const list2 = L.range(5);
//

console.log(list2.next());
// {value: 0, done: false}

위와같이 range 안에서의 로그값을 확인해 보면, range 의 경우 함수 선언과 동시에 순회를 시작하며 0부터 4까지 담아서 출력하게 되며, L.range 의 경우 어떠한 값도 로그로 출력되지 않는것을 볼 수 있습니다.

L.range의 로그값이 출력되는 시점은 함수가 선언되며 출력이 되는게 아닌, 해당 이터레이터의 내부를 순회하며 값을 평가할때에 결과가 출력되게 됩니다. 아래 next()를 통하여 실행했을때 나오는 값으로 확인이 가능합니다.

 

range / L.range 차이점

range의 경우 array로 선언이 된 후 동작이 시작 됩니다.

L.range 는 array로 선언을 하지 않고 하나씩 값을 꺼내어 동작을 하게 됩니다.

 

range의 경우 array로 선언이 된 후 reduce를 통해 값이 처리 될때 해당 reduce 안쪽에서 이터레이터를 만든 후 for...of 를 순회하여 값을 처리 하게 됩니다.

L.range의 경우 실행되었을때 이터레이터를 만들고, reduce 를 통해 이미 만들어진 이터레이터로 for...of 를 순회하며 값을 처리 하기 때문에 조금 더 효율 적이라 볼 수 있습니다.

 

출처: 프로그래머스 - 강사 유인동/함수형 프로그래밍과 ES6

 

 

 

 

 

 

 

 

 

728x90
728x90

curry 라는 함수를 만들어봅니다.

curry는 함수를 값으로 다루며 받아둔 함수를 원하는 시점에 평가 시키는 함수입니다.

우선 함수를 받은 후 함수를 리턴하고, 인자를 받아서 인자가 원하는 갯수만큼의 인자가 들어왔을때 받아두었던 함수를 나중에 평가 시키는 함수 입니다.

코드로 확인해 봅니다.

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

 1. 함수를 받아(f) 함수를 리턴합니다.. () => {};

2. 리턴하는 함수에서는 ( f )함수에서 사용할 첫번째 인자( a )와 나머지 인자( ..._ )를 대신해서 받습니다. ( a, ..._ )

3. 만약 함수의 인자가 2개 이상 전달 되었을 경우( _.length ) 받아둔 함수를 즉시 실행을 합니다.( f(a, ..._) )

4. 만약 2개 이상이 아니라면 다시한번 함수를 리턴합니다 ( () => )

5. 그 이후 들어올 값들을 받은 후 ( ..._ ), 미리 받아 두었던 인자 ( a ) 와 새로받은 ( ..._ ) 인자를 전달하여 함수 ( f )를 실행합니다.

 

    const add = (a, b) => a + b;
    const add2 = curry(add);
    
    // (1)
    console.log(add2(10, 5));
    // > 15
    // (2)
    console.log(add2(10)(5));
    // > 15

위와 같이 add 함수를 curry 로 감싸서 실행한 경우입니다.

(1)

1. add 를 curry 로 감싼 add2 라는 함수에 10 과 5의 인자를 선언합니다.

2. curry  함수가 실행됩니다.

3. add2(10, 5) 의 인자가 2개 이상이므로,  f(a, ..._) 와 같이 실행됩니다. 
-> f = add, ( a, ..._ ) = ( 10, 5 ) --> add2(10, 5) = add(10, 5) -> 15

(2)

1. add 를 curry 로 감싼 add2 라는 함수에 10이라는 인자를 선언한 후, 해당 결과로 나오는 함수에 5라는 인자는 선언합니다.

2. curry 함수가 실행됩니다.

3. add2(10) 의 인자가 1개이므로, (..._) => f(a, ..._) 와 같이 함수를 리턴하여 실행됩니다.

4. 해당 함수를 함수로 구현하여 나중에 선언받는 인자를 선언하는 함수를 실행합니다.
-> f = add, ( ..._ ) = ( 5 ), a = ( 10 ) --> add2(10)(5) = ( 5 ) => add( 10, 5 )

 

(2) 번과 같이 함수를 값으로 리턴하여 기본인자를 각각의 값으로 가지는 함수로 변환하여 병합시킬수 있도록 처리가 가능합니다.

 

이와같은 특성을 활용하여 기존 product의 go, map, filter, reduce 를 활용했던 코드를 curry 를 활용하여 조금더 가독성 좋게 구현해봅니다.

 

우선 map, filter, reduce 를 curry 함수로 감싸줍니다.

    const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
    const filter2 = curry((f, iter) => {
        let res = [];
        for (const a of iter) {
            if (f(a)) res.push(a);
        }
        return res;
    });
    const map2 = curry((f, iter) => {
        let res = [];
        for (const a of iter) {
            res.push(f(a));
        }
        return res;
    });
    const reduce2 = 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;
    });

이후 이전과 동일한 go 함수를 curry 가 적용된 함수를 사용하여 구현해 봅니다.

// (1)
go(
    products,
    products => filter(p => p.price < 20000, products),
    products => map(p => p.price, products),
    prices => reduce(add, prices),
    console.log
);
// > 30000

// (2)
go(
    products,
    filter2(p => p.price < 20000),
    map2(p => p.price),
    reduce2(add2),
    console.log
);
// > 30000

(1) 은 기존의 go를 이용하여 구현한 코드이며, (2) 는 curry 가 적용된 함수를 활용하여 작성된 코드입니다.

기존 products => filter(p => p.price < 20000, products) 에서 curry 를 적용하여 filter(p => p.price < 20000)(products) 로 사용이 가능하게됨에 따라, (2)와 같이 구현이 가능하게 되며 훨씬 더 깔끔하게 작성하여 가독성을 높일 수 있습니다.

 

curry의 경우 한번 작성하면서 조금 이해가 되지 않는 부분이 있어, 여러번 순차적으로 작성된 함수를 따라가보며 이해도를 높여 나갔습니다. 

 

전체 코드 예시입니다.

/**
 * curry
 */
const products = [
    {name: '반팔티', price: 15000, quantity: 1},
    {name: '긴팔티', price: 20000, quantity: 2},
    {name: '핸드폰케이스', price: 15000, quantity: 3},
    {name: '후드티', price: 30000, quantity: 4},
    {name: '바지', price: 25000, quantity: 5}
];
const filter = (f, iter) => {
    let res = [];
    for (const a of iter) {
        if (f(a)) res.push(a);
    }
    return res;
};
const map = (f, iter) => {
    let res = [];
    for (const a of iter) {
        res.push(f(a));
    }
    return res;
};
const go = (...args) => reduce((a, f) => f(a), args);
const reduce = (f, acc, iter) => {
    if (!iter) {
        iter = acc[Symbol.iterator]();
        acc = iter.next().value;
    }
    for (const a of iter) {
        acc = f(acc, a);
    }
    return acc;
};
const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
const filter2 = curry((f, iter) => {
    let res = [];
    for (const a of iter) {
        if (f(a)) res.push(a);
    }
    return res;
});
const map2 = curry((f, iter) => {
    let res = [];
    for (const a of iter) {
        res.push(f(a));
    }
    return res;
});
const reduce2 = 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;
});
const add = (a, b) => a + b;
const add2 = curry(add);

// (1)
console.log(add2(10, 5));
// > 15
// (2)
console.log(add2(10)(5));
// > 15
go(
    products,
    filter2(p => p.price < 20000),
    map2(p => p.price),
    reduce2(add2),
    console.log
);
// > 30000

 

출처: 프로그래머스 - 강사 유인동/함수형 프로그래밍과 ES6

728x90
728x90

현재까지 배웠던 filter, map, reduce를 통해 출력했던 값을 조금 더 가독성있게 일반적인 흐름인 위에서 아래, 왼쪽에서 오른쪽으로 읽어 가며 코드를 확인할 수 있도록 만들어 봅니다.

우선 기존 제품 리스트를 filter, map, reduce를 통해 구현했던 코드를 작성해 줍니다.

// 제품 리스트
const products = [
    {name: '반팔티', price: 15000, quantity: 1},
    {name: '긴팔티', price: 20000, quantity: 2},
    {name: '핸드폰케이스', price: 15000, quantity: 3},
    {name: '후드티', price: 30000, quantity: 4},
    {name: '바지', price: 25000, quantity: 5}
];

// reduce, map, filter, add
function reduce(f, acc, iter) {
    if (!iter) {
        iter = acc[Symbol.iterator]();
        acc = iter.next().value;
    }
    for (const a of iter) {
        acc = f(acc, a);
    }
    return acc;
}
function map(f, iter) {
    let res = [];
    for (const a of iter) {
        res.push(f(a));
    }
    return res;
}
function filter(f, iter) {
    let res = [];
    for (const a of iter) {
        if (f(a)) res.push(a);
    }
    return res;
}
function add(a, b) {
    return a + b;
}

// example (1)
console.log(
    reduce(add,
        map(p => p.price,
            filter(p => p.price < 20000, products)
        )
    )
);

현재의 코드를 기준으로 흐름을 보면 맨 안쪽, 오른쪽 함수인 filter부터 시작하며, 아래에서 위로, 오른쪽에서 왼쪽으로 코드를 읽어 나가야 합니다.

1. products에서 20,000원 미만인 제품을 filter를 통해 골라냅니다. 

2. 조건에 따라 분리된 제품을 map함수를 통하여 가격만 뽑아냅니다.

3. 가격만 뽑아낸 배열을 reduce함수를 통해 add 합니다.

4. add 된 최종 결과값을 console.log를 통해 출력 합니다. 

 

이를 go함수를 이용 하여 가독성 좋게 작성해 봅니다.

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

// example (2)
go(
    products,
    products => filter(p => p.price < 20000, products),
    products => map(p => p.price, products),
    prices => reduce(add, prices),
    console.log
);

go함수를 통하여 작성 한 코드이며 위에서 아래로, 왼쪽에서 오른쪽으로 코드를 읽어 나갈 수 있습니다.

1. 제품(products)을 받습니다..

2. products 를 filter를 통해 20,000원 미만인 제품만 뽑아냅니다.

3. 뽑아낸 products에서 가격만 map함수를 통해 재정의 합니다.

4. 뽑아진 가격들을 reduce와 add를 통해 합산 합니다.

5. 최종값을 console.log를 통해 출력합니다.

 

코드의 길이는 조금 더 늘어났지만 가독성이 좋으므로 읽히기 쉽게 작성 된 코드라고 볼 수 있으며, 진행되는 흐름 및 최종 결과값을 동일 하다는 것을 알 수 있습니다.

 

출처: 프로그래머스 - 강사 유인동/함수형 프로그래밍과 ES6

728x90
728x90

이번엔 pipe 함수를 만들어 봅니다.

pipe함수는 go함수와 다르게 함수를 리턴하는 함수 입니다.

go 함수는 함수들과 인자를 전달하여 즉시 어떤 값을 평가하는데 사용하다면

pipe함수는 함수들이 나열되어있는 합성 된 함수를 만드는 함수입니다.

pipe함수는 아래와 같이 사용하게 됩니다.

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

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

go(
    0,
    a => a + 1,
    a => a + 10,
    a => a + 100,
    console.log
);

/**
 * pipe
 */
const pipe = () => () => {};

const f = pipe(
    a => a + 1,
    a => a + 10,
    a => a + 100,
    console.log
);

console.log(f(0));

pipe함수는 일단 함수를 리턴하는 함수입니다.

위와같은 3개의 함수를 연속적으로 실행하면서 축약하는 하나의 함수를 만들어 함수를 리턴하는 함수를 만들게 됩니다.

pipe함수는 내부에서 go 함수를 사용하는 함수라고 볼 수 있습니다. 우선 내부 함수들을 전달을 받고 나중에 인자를 받습니다.

시작 하는 인자를 받고 받은 인자를 go함수를 실행하여 인자를 주고 그 뒤에 함수들을 주면 파이프 함수가 완성이 됩니다.

작성한 pipe 함수는 아래와 같습니다.

/**
 * pipe
 */
const pipe = (...fs) => (a) => go(a, ...fs);

const f = pipe(
    a => a + 1,
    a => a + 10,
    a => a + 100
);

console.log(f(0));

// > 111

1. 나머지 매개 변수 구문을 통하여 pipe 함수의 함수들을 인자로 받습니다.

2. 시작하는 인자(a)를 받은 후, go함수를 통해 pipe의 인자들(...fs)을 주면 go 와 동일한 기능을 수행하는 pipe 함수가 만들어 집니다.

 

추가로 pipe함수는 go함수와 다르게 조금 더 기능을 추가 해 보도록 합니다.

go 함수의 경우 시작하는 인자가 2개 이상인 경우에는 아래와 같이 작성 할 수 있습니다.

const add = (a, b) => a + b;

go(
    add(0, 1),
    a => a + 1,
    a => a + 10,
    a => a + 100,
    console.log
);

// > 112

하지만 pipe함수와 같은 경우에는 pipe함수를 선언할때 인자에 설정 후 전달을 해야 하는 상황이 발생합니다.

현재 pipe 함수에서 go함수와 동일하게 작성한다면 NaN값이 출력 됩니다.

const f = pipe(
    a => a + 1,
    a => a + 10,
    a => a + 100
);

console.log(f(0));

// > 111

console.log(f(add(0, 1)));

// > 112

/**
 * pipe 인자를 2개 받을 경우
 */
const f2 = pipe(
    (a, b) => a + b,
    a => a + 10,
    a => a + 100
);

console.log(f2(0, 1));

// > NaN

위와 같은 현상을 해결하기 위해, 

pipe함수는 첫번째 함수의 경우에는 실행해줄때 2개이상의 인자를 받는 함수도 사용 할 수 있도록, 구성해줍니다.

/**
 * pipe
 */
const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);
// const pipe = (...fs) => (a) => go(a, ...fs);

const f = pipe(
    a => a + 1,
    a => a + 10,
    a => a + 100
);

console.log(f(0));

// > 111

console.log(f(add(0, 1)));

// > 112

/**
 * pipe 인자를 2개 받을 경우
 */
const f2 = pipe(
    (a, b) => a + b,
    a => a + 1,
    a => a + 10,
    a => a + 100
);

console.log(f2(0, 1));

// > 112

1. 첫번째 인자를 받고(f) 그 나머지 인자(...fs)를 나머지 매개변수 구문을 통하여 구성합니다.

2. 시작하는 인자((0, 1))을 나머지 매개변수 구문을 통하여 구성 후(...as) 해줍니다.

3. go 함수의 첫번재 시작하는 인자로 첫번째 함수인자에 시작 인자를 전달(f(...as))하여 go 함수를 실행 해줍니다. 

 

위와 같이 한다면 go와 동일한 결과값을 가지는 pipe 함수를 구현할 수 있습니다.

 

전체 예시 입니다.

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

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

go(
    0,
    a => a + 1,
    a => a + 10,
    a => a + 100,
    console.log
);

// > 111

const add = (a, b) => a + b;

go(
    add(0, 1),
    a => a + 1,
    a => a + 10,
    a => a + 100,
    console.log
);

// > 112

/**
 * pipe
 */
const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);
// const pipe = (...fs) => (a) => go(a, ...fs);

const f = pipe(
    a => a + 1,
    a => a + 10,
    a => a + 100
);

console.log(f(0));

// > 111

console.log(f(add(0, 1)));

// > 112

/**
 * pipe 인자를 2개 받을 경우
 */
const f2 = pipe(
    (a, b) => a + b,
    a => a + 1,
    a => a + 10,
    a => a + 100
);

console.log(f2(0, 1));

// > 112

 

출처: 프로그래머스 - 강사 유인동/함수형 프로그래밍과 ES6

 

728x90

+ Recent posts