๐Ÿ”ง โ”— 11. Scope ํ•จ์ˆ˜ โ€” `apply`, `run`, `let`, `also`

BamgasiJMยท2026๋…„ 9์›” 28์ผ

OPENRNDR

๋ชฉ๋ก ๋ณด๊ธฐ
11/17
post-thumbnail

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋ฌธ๋ฒ•๋“ค์„ ๋ชจ๋‘ ํ™œ์šฉํ•˜๋ฉด, ์ฝ”๋“œ๋ฅผ ๋” ๊น”๋”ํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Scope ํ•จ์ˆ˜๋Š” ๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ฑฐ๋‚˜ ์ฒด์ด๋‹ํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋ฌธ์ œ: ๊ฐ์ฒด ์ดˆ๊ธฐํ™”๊ฐ€ ๋ณต์žกํ•จ

data class AppSettings(
    var width: Int = 0,
    var height: Int = 0,
    var frameRate: Int = 60,
    var debug: Boolean = false
)

val settings = AppSettings()
settings.width = 800
settings.height = 600
settings.frameRate = 60
settings.debug = true
// ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ๋งค๋ฒˆ ๋ฐ˜๋ณตํ•ด์•ผ ํ•จ

ํ•ด๊ฒฐ: Scope ํ•จ์ˆ˜๋กœ ๊ฐ„๊ฒฐํ•˜๊ฒŒ

val settings = AppSettings().apply {
    width = 800
    height = 600
    frameRate = 60
    debug = true
    // this๊ฐ€ AppSettings์ด๋ฏ€๋กœ, ํ”„๋กœํผํ‹ฐ์— ์ง์ ‘ ์ ‘๊ทผ
}

์ด ๊ธ€์—์„œ๋Š”:

  • ๋„ค ๊ฐ€์ง€ Scope ํ•จ์ˆ˜์˜ ์ฐจ์ด
  • ๊ฐ๊ฐ์„ ์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ
  • OPENRNDR์—์„œ์˜ ์‹ค์ œ ํŒจํ„ด

์„ ๋‹ค๋ฃจ๊ฒ ์Šต๋‹ˆ๋‹ค.


Scope ํ•จ์ˆ˜๋ž€?

Scope ํ•จ์ˆ˜๋Š” ๊ฐ์ฒด์˜ ๋ฒ”์œ„(scope) ์•ˆ์—์„œ๋งŒ ์ž‘๋™ํ•˜๋Š” ํ•จ์ˆ˜๋“ค์ž…๋‹ˆ๋‹ค.

๋„ค ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

  • apply โ€” ๊ฐ์ฒด๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ๋ฐ˜ํ™˜
  • also โ€” ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ ๋ถ€์ˆ˜ํšจ๊ณผ ์‹คํ–‰
  • let โ€” ๋ณ€ํ™˜๋œ ๊ฐ’ ๋ฐ˜ํ™˜
  • run โ€” ๋ณ€ํ™˜๋œ ๊ฐ’ ๋ฐ˜ํ™˜ (์•”๋ฌต์  ๋ฆฌ์‹œ๋ฒ„)

1. apply โ€” ๊ฐ์ฒด ์„ค์ • ํ›„ ๋ฐ˜ํ™˜

apply๋Š” ๊ฐ์ฒด๋ฅผ ์„ค์ •ํ•˜๊ณ  ๊ทธ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

data class AppSettings(var width: Int = 0, var height: Int = 0)

val settings = AppSettings().apply {
    width = 800
    height = 600
}
// settings๋Š” ์„ค์ •์ด ๋๋‚œ ๊ฐ™์€ AppSettings ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

apply ๋ธ”๋ก์˜ ๋ฆฌ์‹œ๋ฒ„๋Š” ์ƒ์„ฑํ•œ ๊ฐ์ฒด์ด๋ฏ€๋กœ this.width ๋Œ€์‹  width๋กœ ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค. ๋ธ”๋ก์—์„œ ๋ฌด์—‡์„ ๋ฐ˜ํ™˜ํ•˜๋“  apply์˜ ๊ฒฐ๊ณผ๋Š” ์›๋ž˜ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

