고차 함수

  • 고차 함수: 다른 함수를 인자로 받거나 함수를 반환하는 함수

함수 타입

  • 코틀린의 타입 추론으로 인해 변수 타입을 지정하지 않아도 람다를 변수에 대입할 수 있다.
  • 반환 타입이 널이 될 수 있는 타입
var canReturnNull: (Int, Int) -> Int? = { x, y -> null }
  • 함수 타입 전체가 널일 될 수 있는 타입
var funOrNull: ((Int, Int) -> Int)? = null
  • 함수 타입에서 파라미터 이름을 지정할 수도 있다.
    • 타입 검사 시 무시되고, 파라미터 이름이 꼭 함수 타입 선언의 파라미터 일므과 일치하지 않지만, 코드 가독성이 좋아지고 IDE 코드 자동 완성을 사용할 수 있다.
performRequest {  
   code, content ->  println("$code, $content")  
}

fun performRequest(  
   callback: (code: Int, content: String) -> Unit  
) {  
   // ...  
}

인자로 받은 함수 호출

  • 인텔리J 아이디어에서는 디버깅할 때 람다 코드 내부를 한 단계씩 실행해볼 수 있는 스마트 스테핑(smart stepping)을 제공한다.

자바에서 코틀린 함수 타입 사용

  • 컴파일된 코드 안에서 함수 타입은 일반 인터페이스로 바뀐다.
  • 코틀린 표준 라이브러리는 함수 인자의 개수에 따라 Function0<R>(인자가 없는 함수), Function1<P1,R?>(인자가 하나인 함수) 등의 인터페이스를 제공한다.
    • 각 인터페이스에는 invoke 메소드 정의가 하나 들어있다.
  • 자바 8 람다를 넘기면 자동으로 함수 타입의 값으로 변환된다.

  • 자바 8 이전 자바에서는 필요한 FunctionN 인터페이스의 invoke 메소드를 구현하는 무명 클래스를 넘기면 된다.

디폴트 값을 지정한 함수 타입 파라미터나 널이 될 수 있는 함수 타입 파라미터

  • 파라미터를 함수 타입으로 선언할 때도 디폴트 값을 정할 수 있다.
  • 널이 될 수 있는 함수 타입을 사용할 수도 있다.

함수를 함수에서 반환

  • 함수를 반환하는 함수도 존재한다.
    • 상태나 다른 조건에 따라 달라질 수 있는 로직

람다를 활용한 중복 제거

  • 전략 패턴: 함수 타입을 사용해 전략을 표현할 수 있고 경우에 따라 다른 람다 식을 넘김으로써 중복을 제거하고 여러 전략을 사용할 수 있다.

인라인 함수: 람다의 부가 비용 없애기

  • 코틀린은 보통 람다를 무명 클래스로 컴파일하지만 그렇다고 람다 식을 사용할 때마다 새로운 클래스가 만들어지지는 않는다.
  • 람다가 변수를 포획한다면 람다가 생성되는 시점마다 새로운 무명 클래스 객체가 생긴다. 이런 경우 실행 시점에 무명 클래스 생성에 따른 부가 비용이 든다.
  • inline 변경자를 어떤 함수에 붙이면 컴파일러는 그 함수를 호출하는 모든 문장을 함수 본문에 해당하는 바이트코드로 바꿔치기 해준다.

인라이닝이 작동하는 방식

  • 함수를 호출하는 코드를 함수를 호출하는 바이트코드 대신 함수 본문을 번역한 바이트코드로 컴파일한다.
  • 함수 본문뿐 아니라 함께 파라미터로 전달된 람다의 본문도 함께 인라이닝된다.
  • 인라인 함수를 호출하면서 람다를 넘기는 대신 함수 타입의 변수를 넘길 수 있다.
    • 이런 경우 인라인 함수를 호출하는 코드 위치에서는 람다 본문은 인라이닝 되지 않고, 함수 본문만 인라이닝 된다.
  • 한 인라인 함수를 두 곳에서 각각 다른 다른 람다를 사용해 호출한다면 그 두 호출은 각각 따로 인라이닝 된다.

인라인 함수의 한계

  • 파라미터로 받은 람다를 다른 변수에 저장하고 나중에 그 변수를 사용한다면 인라이닝할 수 없다.
    • 인라이닝할 수 없는 경우에는 “Illegal usage of inline-parameter"라는 메시지와 함께 인라이닝을 금지시킨다.

  • 둘 이상의 람다를 인자로 받는 함수에서 일부 람다만 인라이닝해야 되는 경우
    • 인라이닝 하면 안되는 람다를 파라미터로 받는다면 noinline 변경자를 파라미터 이름 앞에 부텨서 인라이닝을 금지할 수 있다.
inline fun foo(inlined: () -> Unit, noinline noInlined: () -> Unit) {  
   // ...  
}
  • 자바에서 코틀린에 정의한 인라인 함수를 호출하면, 컴파일러는 인라인 함수를 인라이닝하지 않고 일반 함수 호출로 컴파일 한다.

