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
댓글