๋ฒ์ญ-Kotlin pearls 1) Scope Functions
๋ค์ด์ด๊ทธ๋จ์ผ๋ก ์ค์ฝํ ํจ์๋ค์ ๋ํ ์ดํด๋๋ฅผ ๋์ด์

์๋ฌธ : [Kotlin pearls 1]Scope Function]
์ฐ๋ฆฌํํ Kotlin scope ํจ์ ๊ด๋ จ ๊ธ๋ค์ด ํ์ํ ๊น์?
์ฌ์ค ์ ๊ทธ๋ ๋ค๊ณ ์๊ฐํฉ๋๋ค. scope ํจ์์ ๋ํ ๊ธ๋ค์ด ์ ๋ง ๋ง์ง๋ง, ์ด ํจ์๋ค์ ํ์ฉํด์ ๋ฆฌํฉํ ๋ง์ ํ๋ ค๊ณ ํ ๋, ์ด๋ค scope ํจ์๊ฐ ์ ํฉํ์ง ๊ณ ๋ฅด๋ ๋ฒ์ ๋ํ ๋ด์ฉ์ ์ฐพ์ง ๋ชป ํ๊ฑฐ๋ ์.
์๋ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ด ๊น๋ํ๊ฒ ์ ๋ฆฌํด๋ดค์ต๋๋ค:

