코틀린 컨퍼런스가 2019년 이후 4년만에 암스테르담에서 다시 열렸습니다. 영상이 코틀린 youtube 채널에 모두 공개되어서 먼저 키노트를 시청했는데 흥미로운 내용이 많더라구요. 그래서 글로 간추려 정리해보았습니다.
제 개인적인 생각이 조금..은 아니고 다소 많이 첨가되어있습니다. 이게 부디 발표 내용에 대한 이해를 도울 수 있었으면 좋겠습니다.
K2 Compiler
모든 코틀린 코드의 컴파일은 코틀린 컴파일러가 담당하죠. 코틀린 컴파일러는 컴파일러 프론트엔드와 컴파일러 백엔드라는 두 가지 큰 파트로 나눌 수 있는데, 개발자의 경험에 직접적으로 영향을 주는건 주로 프론트엔드입니다. 프론트엔드는 AST(Abstract Syntax Tree)를 생성하고 구문 검사, 문법 검사, 타입 검사 같은 작업을 수행해서 최종적으로 컴파일러 백엔드에게 넘겨줄 IR(Intermediate Representation)을 만들어 내는 일을 합니다.
시간이 지남에 따라 코틀린의 버전이 올라가고 규모가 커지면서 컴파일 속도가 느리다는 이슈가 계속 올라왔고, 이를 해결하려면 컴파일러 프론트엔드를 최적화 해야하는데 기존 구조로는 도저히 답이 안나왔다고 합니다. 그래서 코틀린 개발자들이 내놓은 화끈한 대답은 “컴파일러 프론트엔드를 싹 갈아엎자!” 였고 이게 이름하야 프로젝트 K2 입니다.
2019년 부터 계속된 K2 Compiler 작업이 드디어 끝이 보인다고 합니다. 그리고 이게 정식으로 릴리즈 되는 시점이 곧 코틀린 2.0 이 릴리즈 되는 시점인거죠.
기존 컴파일러 대비 컴파일 타임을 약 2배 정도 개선했다고 합니다.
코틀린 1.10 은 없다.
K2 가 워낙에 큰 규모의 업데이트이다 보니 이게 적용될 버전의 넘버는 1.10 이 아니라 2.0 입니다. 그리고 코틀린 2.0 을 마구마구 사용해보고 많은 피드백을 줬으면 좋겠다고합니다. 개인적으로 작업중인 사이드 프로젝트에서 코틀린 2.0 을 적용해서 개발 중인데 아직까지 큰 문제는 없네요.
그리고 코틀린 1.9 에서 2.0 으로 아무런 문제 없이 스무스하게 넘어갈 수 있도록 작업 중이라고 합니다.
Most Desired Features
코틀린 버전 2.x 에 추가될 기능들에 대해 간략히 소개합니다.
Static Extensions
- KT-11968 (https://youtrack.jetbrains.com/issue/KT-11968/Research-and-prototype-namespace-based-solution-for-statics-and-static-extensions)
- 그동안 요청을 제일 많이 받은 이슈
- 이제 코틀린에서 static 확장함수를 사용할 수 있다!!
fun File.static.open(name: String)
물론 원래도 Companion object
를 사용해서 어느정도는 구현할 수 있지만 그건 어디까지나 확장하려는 base class 가 Companion object
를 가지고 있을 때의 이야기죠. 이제 static 키워드를 사용해 어떤 클래스든 static 확장 함수의 구현이 가능해졌습니다.
Collection Literals
- KT-43871 (https://youtrack.jetbrains.com/issue/KT-43871)
- 코틀린도 Collection Literal 을 지원한다!
- 이미 많은 현대 언어들(Python, JS, Go, Swift, Rust)이 지원중인 기능인데 코틀린은 아직 없었습니다.
// before Kotlin 2.0
val cmdArgs = listOf("-language-version", "2.0")
// after Kotlin 2.0 with collection literals
val cmdArgs = ["-language-version", "2.0"]
Name-based destructuring
- KT-19627 (https://youtrack.jetbrains.com/issue/KT-19627)
- 코틀린은 구조 분해(destructuring) 이라는 기능이 있습니다. 클래스 내부의 변수를 순서대로 꺼내서 변수로 선언하는 기능인데요. (componentN() 함수)
data class Persion(
val firstName: String,
val lastName: String
)
val person = Persion("David", "Kim")
val (firstName, lastName) = person
- 여러모로 유용한 기능이지만 사용자의 실수를 유발하기 딱 좋은 기능이기도 합니다. 이미 여러번 경험해보신 분들도 있을거구요.
val person = Persion("David", "Kim")
val (lastName, firstName) = person
- 위 코드는 아무런 문제 없이 컴파일되고 IDE 조차 아무런 경고를 띄워주지 않습니다. 하지만 우리 비즈니스 로직에는 큰 문제를 일으키겠죠.
- 내부적으로 이름이 아닌 순서에 의존에서 분해하는 원리이기에 어쩔 수 없는 한계이기도 합니다.
- Kotlin 2.0 에서는 이 문제를 해결하겠다고 합니다. (아직 구체적으로 어떻게 해결할건지는 말해주지 않네요? 부디 우아하고 멋진 방식으로 해결해주시기를… 🫡)
Context Receivers
- KT-10468 (https://youtrack.jetbrains.com/issue/KT-10468/Context-receivers-multiple-receivers-on-extension-functions-properties)
- 이미 1.6.20 버전에서 공개 되었고 꾸준히 코틀린 개발자들 사이에서 회자되었던 기능입니다.
- 처음 공개 이후로 많은 피드백을 받았고 아직도 수정중이라고 합니다. 정확히 2.0 버전에 릴리즈 될 지는 확실하지 않고 2.x 대에 공개될거라고 하네요.
- 언급된 김에 간단히 소개하면 Context Receiver 는 코틀린에서 context 기반 설계를 가능하게 해주는 기능입니다.
- 물론 기존에도 확장 함수를 사용해서 이런 설계가 어느정도 가능했습니다. 코루틴이 그랬던 것 처럼요. 하지만 그 한계도 분명히 존재했습니다.
- 특정 클래스(클래스 A 라고 하겠습니다)의 확장 함수를 특정 context(클래스 B)에 종속시키고 싶다면 어떻게 해야할까요?
클래스 B 의 내부에 클래스 A 의 확장 함수를 선언해야합니다.
- 제가 만든 코드라면 상관 없겠지만 써드파티를 가져와서 사용중이라면 위 같은 구조로는 불가능합니다.
- 그리고 동시에 여러개의 Context 를 가질 수 없습니다. 확장 함수를 사용하는 방식이라 어쩔 수 없이 생긴 한계죠. - 예제 코드를 보면 더 확실히 와닿을 것 같은데요. 똑같은 목적의 코드를 Context Receiver 를 사용하지 않은 버전과 사용한 버전을 비교해보고 넘어갑시다.
// Context Receiver 사용 X
class ContextClass {
private val prefix = "PREFIX"
fun <K, V> Map<K, V>.printAll() {
forEach { (key, value) ->
println("$prefix $key and $value")
}
}
}
val context = ContextClass()
val map = mapOf("A" to 1, "B" to 2, "C" to 3)
with(context) {
map.printAll()
}
// Context Receiver 사용 O
class ContextClass {
private val prefix = "PREFIX"
}
context(ContextClass) //ContextClass 외부에 선언할 수 있다!
fun <K, V> Map<K, V>.printAll() {
forEach { (key, value) ->
println("$prefix $key and $value")
}
}
val context = ContextClass()
val map = mapOf("A" to 1, "B" to 2, "C" to 3)
with(context) {
map.printAll()
}
Explicit fields
- KT-14663 (https://youtrack.jetbrains.com/issue/KT-14663)
- 사소해보이지만 아주 유용한 기능
- 기존에 문제가 됐던 코드 먼저 보겠습니다. 저도 이런식의 코드를 많이 사용하는데요.
private val _applicationState = MutableStateFlow(State())
val applicationState: StateFlow<State>
get() = _applicationState
- 같은 변수지만 변경 가능한 타입은 해당 클래스 내부에서만 사용하고, 변경 불가능한 타입으로 클래스 외부에 노출시키도록 하는게 그 의도죠.
- 꼭 필요한 설계인건 맞지만 매번 이렇게 똑같은 변수를 두번씩 선언하는게 여간 번거로운 작업이 아니었죠.
- 이를 아래의 코드처럼 단순하게 선언할 수 있습니다.
val applicationState: StateFlow<State>
field = MutableStateFlow(State())
- 처음에 발표자가 “small but ‘very’ helpful feature” 라고 소개하길래 대체 뭘까 싶었는데 진짜 말그대로 작고 사소하지만 정말정말 유용한 기능이었습니다.. 멋집니다.
Kotlin Notebook
보고 정말 놀랐던 주제 중 하나인데요. 실무에서 데이터 관련 분석이나 시각화를 하려면 보통 Jupyter Notebook 같은 도구를 많이 활용합니다. 이런 도구에 코드를 담은 문서(notebook)를 작성해서 공유하는 식으로 사용하죠. 이제 코틀린으로 notebook 을 작성할 수 있습니다. 플러그인으로 지금도 사용할 수 있으니 시도해보는 것도 좋겠습니다. (https://plugins.jetbrains.com/plugin/16340-kotlin-notebook)
Kotlin DSL
Koltin DSL 이 이제 안드로이드 스튜디오에서 gradle 기본 언어로 선택된다고합니다. (android studio giraffe 버전 부터)
지금까지 계속 Kotlin DSL 을 gradle 언어로 사용할 것을 권장해왔지만 이제 디폴트라고 선언했네요. 저도 개인적으로 gradle 에서 이제 그루비보다는 Kotlin DSL 이 장점이 훨씬 많다고 느꼈습니다.
Kotlin at Google
구글 내부에서도 코틀린의 사용 빈도가 폭발적으로 증가했다고 합니다. 젯브레인과 협력하며 코틀린 에코시스템에 활발히 기여중이라고 하네요.
Kotlin Multiplatform
코틀린 멀티플랫폼이 소개된지는 꽤 됐는데 그동안 개인적으로 관심을 크게 안가지고 있었습니다. 제가 하는 일이 주로 서버사이드 애플리케이션 개발이기도 하고 코틀린 멀티플랫폼이 그다지 강력해보이지 않았거든요. 크로스 플랫폼이라는 개념 자체도 장점보다는 단점이 많아 보였구요.
하지만 이번 컨퍼런스 발표를 보고 나서는 생각이 조금 달라졌습니다. 크로스 플랫폼과 네이티브의 장점만 가져가려는 생각이 굉장히 흥미로웠습니다. 굳이 모든 코드를 공유하는게 아니라 공유할 부분만 모듈 식으로 공유하는 개념으로 보입니다.
그리고 이제는 Compose 로 android 와 IOS 는 UI 코드도 공유가 가능해졌습니다. 아직 알파버전이지만 compose for desktop 을 보면 IOS 도 stable 단계에 다다랐을 때 어떤 모습일지 기대가 됩니다.
주저리
젯브레인이 그리는 코틀린의 미래를 이번 KotlinConf’23 를 통해 조금이나마 엿볼 수 있었습니다. 코틀린은 기존 JVM 기반 언어들이 가지고있던 문제를 해결하고자 탄생했고 이제는 더 나아가 멀티플랫폼이라는 문제를 완성도있게 해결하려고합니다.
코틀린을 초창기부터 사용했던 건 아니지만 그래도 1.5 버전 정도 부터 본격적으로 학습함과 동시에 업데이트 내역이나 유트랙 이슈들을 지켜봐왔는데, 코틀린이라는 프로그래밍 언어는 언어 자체의 장점은 차치하더라도 활발한 커뮤니티를 동력으로 빠른 발전이 가능하다는 것이 정말 큰 강점인 것 같습니다.
나눠주는 티셔츠가 꽤 이뻐보였습니다.
맨 처음에 안드로이드 개발자 손! 서버 사이드 개발자 손! 하는데 안드로이드 개발자 많은거야 코틀린 컨퍼런스니 당연하다 싶었는데 서버 사이드 개발자도 그에 못지않게 많아보여서 놀랐습니다.
언젠가는 꼭 현장 참여해보고싶네요.😎 (KotlinConf’24..?)