더북(TheBook)

3.3.3 고차 함수 구현하기

3.2.8절에서 함수를 합성하는 fun 함수를 작성했다. 이 함수는 인자로 두 함수로 이뤄진 튜플을 받아 새로운 함수를 반환했다. 하지만 fun 함수(실제로는 메서드)를 사용하는 대신에 함수 값을 사용할 수도 있다. 함수를 인자로 받거나 함수를 결과로 돌려주는 함수를 고차 함수(HOF, Higher-Order Function)라고 부른다.

 

연습문제 3-4

두 함수를 합성하는 함수 값을 만들라. 예를 들어 3.2.8절 예제에서 봤던 squaretriple을 함수 값으로 다시 정의하고7, 이 둘을 합성한 squreOfTriple을 만들어라.

해법

올바른 순서를 따른다면 쉽게 이 문제를 해결할 수 있다. 맨 먼저 해야 할 일은 타입을 적는 것이다. 이 함수는 두 가지 인자를 받는다. 따라서 커리한 함수여야 한다. 함수의 두 인자와 반환 타입은 Int에서 Int로 가는 함수다. 이런 함수의 타입을 적으면 다음과 같다.

(Int) -> Int

이 타입을 T라고 부르자. 이제 T 타입의 인자(첫 번째 인자)를 받아서 T 타입의 함수 값(두 번째 인자)을 T(반환 값)로 반환하는 함수를 만들고 싶다. 이 함수의 타입은 다음과 같다.

(T) -> (T) -> T

T를 원래 값으로 치환하면 실제 타입을 얻는다.

((Int) -> Int) -> ((Int) -> Int) -> (Int) -> Int

이 타입은 길이가 너무 길다는 문제가 있을 뿐이다! 이제 구현을 추가하자. 타입을 정하는 것보다 구현하는 것이 더 쉽다.

{ x -> { y -> { z -> x(y(z)) } } }

전체 코드는 다음과 같다.

val compose: ((Int) -> Int) -> ((Int) -> Int) -> (Int) -> Int =
                               { x -> { y -> { z -> x(y(z)) } } }

원한다면 타입 추론의 힘을 빌어서 반환 타입을 생략할 수도 있다. 대신 각 인자의 타입을 지정해야 한다.

val compose = { x: (Int) -> Int -> { y: (Int) -> Int ->
                                   { z: Int -> x(y(z)) } } }

아니면 타입 별명을 사용해도 된다.

typealias IntUnaryOp = (Int) -> Int

val compose: (IntUnaryOp) -> (IntUnaryOp) -> IntUnaryOp =
                             { x -> { y -> { z -> x(y(z)) } } }

이 코드를 squaretriple 함수를 사용해 테스트할 수 있다.

typealias IntUnaryOp = (Int) -> Int

val compose: (IntUnaryOp) -> (IntUnaryOp) -> IntUnaryOp =
                             { x -> { y -> { z -> x(y(z)) } } }

val square: IntUnaryOp = { it * it }

val triple: IntUnaryOp = { it * 3 }

val squareOfTriple = compose(square)(triple)

이 코드는 첫 번째 인자를 적용하는 것부터 시작한다. 첫 번째 인자를 적용해 얻은 새 함수에 두 번째 인자를 적용한다. 두 번째 인자를 적용한 결과도 함수다. 이 함수는 인자로 넘긴 두 함수의 합성 함수다. 이 새 함수를 (예를 들어) 2에 적용하면 2triple을 먼저 적용하고 그 결과에 square를 적용한 값을 얻는다(이 과정은 함수 합성의 정의와 일치하는 계산이다).

println(squareOfTriple(2))

36

파라미터 순서에 주의하라. triple이 먼저 적용되고 squaretriple이 반환한 결과에 적용된다.

 

 


7 역주 3.2.8절에서는 fun을 사용해 squaretriple을 정의했지만, 여기서는 익명 함수를 둘 정의하고 그 익명 함수를 각각 squretriple이라는 변수에 대입해 이름을 붙여야 한다. 앞에서 fun으로 정의한 함수를 다른 함수에 전달할 때는 앞에 ::를 붙여서 함수 참조를 넘겨야 했지만, 익명 함수에 이름을 부여하면 :: 없이 이름을 사용해 함수를 가리킬 수 있다. 코틀린 함수형 프로그래밍에서 일반적으로 함수나 메서드는 fun으로 정의한 함수를, 함수 값은 익명 함수를 가리킨다는 점을 다시 한번 기억하자.

신간 소식 구독하기
뉴스레터에 가입하시고 이메일로 신간 소식을 받아 보세요.