본문 바로가기

Language/Java Script

함수형 프로그래밍

1. 함수형 프로그래밍의 개념

 - 함수형 프로그래밍은 함수의 조합으로 작업을 수행함을 의미한다. 중요한 것은 이 작업이 이루어지는 동안 작업에 필요한 데이터와 상태는 변하지 않는다는 점이다.

 - 변할 수 있는 건 오로지 함수 뿐이다. 이 함수가 바로 연산의 대상이 된다.

//특정 문자열을 암호화 하는 함수 --> 순수함수 (Pure function)
f1 = encrypt1;
f2 = encrypt2;
f3 = encrypt3;

//데이터
pure_value = 'zzoon';

//암호화 함수를 받아서 입력받은 함수 --> 고계함수(Higher-order function)
encrypted_value = get_encrypted(x);

//처리 프로세스
encrypted_value = get_encrypted(f1);
encrypted_value = get_encrypted(f2);
encrypted_value = get_encrypted(f3);

함수형 프로그래밍의 반대되는 개념을 명령형 프로그래밍(Imperative Programming)

명령형 프로그래밍은 컴퓨터가 수행할 일의 명령을 순서대로 기술하는 프로그래밍 방식이다.

명령형 프로그래밍 함수는 함수형 프로그래밍 언어 함수처럼 입력값을 받고 출력값을 계산하는 순수한 의미의 함수도 있지만, 특정 작업을 수행하는 여러 가지 명령이 기술되어 있는 함수도 있다. 이러한 종류의 함수를 프로시저라고 한다.

 

프로시저는 함수형 프로그래밍의 순수함수와는 목적 자체가 다르다.

ex) int ret = printf("print this to screen\n");

--> 화면에 출력되는게 목적이지 return값은 거의 사용하지 않는다.

 

2. 자바스크립트에서의 함수형 프로그래밍

 - 필요조건 : 일급객체로서의 함수, 클로저

var f1 = function(input){
	var result;
    /* 암호화 작업 수행 */
    result = 1;
    return result;
}

var f2 = function(input){
	var result;
    /* 암호화 작업 수행 */
    result = 2;
    return result;
}

var f3 = function(input){
	var result;
    /* 암호화 작업 수행 */
    result = 3;
    return result;
}

var get_encrypted = function(func){
	var str = 'zzoon';
    return function(){
    	return func.call(null, str);
    }
}

var encrypted_value = get_encrypted(f1)();
console.log(encrypted_value);
var encrypted_value = get_encrypted(f2)();
console.log(encrypted_value);
var encrypted_value = get_encrypted(f3)();
console.log(encrypted_value);

1) 배열의 각 원소를 활용한 연산

- 명령형 프로그래밍 방식

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));

- 함수형 프로그래밍 방식

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));

--> 명령형 프로그래밍 방식보다 한단계 높은 모듈화가 가능하다.

 

2)팩토리얼

 - 명령형 프로그래밍 방식

function fact(num){
	var val = 1;
    for(var i = 2; i<=num ;i++)
    	val = val * i;
    return val;
}

console.log(fact(100));
function fact(num){
	if(num == 0) return 1;
    else return num * fact(num-1);
}
console.log(fact(100));

 · 메모이제이션 패턴

var fact = function(){
	var cache = {'0' : 1};
    var func = function(n){
    	var result = 0;
        
        if(typeof(cache[n])==='number'){
        	result = cache[n];
        }else {
        	result = cache[n] = n * func(n-1);
        }
        return result;
    }
    return func;
}();
console.log(fact(10));
console.log(fact(20));
function Calculate(key, input, func){
	Calculate.data = Calculate.data || {};
    
    if(!Calculate.data[key]){
    	var result;
        result = func(input);
        Calculate.data[key] = result;
    }
    return Calculate.data[key];
}

var result = Calculate(1, 5, function(input){
	return input * input;
});

console.log(result);

