함수형 프로그래밍 5 코드를 값으로 다루기

go

첫 번째 parameter를 두 번째 parameter로 전달한다.
이를 마지막 parameter까지 반복하는 함수이다.
즉 이전 함수의 실행결과를 다음 함수의 인자로 전달하는 함수이다.
값을 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 go = (...args) => reduce((a, f) => f(a), args);

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

고차함수가 한 번에 이해되지 않아서 콘솔로 실행 순서를 추적해보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const reduce = (f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter) {
console.log(f, acc, a);
acc = f(acc, a);
}
return acc;
};

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

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

// (a, f) => f(a) 0 (a) => a + 1
// (a, f) => f(a) 1 (a) => a + 10
// (a, f) => f(a) 11 (a) => a + 100
// (a, f) => f(a) 111 ƒ log() { [native code] }
// 111

실행 순서는 다음과 같다.

  1. reduce에 콜백함수 c (a,f)=>f(a)와 인자배열을 전달하는 함수인 go를 만든다.
  2. reduce로 전달된 인자가 2 개밖에 없으므로 args[0]이 acc가 된다. 이 때 args는 콜백함수만 존재하는 이터러블이 된다.
  3. args[0]은 이미 평가되었으므로 args[1]부터 args를 순회하며 acc에 c에 args 이러터블의 value를 전달한 값을 저장한다. 즉 acc에는 (acc,args[Symbol.iterator]().next().value)=>args[Symbol.iterator]().next().value(a)가 실행되어 저장된다.
  4. 모든 콜백함수의 실행을 마친 후 acc를 반환한다.

pipe

go와 같은 역할을 하지만 값이 아닌 함수를 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 go = (...args) => reduce((a, f) => f(a), args);

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

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

console.log(f(0, 1));
///111

마찬가지로 콘솔로 실행순서를 추적해보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 go = (...args) => reduce((a, f) => f(a), args);

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

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

console.log(f);

// (...as) =>
// go(f(...as), ...fs)

console.log(f(0, 1));
///111

실행순서는 다음과 같다.

  1. pipe에 함수를 평가한 값을 parameter로 받도록 하기위해 args의 첫 번째 함수를 따로 분리한다.
1
2
3
4
const pipe =
(f, ...fs) =>
(...as) =>
go(f(...as), ...fs);
  1. f에 pipe와 콜백을 전달한다. f엔 다음과 같은 함수객체가 저장된다.
    f의 실행컨텍스트엔 pipe에 저장된 첫 번째 함수가 있다.
1
f = (...as) => go(f(...as), ...fs);
  1. f에 0,1을 전달한다. f(0,1)은 add(0,1)이므로 1로 평가되어 전달된다.
    즉 acc의 초기값은 1이 된다.
1
f = (0,1) => go(f(0,1), ...fs);
  1. go 내부에서 acc를 계산한후 반환한다.
1
2
console.log(f(0, 1));
// 111

curry

함수를 parameter로 받아서 원하는 개수만큼의 parameter가 들어왔을 때 평가시켜 반환하는 함수이다.
코드의 재사용성을 높여준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const curry =
(f) =>
(a, ..._) =>
_.length ? f(a, ..._) : (..._) => f(a, ..._);
//parameter가 2개 이상이라면 바로 실행하고 두 개보다 작다면 이후 parameter를 받아서 실행하는 함수이다.

const mult = curry((a, b) => a * b);
console.log(mult);
//(a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._)
console.log(mult(1));
// (..._) => f(a, ..._)
console.log(mult(1)(2));
// 2

const mult3 = mult(3);
console.log(mult3(10));
// 30
console.log(mult3(5));
// 15
console.log(mult3(3));
// 9

go 응용하기

다음의 코드는 아래와 같이 go를 이용해서 다시 작성할 수 있다.

1
2
3
4
5
6
7
8
9
console.log(
reduce(
add,
map(
(p) => p.price,
filter((p) => p.price < 20000, products)
)
)
);

다음 파라미터의 인자로 이전 파라미터의 실행값이 전달되는 go를 이용해서 다시 작성하였다.

1
2
3
4
5
6
7
go(
products,
(products) => filter((p) => p.price < 20000, products),
(products) => map((p) => p.price, products),
(prices) => reduce(add, prices),
console.log
);

filter,reduce,map에 모두 curry를 적용했을 경우엔 다음과 같이 수정할 수 있다.

