Kotlin - Scope Function

Updated:

  • Scope Function

    • Kotlin 의 standard library 는 몇몇 유용한 함수를 제공한다. 그중 가장 많이 쓰이고 언급되는

      apply, with, let, also, run 에 대해 적어본다.

    • 처음 공부 하면서 5가지의 역할이 크게 차이가 없다고 이해를 하면서 코딩하다보니 어려움이 많았지만 익숙해 지면 아주 유용하다고 생각한다.

    • 아래 차트를 보면 이해하는데 훨씬 편하다고 생각된다. (이미지 출처)

      scope function

      결과와 람다 표현식 차트 (이미지 출처)

      scope function2


  • let

    함수를 호출하는 객체를 이어지는 블록의 인자로 넘기고, 결과값을 반환.

    • 표준 함수 정의 - T, R은 형식 매개변수로 어떤 자료형이라도 사용가능하다는 표시를 일반화한 문자

      public inline fun <T, R> T.let(block: (T) -> R): R { ... return block(this) }
      
    • 예시

      fun main(){
          val score:Int? = 32
          
          fun checkScore(){
              if(score != null)
                  println("Score : $score")
          }
          
          fun checkScoreUsingLet(){
              score?.let{println("Score : $it")}
              val str = score.let { it.toString() }
              println(str)
          }
          
          checkScore() // Score : 32
          checkScoreUsingLet() // Score : 32
      }
          
      //null 가능성 있는 객체에서 활용
      obj?.let { // obj가 null이 아닐 경우 작업 수행 (Safe calls + let 사용)
          Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show()
      }
      
    • 체이닝(메서드 혹은 함수를 연속적으로 호출하는 기법) 에서도 사용 가능하다. 가독성을 고려 할 때 너무 많은 let 사용은 권장되지 않음.

      var a = 1
      var b = 2
          
      a = a.let { it + 2 }.let {
          val i = it + b
          i  // 마지막 식 반환
      }
      println(a) //5
      

  • also

    함수를 호출하는 객체 T를 이어지는 block에 전달하고 객체 T 자체를 반환.

    let 과 역할이 거의 동일해 보이지만 also는 결과와 상관없이 객체를 반환한다.

    • 표준 함수 정의

      public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
      
    • 예시

      fun main() {
          data class Person(var name: String, var skills: String)
          
          var person = Person("Kildong", "Kotlin")
          
          val a = person.let {
              it.skills = "Android"
              "success"
          }
          println(person) // -> Person(name=Kildong, skills=Android)
          println("a: $a") // -> a: success
          
          val b = person.also {
              it.skills = "Java"
              "success"
          }
          println(person) // -> Person(name=Kildong, skills=Java)
          println(b) // -> Person(name=Kildong, skills=Java)
      }
      

  • apply

    also와 마찬가지로 호출하는 객체를 전달하고 반환한다.

    특정 객체를 생성하면서 함께 호출해야하는 초기화 코드가 있을 경우 사용 가능, 람다식이 확장함수로 처리가 가능하다.

    • 표준 함수 정의

      public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
      
    • 예시

      fun main() {
          data class Person(var name: String, var skills: String)
          
          var person = Person("Kildong", "Kotlin")
          
          person.apply { this.skills = "Swift" }
          println(person) // -> Person(name=Kildong, skills=Swift)
          
          val returnObj = person.apply {
              name = "Sean"
              skills = "Java"
          }
          println(person) // -> Person(name=Sean, skills=Java)
          println(returnObj) // -> Person(name=Sean, skills=Java)
      }
      

  • run

    인자가 없는 익명함수처럼 단독 사용하거나, 확장 함수 형태로 호출하는 형태 2가지로 사용가능하다.

    독립적으로 사용시 block에 처리할 내용을 넣어주며 마지막 식이 반환됨

    • 표준 함수 정의

      public inline fun <R> run(block: () -> R): R  = return block()
      public inline fun <T, R> T.run(block: T.() -> R): R = return block()
      
    • 예시

      fun main(){
          //apply vs run
          data class Person(var name: String, var skills: String)
          
          var person = Person("Kildong", "Kotlin")
          
          val returnObj = person.apply {
              name = "Sean"
              skills = "Java"
              "success" // -> 사용되지 않음
          }
          println(person) // Person(name=Sean, skills=Java)
          println(returnObj) // Person(name=Sean, skills=Java)
          
          val returnObj2 = person.run {
              name = "Dooly"
              skills = "C#"
              "success"
          }
          println(person) // Person(name=Dooly, skills=C#)
          println(returnObj2) // success
      }
          
          
      //safe call 사용 가능
      obj?.run{
        ...
      }
      

  • with

    run 과 기능이 거의 동일하지만 receiver로 전달할 객체를 처리한다. 확장 함수 형태가 아니고 단독으로 사용되고 세이프콜은 지원하지 않기에 let과 함께 사용한다. 혹은 null 인 경우를 처리하려면 run을 확장 함수 형태로 사용한다.

    • 표준함수

      public inline fun <T, R> with(receiver: T, block: T.() -> R): R  = receiver.block()
      
    • 예시

      fun main{
          data class User(val name: String, var skills: String, var email: String? = null)
          
          val user = User("kildong", "default")
          
          val result = with(user) {
              skills = "Kotlin"
              email = "kildong@exmaple.com"
          }
          
          println(user) //User(name=kildong, skills=Kotlin, email=kildong@exmaple.com)
          println(result) //kotlin.Unit
      }
          
          
      //null 경우 처리 -> let과 함께 사용하거나 run사용
      supportActionBar?.let {
          with(it) {
              setDisplayHomeAsUpEnabled(true)
              setHomeAsUpIndicator(R.drawable.ic_clear_white)    
          }
      }
          
      supportActionBar?.run {
          setDisplayHomeAsUpEnabled(true)
          setHomeAsUpIndicator(R.drawable.ic_clear_white)
      }