본문 바로가기

Web/JavaScript

함수형 자바스크립트 6. _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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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 ];
});
 
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;
}
 
function _pipe() {
    var fns = arguments;
    return function(arg) {
        return _reduce(fns, function(arg, fn) {
            return fn(arg);
        }, arg);
    }
}
 
function _go(arg) {
    var fns = _rest(arguments);
    return _pipe.apply(null, fns)(arg);
}
 
var _map = _curryr(_map),
    _filter = _curryr(_filter);
cs


외부 다형성

함수형 프로그래밍에서는 예외적인 데이터가 들어오는 것에 대하여 

다형성을 높이는 것으로 해결하기도 한다.


자바와 같은 언어를 사용하면 if 문을 이용해 형체크를 엄격하게 하거나, 

try catch를 이용해 예외적인 상황에 적절하게 대처하곤 한다.


그런 방식과 달리 예외 데이터가 들어와도 에러가 안나고,

적절한 값을 반환하여 자연스럽게 흘러가도록 다루는 방식도 있다.


변경전 _each

1
2
3
4
5
6
7
function _each( list, iter ) {
    for ( var i = 0; i < list.length; i++ ) { 
        iter( list[i] );        
    }
 
    return list;
}
cs
_each(null, function() { } ); 와 같이 잘못된 데이터를 넘기면 에러가 발생한다


변경후 _each

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var _length = _get('length');
function _each( list, iter ) {
    for ( var i = 0, len = _length(list); i < len; i++ ) { 
        iter( list[i] );        
    }
 
    return list;
}
 
_each(nullconsole.log);
_go(null,
    _filter(function(v) { return v % 2; }),
    _map(function(v) { return v * v; }),
    console.log
);
cs

_each는 null이 들어올 경우 아무런 행동도 하지 않게 되며,

_filter와 _map은 그저 빈 배열을 반환하게 된다.

하지만, 여전히 _each는 매개변수로 length를 갖는 배열의 데이터에만 사용 가능하다.


_keys 만들기 (object의 key 목록을 안전하게 가져오기)

1
2
3
4
5
6
7
8
9
10
11
12
13
/* _keys 만들기 (안전한 Object.keys)*/
console.log( '_keys 만들기' );
console.log( _keys({ name"ID", age: 33 }) );
console.log( _keys([1234]) );
console.log( _keys(10) );
console.log( _keys(null) );
 
function _is_object( obj ) {
    return typeof obj == 'object' && !!obj;
}
function _keys( obj ) {
    return _is_object( obj ) ? Object.keys( obj ) : [];
}
cs

매개변수의 데이터형이 object일 경우 Object의 keys를 반환하고,

object가 아닐 경우 빈 배열을 반환한다.


어떤 형태의 데이터가 들어오던지 다형성을 높여서 그럴싸한 값을 리턴하므로,

연속적인 함수 실행에 무리가 없다.


_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
37
38
39
40
41
42
43
44
45
/* Array가 아니지만 k, v면서 루프를 돌만한.... */
console.log( '_each의 외부 다형성 높이기' );
function _each( list, iter ) {
    var keys = _keys( list );
 
    for ( var i = 0; i < keys.length; i++ ) { 
        iter( list[keys[i]] );        
    }
 
    return list;
}
 
_each({
    13'ID',
    19'HD',
    29'YD',
}, function(name) {
    console.log( name );
});
 
console.log( _map({
    13'ID',
    19'HD',
    29'YD',
}, function(name) {
    return name.toLowerCase();
}) );
 
_go(users,
    _map(function(user) {
        return user.name;
    }),
    _map(function(name) {
        return name.toLowerCase();
    }),
    console.log);
 
_go(null,
    _map(function(user) {
        return user.name;
    }),
    _map(function(name) {
        return name.toLowerCase();
    }),
    console.log);
cs

이번에는 list의 length를 참조하는 것이 아닌,

_keys 함수로 list의 키 목록을 안전하게 가져온 뒤,

해당 key 배열의 length를 참조하도록 변경했다.


이전에 변경한 _each와는 달리 length를 매개변수로 갖고 있지 않지만,

데이터가 (k, v)로 되어 있는 Array Like 객체의 데이터에도 접근이 가능하다.


정리

형체크를 엄격하게 하는 프로그래밍도 잇지만,

이렇게 다형성을 극대화 시키면서 프로그래밍하는 방식도 있다.


개발자가 어떤 데이터가 들어올 것인지 알고 있기 때문에,

보조함수를 통해서 다형성을 극대화 시킨다.

1
2
3
4
5
6
7
8
9
_go({
        1: users[0],
        3: users[1],
        5: users[2],
    },
    _map(function(user) {
        return user.name.toLowerCase();
    }),
    console.log);
cs