1
2
3
4
5
6
7
8
9
10
go(
products,
filter((p) => p.price < 20000),
// filter((p) => p.price < 20000)(products) 가 되고 filter에 2개의 parameter가 전해져서 filter가 실행된다.
map((p) => p.price),
reduce(add),
console.log
);

// 파라미터가 전달되기 때문에 생략하여도 가능하다.

함수 조합으로 함수 만들기

pipe를 이용하여 중복을 제거할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
go(
products,
filter((p) => p.price < 20000),
map((p) => p.price),
reduce(add),
log
);

go(
products,
filter((p) => p.price < 20000),
map((p) => p.price),
reduce(add),
log
);

같은부분을 함수로 작성하여 중복을 제거하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const total_price = pipe(
map((p) => p.price),
reduce(add)
);

const base_total_price = (predi) => pipe(filter(predi), total_price);

go(
products,
base_total_price((p) => p.price < 20000),
console.log
);

go(
products,
base_total_price((p) => p.price >= 20000),
console.log
);

Ref

함수형 프로그래밍과 JavaScript ES6+

함수형 프로그래밍 4 map,filter,reduce

map

아래의 함수는 이터러블에 사용 가능하게 작성된 map 함수이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const products = [
{ name: "반팔티", price: 15000 },
{ name: "긴팔티", price: 20000 },
{ name: "핸드폰케이스", price: 15000 },
{ name: "후드티", price: 30000 },
{ name: "바지", price: 25000 },
];

const map = (f, iter) => {
// f를 통해 어떤 값을 반환할지 정한다.
let res = [];

for (const p of iter) {
res.push(f(p));
}

return res;
};

console.log(map((p) => p.name, products));
// ['반팔티','긴팔티','핸드폰케이스','후드티','바지']

console.log(map((p) => p.price, products));
// [15000,20000,15000,30000,25000]

filter

아래의 함수는 이터러블에 사용 가능하게 작성된 filter 함수이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const products = [
{ name: "반팔티", price: 15000 },
{ name: "긴팔티", price: 20000 },
{ name: "핸드폰케이스", price: 15000 },
{ name: "후드티", price: 30000 },
{ name: "바지", price: 25000 },
];

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

log(...filter((p) => p.price < 20000, products));
// [{name:'반팔티',price:15000},,{name:'핸드폰케이스',price:15000}]

console.log(filter((p) => p.price >= 20000, products));
// [{name:'긴팔티',price:20000},{name:'후드티',price:30000},{name:'바지',price:25000}]

log(filter((n) => n % 2, [1, 2, 3, 4]));
// [1, 3];

log(
filter(
(n) => n % 2,
(function* () {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
})()
)
);
// [1,3,5]

reduce

아래의 함수는 이터러블에 사용 가능하게 작성된 filter 함수이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const nums = [1, 2, 3, 4, 5];

const reduce = (f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
// parameter가 2 개만 들어왔을 경우 iter가 없는 경우이다.
// 이 때는 자기 자신을 반환하는 iterable의 성질을 이용해서 iter를 만들어준다.
for (const a of iter) {
acc = f(acc, a);
}
return acc;
};

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

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

console.log(add(add(add(add(add(0, 1), 2), 3), 4), 5));
// 15

console.log(reduce(add, [1, 2, 3, 4, 5]));
// == console.log(reduce(add, 1, [2, 3, 4, 5]))
// 15

단순 숫자 계산이 아니여도 활용할 수 있다.
아래는 products배열의 price를 더하는 코드이다.(다형성)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const products = [
{ name: "반팔티", price: 15000 },
{ name: "긴팔티", price: 20000 },
{ name: "핸드폰케이스", price: 15000 },
{ name: "후드티", price: 30000 },
{ name: "바지", price: 25000 },
];

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;
};

console.log(
reduce((total_price, product) => total_price + product.price, 0, products)
);
// 105000

다형성

위에서 작성한 map,filter,reduce함수는 이터러블 프로토콜을 따르는 객체에 전부 적용할 수 있다.
관련된 연산자도 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const map = (f, iter) => {
let res = [];
for (const p of iter) {
res.push(f(p));
}
return res;
};
console.log(document.querySelectorAll("*").map((ele) => ele.nodeName));
// 배열이 아니기 때문에 에러가 발생한다.
console.log(map((el) => el.nodeName, document.querySelectorAll("*")));
// nodeList는 이터러블 프로토콜을 따르기 때문에 동작한다.