result = Calculate(2, 5, function(input){
	return input * input / 4;
});

console.log(result);

console.log(Calculate(1));
console.log(Calculate(2));

- jQuery에서 data()메서드 메모이제이션 패턴

data : function(elem, name, data){
	....
    var id = elem[expando];
    ....
    
    // Only generate the data cache if we're
    // trying to access or manipulate it
    if(name && !jQuery.cache[id])
    	jQuery.cache[id] = {};
    
    //Prevent overriding the named cache with undefined values
    if(data != undefined)
    	jQuery.cache[id][name] = data;
    
    //Return the named cache data, or the ID for the element
    return name ? jQuery.cache[id][name] : id;
},

Function.prototype.memoization = function(key){
	var arg = Array.prototype.slice.call(arguments, 1);
    this.data = this.data || {};
    
    return this.data[key] !== undefined ? 
    	this.data[key] : this.data[key] = this.apply(this, arg);
};

function myCalculate1(input){
	return input * input;
}

function myCalculate2(input){
	return input * input / 4;
}

myCalculate1.memoization(1, 5);
myCalculate1.memoization(2, 4);
myCalculate2.memoization(1, 6);
myCalculate2.memoization(2, 7);

console.log(myCalculate1.memoization(1)); 
// 출력값 : equal to console.log(myCalculate1.data[1]);
console.log(myCalculate1.memoization(2));
// 출력값 : eqaul to console.log(myCalculate1.data[2]);
console.log(myCalculate2.memoization(1));
// 출력값 : eqaul to console.log(myCalculate2.data[1]);
console.log(myCalculate2.memoization(2));
// 출력값 : equal to console.log(myCalculate2.data[2]);

3)피보나치 수열

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(10));
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));

3. 자바스크립트에서 함수형 프로그래밍을 활용한 주요 함수

1) 함수 적용

 - Function.prototype.apply 함수로 함수 호출을 수행할 수 있다.

 - 함수 적용(Applying functions)은 함수형 프로그래밍에서 사용되는 용어이다.

 - 함수는 인자 혹은 반환 값으로 전달된 함수를 특정 데이터에 적용시키는 개념으로 이해해야 한다.

 - func.apply(Obj, Args) : func함수를 Obj객체와 Args 인자 배열에 적용시킨다!

2) 커링

 - 특정 함수에서 정의된 인자의 일부를 넣어 고정시키고, 나머지를 인자로 받는 새로운 함수를 만드는 것

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)); // 출력 결과 : 1 x 2 + 3 = 5
var new_func2 = curry(calculate, 1, 3);
console.log(new-func2(3)); // 출력 결과 : 1 x 3 + 3 = 6

 · curry() 함수의 역할 : 함수로 넘어온 인자를 args에 담아 놓고, 새로운 함수 호출로 넘어온 인자와 합쳐서 함수를 적용한다. 이러한 커링은 함수형 프로그래밍 언어에서 기본적으로 지원하는데, 자바스크립트에서는 기본적으로 제공하지는 않는다.

--> 사용자가 직접 정의해서 사용할 수 있다.

Function.prototype.curry = function(){
	var fn = this, args = Array.prototype.slice.call(arguments);
    return function(){
    	return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
    };
};
/* slice 메서드
	Array.prototype에 정의되어 있다.
    인자로 첫번째 인덱스와 마지막 인덱스(옵션)을 주어 배열로 잘라서 복사본을 반환한다.
    커링에서 함수의 인자를 arguments객체로 조작할 때
    이 메서드를 이용하여 배열로 만든 후 손쉽게 조작할 수 있다.
*/

- calcuate() 함수의 첫 번째 인자와 세 번째 인자를 고정하고 싶을 때

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)); // 출력 결과 : 1 x 3 + 4 = 7

curry2() 함수를 사용할 때 주의할 점은 curry2를 호출할 때 calculate() 함수가 원하는 인자를 전부 넣어야 한다.