Use this๋ a single object์ method๋ฅผ (์ฃผ๋ก) ํธ์ถํ๋ค๋ ์๋ฏธ์
๋๋ค. ์ฆ, ์ด object์ ๋ํ ์ฐธ์กฐ๋ this ์ฃ .
Pass it์ object๋ฅผ ๋ค๋ฅธ methods/functions๋ก ๋๊ธด๋ค๋ ๊ฒ์ผ๋ก, ํด๋น object์ ๋ํ ์ฐธ์กฐ๋ผ it์ ์ฌ์ฉํฉ๋๋ค.
Result๋ block์์ ๋์จ ๊ฒฐ๊ณผ๊ฐ์ ๋ฐํํด์ค์ผ ํ๋ค๋ ๋ป์ ๋๋ค.
Side-effects๋ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ํ์ง ์๊ณ , Unit(์ฆ, void)๋ฅผ ๋ฐํ, ๊ทธ๋ฆฌ๊ณ ์ด๋ฅผ Fluent Interface style๋ก ๊ฒฐํฉํ ๊ฒ์ ๋๋ค.
2 Questions
๊ทธ๋์ object(๋์ object)๊ฐ ์ํ ์ฌ์ฉ๋ ๋, ์ฝ๋๋ฅผ ๋จ์ํ ์ํค๋ ๋ฐฉ๋ฒ์ด ๊ถ๊ธํ๋ค๋ฉด, ๋ค์๊ณผ ๊ฐ์ ์ง๋ฌธ์ ์ค์ค๋ก์๊ฒ ํด๋ณด๋ฉด ๋ฉ๋๋ค:
1. ํด๋น object์ method๋ฅผ ํธ์ถํด์ผ ํ๋๊ฐ? ํน์ ๋ค๋ฅธ method/function์ arguments๋ก ๋๊ฒจ์ผ ํ๋๊ฐ?
2. ๊ฒฐ๊ณผ๊ฐ์ด ํ์ํ๊ฐ? ํน์ ์ํ(state)๋ง ๋ณ๊ฒฝํด๋ ๋๋๊ฐ?
How to choose a correct Scope Function
apply
ํ object์ ์ฌ๋ฌ ๋ฒ method ํธ์ถํด์ผ ํ๊ณ , ๋ฐํ๊ฐ์ด ํ์์์ ๋ ์ ์ฉํฉ๋๋ค(setter ๊ฐ์ด).
์๋ ์ฝ๋์์ ?๋ฅผ ์ฌ์ฉํ๋๋ฐ, ๋๋จธ์ง ํ์ฅ ํจ์๋ค๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก, ๋์ object๊ฐ null์ด๋ฉด, block์ ํธ์ถ๋์ง ์์ต๋๋ค. block ๋ด๋ถ์ object๋ ํญ์ non-nullable type์
๋๋ค.
fun updateItem(itemId: String, newName: String, newPrice: Double) = itemsMap.get(itemId)?.apply {
enabled = true
desc = newName
price = newPrice
}
with
์๋ ์ฝ๋์ ๋ํด ์ ๊น ์ค๋ช ํ์๋ฉด, apply์ ๊ฑฐ์ ์ ํํ๊ฒ ๋๊ฐ์ด ์๋ํ๋๋ฐ, object๊ฐ nullable type์ผ ๋์๋ object๋ฅผ nullable์ธ ์ํ๋ก block์ ํธ์ถํฉ๋๋ค.
์ ๋ ๋ณดํต ํน์ object๋ฅผ ๊ฐ๊ณ ํธ์ถํ๋ method๋ค์ ์์ด ๋๋ฌด ๋ง์์ ์ฝ๋๊ฐ ๊ธธ์ด์ง๋ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํฉ๋๋ค. ํนํ ๋ณ์๋ช ์ด ๊ธธ ๋์. ์๋ฅผ ๋ค์ด:
// ... long method
val total = with(ridicurioslyLongNameOfFrameworkSingleton){
setSomething(a)
setSomethingElse(b)
// do other things...
getTotal()
}
// ...
let
let์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ํจ์์ผ ๊ฒ ๊ฐ์๋ฐ(์ ์ด๋ ์ ๋ ๊ทธ๋ ์ต๋๋ค), ํนํ ?์ ํจ๊ป ์ฌ์ฉ๋ ๋ ์ ์ฉํฉ๋๋ค. value๊ฐ null์ด ์๋ ๊ฒฝ์ฐ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์ฃ . ์ฝ๋ ์ null ์ฒดํฌํ๋ ์ฝ๋(if(obj == null) {โฆ})์ ๊ฑฐ๋ฅผ ๊ณ ๋ คํ ์ ์๊ฒ ํด์ฃผ์ฃ :
fun fullName(firstName: String, middleName: String?, familyName: String) = middleName?.let{
"$firstName ${it.first()}. $familyName"
} ?: "$firstName $familyName"
run
run์ object๋ฅผ ๊ฐ๊ณ ์ฌ๋ฌ method๋ค์ ํธ์ถํ ๋ค ๋ง์ง๋ง ๊ฒฐ๊ณผ๊ฐ์ ๋ฐํํ๊ณ ์ถ์ ๊ฒฝ์ฐ ์ ์ฉํฉ๋๋ค. ์๋ฅผ ๋ค์ด:
fun cartTotal(cart: Cart, items: List<CartItem>) = cart.run{
items.foreach{addItem(it)}
calcTotal()
}
also
๋ฐํํด์ผ ํ๋ ๊ฐ์ด ์๋๋ฐ, ๊ทธ ์ ์ โ๋ค๋ฅธ ์์ ์๋โ ํ์ํ ๊ฒฝ์ฐ ์ ์ฉํฉ๋๋ค: ์๋ฅผ ๋ค์ด:
fun createUser(userName: Stirng): Int =
myAtomicInt.getAndIncrement().also { users.put(it, userName) }
Scope Function Signatures
์ฐธ๊ณ ๋ก ์๊ทธ๋์ฒ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
public inline fun <T, R> T.run(block: T.() -> R): R = block()
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
์ฝํ๋ฆฐ์ ์ต์ํ์ง ์์ ์ฌ๋๋ค์๊ฒ scope ํจ์๋ฅผ ์ฌ์ฉํ ์ฝ๋๋ฅผ ๋ณด์ฌ์ฃผ๋ฉด, โ๊ทธ๊ฑธ ์จ๋ ๋ณ ์ด์ ๋ ์๊ณ , ๊ทธ๋ฅ ๋ณต์กํดโ๋ผ๋ ๋ฐ๋ก ์ ํํ ๋ฃ์ต๋๋ค.
scope ํจ์ ์ฌ์ฉ ์, ์์ ์ด์ ์ ์ฝ๋ ๋ช ์ค ์ ์ฝํ๋ ๊ฒ์ด๊ณ , ๋ ํฐ ์ด์ ์ ์์ ๋ณ์(temporary variables)์ ์ฌ์ฉ์ ํผํ๊ฒ ํด์ค๋ค๋ ๊ฒ์ ๋๋ค.
์์ ๋ณ์๋ค์ ๋ณดํต FPFunctional Programming ์คํ์ผ์์ ๋ณ๋ก ์ข์ง ์์ ๋ฐฉ์์ผ๋ก ์ฌ๊ฒจ์ง๋ฉฐ, ๋ณ์๋ช ์ ํท๊ฐ๋ฆฌ๊ฑฐ๋ ์ฝ๋ ๋ณต์ฌ-๋ถ์ฌ๋ฃ๊ธฐ ์, ์ฌ์ํ ๋ฒ๊ทธ์ ์์ธ์ด ๋ฉ๋๋ค.(๋ฌผ๋ก ๊ทธ๋ฐ ์ค์๋ฅผ ์ ํ๊ฒ ์ง๋ง์!)
๋ํ FP ๋ฐฉ์์์ (์ข ๋ ์ ํํ ์๋ ์ ๋ฌํ๋ค๋ ์๋ฏธ์์) ํจ์ ์ฌ์ฉ ์, single expression declaration form(์ด์ฒ๋ผ ์๊ธด.. fun f = ...)์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข์๋ฐ, scope ํจ์๊ฐ ์ด๋ฅผ ๋์์ค๋๋ค.