INSIDE JAVASCRIPT – 7. 함수형 프로그래밍 – 바인드 (bind)



INSIDE JAVASCRIPT – 7. 함수형 프로그래밍 – 바인드 (bind)

0 0


JsPPT-7


On Github Lunatical / JsPPT-7

INSIDE JAVASCRIPT

7. 함수형 프로그래밍

2015.01.07

Kim-down

자바스크립트로 함수형 프로그래밍에서 제시하는 방법론 중 일부는 구현가능하지만. 순수한 함수형 프로그래밍 언어라고는 말하지않는다.

함수의 조합으로 작업을 수행함을 의미한다. 작업이 이루어 지는 동안 작업에 필요한 데이터와 상태는 변하지 않는다.

순수 함수형 언어는 변수가 없다. 대입이 아닌 정의로 볼 수 있다. a = 3 이라는것을 명령형에서는 a에 3을 대입하는게 되겠지만 함수형에서는 a는 3 이라고 정의한것이다!.

함수형 프로그래밍에 대해 설명하기 위해.예제를 준비해 보았다. 다음 예제는 명령형 프로그래밍으로 작성된것이다.
function sum(arr) {
    var len = arr.length;
    var i = 0, sum = 0;
    for(; i < len; i++) {
        sum += arr[i];
    }
    return sum;
}

var arr = [1, 2, 3, 4];
console.log(sum(arr));
                        

이 함수는 단순히 배열의 각 원소의 합을 구하는 함수이다.

이번에는 배열의 각 원소를 곱한 값을 구하고 싶어졌다..

function multiply(arr) {
    var len = arr.length;
    var i = 0, result = 1;

    for (; i < len; i++) {
        result *= arr[i];
    }
    return result;
}

var arr = [1, 2, 3, 4];
console.log(multiply(arr)); // Result 24
                        

전 예제와 이 예제는 문제 하나하나를 각각의 함수로 구현하여 문제를 풀고있다. 배열의 각 원소를 또 다른 방식으로 산술하여 결과를 얻으려면 새로운 함수를 구현해야한다. 함수형 프로그래밍을 방식으로 작성하면 이러한 수고를 덜 수 있다.

function reduce(func, arr, memo) {
    var len = arr.length,
        i = 0,
        accum = memo;

    for (; i < len; i++) {
        accum = func(accum, arr[i]);
    }
    return accum;
}
                        

reduce 함수는 함수와 배열을 인자로 받고 루프를 돌면서 함수를 실행시킨다.

function reduce(func, arr, memo) {
    var len = arr.length,
        i = 0,
        accum = memo;

    for (; i < len; i++) {
        accum = func(accum, arr[i]);
    }
    return accum;
}
var arr = [1, 2, 3, 4];
var sum = function(x, y) {
    return x+y;
};

var multiply = function(x, y) {
    return x*y;
};

console.log(reduce(sum, arr, 0));
console.log(reduce(multiply, arr, 1));
                                
예 에서 보는바와 같이 함수형 프로그래밍을 이용하여 코드를 훨씬 간결하게 작성하고, 기존 프로그래밍 방식보다 한단계 위의 모듈화를 이룰 수 있다.
이번엔 팩토리얼을 구현해 보자. 이번에도 명령형 프로그래밍으로 먼저 구현해보자.
function fact(num) {
    if (num == 0) return 1;
    else return num * fact(num-1);
}
console.log(fact(100)); // Result : 9.33262154439441e+157
                        

큰 무리없이 팩토리얼을 구현하는데 성공하였다. 하지만 이런종류의 함수 구현은 항상 성능을 고려하여야한다. 이를 함수형 프로그래밍으로 성능을 고려하여보자.

처음 10! 을 실행한 후 20! 을 실행한다고 가정해보자. 20!을 실행할 떄에는 앞에서 실행한 10!을 중복하여 계산한다. 먼저 계산한 값을 캐시에 저장하여 사용할수 있는 함수를 만들어 작성해보자