function* gen() {
yield 2;
yield 3;
yield 4;
}

console.log(
map((a) => a * a),
gen()
);
// 제너레이터에도 동작한다
// [4,9,16]

제너레이터 또한 이터러블 프로토콜을 따르기 때문에 제너레이터에도 적용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const map = (f, iter) => {
let res = [];
for (const p of iter) {
res.push(f(p));
}
return res;
};

function* gen() {
yield 2;
yield 3;
yield 4;
}

console.log(
map((a) => a * a),
gen()
);
// 제너레이터에도 동작한다
// [4,9,16]

마찬가지로 map과 set에도 적용할 수 있다.

1
2
3
4
5
let m = new Map();
m.set("a", 10);
m.set("b", 20);
console.log(new Map(map(([k, a]) => [k, a * 2], m)));
// [[a,20],[b,40]]

Ref

함수형 프로그래밍과 JavaScript ES6+

함수형 프로그래밍 3 generator와 iterator

함수형 프로그래밍의 필수개념인 generator에 대해 알아보았다.

generator

generator는 iterator이자 iterable을 생성하는 함수이다.

  • 함수를 선언할 때 함수명 앞에 *를 붙여 만든다
  • yield를 통해 iterator를 반환한다.
  • generator를 이용하며 어떤 값이든 순회할 수 있는 형태로 작성할 수 있다.
  • generator 내부 반환되는 값은 done:true일 때의 value로 반환된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function* gen() {
yield 1;
if (false) yield 4;
// 출력이 이루어지지 않는다.
yield 2;
yield 3;
return 100;
// 반환값은 순회할 경우 출력되지 않는다. done이 true이기 때문이다.
}

let iter = gen();
console.log(iter[Symbol.iterator]() == iter); // true
console.log(iter.next()); // {value:1,done:false}
console.log(iter.next()); // {value:2,done:false}
console.log(iter.next()); // {value:3,done:false}
console.log(iter.next()); // {value:100,done:true}

for (const a of gen()) console.log(a);
// 1
// 2
// 3

generator를 작성하는 여러가지 방법

아래의 oods 함수는 홀수를 value로 하는 iterable을 반환하는 generator이다.

1
2
3
4
5
6
7
8
9
10
11
function* oods() {
yield 1;
yield 3;
yield 5;
}

let iter2 = oods();
console.log(iter2.next()); // {value:1,done:false}
console.log(iter2.next()); // {value:3,done:false}
console.log(iter2.next()); // {value:5,done:false}
console.log(iter2.next()); // {value:undefined,done:true}

generator는 for문을 이용하여 작성할 수 있다.

1
2
3
4
5
6
7
8
9
10
function* oods(l) {
for (let i = 0; i < l; i++) {
if (i % 2) yield i;
}
}
let iter2 = oods(6);
console.log(iter2.next()); // {value:1,done:false}
console.log(iter2.next()); // {value:3,done:false}
console.log(iter2.next()); // {value:5,done:false}
console.log(iter2.next()); // {value:undefined,done:true}

또한 generator는 while을 이용하여 작성할 수 있다.

아래의 infinity는 무한수열을 만드는 generator이고 limit 함수는 l까지는 a를 yield하다가 l이 될 경우 return 하는 generator이다.
infinity generator와 limit generator를 이용하여 odds generator도 새로 정의하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function* infinity(i = 0) {
while (true) yield i++;
}

function* limit(l, iter) {
for (const a of iter) {
yield a;
if (a == l) return;
}
}

function* oods(l) {
for (const a of limit(l, infinity(1))) {
if (a % 2) yield a;
}
}

let iter4 = oods(6);
iter3.next(); // {value:1,done:false}
iter3.next(); // {value:3,done:false}
iter3.next(); // {value:5,done:false}
iter3.next(); // {value:undefined,done:true}

for (const a of oods(6)) console.log(a);
// {value:1,done:false}
// {value:3,done:false}
// {value:5,done:false}
// {value:undefined,done:true}

iterable/iterator 프로토콜

generator는 iterable/iterator 프로토콜을 따르고 있기 떄문에 iterable/iterator 프로토콜을 따르는 여러 연산이나 함수들을 적용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function* infinity(i = 0) {
while (true) yield i++;
}

function* limit(l, iter) {
for (const a of iter) {
yield a;
if (a == l) return;
}
}

