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에는 팩토리얼을 연산한 값을 저장하고있다. 연산을 수행하는 과정에서 캐시에 저장된 값이 있으면 곧바로 그 값을 반환하는 방식이다.
일반 프로그래머의 상식으로는 도저히 이해가 안 될 결정인데 일부러 난해한 프로그래밍 언어라도 만들 생각으로 만들었을까?
그게 아니고 함수형 언어는 수학의 '함수'를 프로그래밍 언어 설계에 적극적으로 반영한 것이다. 수학의 함수는 입력이 같으면 출력도 같다. 그러니까 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인 요소를 새로운 함수를 호출할 때 넘어온 인자로 대체한다. 이와같이 함수를 부분적으로 적용햐여 새로운 함수를 반환받는 방식을 함수의 부분적용 이라고 부른다.
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()함수가 사용된다.
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")
간단히 알아두고 넘기자. 이전 함수를 사용자가 원하는함수로 덮어씌우기 위한 함수이다.
기존에 제공되는 함수에 사용자가 원하는 로직을 추가하고 싶거나.
기존에 있는 버그를 피해가고자 할 때 사용된다.