코틀린 2.1에 새로운 언어 기능 3가지가 프리뷰 형태로 추가됐습니다. 그에 대해서 간략히 살펴 봅니다.
새로운 기능을 테스트 하면서 몇 가지 공식 문서와 실제 내용이 일치하지 않는 부분이 있어서 테스트한 환경을 적어 둡니다. 내용을 보시면서 공식 문서와 다르다고 얘기한 부분들에 대해서는 이 환경을 감안하고 보시면 좋을 거 같습니다.
- Gradle Kotlin Plugin: 언어 버전 2.1.10 으로 설정
- IDE: IntelliJ IDEA Community Edition 2024.3.2.2
1. 인수(subject)가 있는 when의 가드(guard) 조건
when 에서 subject는 when 넘겨지는 인수를 말합니다. 코틀린에서는 when을 서브젝트가 있는 when (subject) { ... } 형태와 서브젝트가 없는 when { ... } 형태 모두 사용 가능합니다. 인수라고 표현해도 무방하기는 하며, Kotlin 공식 문서에서는 이 부분을 subject라는 단어를 사용하고 있습니다. 이 문서에서는 subject를 인수라고 표현하겠습니다.
guard라는 단어를 보호나 경계 등으로 번역할 수 있겠지만 이 문서에서는 그냥 가드라고 표현하겠습니다.
검사 대상이 있는 when 표현식이나 문에 가드(guard) 조건이 추가됐습니다. 가드 조건은 하나의 분기에서 좀 더 복잡하고 정교한 조건을 지정할 수 있게 해 줍니다. 코드를 통해서 살펴 보겠습니다.
sealed interface Animal {
val eatsPlants: Boolean
data class Cat(
val mouseHunter: Boolean,
override val eatsPlants: Boolean,
) : Animal {
fun feedCat() {
println("feedCat")
}
fun feedLettuce() {
println("feedLettuce")
}
}
data class Dog(
val breed: String,
override val eatsPlants: Boolean
) : Animal {
fun feedDog() {
println("feedDog")
}
}
}
fun giveLettuce() {
println("Giving lettuce to the animal that eats plants.")
}
fun feedAnimal(animal: Animal) {
when (animal) {
// 기본 조건만 있는 경우. Animal이 Dog일 때 animal.feedDog() 결과 반환
is Animal.Dog -> animal.feedDog()
// 기본 조건과 가드 조건이 있는 경우
// animal이 Animal.Cat이고 mouseHunter가 아닌 경우 animal.feedCat() 결과 반환
is Animal.Cat if !animal.mouseHunter -> animal.feedCat()
// animal이 Animal.Cat이고 mouseHunter이고 eatsPlants 가 true인 경우
// 한 가지 이상한 점은 -> 다음 부분에서도 위쪽의 "if !animal.mouseHunter -> " 다음 부분처럼
// animal이 Animal.Cat 으로 인식돼야 하는데, 여기서는 Animal 인터페이스로 인식돼서
// Animal.Cat#giveLettuce를 호출 못 한다는 것입니다.
// 아직 프리뷰 단계로 온전하지 못한 거 같습니다.
else if animal.eatsPlants -> giveLettuce()
// Returns "Unknown animal" if none of the above conditions match
else -> println("Unknown animal")
}
}
가드 조건은 앞에 있는 기본 조건이 true일 때만 평가됩니다. 기본 조건이 false 면 (어쩌면 당연히) 조건을 따지지 않습니다. 가드 조건에는 else if 도 지원합니다. 다만 else if는 아직 온전하지 않은 것 같습니다. 위에 예제 코드에 주석을 참고해 주세요.
가드 조건이 없는 경우에는 else 가 없는 경우 조건이 맞는 경우만 실행하고 그렇지 않으면 아무 실행도 하지 않습니다. 하지만, 가드 조건이 포함돼 있는 경우에는 else 가 없는 경우 모든 경우를 충족시켜야 합니다. 공식 문서에 보면 when이 문(statement)일 때는 가드 조건이 있어도 else는 생략 가능하다고 얘기하고 있습니다. 즉, when 표현식일 때만 이렇게 조건을 충족 시켜야 한다고 하는데요, 아직 프리뷰 버전이라 그런 건지 제 환경에 문제인지 문과 표현식 모두에 표현식과 같은 조건이 요구됐습니다. 이 부분은 프리뷰 상태에서 실제 사용하게 되는 경우 주의할 필요가 있을 거 같습니다.
// 가드 조건이 없는 경우 else 없어도 가능
when (animal) {
is Animal.Dog -> animal.feedDog()
is Animal.Cat-> animal.feedCat()
}
// 가드 조건이 있는 경우에 else를 없애려면 모든 경우를 충족해야 함
when (animal) {
is Animal.Dog -> animal.feedDog()
is Animal.Cat if !animal.mouseHunter -> animal.feedCat()
is Animal.Cat -> TODO()
}
다음과 같이 복합 조건도 (어쩌면 당연히) 사용할 수 있습니다.
when (animal) {
is Animal.Cat if (!animal.mouseHunter && animal.hungry) -> feedCat()
}
가드 조건은 다음과 같이 콤마로 구분된 다중 조건일 때는 사용하지 못합니다.
0, 1 -> print("x == 0 or x == 1")
현재는 프리뷰 단계이므로 가드 조건을 사용하기 위해서는 다음과 같은 옵션이나 설정이 필요합니다.
1. 컴파일러로 직접 컴파일 할 때
kotlinc -Xwhen-guards main.kt
2. Gradle을 사용할 때 - Gradle build 파일
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xwhen-guards")
}
}
2. 비지역(non-local) break와 continue
인라인 함수에 넘겨지는 람다에서 break나 continue를 사용할 수 있게 됐습니다. 예제 코드를 보면 바로 알 수 있습니다.
data class Foo(var bar: String?)
fun processList(elements: List<Foo>): Boolean {
for (element in elements) {
val variable = element.bar ?: run {
continue
}
if (variable == "baz") return true
}
return false
}
run 은 inline 함수이고 여기에 넘겨진 람다에서의 continue는 바깥에 있는 for 문을 현재 루프를 건너 뛰게 합니다. 이전에는 return 만 가능하던 것이 이제 break와 continue도 가능해 졌습니다.
현재는 프리뷰 단계이므로 가드 조건을 사용하기 위해서는 다음과 같은 옵션이나 설정이 필요합니다.
1. 컴파일러로 직접 컴파일 할 때
kotlinc -Xnon-local-break-continue main.kt
2. Gradle을 사용할 때 - Gradle build 파일
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xnon-local-break-continue")
}
}
3. 템플릿 문자열에 다중 $를 붙인 표현식 삽입
코틀린 공식 문서에서 표현한 "Multi-doller string interpolation"을 의미를 나타내기 위해 풀어 쓰다보니 제목이 좀 이상하기도 합니다만 내용은 간단합니다. 문자열에 표현식을 쓸 때 $하나로 시작하던 것을 원하는 수 만큼 지정할 수 있다는 것입니다. 이 게 추가된 이유는 문자열에 돈 달러를 표현하기 위해서 $를 써야할 때나 다른 문자열 템플릿 엔진이 $를 표현식을 사용하는 경우 ${'$'} 식으로 사용할 수 밖에 없던 것을 개선하기 위한 것입니다.
val foo = 2000
// bar = "$2000"
val bar = $$"$$$foo"
현재는 프리뷰 단계이므로 가드 조건을 사용하기 위해서는 다음과 같은 옵션이나 설정이 필요합니다.
1. 컴파일러로 직접 컴파일 할 때
kotlinc -Xmulti-dollar-interpolation main.kt
2. Gradle을 사용할 때 - Gradle build 파일
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xmulti-dollar-interpolation")
}
}
4. 멀티플랫폼: expect로 정의한 클래스나 인터페이스를 Java에서 같은 FQN으로 구현
코틀린 멀티플랫폼에서는 공통적으로 사용하는 클래스나 인터페이스를 expect 키워드를 붙여 정의를 하고 이를 각 플랫폼별로 실제 구현하는데 두 가지 방법이 있을 수 있습니다.
첫번째는 직접 실현(direct actualization)이라고 해서, 클래스 아이디를 같게 선언하는 것입니다. 두 번째는 actual typealias를 사용하여 클래스 아이디를 변경하여 선언하는 것입니다.
여기에서 Java쪽에 첫번째 방법이 가능해졌습니다. 이 기능도 실험적으로 추가됐지만, 현재로서는 본격적으로 공유하고 사용하라고 하지는 않을 예정인가 봅니다. 릴리스 계획을 보면 대략적으로 "... 하지만 공식 문서에서 이를 언급하지 않으며, 홍보하지도 않고, 사람들이 직접 시도해 보도록 장려하는 의미에서의 "실험적" 기능으로 간주하지도 않습니다." 식으로 적혀 있습니다.
실제로 2.1.0 새로운 기능을 설명하는 페이지에도 언급이 없고, (제가 못 찾았을 수도 있지만) 공식 문서에도 내용을 찾을 수 없습니다. 다만, 요즘 Kolin 공식 사이트에서 하고 있는 설문 조사에서 언급을 하고 있습니다. 관심이 있으시거나 멀티플랫폼을 사용하시는 분은 다음의 링크를 참고하시면 좋을 거 같습니다.
https://youtrack.jetbrains.com/issue/KT-67202#release-plan
KMP feature request: actualize expect by Java but use the same FQN : KT-67202
Currently, it's possible to actualize Kotlin with Java class by using "actual typealias", but "actual typealias" imposes a restriction: FQNs of the Java class and expect class must be different (it's not possible to create "actual typealias" that "targets
youtrack.jetbrains.com
참고
'Kotlin' 카테고리의 다른 글
공식 문서로 배우는 코틀린 - 42. Scope functions (0) | 2024.03.18 |
---|---|
공식 문서로 배우는 코틀린 - 41. Reflection (0) | 2024.03.17 |
공식 문서로 배우는 코틀린 - 40. Destructuring declarations (0) | 2024.03.17 |
공식 문서로 배우는 코틀린 - 39. Annotations (0) | 2024.03.16 |
공식 문서로 배우는 코틀린 - 38. This expressions (0) | 2024.03.16 |