function* oods(l) {
for (const a of limit(l, infinity(1))) {
if (a % 2) yield a;
}
}

console.log(...odds(10));
// 1 3 5 7 9
console.log([...oods(10), ...oods(20)]);
// 1 3 5 7 9 1 3 5 7 9 11 13 15 17 19

const [head, ...tail] = oods(5);
console.log(head);
// 1
console.log(tail);
// 3 5

const [a, b, ...rest] = oods(10);
console.log(a);
// 1
console.log(b);
// 3
console.log(rest);
// 5 7 9

Ref

함수형 프로그래밍과 JavaScript ES6+

함수형 프로그래밍 2 Iterator 프로토콜

리스트를 순회하는 방법엔 여러방법과 Iterator에 대해 알아보려고 한다.

for i++

ES6 이전에는 for문을 통하여 리스트를 순회하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 배열 순회
const list = [1, 2, 3];
for (var i = 0; i < list.length; i++) {
console.log(list[i]);
}
// 유사 배열 순회
const str = "abc";
for (var i = 0; i < str.length; i++) {
console.log(str[i]);
}

// 1
// 2
// 3
// a
// b
// c

for of

ES6에선 for of문이 추가되어 좀 더 명령형에서 선언형으로 바뀌었다.
인덱스로 직접 접근할 수 없는 리스트도 순회할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//배열 순회
const list = [1, 2, 3];
for (const a of list) {
console.log(a);
}
//유사배열 순회
const str = "abc";
for (const a of str) {
console.log(a);
}

// 1
// 2
// 3
// a
// b
// c

const set = new Set([1, 2, 3]);
for (const a of set) console.log(a);
// 1
// 2
// 3

const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
for (const a of map) console.log(a);
// ['a',1]
// ['1',2]
// ['1',3]

Iterable/Iterator

for of는 Iterable을 이용하여 구현 되어있어 동작원리를 이해하기 위해선 Iterable에 대해 먼저 이해해야한다.

Iterable

실행시에 Iterator를 리턴하는 함수인 Symbol.iterator 를 가진 값이다.

Iterable이 되기 위해선 다음과 같은 조건을 만족해야한다.

  • 객체 내에 [Symbol.iterator] 메서드가 존재해야 한다.
  • [Symbol.iterator] 메서드는 Iterator 객체를 반환해야 한다.

Iterator

{value,done} 객체를 리턴하는 next()를 가진 값이다.

  • 객체 내에 next 메서드가 존재해야 한다.
  • next 메서드는 IteratorResult 객체를 반환해야 한다.
  • IteratorResult 객체는 done: boolean과 value: any 프로퍼티를 - 가진다.
  • 이전 next 메서드 호출의 결과로 done 값이 true를 리턴했다면, 이후 호출에 대한 done 값도 true여야 한다.

Well-formed iterable

Iterator면서 Iterable인 객체를 Well-formed iterable이라고 한다.
즉 이터레이터의 이터러블이 자기 자신을 반환하면 Well-formed iterable이다.

Iterable/Iterator 프로토콜

Iterable을 for…of, 전개 연산자 등과 함께 동작하도록 한 규약이다.

for of의 동작원리

  1. for of는 Iterable 객체에 사용할 수 있다.
  2. for of를 호출하게 되면 Symbol.iterator를 통해 Iterator를 반환한다.
  3. 즉 for of는 Iterable/Iterator 프로토콜에 따라 동작한다.
1
2
3
4
5
6
7
const list = [1, 2, 3];
for (const a of list) {
// for of는 Iterable/Iterator 프로토콜에 따라 동작한다.
// 즉 반환된 Iterator의 value를 a로 참조한다.
// done이 true가 되면 반복문을 빠져나온다.
console.log(a);
}

따라서 코드를 다음과 같이 수정할 수 있다.

1
2
3
4
5
6
7
let list = [1, 2, 3];
let iterator = list[Symbol.iterator]();
while (1) {
const { value, done } = iterator.next();
if (done === true) break;
console.log(value);
}

for of 응용하기

Map과 Set에는 Iterator를 반환하는 여러 메서드가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
for (const a of map.keys()) console.log(a);
// Map,Set의 key를 value로 하는 Iterator를 반환하는 메서드이다.
// a
// b
// c
for (const a of map.values()) console.log(a);
// Map,Set의 value를 value로 하는 Iterator를 반환하는 메서드이다.
// 1
// 2
// 3
for (const a of map.entries()) console.log(a);
// Map,Set 전체를 value로 하는 Iterator를 반환하는 메서드이다.
// ['a',1]
// ['1',2]
// ['1',3]

