Post

함수형 프로그래밍에 대한 학습 정리

함수형 프로그래밍

클로저

어떠한 함수 내부(scope 범위 안에) 선언된 또다른 함수를 일컫습니다. 클로저를 포함하고 있는 함수가 실행된 후 함수의 정보가 콜스택에서 사라지게 되더라도, 클로저로 인해 해당 함수에서 필요했던 내용을, 참조가 끊어져 가비지콜렉터가 동작하기 전까지 힙에 저장해 두게 됩니다.

1
2
3
4
5
6
7
8
9
10
function createCounter(addValue) {
  let count = 0;
  return function () {
    count += addValue;
    console.log(count);
  };
}

const counterBy1 = createCounter(1);
const counterBy3 = createCounter(3);

이처럼, 내부의 익명 클로저 함수를 반환하는 외부 함수의 지역 변수와 매개변수를 인스턴스의 필드처럼 사용할 수 있습니다. 이처럼 외부 함수에 선언된 변수를 사용할 수 있는 것을 lexical scope라 합니다. lexcial scope에 저장된 이 count와 addValue는 함수 실행 외에 다른 방법으로는 접근될 수 없습니다. 또한, 클래스 방식보다 메모리도 덜 차지한다는 장점이 있습니다.

자바스크립트 제너레이터 함수 function*

  • 여러개 값을 필요에 따라 하나씩 반환(yield)합니다.
  • 함수를 호출하면 코드가 실행되는 것이 아니라, 대신 실행을 처리할 특별 객체 “제너레이터 객체”가 반환됩니다.
  • generator.next()는 가장 가까운 yield<value>를 만날 때까지 실행을 계속하고, 만나면 반환됩니다.
  • return에 다다를때까지 next()를 호출하면 반복됩니다.
  • 지연 평가를 구현하는데 사용할 수 있습니다.
  • if문 대신 계산을 횟수 등을 제어하여 최적화합니다.

커링

여러 인자를 받는 함수를 하나의 인자만 받는 함수들의 체인으로 변환합니다.

1
2
3
4
const curry =
  (f) =>
  (a, ...bs) =>
    bs.length ? f(a, ...bs) : (...bs) => f(a, ...bs); // 다시 함수를 반환해서 bs를 받아오게 됨.

Monad

모나드(Monad): 값을 감싸고 그 값을 변환하는 연산을 추상화한 구조입니다. 엄밀히 따지면 다르지만, 배열과 Promise도 모나드의 하나라고 볼 수 있습니다.

모나드는 콜백함수로 순수함수를 받아서 값을 변환합니다. 이때 반환되는 값이 같은 형태가 되도록 하여 메소드 체이닝이 가능하게 됩니다. 배열의 map을 상상해볼 수 있습니다. 다만, 모나드는 map에 더해 flatMap의 기능도 가지고 있어야 합니다.

배열 map의 콜백함수에 배열을 반환하는 함수를 넣어줄 때, 값이 2중으로 포장됩니다. flatMap은 이러한 2중 포장을 막아줍니다. map에 해당하는 기능을 가지고 있으면 functor, flatMap까지 가지고 있다면 monad의 기능까지 가지고 있다고 보셔도 됩니다.

map은 flatMap이 명시적으로 구분되어 있지만, promise의 then 메소드는 promise를 반환하는데, 그 안의 콜백함수에서 Promise.resolve로 또다시 promise를 반환한다면 then은 flat map 방식으로 자동으로 2중 포장을 막아줍니다.

또다른 모나드 Maybe를 직접 만들어봅시다. 모나드에는 unit, map, flatMap이 필수 요소입니다.

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
39
40
41
42
class Maybe {
  constructor(value) {
    this.value = value;
  }

  // Unit (or Return)
  static just(value) {
    return new Maybe(value);
  }

  // Unit (or Return)
  static nothing() {
    return new Maybe(null);
  }

  isNothing() {
    return this.value === null || this.value === undefined;
  }

  // Functor
  map(fn) {
    if (this.isNothing()) {
      return Maybe.nothing();
    }
    return Maybe.just(fn(this.value));
  }

  // Bind (or FlatMap)
  flatMap(fn) {
    if (this.isNothing()) {
      return Maybe.nothing();
    }
    return fn(this.value);
  }

  getOrElse(defaultValue) {
    if (this.isNothing()) {
      return defaultValue;
    }
    return this.value;
  }
}
  • Unit은 주어진 값을 모나드로 감싸며, 위의 just와 nothing에 해당하며 지금은 Maybe라는 클래스로 반환합니다. Promise에서는 resolve에 해당합니다.

  • functor 혹은 map은 주어진 함수 실행 결과를 다시 같은 모나드에 담아 내보냅니다. 만약 값이 없다면 nothing을 반환합니다.

  • bind 혹은 flatMap은 map과 같지만 값을 내보낼 때 모나드로 감싸지 않고 함수를 적용한 값을 그대로 내보냅니다.

  • 추가로 엄밀한 모나드로 분류되기 위한 법칙은 다음과 같습니다.

    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
    39
    40
    41
    42
    43
    44
    45
    
    // Left Unit Law
    function leftUnitLaw() {
      const f = (x) => Maybe.just(x + 1);
      const x = 5;
    
      const result1 = Maybe.just(x).flatMap(f);
      const result2 = f(x);
    
      console.log(
        "Left Unit Law:",
        result1.getOrElse(null) === result2.getOrElse(null)
      ); // true
    }
    
    // Right Unit Law
    function rightUnitLaw() {
      const m = Maybe.just(5);
    
      const result1 = m.flatMap(Maybe.just);
      const result2 = m;
    
      console.log(
        "Right Unit Law:",
        result1.getOrElse(null) === result2.getOrElse(null)
      ); // true
    }
    
    // Associativity Law
    function associativityLaw() {
      const f = (x) => Maybe.just(x + 1);
      const g = (x) => Maybe.just(x * 2);
      const m = Maybe.just(5);
    
      const result1 = m.flatMap(f).flatMap(g);
      const result2 = m.flatMap((x) => f(x).flatMap(g));
    
      console.log(
        "Associativity Law:",
        result1.getOrElse(null) === result2.getOrElse(null)
      ); // true
    }
    
    leftUnitLaw();
    rightUnitLaw();
    associativityLaw();
    
    • Left Unit Law 왼쪽 단위 법칙: 모나드로 감싸 적용한 결과는 값을 그냥 적용한 결과와 같아야 합니다.
    • Right Unit Law 오른쪽 단위 법칙: 모나드에 해당 값을 반환하는 값을 적용한 결과는 원래의 모나드와 같아야 합니다.
    • Associativity Law 결합 법칙: 모나드에 f 이후 g를 적용한 결과는 f와 g를 합성한 하나의 함수를 적용한 것과 결과가 동일해야 합니다.

    f(g(x)) = f(g(x)) x가 잘못되면 f(g(x)) = x 처럼 되게 혹은 f(g(x)) = g(x) 처럼 되게

This post is licensed under CC BY 4.0 by the author.