본문 바로가기
프로그래밍 놀이터/Kotlin

[도서 정리] Android Development with Kotlin - Functions as First-Class Citizens

by 돼지왕왕돼지 2018. 12. 14.

Android Development with Kotlin - Functions as First-Class Citizens




이 정리글은 Kotlin in Action 책을 보고 실무에 Kotlin 을 사용하던 사람이 몰랐던 내용이나 remind 하고 싶은 내용을 위주로 정리한 글입니다.

제대로 내용을 파악하시려면 책을 구매해서 읽어보세욤~



-

아래와 같은 function 정의를 anonymous function 이라 부른다.

val a: (Int) -> Int = fun(i: Int) = i * 2



-

lambda 에서의 return 은 lambda 를 return 하는 것이 아닌 정의하며 호출하는 함수의 return 을 이야기한다.

만약 lambda 를 return 하고 싶다면 아래와 같이 하면 된다.

var aLambda: (Int) -> Int = exit@{ i:Int ->
    return@exit i*2
}

위의 식은 아래와 동일하다.


람다의 last statement 가 return value 가 된다.

var aLambda = { i:Int -> i*2 }



-

Kotlin 의 lambda 는 function body 에서 바깥쪽 local variable 값을 바꿀 수 있다.

이런 특성을 가진 lambda 를 closure 라고 한다.

var i = 1 
val a: () -> Int = { ++i } 
println (i)     // Prints: 1 
println (a())   // Prints: 2 
println (i)     // Prints: 2 
println (a())   // Prints: 3 
println (i)     // Prints: 3



-

Kotlin 1.1 부터 Type alias 를 사용 가능하다.

data class User(val name:String, val surname:String)
typealias Users = List<User>

type alias 는 top level 에 정의되어야 한다. 그리고 type alias 에도 visibility modifier 가 적용된다.



-

type alias 는 주로 generic type 을 사용해서 길게 정의되는 type 을 간단히 표시하기 위해 사용된다.

typealias Dictionary<V> = Map<String, V>
typealias Array2D<T> = Array<Array<T>>

typealias Action<T> = (T) -> Unit
typealias CustomHandler = (Int, String, Any) -> Unit



-

Kotlin 1.1 부터는 lambda parameter 에도 destructuring declaration 을 사용할 수 있다.

val showUser: (User) -> Unit = { (name, surname, phone) ->
    println(“$name $surname have phone number: $phone”)
}


이 때 사용하고 싶지 않은 녀석들은 underscore 처리할 수 있고,

앞선 순서대로 사용한다면 사용하지 않는 뒤쪽 녀석들은 생략 할 수 있다.



-

inline function 의 경우 recursive call 이나 자신보다 visibility restriction 이 있는 함수 call 은 불가능하다.

( 사용할 수 없는 코드가 inject 될 수 있기 때문 )


코틀린에서 suppress warning 을 통해 visibility restriction 이 있는 경우에도 사용은 할 수 있지만, bad practice 로 알려져 있다.


또한 inline function 으로 전달된 function type 은 inline 이 아닌 function 에 전달될 수 없다.



-

inline 이 아닌 function call 의 경우 실제 compile 이 되면 익명 클래스로 컴파일되기 때문에 non-local return 이 되어버린다. 만약 forEach function 이 inline 이 아니라면 아래 코드는 compile error 가 발생한다.

fun maxBounded(list: List<Int>, upperBound: Int, lowerBound: Int): Int {
    var currentMax = lowerBound 
    forEach(list) { i ->
        when { 
            i > upperBound -> return upperBound // !!! compile error if forEach is not inline !!!
            i > currentMax -> currentMax = i 
        } 
    } 
    return currentMax 
}



-

inline function 에서 function type 을 바로 사용하는 것이 아니라, 다른 context 에서 실행시키고 싶을 때가 있다.

그러나 기본 inline function 의 type parameter 는 그렇게 사용하는 것이 허용되지 않는다. non-local return 을 허용하게 되기 때문이다.

compiler 에게 non-local return 이 allow 되지 않는다는 것을 알리기 위해서는 crossinline 을 annotate 해주어야 한다.

fun boo(f: () -> Unit){
    ...
}

inline fun foo(crossinline f: () -> Unit){
    boo{ println(“A”); f() }
}

fun main(args: Array<String>){
    foo { println(“B”) }
}

한 마디로 정리하면 crossinline 으로 정의된 function type 은 lambda expression context 에서 사용되거나, local function 에서 사용될 수 있도록 허용한다.



-

Inline properties


Kotlin 1.1 부터 backing field 가 없는 property 들에 대해 사용할 수 있다.

var viewIsVisible: Boolean // getter, setter 대신 여기에 inline 을 주어도 된다.
inline get() = findViewById(R.id.view).visibility == View.VISIBLE 
inline set(value) { 
  findViewById(R.id.view).visibility = if (value) View.VISIBLE  else View.GONE
}



-

function references


top-level 에 정의된 lambda 는 function reference 로 전달할 수 있다.

list.filter(::isOdd)



-

function reference 는 reflection 의 한 예이다.

그래서 다음과 같이 annotation 이나 parameter 들 정보도 가져올 수 있다.

val annotations = ::isOdd.annotations
val parameters = ::isOdd.parameters



-

function reference 는 reflection 이지만서도 그대로 function type 으로 사용될 수도 있다.

val predicate: (Int) -> Boolean = ::isOdd



-

일반 function 도 function reference 로 받을 수 있다.

이 때 syntax 는 Type::functionName

val nonEmpty = listOf("A", "", "B", "") .filter(String::isNotEmpty)

class User { fun wantToEat(food: Food): Boolean { // ... } } val func: (User, Food) -> Boolean = User::wantToEat

object MathHelpers { 
    fun isEven(i: Int) = i % 2 == 0 
} 

class Math { 
    companion object { 
        fun isOdd(i: Int) = i % 2 == 1 
    } 
} 

// Usage 
val evenPredicate: (Int)->Boolean = MathHelpers::isEven 
val oddPredicate: (Int)->Boolean = Math.Companion::isOdd



-

Kotlin 1.1 부터는 bound reference 가 가능하다.

bound reference 는 specific object 에 bound 된 function reference 이다.

getUsers().smartSubscribe ( 
    onStart = view::showProgress, 
    onNext = this::onUsersLoaded, 
    onError = view::displayError, 
    onFinish = view::hideProgress 
)



-

DTO 는 data transfer object 의 약자로 process 간 data 를 전달하기 위한 object 이다.

Kotlin 에서는 constructor 들도 function reference 로 사용할 수 있기 때문에 DTO 에 사용하기 좋다.

val mapper: (UserDto) -> User = ::User



댓글0