3.3.3 고차 함수 구현하기
3.2.8절에서 함수를 합성하는 fun 함수를 작성했다. 이 함수는 인자로 두 함수로 이뤄진 튜플을 받아 새로운 함수를 반환했다. 하지만 fun 함수(실제로는 메서드)를 사용하는 대신에 함수 값을 사용할 수도 있다. 함수를 인자로 받거나 함수를 결과로 돌려주는 함수를 고차 함수(HOF, Higher-Order Function)라고 부른다.
연습문제 3-4
두 함수를 합성하는 함수 값을 만들라. 예를 들어 3.2.8절 예제에서 봤던 square와 triple을 함수 값으로 다시 정의하고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)) } } }
이 코드를 square와 triple 함수를 사용해 테스트할 수 있다.
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에 적용하면 2에 triple을 먼저 적용하고 그 결과에 square를 적용한 값을 얻는다(이 과정은 함수 합성의 정의와 일치하는 계산이다).
println(squareOfTriple(2)) 36
파라미터 순서에 주의하라. triple이 먼저 적용되고 square는 triple이 반환한 결과에 적용된다.
7 역주 3.2.8절에서는 fun을 사용해 square와 triple을 정의했지만, 여기서는 익명 함수를 둘 정의하고 그 익명 함수를 각각 squre와 triple이라는 변수에 대입해 이름을 붙여야 한다. 앞에서 fun으로 정의한 함수를 다른 함수에 전달할 때는 앞에 ::를 붙여서 함수 참조를 넘겨야 했지만, 익명 함수에 이름을 부여하면 :: 없이 이름을 사용해 함수를 가리킬 수 있다. 코틀린 함수형 프로그래밍에서 일반적으로 함수나 메서드는 fun으로 정의한 함수를, 함수 값은 익명 함수를 가리킨다는 점을 다시 한번 기억하자.