그 중에서 고정시키지 않을 인자를 undefined로 넘기면 된다.

curry2()에서는 curry2()를 호출할 때 넘어온 인자로 루프를 돌면서 undefined인 요소를 새로운 함수를 호출할 때 넘어온 인자로 대체한다.

이와 같이 함수를 부분적으로 적용하여 새로운 함수를 반환받는 방식을 함수의 부분 적용(Partially applying functions)이라고 한다.

 

기존 함수로 인자가 비슷한 새로운 함수를 정의하여 사용하고 싶을 때, 이와 같은 방법으로 유용하게 사용할 수 있다.

 

3)bind

Function.prototype.bind = function (thisArg){
	var fn = this,
    slice = Array.prototype.slice,
    args = slice.call(arguments, 1);
    return function(){
    	return fn.apply(thisArg, args.concat(slice.call(arguments)));
    };
}

bind() 함수는 커링 기법을 활용한 함수이다. 커링과 같이 사용자가 고정시키고자 하는 인자를 bind()함수를 호출할 때 인자로 넘겨주고 반환받은 함수를 호출하면서 나머지 가변인자를 넣어줄 수 있다.

Curry() 함수와 다른 점은 함수를 호출할 때 this에 바인딩 시킬 객체를 사용자가 넣어줄 수 있다는 점이다.

 

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 : "zzoon"};
var myfunc = print_all.bind(myobj);
myfunc(); // "name : zzoon" 출력

var myfunc1 = print_all.bind(myobj, "iamhjoo", "others");
myfunc1("insidejs");

/* 다음을 출력한다.
name : zzoon
0 : iamhjoo
1 : others
2 : insidejs
*/

myfunc() 함수는 myobj객체를 this에 바인딩시켜 print_all() 함수를 실행하는 새로운 함수이다.

my-func1()을 실행하면 인자도 bind()함수에 모두 넘겨진다.

 

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

