본문 바로가기

Web/JavaScript

함수형 자바스크립트 5. _reduce, _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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
var users = [
    { id: 1name"ID", age: 36 }, 
    { id: 2name"BJ", age: 32 },
    { id: 3name"JM", age: 32 },
    { id: 4name"PJ", age: 27 },
    { id: 5name"HA", age: 25 },
    { id: 6name"JE", age: 26 },
    { id: 7name"JI", age: 31 },
    { id: 8name"MP", age: 23 }
];
 
function _filter( list, predi ) {
    var new_list = [];
    _each( list, function(val) {
        if ( predi(val) ) {
            new_list.push( val );
        }
    });
    return new_list;
}
 
function _map( list, mapper ) {
    var new_list = [];
    _each( list, function(val) {
        new_list.push( mapper(val) );
    });
    return new_list;
}
 
function _each( list, iter ) {
    for ( var i = 0; i < list.length; i++ ) { 
        iter( list[i] );        
    }
 
    return list;
}
 
function _curry( fn ) {
    return function( a, b ) {
        return arguments.length == 2 ?
            fn( a, b ) : function( b ) { return fn( a, b );
        }
    }
}
 
function _curryr( fn ) {
    return function( a, b ) {
        return arguments.length == 2 ?
            fn( a, b ) : function( b ) { return fn( b, a );
        }
    }
}
 
var _get = _curryr(function( obj, key ) {
    return obj == null ? undefined : obj[ key ];
});
cs



_reduce

재귀적으로 iterator를 실행해준 결과를 만드는 함수

인자로 받은 iterator를 list에 연속적으로 적용하면서 값을 축약해나가는 함수

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
var slice = Array.prototype.slice;
function _rest(list, num) {
    return slice.call(list, num || 1);
}
 
function _reduce( list, iter, memo ) {
    if ( arguments.length == 2 ) {
        memo = list[0];
        /* array에 종속, Array Like에 불가
        list = list.slice(1); */
        list = _rest(list);
    }
 
    _each(list, function(val) {
        memo = iter( memo, val );    
    });
    return memo;
}
 
/* array를 받아 값을 축약해나간다 */
console.log(
    _reduce([123], function(a, b) {
        return a + b;
    }, 0)
); // 6
 
console.log(
    _reduce([123], function(a, b) {
        return a + b;
    }, 10)
);
 
console.log(
    _reduce([123], function(a, b) {
        return a + b;
    })
);
cs

1. memo = add(0, 1);

2. memo = add(memo, 2);

3. memo = add(memo, 3);

4. return memo;

와 같이 memo에 값을 저장하면서 축약해 나간다.


_pipe

함수들을 받아서 연속적으로 실행하는 함수를 반환하는 함수

reduce는 pipe를 축약한 함수이다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function _pipe() {
    var fns = arguments;
    return function(arg) {
        return _reduce(fns, function(arg, fn) {
            return fn(arg);
        }, arg);
    }
}
 
var f1 = _pipe(
    function(a) { return a + 1; }, 
    function(a) { return a * 2; },
    function(a) { return a * a; });
 
console.log( f1(1) ); // 16
cs

_pipe의 수행 결과는 누적변수인 arg를 매개변수로 받아 다시 _reduce를 수행하는 함수이다.


_go

_pipe는 함수를 반환하는 함수이지만, _go는 _pipe와 달리 즉시 호출한다

1
2
3
4
5
6
7
8
9
10
11
/* go, 즉시 실행되는 pipe */
function _go(arg) {
    var fns = _rest(arguments);
    return _pipe.apply(null, fns)(arg);
}
 
_go(1,
    function(a) { return a + 1; }, 
    function(a) { return a * 2; },
    function(a) { return a * a; },
    console.log); // 16
cs


_cuuryr을 이용해 코드 줄이기

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
39
40
41
42
43
var _map = _curryr(_map),
    _filter = _curryr(_filter);
 
