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

vencott·2021년 6월 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); // 1
var encrypted_value = get_encrypted(f2)();
console.log(encrypted_value); // 2
var encrypted_value = get_encrypted(f3)();
console.log(encrypted_value); // 3

자바스크립트에서 함수는 일급 객체이므로 함수의 인자로 함수를 넘기고, 결과로 함수를 반환할 수도 있다

변수 str 값이 영향을 받지 않게 하려고 클로저를 사용하였다

get_encrypted() 함수에서 반환하는 익명 함수가 클로저이다

이 클로저에서 접근하는 변수 str은 자유변수이며 외부에서 접근할 수 없으므로 클로저로 함수형 프로그래밍의 개념을 정확히 구현할 수 있다

7.2.1 배열의 각 원소 총합 구하기

다음 코드는 일반적으로 배열의 합을 구할 때 작성하는 코드이다

function sum(arr) {
    var len = arr.length;
    var i = 0;
    var 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;
    var result = 0;

    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;
    var i = 0;
    var accum = memo;

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

    return accum;
}

var sum = function (x, y) {
    return x + y;
};

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

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

위와 같이 함수형 프로그래밍을 이용하여 코드를 훨씬 간결하게 작성할 수 있고 다른 문제가 나오더라도 사용자가 해당 연산을 하는 함수를 작성하여 reduce() 함수로 결과를 얻을 수도 있다

이처럼 함수형 프로그래밍은 기존 프로그래밍 방식보다 한단계 높은 모듈화를 이룰 수 있다

7.2.2 팩토리얼

다음은 팩토리얼을 명령형 프로그래밍 방식으로 구현한 코드이다

// 일반
function factorial(num) {
    var value = 1;
    for (var i = 2; i <= num; i++)
        value = value * i;
    return value;
}

console.log(factorial(3));

// 재귀
function recursive_factorial(num) {
    if (num === 0)
        return 1;
    else
        return num * recursive_factorial(num - 1);
}

console.log(recursive_factorial(5));

이 두 예제는 큰 무리 없이 팩토리얼을 구현하지만 이를 함수형 프로그래밍 방법으로 구현하여 성능이 향상될 수 있다

먼저 팩토리얼의 특성을 살펴볼 필요가 있다

처음 10!을 실행한 후 20!을 실행하면 20!을 실행할 때는 앞에서 실행한 10!을 중복하여 계산한다

이렇게 중복되는 값, 즉 앞서 연산한 결과를 캐시에 저장하여 사용할 수 있는 함수를 작성한다면 성능 향상에 도움이 된다

var factorial = function () {
    var cache = {
      '0': 1
    };

    var func = function (n) {
        var result = 0;

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

        return result;
    };

    return func;
}();

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

위 예제에서 factorial은 cache에 접근할 수 있는 클로저를 반환받는다

클로저로 숨겨지는 cache에는 팩토리얼을 연산한 값을 저장하고 있고 연산을 수행하는 과정에서 캐시에 저장된 값이 있으면 곧바로 그 값을 반환하고 없으면 계산후 캐시에 저장하는 방식이다

메모이제이션(memoization) 패턴

메모이제이션 패턴은 계산된 결과를 함수 프로퍼티값으로 담아 놓고 나중에 사용하는 패턴이다

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));
console.dir(Calculate);

예제에서 보는 것과 같이 함수 Calculate() 프로퍼티에 data 프로퍼티를 만들어 객체를 할당하였다

이곳에 사용자는 자신이 원하는 값을 원하는 키로 저장해 놓을 수 있다

jQuery 에서는 data()라는 메서드로 이 메모이제이션 패턴을 사용하였다

7.2.3 피보나치 수열

마지막으로 피보나치 수열을 함수형 프로그래밍 방식으로 구현해보자

var fibo = function () {
    var cache = {
        '0': 0,
        '1': 1
    };

    var func = function (n) {
        var result;

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

        return result;
    };

    return func;
}();

console.log(fibo(10));

클로저를 이용하여 cache를 캐시로 활용한다는 것이 똑같다

피보나치 수열을 계산하는 함수를 인자로 받는 함수를 만들어보자

var cacher = function (cache, func) {
    var calculate = function (n) {
        var result;
        if (typeof (cache[n]) === 'number')
            result = cache[n];
        else {
            result = func(calculate, n);
            cache[n] = result;
        }

        return result;
    };

    return calculate;
};

cacher 함수는 사용자 정의 함수와 초기 cache값을 받아 연산을 수행한다

사용자는 이 함수의 인자로 피보나치 수열을 연산하는 함수 혹은 팩토리얼을 연산하는 함수를 전달하여 사용한다

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

출처: 송형주, 고현준 저, 『인사이드 자바스크립트』, 한빛미디어(2014)

profile
Backend Developer

0개의 댓글