본문 바로가기

프로그래밍/SICP

고차함수를 이용한 추상화

반응형

고차함수란?

동일한 프로그래밍 패턴이 서로 다른 여러 함수에 쓰이는 경우를 흔히 볼 수 있다.
이런 패턴을 하나의 추상적 개념으로 표현하기 위해 함수를 인수로 받거나 함수를 값으로 돌려주는 함수가 아주 유용하게 쓰일 수 있는데, 이러한 함수를 고차 함수(higher-order function)라고 한다.

고차함수는 추상의 관점에서 문제를 고찰하고, 그런 추상들을 프로그래밍 언어의 요소들로서 명시적으로 표현할 수 있게하여
추상들을 여타 계산 요소들과 마찬가지 방식으로 다룰수 있게 한다는 점에서 중요하다.

또, 자바스크립트에서는 함수에 일급요소 자격을 부여하기 때문에 고차함수를 유연하게 정의하고 사용할 수 있다.

일급요소란?

프로그래밍 언어는 계산적 요소들의 조작 방식에 이런저런 제약을 가하는데, 이 제약이 가장 적은 요소를 일급(first-class) 요소라고 부른다.
일급 요소에는 다음같은 권리와 특권이 있다.

  • 이름으로 지칭할 수 있다.
  • 함수에 전달하는 인수가 될 수 있다.
  • 함수가 돌려주는 반환값이 될 수 있다.
  • 자료 구조에 포함할 수 있다.

함수에 일급 자격을 부여했을 때 장단점

  • 장점 : 프로그래밍 언어의 표현력을 크게 개선한다.
  • 단점 : 함수가 실행중이 아닌 동안에도 함수의 자유 이름들을 메모리(환경)에 유지해야 한다.

고차함수 정의하기

특정 패턴(컨셉)을 일반화 및 추상화하여 고차함수로 정의하는 과정을 예시로살펴보자.

위는 급수의 합을 표현하는 시그마 표기를 수식으로 나타낸 것이다.
이 시그마 개념을 어떻게 프로그래밍적으로 표현하고 사용할 수 있을지 생각해보면

  1. '급수의 합' 개념을 sigma라는 함수로 정의하고
  2. 급수를 적용할 f함수와 a(하계), b(상계) 를 인수로 받게하면 될 것 같다.

이렇게 어떤 개념을 코드로 추상화 하고자할 때 호출부의 인터페이스를 먼저 생각해보는게 도움이 된다.

sigma(f, a, b);

여기서 sigma는 f라는 함수인자를 받고있기 때문에 고차함수이다.
시그마의 계산식을 보면 f함수의 인자를 a부터 b까지 1씩 증가시키며 더해나간다.
이 동작을 다음과 같이 재귀함수를 이용하여 간단하게 정의할 수 있다.

function sigma(f, a, b){
  return a > b ? 0 : f(a) + sigma(f, a + 1, b);
}

아래와 같이 반복적 과정(꼬리 재귀형태)로 정의할수도 있다.

function sigma(f, a, b){
  function iter(a, result){
    return a > b ? result : iter(a + 1, result + f(a));
  }
  return iter(a, 0);
}

규칙 일반화 하기

이제 여기서 특정 규칙을 일반화하여 함수로 추상화 해보자.
지금까지의 sigma 함수에서는 합산이 적용되는 함수의 인자 n이 1씩 증가 하는 규칙이 있었다.
n의 변경 규칙 을 함수로 추상화 할 것이다.

sigma(f, a, next, b);
// 선형적 정의
function sigma(f, a, next, b){
  return a > b ? 0 : f(a) + sigma(f, next(a), next, b);
}
// 반복적 정의
function sigma(f, a, next, b){
  function iter(a, result){
    return a > b ? result : iter(next(a), result + f(a));
  }
  return iter(a, 0);
}

추상화 레이어 높이기

지금까지는 합산의 개념을 고차함수로 정의하는 과정이었다.

합산을 한 단계 더 높은 추상화 레벨에서 바라본다면, 누산(일단의 항들을 어떤 일반적인 함수로 조합하는 연산)의 한 특수사례로 생각할 수 있다.

이 점에 착안해서 accumulate라는 누산 함수를 정의하는 방식으로 추상화 레이어를 한 단계 높여보자.

그럼 다음 두가지 인자가 추가로 필요해진다.

  • combiner : 두개의 항을 받아 각 항을 조합하는 연산 방식(합산에서는 + 연산)
  • null_value : 누산할 항이 더이상 없을 때 사용할 기준값(합산에서는 0)
accumulate(combiner, null_value, f, a, next, b);
// 선형적 정의
function accumulate(combiner, null_value, f, a, next, b){
    return combiner(term(a),
            next(a) > b
            ? null_value
            : accumulate(combiner, null_value, f, next(a), next, b)
        );
}

// 반복적 정의
function accumulate(combiner, null_value, f, a, next, b){
    function iter(a, result){
        return a > b ? result : iter(next(a), combiner(result, f(a)))
    }
    return iter(a, null_value);
}

이제 accumulate 함수를 이용해서 합산을 적용하면 다음과 같이 작성할 수 있다.

accumulate((x, y) => x + y, a, f, 0, next, b);

다른 예로 승산(수열의 곱)은 이렇게 사용할 수 있다.

accumulate((x, y) => x * y, a, f, 1, next, b);
람다 표현식(aka 화살표 함수)
(매개변수들) => 표현식
함수의 이름과 return 키워드, 함수 본문의 중괄호 쌍을 생략하여 간결하게 작성하는 함수 선언 방식이다.

 

반응형