7. 함수형 프로그래밍
- 프로그래밍의 여러 가지 패러다임 중 하나
- 함수형 프로그래밍에 좀 더 관심이 있다면, Lisp 또는 Haskell과 같은 언어 공부 추천
함수형 프로그래밍의 개념
- 함수의 조합으로 작업을 수행함을 의미
- 중요: 해당 작업이 이루어지는 동안 작업에 필요한 데이터와 상태는 변하지 않음 ⇒ 변하는 것은 오직 함수 뿐
- 예시) 함수형 프로그래밍을 표현하는 슈도 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// 특정 문자열을 암호화하는 함수가 여러 개 있다고 가정 f1 = encrypt1; f2 = encrypt2; f3 = encrypt3; // 여기서 f1, f2, f3는 입력값이 정해지지 않고, 서로 다른 암호화 알고리즘만 있음 pure_value = 'zoe'; // 암호화할 문자열 encrpyted_value = get_encrypted(x); // 암호화된 문자열 // :암호화 함수를 받아 입력받은 함수로 pure_value를 암호화한 후 반환 // 즉, 다음과 같이 처리 encrypted_value = get_encrypted(f1); encrypted_value = get_encrypted(f2); encrypted_value = get_encrypted(f3);
이를 그림으로 표현
![]()
- 여기서
pure_value는 작업에 필요한 데이터며, 작업이 수행되는 동안 변하지 않음 -
encrypted()가 작업하는 동안 변할 수 있는 건 오로지 입력으로 들어오는 함수 뿐 - 반대로 이야기하면,
f1, f2, f3는 외부(여기서는zoe라는 변수)에 아무런 영향을 미치지 않는 함수라고 할 수 있음 ⇒ 순수 함수 -
get_encrypted()함수:- 인자로서
f1, f2, f3함수를 받음 - 해당 예시에는 결과 값이 encrypted_value 지만, 결과 값을 다른 형태의 함수로서도 반환 가능 → 함수를 또 하나의 값으로 간주, 함수의 인자 또는 반환값으로 사용 가능한 함수 ⇒ 고계 함수
- 인자로서
- 해당 예시에서, 프로그래머는 입력으로 넣을 암호화 함수를 새롭게 만드는 방식으로 암호화 방법 개선 가능
- 내부 데이터 및 상태는 그대로 둔 채, 제어할 함수를 변경 및 조합함으로써 원하는 결과를 얻어내는 것이 함수형 프로그래밍의 특성
- 높은 수준의 모듈과가 가능
- 순수 함수의 조건을 충족하는 함수 구현으로 모듈 집약적인 프로그래밍 가능
- 간단한 모듈의 재구성, 조합으로 프로그래밍 가능
💡 명령형 프로그래밍
- 함수형 프로그래밍의 반대 개념
- C등의 언어로 구현했던 대부분의 프로그래밍 방식이 이것
- 컴퓨터가 수행할 일의 명령을 순서대로 기술하는 프로그래밍 방식
- 함수형 프로그래밍 언어 함수처럼 입력값을 받고 출력값을 계산하는 순수한 의미의 함수도 있지만, 특정 작업을 수행하는 여러 가지 명령이 기술되어 있는 함수도 있음 ⇒ 프로시저 함수
- 프로시저는 함수형 프로그래밍의 순수 함수와는 목적 자체가 다름
- 예시)
printf()
1 int ret = printf("print this to screen\n");
printf함수 역시 입력값과 결과값(반환값)이 있음- 중요한 것은 결과값이 아니라,
printf함수가 실행되며 입력값을 화면에 출력하는 동작이 중요- 결과값은 이 동작이 제대로 수행되었는지를 알려주는 보조적 역할
- 실제
printf결과값을 받아 처리하는 코드 자체가 없는 경우도 많음명령형 프로그래밍 함수는 이처럼 특정 작업의 순차적인 명령을 기술하는 데 중점을 둠
⇒ 함수형 프로그래밍에서 말하는 순수 함수와는 거리가 있음
- 함수형 프로그래밍 함수는 순수 함수로서, 외부에 아무런 영향을 주지 않는 선에서 자신의 로직을 처리, 결과를 반환하는 역할
- 결과값을 얻는 것이 함수 호출의 목적이고, 결과값으로 또 다른 작업을 처리하게 됨
자바스크립트에서의 함수형 프로그래밍
- 자바스크립트에서도 함수형 프로그래밍이 가능
- 일급 객체로서의 함수, 클로저를 지원하기 때문
- 예시) 앞선 암호화 예시를 자바스크립트로 구현
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
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 = 'zoe'; return function() { // 여기서 반환하는 익명 함수가 클로저 return func.call(null, str); } } // 이 클로저에서 접근하는 변수 str은 외부에서는 접근 불가 // :클로저로 함수형 프로그래밍의 개념을 정확히 구현 가능 var encrypted_value = get_encrypted(f1)(); console.log(encrypted_value); var encrpyted_value = get_encrypted(f2)(); console.log(encrypted_value); var encrpyted_value = get_encrypted(f3)(); console.log(encrypted_value);
- 자바스크립트에서 앞서 예로 든 함수형 프로그래밍 슈도(pseudo) 코드 구현 가능
- 함수가 일급 객체로 취급되기 때문에 가능한 것
- 함수의 인자로 함수를 넘기고, 결과로 함수를 반환 가능
- 또한, 변수
str값이 영향을 받지 않게 하려고 클로저를 사용
-
1. 배열의 각 원소 총합 구하기
- 배열의 각 원소 총합 구하기 - 명령형 프로그래밍 방식
1 2 3 4 5 6 7 8 9 10 11 12
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)); // 10
- 배열의 각 원소 총 곱한 값 구하기 - 명령형 프로그래밍 방식
1 2 3 4 5 6 7 8 9 10 11 12
function multiply(arr) { var len = arr.length; var i = 0, result = 0; for (; i<len; i++) { result *= arr[i]; } return result; } var arr = [1,2,3,4]; console.log(multiply(arr)); // 10
명령형 프로그래밍 방식으로 작성된 코드: 문제 하나하나를 각각의 함수로 구현해 해결
⇒ 배열의 각 원소를 또 다른 방식으로 산술하여 결과값을 얻으려면 새로운 함수를 다시 구현해야 함
- 배열의 각 원소 계산 결과 값 구하기 - 함수형 프로그래밍 방식
1 2 3 4 5 6 7 8
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()함수는 함수와 배열을 인자로 넘겨받고, 루프를 돌며 함수를 실행 - 함수 실행 후, 얻은 결과값은 변수
accum에 계속 저장 - 위 작업을 원소 개수만큼 루프를 돌면서 수행
- 루프 종료 후, 최종적으로
accum값을 반환 - 사용자는
reduce()함수의 인자로 들어가는 함수를 직접 정의할 수 있음 ```javascript 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)); ```
![]()
-
- 함수형 프로그래밍을 이용해 코드를 훨씬 간결하게 작성 가능
- 다른 문제가 나오더라도 사용자가 해당 연산을 하는 함수를 작성해
reduce()함수로 결과를 얻을 수 있음 - 함수형 프로그래밍은 기존 프로그래밍 방식보다 한 단계 높은 모듈화가 가능
💡 reduce() 함수
- 자바스크립트에서 범용적으로 사용되는 함수
- 각 배열의 요소를 처음부터 하나씩 뽑아 연산에 사용, 최종 결과값을 도출
2. 팩토리얼
- 팩토리얼 구현하기 - 명령형 프로그래밍 방식
1 2 3 4 5 6 7 8 9 10
// 방법1 function fact(num) { var val = 1; for (var i = 2; i <= num; i++) { val = val * i; } return val; } console.log(fact(100));
1 2 3 4 5
// 방법2; 재귀호출 function fact(num) { if (num == 0) return 1; else return num * fact(num-1); }
- 팩토리얼 구현하기 - 함수형 프로그래밍 방식(성능을 고려한 구현)
- 포인트) 처음
10!을 실행 후,20!을 실행한다고 가정했을 때, 앞서 실행한10!은 중복됨 ```javascript var fact = function() { var cache = {‘0’: 1};
var func = function(n) { var result = 0;
1 2 3 4 5 6
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)); ```
-
fact는cache에 접근할 수 있는 클로저를 반환받음 - 클로저로 숨겨지는
cache에는 팩토리얼을 연산한 값을 저장하고 있음 - 연산을 수행하는 과정에서 캐시에 저장된 값이 있으면 곧바로 그 값을 반환하는 방식
- 한 번 연산된 값을 캐시에 저장하고 있으므로, 중복된 연산을 피해 보다 나은 성능의 함수를 구현할 수 있음
- 포인트) 처음
💡 메모이제이션(Memoization) 패턴
- 메모이제이션: 계산 결과를 저장해 놓아 이후 다시 계산할 필요 없이 사용할 수 있게 한다는 컴퓨팅 용어
- 기본적으로 계산된 결과를 함수 프로퍼티 값으로 담아 놓고, 나중에 사용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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));
- 함수
Calculate()프로퍼티에 data 프로퍼티를 만들어 객체 할당- 사용자는 이곳에 자신이 원하는 값을 원하는 키로 저장해 놓을 수 있음
- 한 번 계산된 값이 들어가면 그 이후에는 해당 키로 저장해 놓은 값을 받아 사용 가능 ⇒ 일종의 캐시 역할
- jQuery에서는
data()라는 메서드로 이 메모이제이션 패턴을 사용- jQuery 1.2.2 에서 구현된
data()메서드 일부
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; }, }
- jQuery 역시 예제와 같이 jQuery.cache에 사용자가 자신의 데이터를 넣을 수 있고, 이를 다시 받아서 사용할 수 있음 ⇒ 사용자에게 편의 제공
- 예제에서는
Calculate()함수에 자신의 함수를 인자로 넘겨 실행되게 간단히 작성됨- 보다 범용적으로 사용하는 방법:
Function.prototype에memoization()함수 넣기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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); myCalculate1.memoization(1, 6); myCalculate1.memoization(2, 7); console.log(myCalculate1.memoization(1)); // equal to console.log(myCalculate1.data[1]); console.log(myCalculate1.memoization(2)); // equal to console.log(myCalculate1.data[2]); console.log(myCalculate2.memoization(1)); // equal to console.log(myCalculate2.data[1]); console.log(myCalculate2.memoization(2)); // equal to console.log(myCalculate2.data[2]);
- 이처럼
Function.prototype에 메서드를 정의해 놓으면 특정 값을 리턴하는 모든 함수에서 유용하게 사용 가능- 주의) 한 번 값이 들어간 경우 계속 유지되므로 이를 초기화하는 방법 역시 피요
- 참고) jQuery에서는
cleanData(이전 버전:removeData)라는 메서드를 제공해 이 같은 작업 수행
💡 여기서 사용된 방법 역시 유용한 패턴에서 소개된 메모이제이션 기법. 유용한 패턴에서는 함수 객체의 한 프로퍼티를 캐시로 사용, 여기서는 클로저로 감춰지는 객체를 캐시로 사용.
3. 피보나치 수열
- 메모이제이션 기법 적용 - 함수형 프로그래밍 방식
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
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));
- 클로저를 이용하여
cache를 캐시로 활용 - 차이점)
cache의 초기값과 함수를 재귀 호출할 때 산술식이 다름
- 클로저를 이용하여
- 팩토리얼과 피보나치 수열을 계산하는 함수를 인자로 받는 함수
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
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; };
-
cacher함수는 사용자 정의 함수와, 초기cache값을 받아 연산 수행 - 사용자는 이 함수의 인자로 피보나치 수열을 연산하는 함수 혹은 팩토리얼을 연산하는 함수를 정의하여, 다음과 같이 사용 가능 ```javascript // 활용 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)); ```
-
- 함수형 프로그래밍이 수학에서 출발한 문제 해결 방법론이므로, 수학 문제 프로그래밍에 있어서 이득을 볼 수 있음
자바스크립트에서의 함수형 프로그래밍을 활용한 주요 함수
- 자바스크립트에서 범용적으로 사용되는 함수형 프로그래밍 기반의 여러 가지 함수 알아보기
1. 함수 적용
-
Function.prototype.apply함수로 함수 호출을 수행할 수 있음 - 이름이
apply인 이유: 함수 적용(Applying functions) 은 함수형 프로그래밍에서 사용되는 용어 - 함수형 프로그래밍에서는 특정 데이터를 여러 가지 함수를 적용시키는 방식으로 작업 수행
- 여기서 함수는 단순 입력을 넣고 출력을 받는 기능 뿐 아니라, 인자 혹은 반환 값으로 전달된 함수를 특정 데이터에 적용시키는 기능도 수행함
- 때문에, 자바스크립트에서 함수를 호출하는 역할을 하는 메서드를
apply라고 명명하게 됨 - 표현 예시)
func.apply(Obj, Args)와 같은 함수 호출: ‘func함수를Obj객체와Args인자 배열에 적용시킨다’2. 커링
- 특정 함수에서 정의된 인자의 일부를 넣어 고정시키고, 나머지를 인자로 받는 새로운 함수를 만드는 것을 의미
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
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)); // 5 (1 * 2 + 3 = 5) var new_func2 = curry(calculate, 1, 3); console.log(new_func2(3)); // 6 (1 * 3 + 3 = 6)
![]()
-
calculate()함수는 인제 3개를 받아 연산 수행, 결과값 반환 -
curry()함수로 만든 함수:- 첫 번째 인자를 1로 고정시킨 새로운 함수
new_func1() - 첫 번째, 두 번째 인자를 1, 3으로 고정시킨
new_func2()함수
- 첫 번째 인자를 1로 고정시킨 새로운 함수
- 여기서 핵심적인 역할을 하는
curry()함수의 역할:-
curry()함수로 넘어온 인자를args에 담음 - 새로운 함수 호출로 넘어온 인자와 합쳐서 함수 적용
-
-
- 이러한 커링은 함수형 프로그래밍 언어에서 기본적으로 지원하나, 자바스크립트에서 제공하지는 않음
- 그러나, 사용자는 다음과 같이
Function.prototype에 커링 함수를 정의하여 사용할 수 있음1 2 3 4 5 6
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 객체로 조작할 때, 이 메서드를 이용해 배열로 만든 수 손쉽게 조작
-
사용자가
calculate()함수의 첫 번째 인자와 세 번째 인자롤 고정하고 싶다면?⇒ 앞서 소개한
curry()함수로는 불가능. 다음과 같이 문제 해결.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
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)); // 7 (1 * 3 + 4 = 7)
![]()
-
curry2()함수를 사용할 때 주의점:-
curry2()호출 시, `calculate()~ 함수가 원하는 인자를 전부 넣어야 함 - 그 중 고정시키지 않을 인자를 undefined로 넘김
-
-
curry2()에서는curry2()를 호출할 때 넘어온 인자로 루프를 돌면서, undefined인 요소를 새로운 함수를 호출할 때 넘어온 인자로 대체 이와 같이 함수를 부분적으로 적용, 새로운 함수를 반환받는 방식을 함수의 부분 적용이라고 함
⇒ 함수의 부분 적용을 가장 잘 구현한 예제가
curry()메서드
-
- 커링은 함수형 프로그래밍에서는 기본적인 구현 방법
- 자바스크립트에서 기존 함수로 인자가 비슷한 새로운 함수를 정의하여 사용하고 싶을 때, 이와 같은 방법으로 유용하게 사용 가능
3. bind
-
bind()함수의 가장 기본적인 기능을 구현한 코드1 2 3 4 5 6 7 8 9
Function.prototype.bind = function() { var fn = this, slice = Array.prototype.slice, args = slice.call(arguments, 1); return function() { return fn.apply(thisArg, args.concat(clice.call(arguments))); }; }
- 앞서 설명된
curry()함수와 같이,bind()함수 또한 커링 기법을 활용한 함수임 - 커링과 같이 사용자가 고정시키고자 하는 인자를
bind()함수 호출 시 인자로 넘겨주고, 반환받은 함수를 호출하며 나머지 가변 인자를 넣어줄 수 있음 -
curry()와 차이점: 함수를 호출할 때 this에 바인딩시킬 객체를 사용자가 넣어줄 수 있음 -
curry()함수가 자바스크립트 엔진에 내장되지 않은 것도bind()함수로 충분히 커버가 가능하기 때문 ```javascript 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: ‘zoe’};
var myfunc = print_all.bind(myobj); myfunc(); // name : zoe
var myfunc1 = print_all.bind(myobj, ‘iam’, ‘others’); myfunc1(‘js’); /* name : zoe 0 : iam 1 : others 2 : js */ ```
![]()
-
myfunc()함수는myobj객체를 this에 바인딩시켜print_all()함수를 실행하는 새로운 함수 - 또한,
myfunc()를 실행하면, 인자도bind()함수에 모두 넘겨짐 - 이와 같이, 특정 함수에 원하는 객체를 바인딩시켜 새로운 함수를 사용할 때
bind()함수가 사용됨
- 앞서 설명된
💡 bind() 함수의 또 다른 기능
bind()함수로 받환받은 함수는 바인딩한 함수를 상속하는 기능까지 제공- ECMA 5로 지원하는
Function.prototype.bind함수는 모질라 개발자 사이트에서 다음과 같이 소개됨
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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; }; }- 다른 점을 확인할 수 있는가?
1 2 3 4 5 6 7 8 9 fNOP = function () {}, fBound = function () { // ... }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound;
- 객체지향 프로그래밍(6장)에서 많이 본 종류의 코드.
- 반환되는 함수 fBound는 현재 this에 바인딩된 함수 객체를 상속받음.
- 따라서, 반환받은 fBound 함수로 new라고 생성된 객체는 현재 함수의 prototype 프로퍼티에 접근 가능
- 이를 활용한 예제
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Person(arg) { if (this.name === undefined) this.name = arg ? arg : 'zoe'; console.log("name: " + this.name); } Person.prototype.setName = function(value) { this.name = value; }; Person.prototype.getName = function() { return this.name; }; var myobj = { name: 'iam' }; var new_func = Person.bind(myobj); new_func(); // (출력) name: iam var obj = new new_func(); // (출력) name: zoe console.log(obj.getName()); // (출력) zoe
4. 래퍼
- 특정 함수를 자신의 함수로 덮어쓰는 것
- 물론, 사용자는 원래 함수 기능을 잃지 않은 상태로 자신의 로직을 수행해야 함
- 객체지향 프로그래밍에서 다형성의 특성을 살리기 위해 오버라이드를 지원하는데, 상당히 유사한 개념임
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
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('zoe'); // value: zoe // wrapper value: 20
-
Function.prototype의original함수: 인자로 넘어온 값을 value에 할당, 출력하는 기능 - 사용자가 이를 덮어쓰기 위해
wrap함수 호출 - 여기서, 사용자는 자신의 익명 함수의 첫번째 인자로 원해 함수의 참조를 받을 수 있음
- 이 참조로 원래 함수를 실행하고, 자신의 로직을 수행 가능
-
-
wrap함수에 대해 좀 더 자세한 해설-
curry(),bind()함수와 상당히 유사함 중요: 사용자가 세 번째 인자로 받은 함수
wrapper()를apply()함수로 호출함→ 인자의 첫 번째가 [fn]
- 기존 함수의 참조를 첫 번째 인자로 넘김으로써, 사용자는 이 함수에 접근 가능
- 클로저를 절묘하게 사용한 함수형 프로그래밍의 한 방법
- 문제: 원래 함수
original()이 호출될 때의this, 반환되는 익명 함수가 호출될 때의this가 다름- 해결:
bind함수를 적절히 이용 -
apply를 호출할 때, 첫 번째 인자로 [fn] 대신 [fn, bind(this)]를 쓰면 문제를 해결할 수 있음 - 원래 함수에 반환되는 익명 함수의 this를 바인딩하는 것
- 해결:
-
- 래퍼는 기존에 제공되는 함수에서 사용자가 원하는 로직을 추가하고 싶다거나, 기존에 있는 버그를 피하고자 할 때 많이 사용됨
- 특히, 특정 플랫폼에서 버그를 발생시키는 함수가 있을 경우, 이를 컨트롤할 수 있으므로 상당히 용이
5. 반복 함수
- 자바스크립트 뿐만 아니라, 다른 여러 언어에서도 구현되어 널리 사용되는 함수들
- 자바스크립트에서 함수형 프로그래밍 특성을 활용해 매우 유용하게 사용 가능
5-1. each
- 배열의 각 요소, 객체의 각 프로퍼티를 하나씩 꺼냄 → 차례대로 특정 함수에 인자로 넣어 실행시킴
- 대부분의 자바스크립트 라이브러리에 기본적으로 구현되어 있는 함수
-
each,forEach라는 이름으로 제공됨 - jQuery 1.0의
each()함수 코드1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
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 zoe = { name: 'zoe', age: 30, sex: 'female' }; each(zoe, function(idx, value) { console.log(idx + " : " + value); });
![]()
-
obj에 length가 있는 경우 (ex. 배열)와 없는 경우 (ex. 객체)로 나눔 → 루프를 돌며 각 요소를 인자로 하여 차례대로 함수 호출 -
each()함수는 다양한 언어에서 기본적으로 제공됨 - 자바스크립트에서도 이 방식으로 정의하여 사용할 수 있음
- 주의:
each()함수에서 사용자 함수를 호출할 때 넘기는 인자의 순서, 구성이 라이브러리에 따라 다를 수 있음 - 보통 인덱스와 값의 순서로 인자를 전달하나, 약간 다를 수 있음
-
💡 다음 함수는 순수한 의미의 함수형 프로그래밍 예제는 아님
1 2 3 function (idx, value) { console.log(idx + " : " + value); }
each()의 인자로 넘기는 이 함수가 순수 함수가 아닌 프로시저이기 때문
5-2. map
- 주로 배열에 많이 사용되는 함수
- 배열의 각 요소를 꺼내 사용자 정의 함수를 적용시켜 새로운 값을 얻은 후, 새로운 배열에 넣음
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
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]
![]()
- 배열의 각 요소를 하나씩 꺼내 사용자 함수를 적용시킨 뒤, 그 값을 계속해서 누적시킴
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
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]; accumualted_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); // (출력) 14 = 1 * 1 + 2 * 2 + 3 * 3
![]()
- 배열의 각 요소를 순차적으로 제곱한 값을 더해 누적된 값을 반환받는 예제
- 각 요소를 사용자가 원하는 특정 연산으로 누적된 값을 반환받고자 할 때 유용하게 사용됨







