with, run, let, apply, also
코틀린에는 자바에는 없는 범위 지정 함수라는 개념이 있다. 범위 지정 함수는 with, run, let, apply, also 함수로 구성되어 있다. 서로 유사한 점이 많아 익히기 쉬운 반면 어느 상황에서 어떤 함수를 쓰는 것이 적절한지 혼란스럽기도 하다. 범위 지정 함수가 무엇인지 간단히 말하면 특정 범위(scope) 내에서 특정 객체를 this 또는 it으로 사용할 수 있는 것이다. 다시 말하자면 중괄호로 표현되는 블록(block) 내에서 특정 객체를 this 또는 it으로 표현할 수 있고 이를 통해서 코드를 간결하게 할 수 있다.
범위 지정 함수를 이해하기 위해서는 먼저 수신 객체와 수신 객체 지정 람다라는 용어에 대한 이해가 필요하다.
수신 객체를 이야기하려면 빠질 수 없는 것이 확장 함수이다. 또한 리시버가 있는 함수 리터럴도 도움이 된다.
다음 확장 함수 예제를 살펴보자.
Car 클래스 내에 멤버 함수를 추가하지 않고 확장 함수를 사용해서 getInfo() 함수를 추가하였다. 확장 함수에서 수신 객체는 Car 자체다. 좀 더 디테일하게 구분하면 Car는 수신 객체이자 수신 객체 타입이다. (간단히 Receiver라고도 함) 그리고 Car는 블록 내에서는 this로 사용한다. 즉 this = Car = 수신 객체이다. (일반 함수에서 this를 사용하는 경우에는 해당 함수가 정의된 클래스를 의미한다. 보통 멤버 변수명과 지역 변수명이 동일할 경우 this.변수명으로 구분하여 접근한다.)
수신 객체 지정 람다는 람다에 확장 함수처럼 수신 객체를 사용한 것이다. 즉 다음과 같은 형태이다.
T.() -> R
확장 함수에서 수신 객체를 사용하여 블록 내에 객체를 전달했듯이 수신 객체 지정 람다 또한 수신 객체를 이용하여 객체를 전달한다. 따라서 수신 객체 지정 람다에서는 수신 객체를 this로 대신할 수 있다. 또한 this를 생략하고 해당 객체의 멤버에 바로 접근이 가능하다.
반면 일반 람다는 객체를 인자로 전달한다. 람다의 매개변수가 하나뿐이고 컴파일러가 타입을 추론할 수 있을 때 객체는 기본 매개변수인 it으로 받을 수 있다.
(T) -> R
정리하면 수신 객체 지정 람다에서 수신 객체는 this로 사용 가능하고 일반 람다에서는 it으로 사용 가능하다.
[참고] 제네릭 약어의 의미
E - Element
K - Key
N - Number
T - Type
V - Value
R - Return Type
여기까지의 개념을 숙지했다면 비로소 범위 지정 함수에 대해서 살펴볼 수 있는 준비가 된 것이다.
공식문서는 다섯 가지의 범위 함수를 다음과 같이 설명한다.
주석에 해당하는 설명을 코드로 작성하면 다음과 같이 된다.
다섯 가지의 범위 지정 함수를 자세히 보면 비슷한 부분들이 있는 것을 알 수 있다.
with : 일반 함수의 형태로 첫 번째 인자에 객체를 명시적으로 전달하고 두 번째 인자에 수신 객체 지정 람다를 전달한다. 리턴 값은 블록의 실행 결과이다.
run : 인자에 수신 객체 지정 람다를 전달한다. 리턴 값은 블록의 실행 결과이다.
let : 인자에 일반 람다를 전달한다. 리턴 값은 블록의 실행 결과이다.
apply : 인자에 수신 객체 지정 람다를 전달한다. 리턴 값은 수신 객체이다.
also : 인자에 일반 람다를 전달한다. 리턴 값은 수신 객체이다.
아래는 위 내용을 아주 잘 정리한 블로그에 있는 표이다. 이것 이상으로 깔끔하게 할 수 없을 것 같아서 조금 수정하여 첨부한다.
각 함수의 특성을 이해하면 필요할 때 적절하게 사용할 수 있을 것이다. 어떤 상황에서 어떤 함수를 꼭 써야 한다는 법은 없겠지만 공식 문서에서 추천하는 방법은 있으니 참고하면 좋을 것 같다.
Reference)
https://medium.com/@fatihcoskun/kotlin-scoping-functions-apply-vs-with-let-also-run-816e4efb75f5