var fact = function() {
    var cache = {'0' : 1};
    var func = function(n) {
        var result = 0;

        if (typeof(cache[n]) === 'number') {
            result = cache[n];
        } else {
            cache[n] = n * func(n-1);
            result = cache[n]
        }
        return result;
    }
    return func;
}();

console.log(fact(10));
console.log(fact(20));
                        

fact 는 cache 에 접근할 수 있는 클로저를 반환받는다. cache에는 팩토리얼을 연산한 값을 저장하고있다. 연산을 수행하는 과정에서 캐시에 저장된 값이 있으면 곧바로 그 값을 반환하는 방식이다.

Point!

함수형 프로그래밍에서는 함수에 같은 값을 넣었을 시 무조건 그 값이 나오도록 설계되고있다!.

일반 프로그래머의 상식으로는 도저히 이해가 안 될 결정인데 일부러 난해한 프로그래밍 언어라도 만들 생각으로 만들었을까?

그게 아니고 함수형 언어는 수학의 '함수'를 프로그래밍 언어 설계에 적극적으로 반영한 것이다. 수학의 함수는 입력이 같으면 출력도 같다. 그러니까 f(x)=x+1 인 함수를 정의했다면 f(1)=2다. 다른 답은 없다. 함수형 언어의 함수도 마찬가지로 function boo(1)의 수행 결과가 2였다면 언제 어느때든 boo(1)은 2만 나온다. 하지만 함수형 언어가 아닌 언어에서는 boo(1)이 3도 나올 수 있고 4도 나올 수 있다.

Memoization만 건든게 함수형과 무슨관계인가?. 함수형 프로그램은 위의 특징으로 얻는 이득으로 인해 캐싱이 가능하다. 그 함수를 호출한 파라메터를 알고있고 그 수행한 결과를 안다면 계산할 필요가 있는가?

결과를 아는이상 계산은 필요가없스!

이번엔 피보나치 수열까지 구현해보자.
var fibo = function() {
    var cache = {'0' : 0, '1' : 1};

    var func = function(n) {
        if (typeof(cache[n]) === 'number') {
            result = cache[n];
        } else {
            result = cache[n] = func(n-1) + func(n-2);
        }
        return result;
    }
    return func;
}();

console.log(fibo(5));
                        
이전 팩토리얼 함수와 다른점은 캐시의 초기값과 재귀호출시 산술식이 다르다는것. 함수형 프로그래밍 으로 피보나치 수열과 팩토리얼을 계산하는 함수를 인자로받는 함수를만들어보았다.
var cacher = function(cache, func) {
    var calculate = function(n) {
        if (typeof(cache[n]) === 'number') {
            result = cache[n];
        } else {
            result = cache[n] = func(calculate, n);
        }
        return result;
    }
    return calculate;
};

var fact = cacher({ '0' : 1 }, function(func, n) {
    return n * func(n-1);
});

var fibo = cacher({ '0' : 0, '1' : 1}, function(func, n) {
    return func(n-1) + func(n-2);
});

console.log(fact(10));
console.log(fibo(10));
                        
감각을 키워보자.자바스크립트에서 함수형 프로그래밍을 활용한 주요함수.

커링.

특정함수에서 정의된 인자의 일부를 넣어 고정시키고. 나머지 인자를 받는 새로운 함수를 만드는것을 의미한다.
function calculate(a, b, c) {
    return a*b+c;
}

function curry(func) {
    var args = Array.prototype.slice.call(arguments, 1);

    return function() {
        return func.apply(null,
        args.concat(Array.prototype.slice.call(arguments)));
    }
}

var new_func1 = curry(calculate, 1);
console.log(new_func1(2, 3));
var new_func2 = curry(calculate, 1, 3);
console.log(new_func2(3));
                        