console.log(_map([123], function(val) { return val * 2; }));
console.log(_map(function(val) { return val * 2; })([123]));
 
_go(users,
    function(users) {
        return _filter(users, function(user) {
            return user.age >= 30;
        });
    },
    function(users) {
        return _map(users, _get('name'));
    },
    console.log
);
 
_go(users,
    function(users) {
        return _filter(users, function(user) {
            return user.age < 30;
        });
    },
    function(users) {
        return _map(users, _get('age'));
    },
    console.log
);
 
_go(users,
    _filter(function(user) {
        return user.age >= 30;
    }),
    _map(_get('name')),
    console.log
);
 
_go(users,
    _filter(user => user.age < 30),
    _map(_get('age')),
    console.log
);
cs

_go는 함수들을 _pipe로 전달하여 함수를 결과로 받은 뒤, 해당 파이프라인에 누적 변수를 전달하여 즉시 수행한다.

결국 _pipe가 function(a) { return iter(iter(iter(a, 1), 2), 3); }와 같은 함수가 수행되지 않은 상태로 반환하는 것이라면

_go(a)는 해당 함수에 a를 평가하여 수행하는 함수이다.


7, 19번째 라인의 코드를 살펴보면 users를 누적변수로 전달하고 필터링, 맵, 로그출력에 사용하고 있다.

_filter와 _map은 첫번째 인자로 데이터, 두번째 인자로 각각 iterator, mapper를 받는다.


curry가 무슨일을 하는 함수인지 다시 생각해보자.

curry는 함수의 인자를 하나씩 적용해나가다가, 필요한 인자가 모두 채워지면 함수 본체를 수행하는 기법이다.

curryr은 curry와 같은 기능이지만 인자를 왼쪽이아닌 오른쪽부터 적용해 나간다.


다시 7, 19번째 라인의 코드로 돌아가서,

첫번째 인자로 전달된 users는 _pipe를 통해 각각의 함수에게 전달된다.

_filter, _map과 console.log의 인자로 전달되는 것이다.

_filter와 _map의 경우 인자가 두개이기 때문에 function(users) 처럼 익명함수를 이용해서 직접 매개변수로 users와 보조함수를 전달해야한다.


이를 curryr를 이용하면 인자를 오른쪽부터 하나씩 적용할 수 있다.

_filter의 경우 iterator를 먼저 전달하고 나중에 users가 채워지면 함수 본체를 수행하도록 할 수 있는 것이다.


결국

_filter(function(user) {

return user.age >= 30;

})

처럼 iterator를 먼저 평가해놓은 뒤, _go를 수행하면서 users가 전달되면 _filter함수 본체가 수행된다.


함수형 프로그래밍

함수형 프로그래밍은 함수가 함수를 수행, 함수가 함수를 반환하는 식으로 프로그래밍된다.

함수의 평가 시점이나 과정에서 하나하나의 함수들이 다 부수효과가 없고, 순수 함수들로 구성될 때, 이런 조합성을 만들 수 있다.

# 순수 함수들을 평가 시점을 다루면서 조합성을 강조하는 프로그래밍

# 추상화의 단위를 함수로 하는 프로그래밍


이전에 작성된 명령형 코드

1
2
3
4
5
6
7
var temp_users_name = [];
for ( var i = 0; i < users.length; i++ ) {
    if ( users[i].age >= 30 ) {
        temp_users_name.push( users[i].name );
    }
}
console.log(temp_users_name);
cs

다시 보면 확연한 차이가 느껴질 것이다.


화살표 함수

1
2
3
4
5
6
7
8
9
10
11
12
var a = function(user) { return user.age >= 30; };
var a = user => user.age >= 30;
 
var add = function(a, b) { return a + b };
var add = (a, b) => a + b;
var add = (a, b) => {
    // 추가코드
    return a = b;
}
 
// 객체를 만들어 리턴하는데 한줄인 경우 {}가 함수의 블록으로 인식되어 ()가 필요
var add = (a, b) => ({ val: a + b});
cs