[kotlin] 수신 객체 지정 람다의 철학은?

왕왕조현·2026년 6월 7일

kotlin

목록 보기
6/8
post-thumbnail

안녕하세요!

코드를 뜯고 싶고 맛보고 즐기는 개발자가 되고자 하는 개발자 꿈나무 김조현입니다.

이번 글에서는 수신 객체 지정 람다의 철학에 대해 알아보고자 합니다.

왜 이런 고민을 하게 되었는가?

withContext를 뜯어보는 중에 문뜩 일반 람다가 아닌 CoroutineScope.() → T를 사용한 것을 확인했다.

일반 람다를 넣어도 될텐데, 굳이 수신 객체 지정 람다를 파라미터로 받는 이유가 있을까?

예) ColumnScope.() → Unit, CoroutineScope.() → T 등

일반 람다와 수신 객체 지정 람다의 차이는?

일반 람다를 사용했을 경우 객체를 참조하기 위해 it을 사용해야 한다. 이는 객체의 프로퍼티를 사용하기 위해 계속 it을 참조해서 사용해야한다는 불편함을 겪어야 한다.

Column { scope -> // 매번 scope나 it을 받아야 함
    Text("안녕하세요")
    
    // Column 안에서만 쓸 수 있는 align이나 weight를 쓸 때마다
    // scope를 억지로 끌고 와야 함!
    Button(modifier = Modifier.then(scope.align(Alignment.CenterHorizontally))) {
        Text("버튼")
    }
}

반면 수신 객체 지정 람다를 사용했을 때 람다의 내부에서 this 키워드를 사용할 수 있다. 확장 함수를 사용했을 때는 객체 스스로를 대입하기 때문에 객체를 this로 사용할 수 있다. 그러면 this를 생략할 수 있어 코드가 더 간결해지는 이점을 얻을 수 있다.

Column { // 이 중괄호 안의 this는 이미 ColumnScope!
    Text("안녕하세요")
    
    // this가 생략되어 있으므로, 내 방 안(ColumnScope)에 있는 
    // align 기능을 아무런 접두사 없이 아주 자연스럽게 가져다 씀!
    Button(modifier = Modifier.align(Alignment.CenterHorizontally)) {
        Text("버튼")
    }
}

즉, 수신 객체 지정 람다를 사용했을 때 코드가 더 간결해진다는 이점이 있고, 이는 확장 함수와 관련이 있다.

또한 일반 람다를 사용했을 때는 함수에 인자를 넣기 위해 중괄호 안에 계속해서 it을 참조해줘야 한다는 불편함이 있다. 반면 수신 객체는 스스로 객체를 넣어주기 때문에 this를 사용하여 개발자가 일일이 지정해주지 않아도 된다는 편리함이 있다.

그러면 함수 작성할 때는 왜? 수신 객체 지정 람다를 사용하지 않아??

이런 생각이 든 이유는 위에서 말했듯이 코드가 간결해진다는 이점이 있는데, 왜 코틀린 표준 라이브러리의 모든 함수에서 왜 이걸 안쓸까? 라는 의문이 들었다.

수신 객체 지정 람다를 사용하면 코드를 간결하게 만들어주는 것은 사실이다. 하지만 무분별하게 사용할 경우 this 참조가 충돌하는 스코프 오염이 발생하고, 코드의 출처가 불분명해진다는 단점이 존재한다.

스코프 오염이란?
내가 현재 있는 블록 안에 접근할 수 있는 변수나 함수의 이름이 너무 많고 중복되어서, 컴퓨터나 개발자가 이름이 누구의 것인지 헷갈리게 되는 현상을 말한다.

class ShoppingViewModel {
		val title = "장바구니 화면"
		
		fun updateProduct(product: Product) {
				product.apply {
						println(title)
				}
		}
}

class Product {
		val title = "멋진 운동화"
}

이 코드를 봤을 때 개발자는(나 포함) viewModel 안의 title이 들어갈지, Product 객체의 title이 들어갈지 헷갈릴 수 있다.

ShoppingViewModel 안에 title을 지정해놧으니 updateProduct를 실행했을 때 viewModel에 있는 title이 들어간다고 예상할 수 있다. 하지만 Product 객체를 넣었을 경우 viewModel 안에 초기화한 title이 아닌 객체의 title이 들어가게 된다.

스코프 오염을 막기 위해서는 다음과 같은 방법들이 있다.

  1. 일반 람다를 사용하여 스코프를 분리하는 방법이 있다. 예를 들면 let, also를 사용하는 것이다.

  2. 안쪽의 객체를 사용할 경우 this를 명시해주는 방법이 있다.

  3. DSL 환경에서는 @DslMarker을 사용하면 된다.

    @DslMarker 는 뭘까?
    이 어노테이션을 붙이면 내부 스코프에서 외부 스코프의 함수를 몰래 호출하지 못하도록 컴파일 단계에서 차단할 수 있다. 예를 들면 Jetpack Compose나 복잡한 UI 트리를 만들 때처럼 불가피하게 스코프가 중첩될 때 사용하여 스코프가 오염되는 것을 막을 수 있다.

profile
천천히, 꾸준히, 한 걸음씩

0개의 댓글