- 영역 특화 언어 만들기
- 수신 객체 지정 람다 사용
- invoke 관례 사용
- 기존 코틀린 DSL 예제
일반 구문 | 간결한 구문 | 사용한 언어 특성 |
---|---|---|
StringUtil.capitalize(s) | s.capitalize() | 확장 함수 |
1.to("one") | 1 to "one" | 중위 호출 |
set.add(2) | set += 2 | 연산자 오버로딩 |
map.get("key") | map["key"] | get 메소드에 대한 관례 |
file.use({ f -> f.read() }) | file.use { it.read() } | 람다를 괄호 밖으로 빼내는 관례 |
sb.append("yes") sb.append("no") | with(sb) { append("yes") append("no") } | 수신 객체 지정 람다 |
범용 프로그래밍 언어는 보통 명령적(imperative)이다. 명력적 언어는 어떤 연산을 완수하기 위해 필요한 각 단계를 순서대로 정확히 기술한다.
선언적 언어는 원하는 결과를 기술하기만 하고 그 결과를 달성하기 위해 필요한 세부 실행은 언어를 해석하는 엔진에 맡긴다. 실행 엔진이 결과를 얻는 과정을 전체적으로 한꺼번에 최적화하기 때문에 선언적 언어가 더 효율적인 경우가 자주 있다.
반면 명령적 접근법에서는 각 연산에 대한 구현을 독립적으로 최적화해야 한다.
DSL 단점 : DSL을 범용 언어로 만든 호스트 애플리케이션과 함께 조합하기가 어렵다.
fun buildString(
builderAction: (StringBuilder) -> Unit
) : String {
val sb = StringBuilder()
// 람다 인자로 StringBuilder 인스턴스를 넘긴다.
builderAction(sb)
return sb.toString()
}
val s = buildString {
// it은 StringBuilder 인스턴스를 가리킨다.
it.append("Hello, ")
it.append("World!")
}
println(s) // Hello, World!
fun buildString(
// 확장 함수 타입 : 수신 객체가 있는 함수 타입의 파라미터를 선언한다.
// 수신 객체 타입.파라미터 타입 -> 반환 타입
builderAction: StringBuilder.() -> Unit
) : String {
val sb = StringBuilder()
// StringBuilder 인스턴스를 람다의 수신 객체로 넘긴다.
sb.builderAction()
return sb.toString()
}
val s = buildString {
// "this" 키워드는 StringBuilder 인스턴스를 가리킨다.
this.append("Hello, ")
// "this"를 생략해도 묵시적으로 StringBuilder 인스턴스가 수신 객체로 취급된다.
append("World!")
}
println(s) // Hello, World!
// appendExcl은 확장 함수 타입의 값이다.
val appendExcl : StringBuilder.() -> Unit = { this.append("!") }
val stringBuilder = StringBuilder("Hi")
// appendExcl을 확장 함수처럼 호출할 수 있다.
stringBuilder.appendExcl()
// appendExcel을 인자로 넘길 수 있다.
println(StringBuilder) // Hi!
println(buildString(appendExcl)) // !
fun buildString(builderAction: StringBuilder.() -> Unit): String =
StringBuilder().apply(builderAction).toString()
inline fun <T> T.apply(block: T.() -> Unit) : T {
block()
return this // 수신 객체를 다시 반환한다.
}
inline fun <T, R> with(receiver: T, block: T.() -> R): R =
receiver.block() // 람다를 호출해 얻은 결과를 반환한다.
val map = mutableMapOf(1 to "one")
map.apply { this[2] = "two" }
with(map) { this[3] = "three" }
println(map) // {1=one, 2=two, 3=three}
// 클래스 안에서 invoke 메소드 정의하기
class Greeter(val greeting: String) {
// Greeter 안에 "invoke" 메소드를 정의한다.
operator fun invoke(name: String) {
println("$greeting, $name")
}
}
val bavarianGreeter = Greeter("Servus")
// Greeter 인스턴스를 함수처럼 호출한다.
// 내부적으로 bavarianGreeter.invoke("Dmitry")로 컴파일된다.
bavarianGreeter("Dmitry") // Servus, Dmitry!
// 이 인터페이스는 인자를 2개 받는 함수를 표현한다.
interface Function2<in P1, in P2, out R> {
operator fun invoke(p1: P1, p2: P2): R
}
class ComplexLambda {
private var value: Int = 0
fun initialize(initialValue: Int): ComplexLambda {
value = initialValue
return this
}
fun add(number: Int): ComplexLambda {
value += number
return this
}
fun multiply(factor: Int): ComplexLambda {
value *= factor
return this
}
operator fun invoke(): Int {
return value
}
}
fun main() {
// 복잡한 람다를 여러 메소드로 분리하면서도 호출 가능한 객체 생성
val complexLambda = ComplexLambda()
.initialize(10)
.add(5)
.multiply(2)
val result = complexLambda()
println(result) // 30
}
class DataProcessor {
// 함수 타입 파라미터를 받는 함수
fun <T> processData(data: T, processorFunction: (T) -> Unit) {
// 전달된 함수에 객체 전달
// processorFunction.invoke(data)
processorFunction(data)
}
}
fun main() {
val dataProcessor = DataProcessor()
val stringProcessor: (String) -> Unit = { value ->
println("Processing string: $value")
}
val intProcessor: (Int) -> Unit = { value ->
println("Processing integer: $value")
}
dataProcessor.processData("Hello, Kotlin!", stringProcessor)
dataProcessor.processData(42, intProcessor)
}
interface MyLambda : Function3<Int, Int, Int, Int> {
fun initialize(initialValue: Int): MyLambda
fun add(number: Int): MyLambda
fun multiply(factor: Int): MyLambda
override operator fun invoke(p1: Int, p2: Int, p3: Int): Int {
return p1 + p2 + p3
}
}
class LambdaProcessor : MyLambda {
private var value: Int = 0
override fun initialize(initialValue: Int): MyLambda {
value = initialValue
return this
}
override fun add(number: Int): MyLambda {
value += number
return this
}
override fun multiply(factor: Int): MyLambda {
value *= factor
return this
}
}
fun main() {
val lambdaProcessor = LambdaProcessor()
.initialize(10)
.add(5)
.multiply(2)
val result = lambdaProcessor(1, 2, 3)
println(result) // 18
}
data class Issue(
val id: String, val project: String, val type: String,
val property: String, val description: String
)
// 함수 타입을 부모 클래스로 사용한다.
class ImportantIssuesPredicate(val project: String) : (Issue) -> Boolean {
override fun invoke(issue: Issue): Boolean {
return issue.project == project && issue.isImportant()
}
private fun Issue.isImportant(): Boolean {
return type == "Bug" && (priority == "Major" || priority == "Critical")
}
}
val i1 = Issue("IDEA-154446", "IDEA", "Bug", "Major", "Save settings failed")
val i2 = Issue("KT-12183", "Kotlin", "Feature", "Normal",
"Intention: convert several calls on the same receiver to with/apply")
val predicate = ImportantIssuesPredicate("IDEA")
for (issue in listOf(i1, i2).filter(predicate)) {
println(issue.id)
} // IDEA-154446