고차 함수

고차 함수란 다른 함수를 인자로 받거나 함수를 반환하는 함수이다. 예를 들어 아래 함수는 함수(람다)를 인자로 받으므로 고차 함수이다.

list.filter { x > 0 }

함수 타입

람다를 인자로 받는 함수를 정의하려면 먼저 함수 타입(람다 인자의 타입)을 어떻게 선언할 수 있는지 알아야 한다. 함수 타입을 정의하려면 함수 파라미터의 타입을 괄호 안에 넣고 그 뒤에 화살표 (->)를 추가한 다음 함수의 반환 타입을 지정하면 된다.

(Int, String) -> Unit

예시

  1. Int 파라미터를 2개 받아서 Int 값을 반환하는 함수 : (Int, Int) -> Int
  2. 아무 인자도 받지 않고 아무 값도 반환하지 않는 함수 : () -> Unit

여기서 Unit 타입은 값을 반환하지 않는 함수 반환 타입에 쓰이는 타입이다. 함수 타입을 선언할 때는 반환 값이 없을 경우 반드시 Unit을 명시해야 한다.

널이 될 수 있는 타입과 조합

함수 타입의 반환 타입을 널이 될 수 있는 타입으로 지정할 수 있다.

val canReturnNull: (Int, Int) -> Int? = ( x, y -> null )

함수 타입 자체로 널이 될 수 있다. 널이 될 수 있는 함수 타입을 정의하려면 함수 타입 전체를 괄호로 감싸고 그 뒤에 물음표를 붙인다.

var funOrNull: ((Int, Int) -> Int?)? = null

파라미터 이름과 함수 타입

함수 타입 선언 시 파라미터 이름을 정할 수 있다.

(code: Int, content: String) -> Unit

정의한 이름은 람다에 그대로 사용할 수 있다.

{ code, content -> /*...*/ }

물론 다른 이름으로 바꿔 쓸 수도 있다.

{ code, _ -> /*...*/ }

디폴트 값

함수 타입 또한 디폴트 값을 선언할 수 있다.

fun <T> Collection<T>.joinToString(
  separator: String = ", ",
  prefix: String = "",
  postfix: String = "",
  transform: (T) -> String = { it.toString() }
)

함수 내에서 함수 타입 사용

함수 타입을 함수 내에서 사용하려면 함수 타입의 이름을 이용하여 함수를 호출하듯이 사용한다.

fun foo(callback: () -> Unit) {
  callback()
}

널이 될 수 있는 함수 타입은 함수 실행 전에 함수가 널인지 검사해야 한다. 또는 invoke() 메소드를 활용할 수 있다.

fun foo(callback: (() -> Unit)?) {
  if (callback != null) {
    callback()
  }
  //callback?.invoke()으로도 사용할 수 있다.
}

자바에서 함수 타입 사용하기

함수 타입은 컴파일되면 일반 인터페이스로 바뀐다. 함수 타입의 변수는 FunctionN 인터페이스를 구현하는 객체를 저장한다.

코틀린 표준 라이브러리는 함수 인자에 개수에 따라 Function0<R>(인자가 없는 함수), Function1<P1, R>(인자가 하나인 함수)… 등의 인터페이스를 제공한다. 각 인터페이스에는 invoke 메소드 정의가 하나 들어 있으며 이를 호출하여 함수를 실행할 수 있다.

자바 8 이후는 자바에서 제공하는 람다를 사용해 고차 함수를 쉽게 호출할 수 있다.

fun processTheAnswer(f: (Int) -> Int) {
  println(f(42))
}
processTheAnswer(number -> number + 1)
// 43

자바 8 이전의 자바는 필요한 FunctionN 인터페이스의 invoke 메소드를 구현하는 무명 클래스를 사용할 수 있다.

processTheAnswer(
  new Function1<Integer, Integer> {
    @Override
    public Integer invoke(Integer number) {
      return number + 1;
    }
});
// 43

함수를 반환하는 함수

함수를 반환하는 함수는 함수의 반환형에 함수 타입 선언을 넣으면 된다.

fun getShippingCostCalculator(
	delivery: Delivery): (Order) -> Double { /*...*/ }

고차 함수 안에서 흐름 제어

https://blog.ysmstudio.be/posts/Kotlin-%EC%9D%B8%EB%9D%BC%EC%9D%B8-%ED%95%A8%EC%88%98/ 에서 인라인 함수에 대해 알 수 있습니다.

람다 안의 return

fun lookForAlice(people: List<Person>) {
  people.forEach {
    if (it.name == "Alice") {
      println("Found!")
      return //lookForAlice 함수에서 반환된다.
    }
  }
  println("Alice is not found")
}

람다 안에서 return을 사용하면 람다로부터만 반환되는 게 아니라 그 람다를 호출하는 함수가 실행을 끝내고 반환된다. 자신을 둘러싸고 있는 블록보다 더 바깥에 있는 다른 블록을 반환하게 만드는 return 문을 Non-local return 이라 부른다.

return이 바깥쪽 함수를 반환시킬 수 있는 때는 람다를 인자로 받는 함수가 인라인 함수인 경우뿐이다. 위 예의 forEach는 인라인 함수이므로 람다 본문과 함께 인라이닝된다. 따라서 return 식이 바깥쪽 함수를 반환하도록 쉽게 컴파일할 수 있다.

람다로부터 반환

람다 식에서도 Local return을 사용할 수 있다. 이는 for 루프의 break와 비슷하다. Local return은 람다의 실행을 끝내고 람다를 호출했던 코드의 실행을 계속 이어간다. Local return 과 Non-Local return을 구분하기 위해 레이블을 사용해야 한다.

fun lookForAlice(people: List<Person>) {
  people.forEach label@ {
    if (it.name == "Alice") return@label
  }
  println("Alice might be somewhere")	//이 구문은 항상 출력된다.
}

람다에 레이블을 붙여서 사용하는 대신 람다를 인자로 받는 인라인 함수의 이름을 return 뒤에 레이블로 사용해도 된다. 람다 식에는 레이블이 하나만 붙기 때문에 함수 이름을 레이블로 사용하면 람다 식의 레이블을 지정할 수 없으며 그 반대도 성립한다.

무명 함수

무명 함수는 코드 블록을 함수에 넘길 때 사용할 수 있는 다른 방법이다.

fun lookForAlice(people: List<Person>) {
  people.forEach(fun (person) {
    if(person.name == "Alice") return
    println("${person.name} is not Alice")
  })
}

return은 가장 가까운 함수를 가리킨다. 위 예시에서는 무명 함수를 가리키게 된다.

무명 함수는 일반 함수와 비슷해 보이지만 함수 이름이나 파라미터 타입을 생략할 수 있다.

people.filter(fun (person) : Boolean {
  return person.age < 30
})

무명 함수도 일반 함수와 같은 반환 타입 지정 규칙을 따른다. 또한 식을 본문으로 하는 무명 함수 또한 만들 수 있다.

people.filter(fun (person) = person.age < 30)

사실 return 에 적용되는 규칙은 간단하다. fun 키워드를 사용해 정의된 가장 안쪽 함수를 반환시킨다는 점이다. 람다 식은 fun 키워드를 사용해 정의되지 않으므로 람다 밖의 함수를 반환시키며 무명 함수는 fun 키워드를 사용해 정의되므로 무명 함수를 반환시킨다.

무명 함수는 일반 함수와 비슷해 보이지만 실제로는 람다 식에 대한 문법적 편의일 뿐이다.


© 2021. All rights reserved.

Powered by Hydejack v9.1.6