Chapter8. 고차 함수, 파라미터와 반환 값으로 람다 사용

김신영·2022년 10월 23일
0

kotlin-in-action

목록 보기
10/11
post-thumbnail

고차 함수 (High Order Function)

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

함수 타입

  • (Int, String) -> Unit
  • canReturnNull
    - (Int, String?) -> String?
  • funOrNull
    - ((Int, Int) -> Int)?
  • 함수 타입에서 파라미터 이름을 지정할 수도 있다.
fun performRequest(  
       url: String,  
       callback: (code: Int, content: String) -> Unit  
) {  
    /*...*/  
}  
  
fun main(args: Array<String>) {  
    val url = "http://kotl.in"  
    performRequest(url) { code, content -> /*...*/ }  
    performRequest(url) { code, page -> /*...*/ }  
}

Java에서 Kotlin 함수 타입 사용

  • 함수 타입의 변수는 Java에서는 FuntionN 인터페이스를 구현하는 객체로 저장된다.
interface Function0<R> {
	R invoke();
}

interface Function1<P1, R> {
	R invoke(P1 p1);
}

interface Function2<P1, P2, R> {
	R invoke(P1 p1, P2 p2);
}

// ...
public class UsingForEach {  
    public static void main(String[] args) {  
        List<String> strings = new ArrayList();  
        strings.add("42");  
        CollectionsKt.forEach(strings, s -> {  
           System.out.println(s);  
           return Unit.INSTANCE;  
        });  
    }  
}

함수 타입 파라미터 디폴트 값 설정

  • funOrNull 타입으로 파라미터를 선언
  • 함수 타입 파라미터를 호출하기 위해서는, funParam?.invoke(...)
  • 함수 타입이 invoke메서드를 구현하는 인터페이스 (FunctionN)
fun foo(callback: ((Int) -> Int)?) {
	// ...
	val response = callback?.invoke(1) ?: 0

	// ...
}

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

  • 어떤 함수를 inline 으로 선언하면, 그 함수의 본문이 inline 된다.

  • Sequence.map 함수의 경우, inline 으로 선언되어 있지 않다.

  • 왜냐하면 람다 함수 파라미터를 다른 클래스 생성자의 파라미터로 넘겨주기 때문이다.

  • 따라서 해당 함수 타입의 파라미터를 무명 클래스 인스턴스로 만들어야 한다.

  • 이런 경우 inline 으로 선언하면 안된다.

public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {  
    return TransformingSequence(this, transform)  
}

noinline

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
	// ...
}

Collection 연산 inlining

  • Kotlin에서 제공하는 Collection의 확장함수들은 거의 inline 선언되어 있다.
  • 반면 Sequence의 경우 inline 선언이 되어 있지 않다.
  • 컬렉션의 크기가 큰 경우, Sequence 가 성능에 유리
  • 컬렉션의 크기가 작은 경우, 그냥 inline 활용하는 게 더 유리
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {  
    return filterTo(ArrayList<T>(), predicate)  
}
	
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")  
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {  
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)  
}

자원 관리를 위한 인라인 사용

  • Java에서는 try-wtih-resource
static String readFirstLineFromFile(String path) throws IOException {
	try (BufferedReader br = new BufferedReader(new FileReader(path))) {
		return br.readLine();
	}
}
  • Koltin에서는 Closable 의 확장 함수 use
fun readFirstLineFromFile(path: String): String {  
    BufferedReader(FileReader(path)).use { br ->  
        return br.readLine()  
    }  
}
@InlineOnly  
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {  
    var closed = false  
    try {  
        return block(this)  
    } catch (e: Exception) {  
        closed = true  
        try {  
            this?.close()  
        } catch (closeException: Exception) {  
        }  
        throw e  
    } finally {  
        if (!closed) {  
            this?.close()  
        }  
    }  
}

고차 함수 안에서 흐름 제어

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

non-local return

람다 안에서 return을 사용하면 람다로부터만 반환되는게 아니라,
그 람다를 호출하는 함수가 실행을 끝내고 반환한다.

자신을 둘러싸고 있는 블록보다 더 바깥에 있는 다른 블록을 반환하게 만드는 return문을 non-local return 이라 부른다.

전제 조건

data class Person(val name: String, val age: Int)  
  
val people = listOf(Person("Alice", 29), Person("Bob", 31))  

1. Without Lambda

fun lookForAlice(people: List<Person>) {  
    for (person in people) {  
        if (person.name == "Alice") {  
            println("Found!")  
            return  
        }  
    }  
    println("Alice is not found")  
}  
  
fun main(args: Array<String>) {  
    lookForAlice(people)  // Found!
}

2. non-local return

fun lookForAlice(people: List<Person>) {  
    people.forEach {  
        if (it.name == "Alice") {  
            println("Found!")  
            return  
        }  
    }  
    println("Alice is not found")  
}  
  
fun main(args: Array<String>) {  
    lookForAlice(people)  // Found!
}

3. label을 통한 return

fun lookForAlice(people: List<Person>) {  
    people.forEach {  
        if (it.name == "Alice") return@forEach  
    }  
    println("Alice might be somewhere")  
}  
  
fun main(args: Array<String>) {  
    lookForAlice(people)  // Alice might be somewhere
}

4. 무명함수

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

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

fun main(args: Array<String>) {  
    println(StringBuilder().apply sb@{  
       listOf(1, 2, 3).apply {  
           this@sb.append(this.toString())  
       }  
    })  
}

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

무명 함수 (Anonymous Function)

  • 블록이 본문인 무명 함수는 반환 타입을 명시해야 한다.
  • 식을 본문으로 하는 무명 함수의 반환 타입은 생략할 수 있다.
  • 무명 함수도 일반 함수와 같은 반환 타입 지정 규칙을 따른다.
  • 무명 함수는 일반 함수와 비슷해 보이지만, 실제로는 람다 식에 대한 문법적 편의일 뿐이다.
  • 인라이닝 되는 규칙은 람다와 동일하다.

블록인 본문인 무명 함수 예제

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

식이 본문이 무명 함수 예제

people.filter(fun (person) = )

무명 함수와 람다의 제어 흐름 비교

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

profile
Hello velog!

0개의 댓글