์˜ˆ์ œ 1: ๊ฐ„๋‹จํ•œ ์ดˆ๊ธฐํ™”

data class Circle(
    var x: Double = 0.0,
    var y: Double = 0.0,
    var radius: Double = 10.0,
    var color: ColorRGBa = ColorRGBa.WHITE
)

// apply ์—†์ด
val circle1 = Circle()
circle1.x = 100.0
circle1.y = 200.0
circle1.radius = 50.0
circle1.color = ColorRGBa.RED

// apply๋กœ (๊น”๋”ํ•จ)
val circle2 = Circle().apply {
    x = 100.0
    y = 200.0
    radius = 50.0
    color = ColorRGBa.RED
}

์˜ˆ์ œ 2: ๋ฆฌ์ŠคํŠธ ์ดˆ๊ธฐํ™”

val numbers = mutableListOf<Int>().apply {
    add(1)
    add(2)
    add(3)
    add(4)
    add(5)
}

// ๋˜๋Š” ํ•œ ์ค„๋กœ
val numbers = mutableListOf(1, 2, 3, 4, 5)  // ๋” ๊ฐ„๋‹จํ•จ

OPENRNDR์—์„œ์˜ apply

program {
    val drawer = this.drawer.apply {
        fill = ColorRGBa.WHITE
        stroke = ColorRGBa.BLACK
        strokeWeight = 2.0
    }
    
    extend {
        drawer.clear(ColorRGBa.BLACK)
        
        drawer.circle(400.0, 400.0, 100.0)
    }
}

2. also โ€” ๋ถ€์ˆ˜ํšจ๊ณผ ์‹คํ–‰ ํ›„ ์›๋ณธ ๋ฐ˜ํ™˜

also๋Š” ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฉด์„œ ๋ถ€์ˆ˜ํšจ๊ณผ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

