고차함수란 무엇인가?
10장의 핵심은 고차함수이다. 고차함수는 다른 함수를 인자로 받거나 함수를 반환하는 함수이다. 내가 이해하기로는 결국엔 "함수를 값으로 다룬다." 의 연장선으로 느껴졌다.
그런데 책을 보면서 좀 이해가 안되는 부분이 있었다.
술어 함수?
책에서는 고차 함수의 예를 다음과 같이 설명하고 있었다.
예를 들어 표준 라이브러리 함수인 filter는 술어 함수를 인자로 받으므로 고차 함수다. ...
술어 함수? 구글링을 해도 이에 대한 개념이 나오지 않아 원서엔 어떤 단어로 나와있는지 궁금하여 찾아봤다.

predicate function으로 찾아보니 그제서야 불리언 값을 반환하는 함수임을 알 수 있었다. 그제서야 술어에도 참 거짓을 판단하는 뜻이 있다는 것을 알 수 있었다.
역시 어떨때는 한글로 번역되어있지만 막상 원어로 봤을 때 좀 더 느낌이 잘 와닿는 개념들이 있는 것 같다.
람다를 인자로 받는 함수를 정의하려면
람다를 인자로 받으려면 함수의 타입을 어떻게 지정하는지 알아야 한다고 할 수 있겠다.
함수 파라미터의 타입은 다음과 같이 정의할 수 있을 것이다. 예를 들어 정수형, 문자열의 인자를 받아서 아무것도 반환하지 않는(Unit을 반환하는) 타입은 다음과 같이 써볼 수 있을 것이다.
(Int, String) -> Unit
함수타입 변수도 nullable이 될 수 있다.
처음에는 이 말이 단지 반환 타입이 Int? 처럼 nullable 인줄 알았으나,
다음과 같은 예제를 보고 이해가 되었다.
var canReturnNull(Int, Int) -> Int? = { x, y -> null } // 널을 반환할 수도 있는 함수
var funOrNull((Int, Int) -> Int)? = null // 널이 될수도 있는 함수
첫 번째 함수는널을 반환할 가능성이 있는 함수이다.
두 번째 함수는 함수가 널이 될 수도 있는 경우라고 볼 수 있겠다.
사실 지금까지 이를 사용해 본 적은 없는 것 같긴 한데 기억해 두면 유용하게 사용할 수 있을 듯하다.(아직 어디에 쓸지는 잘 모르겠다. null이 들어왔을 때 디폴트 함수를 지정하는 기능을 생각해봤는데, 이는 디폴트 파라미터 기능을 대신 쓰면 될 것 같긴하다.)
이에 대해서는 추후 좀 더 생각해봐야겠다.
그렇다면 이제 이를 활용해서 직접 고차 함수를 구현하는 예제를 살펴보려고 한다.
간단한 고차 함수 정의해보기
fun twoAndThree(operation: (Int, Int) -> Int) {
val result = operation(2, 3)
println("The result is $result")
}
fun main() {
twoAndThree { a, b -> a + b }
// The result is 5
twoAndThree { a, b -> a * b }
// the result is 6
}
함수를 값으로 다룰 수 있기 때문에 크게 특이한 점은 없이 내가 생각하는 그대로 사용할 수 있겠다고 생각했다!
특이한 점이 있다면, 마지막 파라미터가 람다이므로, 소괄호를 생략하고 대괄호로 감싸 전달할 수 있다는 것이다.
번외, 인텔리제이에서 제공하는 스마트 스테핑
책에서는 스마트 스테핑을 다음과 같이 소개한다.
인텔리제이 IDEA에서는 디버깅할 때 람다 코드 내부를 한 단계씩 실행해볼 수 있는 스마트 스테핑을 제공한다.
말로만 들었을때는 이미 사용하고 있는 기능인 그냥 스테핑과 무슨 차이인지 잘 와닿지 않았다. 특히 돌아보면, 기존에 디버깅할 때에도 디버깅 창에 나와있는 기능 3개만 사용하여 진행했었다.
step over로 스킵하거나, step into를 누르면서 일단 쭉 들어갔다가 너무 깊게 들어가면 step out으로 다시 원래 코드로 돌아오면서 진행했다. (돌아보니 조금 더 인텔리한 디버깅 방법을 찾아봐야할 것 같긴하다.)
그런데 스마트 스테핑에 대해 찾아보니 shift + f7 스마트 스테핑을 이용하면 자기가 원하는 함수를 골라 들어갈 수 있다고 한다. 실제로 진행해보니 정말 한 라인에 step in 할 수 있는 후보를 제공해줬다..

위와 같이 제공된 후보를 마우스 화살표를 통해 선택하여 step in을 해볼 수 있었다. 그런데 이게 웬걸 그냥 step into를 해도 위와 동일하게 디버깅을 할 수 있었다.
알고보니 기본적으로 스마트 스테핑을 허용하는 설정이 디폴트 값으로 체크되어있다고 한다. 찾아보니까 정말 그랬다!

결국 지금까지 스마트 스테핑을 제공받고 있었다.. 물론 진입 후보를 추천해주는지도 모르고 계속 step in을 누르긴 했었으니 좋은 소득을 얻었다고 할 수 있겠다.
혹시 몰라 체크를 해제하고 진행해보니 일반적인 step in은 예상대로 진입 후보를 추천하지 않고 그냥 step in 하는 것을 확인해볼 수 있었다!
자바에서 코틀린 함수 타입 사용하기
코틀린 람다는 자동 SAM(Single Abstract Method) 변환을 통해 자바 메서드에게 넘길 수 있으며, 함수 타입을 사용하는 코틀린 코드도 자바에서 호출할 수 있다고 한다.
보다시피 코틀린 표준 라이브러리의 확장 함수를 사용할 수도 있다. 하지만 확장 함수의 형태가 아닌 수신 객체를 전달하는 형태의 로직이고 Unit 타입의 값도 명시적으로 반환해줘야 한다.
그럼에도 자바에서 코틀린 표준 라이브러리를 이정도면 나름 편하게 사용할 수 있겠다는 생각이 들었다!
함수 타입의 파라미터에 대해 기본값 지정하기
사실 이는 그냥 다른 파라미터와 동일한 방법으로 기본값을 지정할 수 있다. = 뒤에 람다를 넣어 기본값을 선언할 수 있다!
널이 될 수 있는 함수 타입의 파라미터
위에서 한번 언급했듯이 다음과 같이 함수 타입을 nullable 하게 선언할 수 있다. 하지만 아직 동작이 있을 수도 있고 없을 수도 있다는 부분을 사용해 본적이 없어서 잘 써먹을 수 있겠다는 생각은 아직 들지 않는 것 같다. 추후 이에 대한 조사가 완료되면 추가하도록 하겠다!
함수를 함수에서 반환하기
책에서는 다음과 같은 언급을 한다.
함수가 함수를 반환할 필요가 있는 경우는 함수가 함수를 인자로 받아야 할 필요가 있는 경우보다는 적다. 하지만 함수를 반환하는 함수도 여전히 유용하다.
생각해보니 정말 그런 것 같긴 하다. 그럼에도 유용하다는 설명에는 잘 이해가 됐다. 책에서는 예시로 선택한 배송 수단에 따라 배송비를 계산하는 방법이 달라질 수 있는 경우를 구현할 때 사용하면 좋을 것이라고 설명하고 있다.
확실히 이렇게 하면 이 함수의 결과를 받는 곳의 로직이 획기적으로 줄어들고 가독성도 향상될 것이라는 생각이 들었다.