코틀린의 scope function

de_sj_awa·2023년 4월 15일
0
post-custom-banner

1. scope function

1) scope function이란 무엇인가?

  • scope function : 일시적인 영역을 형성하는 함수
fun printPerson(person: Person?) {
	if (person != null) {
    	println(person.name)
        println(person.age)
    }
}
fun printPerson(person: Person?) {
	person?.let {
    	println(it.name)
        println(it.age)
    }
}
  • Safe Call(?.)을 사용해 null이 아닐 때 let을 호출한다.
  • let : 확장함수. 람다를 받아 람다 결과를 반환한다.
public inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}
  • 람다를 사용해 일시적인 영역을 만들고 코드를 더 간결하게 만들거나, method chaining에 활용하는 함수를 scope function이라고 한다.

2) scope function의 분류

  • let : 람다의 결과를 반환한다. 람다 내에서 it을 사용한다.

  • run : 람다의 결과를 반환한다. 람다 내에서 this를 사용한다.

  • also : 객체 그 자체를 반환한다. 람다 내에서 it을 사용한다.

  • apply : 객체 그 자체를 반환한다. 람다 내에서 this를 사용한다.

  • with(파라미터, 람다) : 유일하게 확장함수가 아니다. this를 사용해 접근하고, this는 생략 가능하다.

  • this : 생략이 가능한 대신, 다른 이름을 붙일 수 없다. (확장함수를 파라미터로 받음)

  • it : 생략이 불가능한 대신, 다른 이름을 붙일 수 있다. (일반함수를 파라미터로 받음)

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

public inline fun <T, R> T.run(block: T.() -> R): R {
    return block()
}
  • let은 일반함수를 파라미터로 받고 run은 확장함수를 파라미터로 받는다.
val value1 = person.let {
	it.age
}

val value2 = person.run {
	this.age
}

val value3 = person.also {
	it.age
}

val value4 = person.apply {
	this.age
}

with(person) {
	println(name)
    println(this.age)
}

3) 언제 어떤 scope function을 사용해야 할까

1. let

  • 하나 이상의 함수를 call chain의 결과로 호출할 때
val strings = listOf("APPLE", "CAR")
strings.map { it.length }
	.filter {it > 3}
    .let(::println)
  • non-null 값에 대해서만 code block을 실행시킬 때
val length = str?.let {
	println(it.uppercase())
    it.length
}
  • 일회성으로 제한된 영역에 지역 변수를 만들 때
val numbers = listOf("one", "two", "three", "four")
val modifiedFirstItem = numbers.first()
	.let { firstItem -> 
    	if (firstItem.length >= 5) firstItem else "!$firstItem!"
    }.uppercase()
println(modifiedFirstItem)

2. run

  • 객체 초기화와 반환 값의 계산을 동시에 해야 할 때(ex) 객체를 만들어 DB에 바로 저장하고, 그 인스턴스를 활용할 때)
val person = Person("person", 100).run(personRepository::save)
val person = Person("person", 100).run {
	hobby = "독서"
    personRepository.save(this)
}

3. apply

  • 객체 설정을 할 때 객체를 수정하는 로직이 call chain 중간에 필요할 때
fun createPerson (
	name: String,
    age: Int,
    hobby: String,
): Person {
	return Person(
    	name = name,
        age = age
    ).apply {
    	this.hobby = hobby
    }
}

4. also

  • 객체를 수정하는 로직이 call chain 중간에 필요할 때
mutableListOf("one", "two", "three")
	.also { println("four 추가 이전 지금 값 : $it") }
    .add("four")

5. with

  • 특정 객체를 다른 객체로 변환해야 하는데, 모듈간의 의존성에 의해 정적 팩토리 혹은 toClass 함수를 만들기 어려울 때
return with(person) {
	PersonDto(
    	name = name,
        age = age,
    )
}
  • this를 생략할 수 있어 필드가 많아도 코드가 간결해진다.

4) scope function과 가독성

// 1번 코드
if (person! = null && person.isAdult) {
	view.showPerson(person)
} else {
	view.showError()
}

// 2번 코드
person?.taskIf { it.isAdult }
	?.let(view::showPerson)
    ?: view.showError()

2. 추가로 공부해야 할 내용

  • 제네릭
  • 리플렉션
  • 복잡한 함수형 프로그래밍
  • DSL
  • 동시성 프로그래밍(코루틴)

참고

profile
이것저것 관심많은 개발자.
post-custom-banner

0개의 댓글