val result = mutableListOf(1, 2, 3)
    .also { list ->
        println("List created: $list")  // ๋ถ€์ˆ˜ํšจ๊ณผ
        // also ์ž์ฒด๋Š” ๋ฐ˜ํ™˜๊ฐ’์„ ๋ฐ”๊พธ์ง€ ์•Š์ง€๋งŒ, list์˜ ๊ฐ€๋ณ€ ๋‚ด์šฉ์€ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    }
    // result๋Š” ์›๋ณธ list์™€ ๊ฐ™์Œ

์˜ˆ์ œ 1: ๋””๋ฒ„๊ทธ ์ถœ๋ ฅ๊ณผ ํ•จ๊ป˜

val numbers = (1..10).toList()
    .also { println("Original: $it") }
    .filter { it % 2 == 0 }
    .also { println("Filtered: $it") }
    .map { it * 2 }
    .also { println("Mapped: $it") }

// ์ถœ๋ ฅ:
// Original: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// Filtered: [2, 4, 6, 8, 10]
// Mapped: [4, 8, 12, 16, 20]

์˜ˆ์ œ 2: ๋กœ๊น…๊ณผ ํ•จ๊ป˜ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

data class Particle(
    var position: Vector2,
    var velocity: Vector2
)

val particles = (0 until 100)
    .map { i ->
        Particle(
            position = Vector2(random(800.0), random(600.0)),
            velocity = Vector2(random(-2.0, 2.0), random(-2.0, 2.0))
        )
    }
    .also { particles ->
        println("Created ${particles.size} particles")
    }
    .filter { it.position.x > 100 }
    .also { filtered ->
        println("Filtered to ${filtered.size} particles")
    }

OPENRNDR์—์„œ์˜ also

program {
    val particles = mutableListOf<Particle>()
    
    extend {
        // ํŒŒํ‹ฐํด ์ƒ์„ฑ ๋ฐ ๋กœ๊น…
        particles.add(
            Particle(mouse.position, Vector2(1.0, 0.0))
                .also { p ->
                    println("Particle created at ${p.position}")
                }
        )
    }
}

3. let โ€” ๋ณ€ํ™˜ ํ›„ ๋ฐ˜ํ™˜

let์€ ๊ฐ์ฒด๋ฅผ ๋ณ€ํ™˜ํ•ด์„œ ์ƒˆ๋กœ์šด ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

val original = "Hello"
val transformed = original.let { str ->
    str.uppercase() + "!"
}
println(transformed)  // "HELLO!"

// original์€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ
println(original)     // "Hello"

์˜ˆ์ œ 1: Null ์•ˆ์ „์„ฑ๊ณผ ํ•จ๊ป˜

val name: String? = "Mina"

// let์œผ๋กœ null ์ฒดํฌ
name?.let { n ->
    println("Hello, $n")
    // n์€ String (null์ด ์•„๋‹˜์ด ๋ณด์žฅ๋จ)
}

์˜ˆ์ œ 2: ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜

val point = Vector2(100.0, 200.0)

val distance = point.let { p ->
    sqrt(p.x * p.x + p.y * p.y)
}

println(distance)  // 223.6...

// ๋˜๋Š” ๋žŒ๋‹ค ์ถ•์•ฝ
val distance2 = point.let { sqrt(it.x * it.x + it.y * it.y) }

์˜ˆ์ œ 3: ์ฒด์ด๋‹

val result = mutableListOf(1, 2, 3)
    .let { list ->
        list.map { it * 2 }  // List<Int>๋กœ ๋ณ€ํ™˜
    }
    .let { doubled ->
        doubled.sum()  // Int๋กœ ๋ณ€ํ™˜
    }

println(result)  // 12

OPENRNDR์—์„œ์˜ let

program {
    var selectedPoint: Vector2? = null
    
    extend {
        drawer.clear(ColorRGBa.BLACK)
        
        // selectedPoint๋กœ ๊ณ„์‚ฐ ์ˆ˜ํ–‰
        selectedPoint?.let { point ->
            val distance = (point - mouse.position).length
            drawer.fill = if (distance < 100) ColorRGBa.RED else ColorRGBa.WHITE
            drawer.circle(point.x, point.y, 20.0)
        }
    }
}

4. run โ€” ์•”๋ฌต์  ๋ฆฌ์‹œ๋ฒ„๋กœ ๋ณ€ํ™˜

run์€ ๋ฆฌ์‹œ๋ฒ„๋ฅผ ์•”๋ฌต์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๋ณ€ํ™˜๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

val result = "Hello".run {
    // this == "Hello"
    uppercase() + "!"
}
println(result)  // "HELLO!"

// let๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ, ํŒŒ๋ผ๋ฏธํ„ฐ ๋Œ€์‹  this ์‚ฌ์šฉ
// let: let { str -> str.uppercase() }
// run: run { uppercase() }

์˜ˆ์ œ 1: ๊ฐ„๋‹จํ•œ ๊ณ„์‚ฐ

val circle = Circle(100.0, 200.0, 50.0)

val area = circle.run {
    PI * radius * radius
}
println(area)  // 7853.98...

์˜ˆ์ œ 2: ์กฐ๊ฑด๋ถ€ ์ฒ˜๋ฆฌ

val input: String? = "pattern"

val result = input?.run {
    if (this.isEmpty()) "Empty" else "Not empty: $this"
} ?: "Input is null"

println(result)

OPENRNDR์—์„œ์˜ run

program {
    data class Settings(var scale: Double = 1.0)

    val settings = Settings().run {
        scale = 2.0
        this  // ๋งˆ์ง€๋ง‰ ์‹์ด๋ฏ€๋กœ Settings ๊ฐ์ฒด ๋ฐ˜ํ™˜
    }
}

apply, also, let, run์€ ๋ชจ๋‘ ๋ธ”๋ก์„ ์‹คํ–‰ํ•œ ๋’ค ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ฐจ์ด๋Š” ๋ธ”๋ก ์•ˆ์—์„œ ๊ฐ์ฒด๋ฅผ ์–ด๋–ค ์ด๋ฆ„์œผ๋กœ ๋‹ค๋ฃจ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์–ด๋–ค ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š”์ง€์— ์žˆ์Šต๋‹ˆ๋‹ค. ํ•จ์ˆ˜ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ๋ธ”๋ก์˜ ์ˆ˜์‹  ๊ฐ์ฒด๊ฐ€ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

๋„ค ๊ฐ€์ง€ Scope ํ•จ์ˆ˜ ๋น„๊ต

ํ•จ์ˆ˜ํŒŒ๋ผ๋ฏธํ„ฐ๋ฆฌ์‹œ๋ฒ„๋ฐ˜ํ™˜๊ฐ’์šฉ๋„
apply์—†์Œthis๊ฐ์ฒด์ดˆ๊ธฐํ™”
alsoit์—†์Œ๊ฐ์ฒด๋ถ€์ˆ˜ํšจ๊ณผ
letit์—†์Œ๋ณ€ํ™˜๊ฐ’๋ณ€ํ™˜
run์—†์Œthis๋ณ€ํ™˜๊ฐ’๊ณ„์‚ฐ

์ด ํ‘œ๋Š” ํ™•์žฅ ํ•จ์ˆ˜ ํ˜•ํƒœ์ธ ๊ฐ์ฒด.run { ... }๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. run { ... }์ฒ˜๋Ÿผ ๊ฐ์ฒด ์—†์ด ๋ถ€๋ฅด๋Š” ํ‘œ์ค€ ํ•จ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, ๊ทธ ํ˜•ํƒœ์—๋Š” ์ˆ˜์‹  ๊ฐ์ฒด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์„ ํƒ ๊ธฐ์ค€:

data class Settings(var width: Int = 0, var height: Int = 0)

// apply: ๊ฐ์ฒด ์„ค์ • ํ›„ ๊ฐ™์€ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜
val settings = Settings().apply {
    width = 800
    height = 600
}

// also: ๊ฐ’์„ ์œ ์ง€ํ•˜๋ฉด์„œ ๋กœ๊ทธ ์ถœ๋ ฅ
val numbers = listOf(1, 2, 3).also { println("Created: $it") }

// let: nullable ๊ฐ’์ด ์žˆ์„ ๋•Œ๋งŒ ๋ณ€ํ™˜
val name: String? = "Mina"
val length = name?.let { it.length }

// run: ๋ฆฌ์‹œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•ด ๊ณ„์‚ฐ๊ฐ’ ๋ฐ˜ํ™˜
val radius = 50.0
val area = radius.run { PI * this * this }

์‹ค์ „ ์˜ˆ์ œ 1: ๊ฒŒ์ž„ ๊ฐ์ฒด ์ดˆ๊ธฐํ™”

data class GameCharacter(
    var name: String = "",
    var health: Int = 100,
    var mana: Int = 50,
    var position: Vector2 = Vector2(0.0, 0.0),
    var level: Int = 1
) {
    fun takeDamage(amount: Int) {
        health -= amount
    }
}

// apply๋กœ ์ดˆ๊ธฐํ™”
val hero = GameCharacter().apply {
    name = "Hero"
    health = 200
    mana = 150
    position = Vector2(400.0, 400.0)
    level = 5
}

// also๋กœ ๊ฒ€์ฆ
val validHero = hero.also {
    require(it.health > 0) { "Health must be positive" }
    println("Hero initialized: ${it.name} at level ${it.level}")
}

์‹ค์ „ ์˜ˆ์ œ 2: ๋ฐ์ดํ„ฐ ํŒŒ์ดํ”„๋ผ์ธ

data class DataPoint(
    val x: Double,
    val y: Double,
    val value: Double
)

val results = (0 until 1000)
    .map { i ->
        DataPoint(
            x = i.toDouble(),
            y = random(100.0),
            value = sin(i.toDouble() / 100)
        )
    }
    .also { data ->
        println("Generated ${data.size} data points")
    }
    .filter { it.value > 0 }
    .also { data ->
        println("Filtered to ${data.size} positive values")
    }
    .sortedByDescending { it.value }
    .also { data ->
        println("Top value: ${data.firstOrNull()?.value}")
    }
    .take(10)
    .also { data ->
        println("Took top 10: ${data.size} items")
    }

์‹ค์ „ ์˜ˆ์ œ 3: Null ์•ˆ์ „์„ฑ๊ณผ ์ฒด์ด๋‹

program {
    var cachedResult: List<Vector2>? = null
    
    extend {
        drawer.clear(ColorRGBa.BLACK)
        
        // ์บ์‹œ๋œ ๊ฒฐ๊ณผ ๋˜๋Š” ์ƒˆ๋กœ ๊ณ„์‚ฐ
        val points = cachedResult?.let { cached ->
            println("Using cached result")
            cached
        } ?: run {
            println("Computing new result")
            (0 until 20).map { i ->
                Vector2(i * 40.0, 300.0 + 100.0 * sin(i.toDouble()))
            }
        }.also { points ->
            cachedResult = points  // ์บ์‹œ ์ €์žฅ
        }
        
        // ํฌ์ธํŠธ ๊ทธ๋ฆฌ๊ธฐ
        drawer.fill = ColorRGBa.WHITE
        points.forEach { circle(it.x, it.y, 5.0) }
    }
}

์‹ค์ „ ์˜ˆ์ œ 4: Builder ํŒจํ„ด

data class DrawingContext(
    var strokeColor: ColorRGBa = ColorRGBa.BLACK,
    var fillColor: ColorRGBa = ColorRGBa.WHITE,
    var strokeWeight: Double = 1.0,
    var fontSize: Int = 12
)

fun createContext(init: DrawingContext.() -> Unit): DrawingContext {
    return DrawingContext().apply(init)
}

program {
    val darkContext = createContext {
        strokeColor = ColorRGBa.WHITE
        fillColor = ColorRGBa.BLACK
        strokeWeight = 2.0
    }
    
    val lightContext = createContext {
        strokeColor = ColorRGBa.BLACK
        fillColor = ColorRGBa.WHITE
        strokeWeight = 1.0
    }
    
    extend {
        drawer.clear(ColorRGBa.BLACK)
        
        drawer.fill = darkContext.fillColor
        drawer.stroke = darkContext.strokeColor
        drawer.strokeWeight = darkContext.strokeWeight
        
        drawer.circle(200.0, 400.0, 50.0)
    }
}

์‹ค์ „ ์˜ˆ์ œ 5: ๋ณต์žกํ•œ ์„ค์ • ์ฒด์ด๋‹

program {
    val particles = mutableListOf<Particle>()
    
    val config = object {
        var particleCount = 100
        var minVelocity = -2.0
        var maxVelocity = 2.0
        var particleSize = 5.0
    }.apply {
        particleCount = 150  // ์„ค์ • ์กฐ์ •
        particleSize = 8.0
    }
    
    extend {
        drawer.clear(ColorRGBa.BLACK)
        
        // ํŒŒํ‹ฐํด ์ถ”๊ฐ€
        repeat(3) {
            particles.add(
                Particle(
                    position = mouse.position,
                    velocity = Vector2(
                        random(config.minVelocity, config.maxVelocity),
                        random(config.minVelocity, config.maxVelocity)
                    ),
                    radius = config.particleSize
                ).also { p ->
                    println("Particle added with radius: ${p.radius}")
                }
            )
        }
        
        // ํŒŒํ‹ฐํด ์—…๋ฐ์ดํŠธ ๋ฐ ๋ Œ๋”๋ง
        particles
            .also { list ->
                println("Updating ${list.size} particles")
            }
            .forEach { p ->
                p.update()
                p.draw(drawer)
            }
        
        particles.removeIf { !it.isAlive }
    }
}

Scope ํ•จ์ˆ˜์˜ ์ฒด์ด๋‹

์—ฌ๋Ÿฌ Scope ํ•จ์ˆ˜๋ฅผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

val result = mutableListOf(1, 2, 3)
    .apply {
        add(4)
        add(5)
    }
    .also { list ->
        println("List: $list")
    }
    .map { it * 2 }
    .let { doubled ->
        doubled.sum()
    }

println(result)  // 30

์‹ค์ „ ์˜ˆ์ œ 6: ๊ฒŒ์ž„ ์ƒํƒœ ๊ด€๋ฆฌ

data class Enemy(var position: Vector2, var health: Int)

object GameManager {
    var score = 0
    var level = 1
    var enemies = mutableListOf<Enemy>()
    
    fun updateScore(points: Int) {
        score = (score + points).also { newScore ->
            println("Score updated to $newScore")
        }
        level = score / 1000 + 1
    }
    
    fun createEnemies(count: Int) {
        enemies = (0 until count)
            .map { i ->
                Enemy(
                    position = Vector2(random(800.0), random(600.0)),
                    health = 10 * level
                )
            }
            .toMutableList()
            .also { list ->
                println("Created ${list.size} enemies for level $level")
            }
    }
}

program {
    extend {
        drawer.clear(ColorRGBa.BLACK)
        
        // ๊ฒŒ์ž„ ๊ด€๋ฆฌ์ž ์ƒํƒœ ํ‘œ์‹œ
        drawer.fill = ColorRGBa.WHITE
        drawer.text("Score: ${GameManager.score}", 20.0, 30.0)
        drawer.text("Level: ${GameManager.level}", 20.0, 60.0)
        drawer.text("Enemies: ${GameManager.enemies.size}", 20.0, 90.0)
    }
}

Scope ํ•จ์ˆ˜์˜ ์žฅ์ 

์žฅ์ ์„ค๋ช…
๊ฐ„๊ฒฐ์„ฑ๋ฐ˜๋ณต๋˜๋Š” ๋ณ€์ˆ˜ ์ด๋ฆ„ ์ œ๊ฑฐ
๊ฐ€๋…์„ฑ์˜๋„๊ฐ€ ๋ช…ํ™•ํ•จ
ํ‘œํ˜„๊ฐ์ฒด ์„ค์ •๊ณผ ๊ฐ’ ๋ณ€ํ™˜์„ ๋ธ”๋ก์œผ๋กœ ๊ตฌ๋ถ„
์œ ํšจ๋ฒ”์œ„๋ณ„๋„์˜ ๋ณ€์ˆ˜ ์˜ค์—ผ ์—†์Œ
ํ‘œํ˜„๋ ฅํ•จ์ˆ˜ํ˜• ์Šคํƒ€์ผ๋กœ ์šฐ์•„ํ•จ

์‹ค์ „ ํŒ: ์–ธ์ œ ์–ด๋–ค ํ•จ์ˆ˜๋ฅผ ์“ธ๊นŒ?

data class Settings(
    var debug: Boolean = false,
    var maxConnections: Int = 0
)

// ์ดˆ๊ธฐํ™”: apply
val config = Settings().apply {
    debug = true
    maxConnections = 100
}.also { settings ->
    println("Config: $settings")
}

// ๊ฒ€์ฆ๊ณผ ๊ธฐ๋ก: also
val list = listOf(1, 2, 3).also {
    require(it.isNotEmpty())
    println("List created")
}

// nullable ๊ฐ’ ๋ณ€ํ™˜: let
val data: String? = "42"
val transformed = data?.let { it.toInt() }

// ๊ณ„์‚ฐ: run
val radius = 50.0
val area = radius.run { PI * this * this }

๋‹ค์Œ ๋‹จ๊ณ„

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ์ฝ”ํ‹€๋ฆฐ์˜ ํ•ต์‹ฌ ๋ฌธ๋ฒ•๋“ค:
1. ํ”„๋กœ๊ทธ๋žจ ๊ตฌ์กฐ
2. ๋žŒ๋‹ค์™€ ๋ฆฌ์‹œ๋ฒ„
3. val/var
4. ๋ฒ”์œ„์™€ ๋ฐ˜๋ณต
5. ์ปฌ๋ ‰์…˜
6. Extension ํ•จ์ˆ˜
7. when ์กฐ๊ฑด ๋ถ„๊ธฐ
8. Null ์•ˆ์ „์„ฑ
9. ํด๋ž˜์Šค์™€ ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค
10. Object์™€ ์‹ฑ๊ธ€ํ„ด
11. Scope ํ•จ์ˆ˜ โœ…

๊ฐ ๋ฌธ๋ฒ•์€ ์ž‘์€ ์˜ˆ์ œ์—์„œ ๋๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ž…์ž ์‹œ์Šคํ…œ์ด๋‚˜ ๊ฒฉ์ž ํŒจํ„ด์ฒ˜๋Ÿผ ์‹ค์ œ ์ƒํƒœ์™€ ๊ทธ๋ฆฌ๊ธฐ ๊ทœ์น™์„ ํ•จ๊ป˜ ๋‹ค๋ฃจ๋Š” ์ฝ”๋“œ์— ์ ์šฉํ•˜๋ฉด, ์–ด๋–ค ๋ถ€๋ถ„์„ ๊ณ ์ •ํ•˜๊ณ  ์–ด๋–ค ๊ฐ’์„ ๊ฐฑ์‹ ํ• ์ง€ ํŒ๋‹จํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.


์—ฐ์Šต ๋ฌธ์ œ

1. ๊ธฐ๋ณธ apply

data class Shape(
    var x: Double = 0.0,
    var y: Double = 0.0,
    var size: Double = 10.0
)

val shape = Shape().apply {
    x = 100.0
    y = 200.0
    size = 50.0
}

println(shape)  // Shape(x=100.0, y=200.0, size=50.0)

2. also๋กœ ๋””๋ฒ„๊น…

val numbers = (1..10).toList()
    .also { println("Original: $it") }
    .filter { it % 2 == 0 }
    .also { println("Filtered: $it") }

// ์ถœ๋ ฅ:
// Original: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// Filtered: [2, 4, 6, 8, 10]

3. let์œผ๋กœ null ์ฒ˜๋ฆฌ

program {
    var selected: Vector2? = Vector2(100.0, 120.0)

    extend {
        drawer.clear(ColorRGBa.BLACK)
        selected?.let { pos ->
            drawer.fill = ColorRGBa.CYAN
            drawer.circle(pos.x, pos.y, 20.0)
        }
        // selected = null๋กœ ๋ฐ”๊พธ๋ฉด ์›์€ ๊ทธ๋ ค์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    }
}

4. run์œผ๋กœ ๊ณ„์‚ฐ

data class Circle(val radius: Double)

val circle = Circle(50.0)

val area = circle.run {
    PI * radius * radius
}

println(area)  // 7853.981633974483

5. OPENRNDR ์ข…ํ•ฉ ์˜ˆ์ œ

๋งˆ์šฐ์Šค๋ฅผ ๋ˆ„๋ฅด๋ฉด ํ˜„์žฌ ์œ„์น˜์— ์ž…์ž๋ฅผ ์ตœ๋Œ€ ๋‹ค์„ฏ ๊ฐœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. apply๋กœ ์„ค์ •์„ ๋งŒ๋“ค๊ณ , also๋กœ ์ƒ์„ฑ ์‹œ์ ์„ ๊ธฐ๋กํ•˜๋ฉฐ, let์œผ๋กœ ๋งˆ์ง€๋ง‰ ์„ ํƒ ์œ„์น˜๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๋ˆ„๋ฅด๊ณ  ์žˆ๋Š” ๋™์•ˆ ๊ฐ™์€ ์ž…๋ ฅ์„ ๋ฐ˜๋ณต ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋„๋ก ํ”„๋ ˆ์ž„ ์‚ฌ์ด์˜ ๋ฒ„ํŠผ ์ƒํƒœ๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.

data class Particle(
    var position: Vector2,
    var velocity: Vector2,
    val radius: Double,
    val lifespan: Double,
    var age: Double = 0.0
) {
    val isAlive: Boolean get() = age < lifespan

    fun update() {
        position += velocity
        age += 1.0
    }

    fun draw(drawer: Drawer) {
        val alpha = (1.0 - age / lifespan).coerceIn(0.0, 1.0)
        drawer.fill = ColorRGBa(1.0, 1.0, 1.0, alpha)
        drawer.circle(position.x, position.y, radius)
    }
}

program {
    data class Config(var particleLimit: Int = 100, var particleSize: Double = 5.0)

    val config = Config().apply {
        particleLimit = 200
        particleSize = 8.0
    }
    val particles = mutableListOf<Particle>()
    var selectedPosition: Vector2? = null
    var wasMouseDown = false

    extend {
        drawer.clear(ColorRGBa.BLACK)
        val mouseDown = mouse.buttonDown
        if (mouseDown && !wasMouseDown) {
            selectedPosition = mouse.position
            val remaining = (config.particleLimit - particles.size).coerceAtLeast(0)
            repeat(minOf(5, remaining)) {
                particles.add(
                    Particle(
                        position = mouse.position,
                        velocity = Vector2(random(-2.0, 2.0), random(-2.0, 2.0)),
                        radius = config.particleSize,
                        lifespan = 60.0
                    ).also { particle ->
                        println("Particle created at ${particle.position}")
                    }
                )
            }
        }
        wasMouseDown = mouseDown

        particles.forEach { it.update() }
        particles.removeIf { !it.isAlive }
        particles.forEach { it.draw(drawer) }

        selectedPosition?.let { position ->
            drawer.stroke = ColorRGBa.YELLOW
            drawer.circle(position.x, position.y, 14.0)
        }
    }
}

ํ•œ ๋ฒˆ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ์ž…์ž ๋‹ค์„ฏ ๊ฐœ๊ฐ€ ์ถ”๊ฐ€๋˜๊ณ , ์ „์ฒด ๊ฐœ์ˆ˜๋Š” 200๊ฐœ๋ฅผ ๋„˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ƒ์„ฑ ๋กœ๊ทธ๋Š” ๋งˆ์šฐ์Šค๋ฅผ ๋ˆ„๋ฅด๊ธฐ ์‹œ์ž‘ํ•œ ์ˆœ๊ฐ„์—๋งŒ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. selectedPosition์ด ์•„์ง ์ •ํ•ด์ง€์ง€ ์•Š์•˜๋‹ค๋ฉด let ๋ธ”๋ก์€ ์‹คํ–‰๋˜์ง€ ์•Š์œผ๋ฉฐ, ํ•œ ๋ฒˆ์ด๋ผ๋„ ๋ˆ„๋ฅธ ๋’ค์—๋Š” ๋งˆ์ง€๋ง‰ ๋ˆ„๋ฅธ ์œ„์น˜์— ๋…ธ๋ž€ ์›์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.


๋งˆ์ง€๋ง‰ ์กฐ์–ธ

Scope ํ•จ์ˆ˜๋Š” ๋ฆฌ์‹œ๋ฒ„์™€ ๋ฐ˜ํ™˜๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ๊ตฌ๋ถ„ํ•˜๋ฉด ์„ ํƒ์ด ๋‹จ์ˆœํ•ด์ง‘๋‹ˆ๋‹ค.

๊ธฐ์–ตํ•  ๊ธฐ์ค€:

  • ์ดˆ๊ธฐํ™” โ†’ apply
  • ๋ถ€์ˆ˜ํšจ๊ณผ โ†’ also
  • ๋ณ€ํ™˜ โ†’ let
  • ๊ณ„์‚ฐ โ†’ run

์ด ๋„ค ๊ฐ€์ง€๋ฅผ ๊ธฐ์–ตํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ด ๋ฌธ์„œ๊นŒ์ง€ 0~11๋ฒˆ์—์„œ ๋‹ค๋ฃฌ Kotlin ๋ฌธ๋ฒ•์„ ์‚ดํŽด๋ดค์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ๋Š” ์ด ๋ฌธ๋ฒ•์„ ์ž‘์€ OPENRNDR ์ž‘ํ’ˆ์— ์ ์šฉํ•˜๋ฉฐ ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ธฐ๋Šฅ์„ ์ตํžˆ๋ฉด ๋ฉ๋‹ˆ๋‹ค.


profile
Coding Art with Blender / Three.js / p5.js / GLSL Shader / Processing / nannou

0๊ฐœ์˜ ๋Œ“๊ธ€