curry함수의 역할은 간단하다. 인자를 args에 담아놓고 새로운 함수호출로 넘어온 인자와 합쳐서 함수를 적용. 함수형 프로그래밍언어에서는 기본적으로 지원하는함수지만 자바스크립트에는 없기에 Function.prototype에 넣어서 사용하는게 좋다.
그런데 첫번째, 세번째 인자만 고정하고싶다면 어떻게해야할까 ?
function calculate(a, b, c) {
    return a*b+c;
}

function curry2(func) {
    var args = Array.prototype.slice.call(arguments, 1);

    return function() {
        var arg_idx = 0;
        for (var i = 0; i < args.length && arg_idx < arguments.length; i++){
            if (args[i] === undefined) {
                args[i] = arguments[arg_idx++];
            }
        }
        return func.apply(null, args);
    }
}

var new_func = curry2(calculate, 1, undefined, 4);
console.log(new_func(3));
                        

curry2에서는 호출시 넘어온 인자로 루프를 돌면서 undefined인 요소를 새로운 함수를 호출할 때 넘어온 인자로 대체한다. 이와같이 함수를 부분적으로 적용햐여 새로운 함수를 반환받는 방식을 함수의 부분적용 이라고 부른다.

지금까지 커링을 살펴보았다. 커링은 함수형 프로그래밍에서는 기본적인 구현방법이다. 자바스크립트에서도 기존 함수로 인자가 비슷한 새로운 함수를 정의하여 사용하고싶을때 사용할 수 있다.

바인드 (bind)

Function.prototype.bind = function (thisArg) {
    var fn = this,
    slice = Array.prototype.slice,
    args = slice.call(arguments, 1);
    return function() {
        return fn.apply(thisArg, arg.concat(slice.call(arguments)));
    };
}
                        
bind 는 위와같이 커링기법을 활용한 함수이다. 커링과 같이 사용자가 고정시키고자 하는 인자를 bind()함수를 호출할 때 인자로 넘겨주고 반환받은 함수를 실행할 때 나머지 가변인자를 넣어줄 수 있다. curry()함수와 다른점은 함수를 호출할때 this바인딩시킬 객체를 사용자가 넣을 수 있다. curry함수가 자바스크립트 엔진에 내장되지않은것도 bind()의 존재때문일것이다.
var print_all = function(arg) {
    for (var i in this) console.log(i + " : " + this[i]);
    for (var i in arguments) console.log(i + " : " + arguments[i]);
}

var myobj = { name : "down" };

var myfunc = print_all.bind(myobj);
myfunc();
/*
    name : down
*/
var myfunc1 = print_all.bind(myobj, "kim", "others");
myfunc1("inside js");
/*
    name : down
    0 : kim
    1 : others
    2 : inside js
 */
                        

이와 같이 특정함수에 원하는객체를 바인딩시켜 새로운 함수를 사용할 때 bind()함수가 사용된다.

래퍼 (wrapper)

특정 함수를 자신의 함수로 덮어쓰는것을 말한다. 객체지향의 오버라이드 와 유사하다.
function wrap(object, method, wrapper) {
    var fn = object[method];
    return object[method] = function() {
        return wrapper.apply(this, [fn.bind(this)].concat(
                Array.prototype.slice.call(arguments)
        ));
    };
}

Function.prototype.original = function(value) {
    this.value = value;
    console.log("value :" + this.value);
}

var mywrap = wrap(Function.prototype, "original",
                function(orig_func, value) {
                    orig_func(value);
                    this.value = 20;
                    console.log("wrapper value : " + this.value);
                }
             );

mywrap("down")
                        
간단히 알아두고 넘기자. 이전 함수를 사용자가 원하는함수로 덮어씌우기 위한 함수이다. 기존에 제공되는 함수에 사용자가 원하는 로직을 추가하고 싶거나. 기존에 있는 버그를 피해가고자 할 때 사용된다.

END

Thanks!