본문 바로가기

Web/JavaScript

함수형 자바스크립트 4. 커링(_curry, _curryr)

이전 강의 _map, _filter, _each

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


커링

함수와 인자를 다루는 기법

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

* 자바스크립트는 커링을 지원하지 않지만, 일급 함수를 지원하며 평가 시점을 마음대로 다룰 수 있기 때문에 기법 구현이 가능하다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 );
        }
    }
}
cs


1. 인자로 본체에서 수행할 함수를 받는다.

2-1. 인자가 하나일 경우

- 첫번째 인자인 a를 기억하는 클로저 함수를 리턴한다

- 마지막으로 클로저 함수에 두 번째 인자를 넘기면서 실행하면 함수 본체를 수행한다

2-2. 인자가 두개일 경우

- _curry 함수에서 인자로 받은 본체 함수를 수행한다


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
/* 함수의 인자를 하나만 적용하면 함수를 리턴 */
var add = _curry(function(a, b) {
    return a + b;
});
 
var add10 = add( 10 );
var add5 = add( 5 );
console.log( add10(5) );
console.log( add(5)(3) );
console.log( add5(3) );
console.log( add(10)(3) );
console.log( add(12) );
 
var sub = _curry(function(a, b) {
    return a - b;
});
console.log( sub(105) );
 
var sub10 = sub(10);
console.log( sub10(5) ); // 10에 5를 빼는.... 표현이 맞지 않음
 
sub = _curryr(function(a, b) {
    return a - b;
});
sub10 = sub(10);
console.log( sub10(5) ); // 5에서 10을 빼는.... 표현이 맞음
cs


본체 함수인 function(a, b) { return a + b; }를 값으로 들고 있다가

원하는 시점까지 미뤄둔 뒤, 최종적으로 평가하는 예제이다.


커링을 이용해 add10, add5에서 각각 10과 5를 기억하는 클로저를 반환한다.

이후 해당 클로저에 두번째 인자를 전달하여 수행하면 최종적으로 기억하고 있는 본체를 수행하게 된다.


sub10(5)는 5라는 값에서 10을 뺀다는 표현이 어울리기 때문에

curryr을 사용하면 오른쪽인자부터 적용하면서 표현이 맞게 된다.


object에 있는 값을 안전하게 참조하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var _get = _curryr(function( obj, key ) {
    return obj == null ? undefined : obj[ key ];
});
 
var user1 = users[0];
console.log( user1.name );
console.log( _get(user1, 'name') );
console.log( _get('name')(user1) );
 
var getName = _get('name');
console.log( getName(user1) );
console.log( getName(users[2]) );
console.log( getName(users[4]) );
// 에러 console.log( users[10].name );
console.log( _get(users[10], 'name') );
cs


객체에 있는 값을 안전하게 참조하기 위한 _get 함수이다.

값이 없을 경우 에러가 발생하지 않고 표현상 어울리는 undefined를 반환한다.


_get을 이용해 _map 함수 호출을 간결화 하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
console.log(    
    _map(
         _filter( users, function(user) {
            return user.age >= 30;
        }),
        function(user) {
            return user.name;
        }
    )
);
 
console.log(    
    _map(
         _filter( users, function(user) {
            return user.age < 30;
        }),
        function(user) {
            return user.age;
        }
    )
);
cs


함수형 자바스크립트 3. 반복자(_each)와 내부다형성 에서 등장한 코드이다.

_map 함수의 두번쨰 인자는 어떤 항목을 수집할 것인지를 지정하는 부분인데,

_get 함수를 이용하면 이를 간결화할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
console.log(    
    _map(
        _filter( users, function(user) { return user.age >= 30; }), 
        _get('name')) // iterator를 대신함
);
 
console.log(    
    _map(
        _filter( users, function(user) { return user.age < 30; }), 
        _get('age'))
);
cs