컬렉션 연산 인라이닝

  • filtermap 은 인라인 함수다. 따라서 그 두 함수의 본문은 인라이닝되며, 추가 객체나 클래스 생성은 없다.
    • 하지만 리스트를 걸러낸 결과를 저장하는 중간 리스트를 만든다.
  • asSequence를 통해 리스트 대신 시퀀스를 사용하면, 람다를 저장해야 하므로 람다를 인라인하지 않는다.
    • 인라이닝되지 않기 때문에 크기가 작은 컬렉션은 오히려 일반 컬렉션 연산이 더 성능이 나을 수도 있다.
    • 스퀀스를 통해 성능을 향상시킬 수 있는 경우는 컬렉션 크기가 큰 경우 뿐이다.

함수를 인라인으로 선언해야 하는 경우

  • inline 키워드르 사용해도 람다를 인자로 받는 함수만 성능이 높아질 가능성이 높다.
    • 다른 경우는 주의 깊게 성능을 측정하고 조사해봐야 한다.
  • 일반 함수 호출의 경우 JVM은 이미 강력하게 인라이닝을 지원한다.
    • JVM은 코드 실행을 분석해서 가장 이익이 되는 방향으로 호출을 인라이닝한다.
    • 바이트 코드를 실제 기계어 코드로 번역하는 과정(JIT)에서 일어난다.
  • 람다를 인자로 받는 함수를 인라이닝했을 때 장점
    • 인라이닝을 통해 함수 호출 비용, 람다를 표현하는 클래스와 람다 인스턴스에 해당하는 객체를 만드는 비용을 없앨 수 있다.
    • JVM은 함수 호출과 람다를 인라이닝해 줄 정도로 똑똑하지는 못하다.
    • 인라이닝을 사용하면 일반 람다에서는 사용할 수 있는 몇 가지 기능을 사용할 수 있다.
  • inline 변경자를 붙일 때 주의점
    • 인라이닝하는 함수가 큰 경우 함수 본문에 해당하는 바이트코드를 모든 호출 지점에 복사해 넣어서 바이트코드가 전체적으로 커질 수 있다.
    • 인라이닝을 하지 않고 함수를 직접 호출하면 스택 트레이스가 더 깔끔해진다.

자원 관리를 위해 인라인된 람다 사용

  • 자원 관리 패턴을 만들 때 보통 사용하는 방법은 try/finnaly 문을 사용하되 try 블록을 시작하기 직전에 자원을 획득하고 finally 블록에서 자원을 해제하는 것이다.
  • 코틀린 라이브러리에는 withLock 인라인 함수를 제공하고 있다.

  • 파일의 경우 try-with-resource를 사용해 자원을 닫는다.
  • 코틀린 라이브러리에서는 use 인라인 함수를 제공하고 있다.
  • use 함수는 closable 다원에 대한 확장 함수며, 람다를 인자로 받는다. use는 람다를 호출한 다음에 자원을 닫아준다.

고차함수 안에서 흐름 제어

람다 안의 return문: 람다를 둘러싼 함수로부터 반환

  • 넌로컬 return: 자신을 둘러싸고 있는 블록보다 더 바깥에 있는 다른 블록을 반환하게 만드는 return문
  • return이 바깥쪽 함수를 반환시킬 수 있을 때는 람다를 인자로 받는 함수가 인라인 함수인 경우뿐이다.
  • forEach는 인라인 함수이므로 람다 본문과 함께 인라이닝된다.
  • 인라이닝되지 않은 함수에 전달되는 람다안에서는 return을 사용할 수 없다.
fun test() {
	listOf(1, 2, 3).forEach {  
	   if (it == 2) {  
	      return  
	   }  
	}
}

람다로부터 반환: 레이블을 사용한 return

  • 람다 식에서도 로컬 return을 사용할 수 있다.
  • 로컬 return은 for루프의 break와 비슷한 역할을 한다.
  • 로컬 return은 레이블을 사용해야 한다.
listOf(1, 2, 3).forEach label@{   
if (it == 2) return@label  
}
  • 람다 식에 레이블을 붙이려면 레이블 일므 뒤에 @ 문자를 추가하는 것을 람다를 여는 { 앞에 넣으면 된다.

  • 람다를 인자로 받는 인라인 함수의 이름을 return 뒤에 레이블로 사용해도 된다.
    • 하지만 람다 식의 레이블을 명시하면 함수 이름을 레이블로 사용할 수 없다.

  • 넌로컬 반환문은 장황하고, 람다 안의 여러 위치에 return 식이 들어가야 하는 경우 사용하기 어렵다.

무명 함수: 기본적으로 로컬 return

  • 무명 함수도 일반 함수와 같은 반환 타입 지정 규칙을 따른다.
    • 함수 이름이나 파라미터 타입을 생략할 수 있다.
  • 무명 함수 안에서 레이블이 붙지 않은 return 식은 무명 함수 자체를 반환시킬 뿐 무명 함수를 둘러싼 다른 함수를 반환시키지 않는다.
    • 반대로 람다식은 fun 을 사용해 정의되지 않으므로 람다 본문의 return은 람다 바깥의 함수를 반환시킨다.
  • 무명 함수는 일반 함수와 비슷해 보이지만 실제로는 람다 식에 대한 문법적 편의일 뿐이다.