computer_study

[Kotlin] 05. 고급 함수와 함수형 프로그래밍 활용하기 본문

스터디/코틀린 완벽 가이드

[Kotlin] 05. 고급 함수와 함수형 프로그래밍 활용하기

knowable 2022. 5. 26. 00:57

함수타입

// 함수가 인자를 받지 않는 경우에도 함수 타입의 파라미터 목록에 빈 괄호를 사용해야 한다.
fun measureTime(action: () -> Unit): Long{
    ...
}
//**********
val succes: (Int) -> Int = {n -> n+1} // ok , 타입을 명시해서 컴파일러가 추론 없이 알 수 있도록 할 수 있다.
val error: Int -> Int = {n -> n+1} // error

 

호출 가능 참조

존재하는 함수 정의를 함수 타입의 식으로 사용할 수 있는 단순한 방법.

함수 이름 앞에 ::를 붙여서 호출 가능 참조를 할 수 있다.

fun evalAtZero(f: (Int) -> Int) = f(0)

fun inc(n: Int) = n+1
fun dec(n: Int) = n-1

fun main(){
    fun dec{n: Int} = n-1
    println(evalAtZero(::inc)) // 1
    println(evalAtZero(::dec)) // -1
}

// 생성자에도 가능하다
class Person(val firstName: String, val familyName: String)

fun main(){
    val createPerson= ::Person
    createPerson("John", "Doe")
}
// 클래스 인스턴스의 문맥 안에서 멤버 함수를 호출할 때에는 바인딩된 호출 가능 참조 사용
class Person(val firstName: String, val familyName: String){
    fun hasNameOf(name: String) = name.equals(firstName, ignoreCase = true)
}
fun main(){
    val isJohn = Person("John", "Doe")::hasNameOf
    println(isJohn("JOHN")) // true
    println(isJohn("Jake")) // false

}

오버로딩된 함수를 구분하기 위해선 타입을 지정해주어야 한다.

 

확장함수

  • 어떤 클래스의 멤버인 것처럼 호출할 수 있는 함수 (실제 멤버는 아니다)
  • 일반 함수를 마치 클래스 멤버인 것처럼 쓸 수 있게 해준다.
  • null이 될 수 있는 타입에 대해서도 확장을 정의할 수 있다.
  • 클래스 안에서 확장 함수나 프로퍼티를 선언하여 수신 객체가 두 개 있는 것 보단, 최상위 확장을 우선적으로 사용하는 것이 간단하고 읽기 좋은 코드를 만든다.
// util.kt
package util

fun String.truncate(maxLength: Int): String{
    return if (length <= maxLength) this else substring(0, maxLength)
}

// main.kt
package main

import util.truncate

fun main(){
    println("Hello".truncate(3))
}

 

확장 프로퍼티

일반 프로퍼티와 마찬가지로 확장 프로퍼티에 접근할 수 있도록 한다.

val IntRange.leftHalf: IntRange
    get()=start..(start + endInclusive)/2
    
fun main(){
    println((1..3).leftHalf) // 1..2
    println((3..6).leftHalf) // 3..4
}

 

동반 확장

동반 객체의 성질(클래스에 내포된 객체 중에서 바깥 클래스의 이름을 톨해 객체 멤버에 접근할 수 있다)이 확장의 경우에도 성립한다.

// 확장 클래스
fun IntRange.Companion.singletonRange(n: Int) = n..n

fun main(){
    println(IntRange.singletonRange(5)) // 5..5
    println(IntRange.Companion.singletonRange(3)) // 3..3  이렇게도 가능
}
// 확장 프로퍼티
val String.Companion.Hello
    get() = "Hello"
    
fun main(){
    println(String.HELLO)
    println(String.Companion.HELLO)
}
// 람다에도 확장 수신 객체 활용 가능
fun aggregate(number: IntArray, op: Int.(Int) -> Int): Int{ // 수신 객체 타입 정의
    var result = numbers.firstOrNull()
        ?: throw IllegalArgumentException("Empty array")
    for(i in 1..numbers.lastIndex) result = result.op(numbers[i])
    
    return result
}

fun sum(numbers: IntArray) = aggregate(numbers) {op -> this + op} // this를 사용해 객체 접근
//익명 함수에도 확장 수신 객체 활용 가능
fun sum(numbers: IntArray) = aggregate(numbers, fun Int.(op: Int)=this + op)

 

수신 객체가 있는 호출 가능 참조

수신 객체가 있는 함수값을 정의하는 호출 가능 참조를 만들 수 있다.

fun aggregate(number: IntArray, op: Int.(Int) -> Int): Int{
    var result = numbers.firstOrNull()
        ?: throw IllegalArgumentException("Empty array")
    for(i in 1..numbers.lastIndex) result = result.op(numbers[i])
    
    return result
}

fun Int.max(other: Int) = if (this > other) this else other

fun main(){
    val numbers = intArrayOf(1,2,3,4)
    println(aggregate(numbers, Int::plus)) // 10, Int내장 클래스의 멤버함수 사용
    println(aggregate(numbers, Int::max)) // 4, 같은 구문을 사용해서 파일에 정의된 확장함수 사용
}

 

영역함수

  • 식을 계산한 값을 임시로 사용할 수 있게 해주거나, 지역변수를 선언하지 않아도 식의 값이 들어있는 암시적인 영역을 정의해서 코드를 단순화 시켜주는 함수
  • 인라인 함수이기에 런타임 부가 비용이 없다
  • 남용한다면 코드 가독성은 나빠질 수 있다.
  • 종류
    • run
      • 확장 람다를 받는 확장 함수. 람다의 결과를 돌려준다
      • 확장 함수로 문맥이 없는 run도 있다
    • let
      • run과 비슷
      • 확장 함수 타입의 람다를 받지 않고 인자가 하나뿐인 함수 타입의 람다를 받는다.
    • with
      • run과 비슷하며, with는 확장 함수 타입이 아니라 문맥 식을 with의 첫 번째 인자로 전달해야 한다
    • apply
      • 확장 람다를 받는 확장 함수
      • run과 달리 반환값을 만들지 않는다.
    • also
      • apply와 비슷
      • 인자가 하나 이쓴 람다를 파라미터로 받는다는 차이가 있다.

 

Comments