사용자 정의 Iterable

위에서 언급한 조건을 준수하는 Iterable 객체를 정의하여 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const iterable = {
[Symbol.iterator]() {
let i = 3;
return {
next() {
return i == 0 ? { done: true } : { value: i--, done: false };
},
};
},
};

let iterator = iterable[Symbol.iterator]();
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {done:true}

for (const a of iterable) console.log(a);

// Uncaught TypeError: iterator is not iterable
// Well-formed Iterable이여야 에러가 발생하지 않는다.

Well-formed Iterable을 정의하려면 자기 자신을 반환하는 iterator를 추가하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const iterable = {
[Symbol.iterator]() {
let i = 3;
return {
next() {
return i == 0 ? { done: true } : { value: i--, done: false };
},
[Symbol.iterator]() {
return this;
},
};
},
};

for (const a of iterable) console.log(a);

// Well-formed iterator여야 에러가 발생하지 않는다.

전개 연산자

전개연산자도 Iterable 프로토콜을 따르고 있다.

1
2
3
4
5

const a=[1,2=];
console.log([...a,...[3,4]]) ///[1,2,3,4];
a[Symbol.iterator]=null;
console.log([...a,...[3,4]]) // Uncaught TypeError: a is not iterable

Iterable 프로토콜을 따르고 있기 때문에 Map,Set과 같은 여러 Iterable객체에 적용된다.

1
2
3
4
5
6
7
8
9
const set = new Set([1, 2, 3]);
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);

console.log([...set, ...map]);
// [1,2,3,["a", 1],["b", 2],["c", 3]]

Ref

[Javascript] Iterable과 Iterator 이해하기

함수형 프로그래밍과 JavaScript ES6+

함수형 프로그래밍 1 기본개념

함수형 프로그래밍의 기본 개념에 대해 정리해보았다.

평가

  • 코드가 계산(Evaluation) 되어 값을 만드는 것

일급

  • 값으로 다룰 수 있다.
  • 변수에 담을 수 있다.
  • 함수의 인자로 활용될 수 있다.
  • 함수의 결과로 사용될 수 있다.
1
2
3
4
5
const a = 10;
const add10 = (a) => a + 10;
const r = add10(a);
console.log(r);
// 20

일급 함수

  • 함수를 값으로 다룰 수 있다.
  • 조합성과 추상화의 도구
1
2
3
4
5
6
7
8
9
10
const add5 = (a) => a + 5;
console.log(add5); // (a) => a + 5;
console.log(add5(5)); //10

const f1 = () => () => 1;
console.log(f1); //()=>1;

const f2 = f1();
console.log(f2); //()=>1;
console.log(f2()); //1

고차함수

  • 함수를 값으로 다루는 함수

함수를 인자로 받아서 실행하는 함수(Applicative Programming)

고차함수의 두 가지 유형중 하나 함수를 인자로 받아서 실행하는 함수이다.

apply1

apply1은 함수를 인자로 받아서 1을 넣은 결과값을 리턴해주는 함수이다.

1
2
3
4
5
6
const apply1 = (f) => f(1);
// 함수를 parameter로 다루므로 고차함수
const add2 = (a) => a + 2;

console.log(apply(add2)); //3;
console.log(apply((a) => a - 1)); //0;

times

times는 함수를 인자로 받아서 n만큼 반복하여 실행하는 함수이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const times = (f, n) => {
let i = -1;
while (++i < n) f(i);
};

times(console.log, 3);

// 0
// 1
// 2

times((a) => console.log(a + 10), 3);
// 10
// 11
// 12

함수를 만들어 리턴하는 함수 (클로저를 만들어 리턴하는 함수)

  • addMaker

addMaker는 두 수를 더하는 함수를 만들어서 반환하는 함수이다.
자바스크립트에서 함수객체는 함수가 만들어질 떄의 환경을 가지고 있기 때문에 클로저를 가지고 있다.

1
2
3
4
5
const addMaker = (a) => (b) => a + b;
const add10 = addMaker(10);
console.log(add10); // b => a + b;
console.log(add10(5)); // 15
console.log(add10(10)); // 20

Ref

함수형 프로그래밍과 JavaScript ES6+