Scope Funtions

홍승범·2023년 1월 25일
0

Kotlin

목록 보기
3/8

Overview

코틀린 표준 라이브러리에 포함된 객체 컨텍스트내에서 실행가능한 함수들로서 람다 표현식으로 구성되며, 해당 람다식은 임시적인 스코프를 구성한다.

기본적으로는 블록내의 코드를 실행한다는 동일한 동작을 하지만, 블록내에서 객체가 어떤 방식으로 진입되는지, 그리고 블록의 결과가 어떤 객체를 리턴하는지 차이점이 있다.

아래표는 각 함수의 개략적인 정보이다.

FunctionObject referenceReturn valueIs extension function
letitLambda resultYes
runthisLambda resultYes
run-Lambda resultNo: called without the context object
withthisLambda resultNo: takes the context object as an argument
applythisContext objectYes
alsoitContext objectYes

with

정의

inline fun <T, R> with(receiver: T, block: T.() -> R): R

with의 아규먼트로 객체가 들어오는 일반함수로 정의되어있다. block을 객체에 대한 람다로 정의한다. 리턴값은 람다의 결과값이다.

한 오브젝트에 대해 여러개의 다른 메소드들을 호출해야 할 필요가 있을 때 사용한다. 주어진 인스턴스의 프로퍼티들을 . 연산자 없이 여러번 변경해야할때 사용한다.

webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.userAgentString = “mobile_app_webview”
// 위 코드를 아래와같이 변경할 수 있다.
with(webView.settings){
  javaScriptEnabled = true
  domStorageEnabled = true
  userAgentString = “mobile_app_webview”
  webview // this is last statement, so it will be return type of with
}

let

정의

inline fun <T, R> T.let(block: (T) -> R): R

객체 T에 대한 확장함수로 정의되어있으며, 람다로 객체가 들어오고, 결과값을 리턴하는 함수이다.

객체에 대한 특정된 스코프를 표현하고 싶을때 사용한다.

webView.settings.let { setting ->
  setting.javaScriptEnabled = true
  setting.domStorageEnabled = true
  setting.userAgentString = “mobile_app_webview”
}
// webview의  settings 객체를 범위를 지정해 it이라는 기본 레퍼런스로 사용할 수있다. 
// 위 코드는 명시적으로 it이 setting이라는것을 나타내기 위해 이름을 변경했다.

또한 context object를 다른값으로 변환하는 경우 이를 local scope로 묶어 코드를 깔끔하게 정리할 수 있다. 변수는 let 블록 안에서만 유효하기 때문에 변수 오용등에 대한 고민 없이 편리하게 다른 값으로 변경할 수 있다.

let은 null 체크의 대안으로도 사용할 수 있다.

val len = text?.let {
    println("get length of $it")
    it.length
} ?: 0

text변수가 null이면 let 블럭은 실행되지 않는다. 뒤에 엘비스 연산자로 기본값을 지정할 수 있다.

apply

정의

inline fun <T> T.apply(block: T.() -> Unit): T

Object reference(receiver) 상에서 동작하므로 this가 argument가 되고, 리턴값은 리시버 자신이다. argument가 this이므로 명시적으로 표시할 필요가 없다.

val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)

확장함수의 형태이므로, 객체를 생성해서 할당하기 이전에 사용할 수 있다. 즉 Person을 할당하기전에 생성해서, apply 블록을 실행 후 adam에 할당한다. 이러한 특징으로인해 객체 생성시점에 객체 초기화에 많이 사용된다.

also

정의

inline fun <T> T.also(block: (T) -> Unit): T

확장함수로 정의되는 함수이며, apply와는 다르게(let과 동일하게) argument로 receiver를 전달한다. 즉 람다 내부에서 it등으로 명시적으로 객체를 사용해야 한다.

리턴값은 apply와 동일하게 객체 자신을 리턴한다.

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()

run

특이하게 두가지로 정의되어있다.

inline fun <R> run(block: () -> R): R
inline fun <T, R> T.run(block: T.() -> R): R

첫번째는 receiver 없이 단일 람다함수로 구성되어있으며, 블럭의 결과를 리턴한다.

val hexNumberRegex = run {
    val digits = "0-9"
    val hexDigits = "A-Fa-f"
    val sign = "+-"

    Regex("[$sign]?[$digits$hexDigits]+")
}

for (match in hexNumberRegex.findAll("+123 -FFFF !%*& 88 XYZ")) {
    println(match.value)
}

hexNumberRegex는 run 블록의 결과를 가지게 되는 변수가 된다.

두번째는 확장함수형태이며, apply와 동일하게 receiver상에서 동작하므로 this로 객체를 나타낸다. 또한 람다의 마지막 표현이 결과값이 된다. 이점은 apply와 다르고 with와 같다. 또한 SafeCall(?.)을 통해 null check도 가능하다.

val password: Password = PasswordGenerator().run {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000

       generate()
   }

정리

각 함수의 리턴값에 따라 사용할 수 있을 것이다.
1. 객체의 속성을 변경하거나 작업을 처리하고 해당 객체가 그대로 리턴되어야 하는 경우 : also, apply
2. 작업을 처리하고 그 결과가 필요한 경우 : let, run, with

이외에도 여러 상황에 대해 적합한 함수가 있을 것이다.

  • non-null 객체에 대해 람다를 실행해야 할 때 : let, run
  • local scope에서 람다식을 처리하고 변수로 변경해야 할때 : let
  • 객체의 생성 및 구성 : apply
  • 객체의 구성과 연산 결과의 반환 : run
  • 특정 구문을 구획화하여 실행해야 할 때 : 확장함수가 아닌 run
  • 동일한 객체에 대한 추가 작업을 하고 객체를 받아야 할때 : also
  • 특정 객체의 함수 호출을 그룹화해야 할 때 : with

이 외에도 여러 상황이있고, call chain을 구성하는 등의 방법이 있을 수 있으니 열심히 사용해봐야 한다


http://batmask.net/index.php/2021/12/10/286/
https://sup2is.github.io/2022/06/17/kotlin-scope-function.html
https://kotlinlang.org/docs/scope-functions.html
https://proandroiddev.com/kotlin-standard-functions-or-scoping-functions-let-apply-run-also-with-af1d93a444f1

profile
그냥 사람

0개의 댓글