/* bind() 함수로 반환받은 함수는 바인딩한 함수를 상속하는 기능까지 제공한다.*/
if(!Function.prototype.bind){
	Function.prototype.bind = function(oThis){
    	if(typeof this !== "function"){
        	throw new TypeError("Function.prototype.bind 
            	- what is trying to be bound is not callable");
        }
        
        var aArgs = Array.prototype.slice.call(arguments, 1),
        	fToBind = this,
            fNOP = function(){},
            fBound = function(){
            	return fToBind.apply(this instanceof fNOP && oThis ? this : oThis,
                	aArgs.concat(Array.prototype.slice.call(arguments)));
            };
        fNOP.prototype = this.prototype;
        fBound.prototype = new FNOP();
        
        return fBound;
    };
}

//다른점은 ?
fNOP = function(){},
fBound = function(){
	//...	
};

fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();

return fBound;

// fBound는 this에 바인딩된 함수 객체를 상속받는다.
// 따라서 반환받은 fBound 함수로 new라고 생성된 객체는 
// 현재 함수의 prototype 프로퍼티에 접근할 수 있다.

//예제
function Person(arg){
	if(this.name == undefined) this.name = arg ? arg : "zzoon";
    console.log("Nme : " + this.name);
}

Person.prototype.setName = function(value){
	this.name = value;
};
Person.prototype.getName = function(){
	return this.name;
};

var myobj = {name : "iamhjoo"};
var new_func = Person.bind(myobj);
new_func(); //Name : iamhjoo

var obj = new new_func();   // 출력 결과 : Name : zzoon
console.log(obj.getName()); // 출력 결과 : zzoon

4. 래퍼

 - 특정 함수를 자신의 함수로 덮어쓰는 것 -> 오버라이드랑 비슷하다.

function wrap(object, method, wrapper){
	var fn = object[method];
    return object[method] = function(){
    	return wrapper.apply(this, [fn].concat(
        //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){
	this.value =20;
    orig_func(value);
    console.log("wrapper value : " + this.value);
});

var obj = new mywrap("zzoon");
//출력
//value : zzoon
//wrapper value : 20

Function.prototype에 original이라는 함수가 있고, 이는 인자로 넘어온 값을 value에 할당하고 출력하는 기능을 한다.

이를 사용자가 덮어쓰기 위해 wrap 함수를 호출하였다.

세 번째 인자로 넘긴 자신의 익명 함수를 Function.prototype.original에 덮어 쓰려는 것이다.

여기서 사용자는 자신의 익명 함수의 첫 번째 인자로 원래 함수의 참조를 받을 수 있다.

이 참조로 원래 함수를 실행하고 자신의 로직을 수행할 수 있다.

 

사용자 세 번째 인자로 받은 함수 wrapper()를 apply()함수로 호출한다는 것인데, 인자의 첫 번째가 [fn]이다.

이렇게 기존 함수의 참조를 첫 번째 인자로 넘김으로써 사용자는 이 함수에 접근할 수 있다.

 

문제점 - 원래 함수 original()이 호출될 때의 this와 반환되는 익명함수가 호출될 때의 this가 다르다.

해결방법 - 첫번째 인자로 [fn] 대신 [fn.bind(this)]를 쓰면 이 문제가 해결된다.

              · 원래 함수에 반환되는 익명 함수의 this를 바인딩 하는 것

 

5. 반복 함수

1)each

function each(obj, fn, args){
	if(obj.length == undefined){
    	for(var i in obj)
        	fn.apply(obj[i], args || [i, obj[i]]);
    }
    else{
    	for(var i = 0; i < obj.length; i++)
        	fn.apply(obj[i], args || [i, obj[i]]);
    }
    return obj;
};

each([1,2,3], function(idx, num){
	console.log(idx + " : " + num);
});

var zzoon = {
	name : "zzoon",
    age : 30,
    sex : "Male"
};

each(zzoon, function(idx, value){
	console.log(idx + " : " + value);
});

2)map

  - 배열의 각 요소를 꺼내서 사용자 정의 함수를 적용시켜 새로운 값을 얻은 후, 새로운 배열에 넣는다.

Array.prototype.map = function(callback){
	// this가 null인지, 배열인지 체크
    // callback이 함수인지 체크
    
    var obj = this;
    var value, mapped_value;
    var A = new Array(obj.length);
    
    for(var i = 0; i<obj.length; i++){
    	value = obj[i];
        mapped_value = callback.call(null, value);
        A[i] = mapped_value;
    }
    return A;
};

var arr = [1,2,3];
var new_arr = arr.map(function(value){
	return value * value;
});

console.log(new_arr); // 출력 결과 : [1, 4, 9]

3)reduce

  - 배열의 각 요소를 하나씩 꺼내서 사용자의 함수를 적용시킨 뒤, 그 값을 계속해서 누적시킨다.

Array.prototype.reduce = function(callback, memo){
	// this가 null인지, 배열인지 체크
    // callback이 함수인지 체크
    
    var obj = this;
    var value, accumulated_value = 0;
    
    for(var i = 0; i < obj.length; i++){
    	value = obj[i];
        accumulated_value = callback.call(null, accumulated_value, value);
    }
    
    return accumulated_value;
};

var arr = [1,2,3];
var accumulated_val = arr.reduce(function(a, b){
	return a + b*b;
});

console.log(accumulated_val); // 출력 결과 : 1 x 1 + 2 x 2 + 3 x 3 = 14

'Language > Java Script' 카테고리의 다른 글

자바스크립트 실행 순서  (0) 2020.03.15
jQuery  (0) 2020.03.05
객체지향 프로그래밍 응용 예제  (0) 2020.03.05
객체지향 프로그래밍  (0) 2020.03.04
자바스크립트 클로저  (0) 2020.02.24