DSL 을 Kotlin / Scala 에서 구현할때 자주 사용하는 기법중 하나는 Last Parameter 의 람다 를 {} <- 이거 로 표현할수 있다는것이다
별거 아니라고 생각할수도 있는데, Internal DSL 이라는거 자체가 대충 지금 쓰는 언어에 (선언적으로) 조화롭게 어울려서 잘 들어가 있으면 걍 Internal DSL 이다.
Java 에서는 This 를 이용해서 Fluent API Chain 으로 DSL 을 구축하는거처럼.. (여담. Fluent API 가 DSL 이라는거는 머리로는 이해하지만 가슴으로 인정할수없다)
멋들어지게 프로그래밍을 잘하는 사람은 DSL 을 구축하기 위하여 패턴매칭, infix operator, Monad, Macro 같은 메타 프로그래밍 ... 등등 기술을 이용해서 뚝딱뚝딱 짜서 비즈니스로직을 멋들어지게 구축하는데,
그정도까지 가지 않아도 마지막 람다 파라미터를 {} 이것으로 표현하는것만으로도 꽤 멋들어진 DSL 을 만들 수 있다.
Kotlin 계열에서 멋들어지게 짜여진 DSL 을 구경하고 싶다면 JetBrains/Exposed: Kotlin SQL Framework (github.com) 참고
암튼 마지막 Parameter를 {} 로 표현하면 멋들어진 표현식이 가능하다.
필자는 보통 코드 중복이나 Depth 를 (멋있게) 줄이고싶을때 주로 사용하는데 예를들면 다음과 같다.
fun main(args: Array<String>) {
val inPath = "C:\\Users\\dldnj\\Desktop\\reader.txt"
val outPath = "C:\\Users\\dldnj\\Desktop\\writer.txt"
// DSL 안 쓸때
FileReader(inPath).use { fileReader ->
FileWriter(outPath).use { fileWriter ->
fileWriter.write(fileReader.readText())
}
}
// DSL 쓸때
fileScope(inPath, outPath) { (fileReader, fileWriter) ->
fileWriter.write(fileReader.readText())
}
}
// DSL 용 헬퍼 Class / 함수
data class Rewriter(val fileReader: FileReader, val fileWriter: FileWriter)
fun fileScope(inPath: String, outPath: String, block: (Rewriter) -> Unit) {
FileReader(inPath).use { fileReader ->
FileWriter(outPath).use { fileWriter ->
block(Rewriter(fileReader, fileWriter))
}
}
}
DSL 쓰는것이 깔끔하다
좀더 로직적인 것을 넣어도 좋다
fun main(args: Array<String>) {
val (inDir, outDir) = ("C:\\Users\\dldnj\\Desktop\\in" to "C:\\Users\\dldnj\\Desktop\\out")
// 명령형
for (count in 1..2) {
FileReader("$inDir\\$count.txt").use { fileReader ->
FileWriter("$outDir\\$count.txt").use { fileWriter ->
fileWriter.write(fileReader.readText())
}
}
}
// 선언적 DSL
repeatWrite(2, inDir, outDir) { (fileReader, fileWriter) ->
fileWriter.write(fileReader.readText())
}
}
data class Rewriter(val fileReader: FileReader, val fileWriter: FileWriter)
fun fileScope(inPath: String, outPath: String, block: (Rewriter) -> Unit) {
FileReader(inPath).use { fileReader ->
FileWriter(outPath).use { fileWriter ->
block(Rewriter(fileReader, fileWriter))
}
}
}
fun repeatWrite(repeat: Int, inDirPath: String, outDirPath: String, block: (Rewriter) -> Unit) {
(1..repeat).forEach { count ->
fileScope("$inDirPath\\$count.txt", "$outDirPath\\$count.txt", block)
}
}
좀더 광기를 첨가하면 이런직도 가능하다. Feat data class
import java.io.FileReader
import java.io.FileWriter
fun main(args: Array<String>) {
// 아래 선언만으로 save 폴더에 , save1.txt ~ save5.txt 가 생성된다.
fileWrite {
count = 5
file = file {
dir = "C:\\Users\\dldnj\\Desktop\\save"
name = "save$$"
extension = "txt"
}
fileContent = content {
content = "Hello World"
}
}
}
// DSL 용 Helper
data class file (
var dir: String = "",
var name: String = "",
var extension: String = ""
)
fun file(init: file.() -> Unit): file {
val file = file()
file.init()
return file
}
data class content (
var content: String = ""
)
fun content(init: content.() -> Unit): content {
val content = content()
content.init()
return content
}
fun fileWrite(block: FileWrite.() -> Unit) = FileWrite().apply(block).write()
class FileWrite {
var count: Int = 0
var file: file = file()
var fileContent: content = content()
fun write() {
(1..count).forEach {
FileWriter("${file.dir}\\${file.name.replace("$$",it.toString())}.${file.extension}").use { fileWriter ->
fileWriter.write(fileContent.content)
}
}
}
}
물론 이렇게 까지는 잘 안하지만 어디까지 표현할수있는지 알려주기 위하여.. (Helper 가 너무 복잡해짐)
역시 DSL 은 어차피 S-exp 로 표현되는 Clojure 가 최고
간지난다고 표현했지만 사실 가독성에 큰 영향을 미치는 요인이다.
꼴랑 파라미터 () 여기에 안들어가고 따로 {} 로 표현된다는 것 뿐이지만, Kotlin 에 맞는 스타일로 표현된다것, 즉 그 언어에 내장된 언어처럼 표현되는것이 DSL 이 가지는 강점이다.
언어를 사용하는 입장에서는, 그 언어다움이 굉장히 중요하다. 함수를 파라미터로 넘기는거는 FP 언어 계열에서는 너무 당연해서 딱히 불편하지도 않다, 하지만 Kotlin 과 같은 문법에서 모든 함수가 그렇게 표현되면 상당히 이질적이다.
'프로그래밍 언어 노트 > JAVA | Kotlin' 카테고리의 다른 글
[Spring Boot] + Kotlin 에서 Route + Beans DSL 을 사용해보자 (0) | 2023.02.25 |
---|---|
Kotlin SQL DSL 을 구축해보자! 쓸 수 있는 방법을 전부 동원해봐서! (0) | 2023.02.18 |
[Jackson] Subtype 별 Polymorphic De/Serialization 을 제공하는 Deduction (0) | 2022.05.01 |
[JVM] Typereference 와 Type Erasure (타입 소거) (0) | 2021.12.07 |
JVM GC와 Reference (0) | 2019.11.26 |