2022년 2월 5일 토요일

[코틀린] 1.6 변경사항

 

언어 문법

when절에서의 sealed

sealed 클래스를 이용한 when 절에서 sealed 클래스의 모든 서브클래스를 철저하게 확인하여 컴파일타임에 경고로 알려주는 기능이 정식으로 지원됩니다. 이러한 문법은 대수적(algebraic) 자료형으로 도메인을 설계해야 할 때 유용합니다. 예를들어, 연락 히스토리를 표현하는 모델을 다음과 같이 sealed 클래스 구조로 표현했다고 가정하겠습니다.

sealed class Contact {
   data class PhoneCall(val number: String) : Contact()
   data class TextMessage(val number: String) : Contact()
   data class InstantMessage(val type: IMType, val user: String) : Contact()
}

만약, 각기 다른 연락 히스토리 타입마다 다른 결과를 반환하는 코드를 작성한다면, 컴파일러는 개발자가 모든 타입을 확인하지 못한 경우에 에러로 표시해 줄 것입니다.

fun Rates.computeMessageCost(contact: Contact): Cost =
   when (contact) { // ERROR: 'when' expression must be exhaustive
       is Contact.PhoneCall -> phoneCallCost
       is Contact.TextMessage -> textMessageCost
   }

이는 개발자가 잊어버리고 확인하지 못할법한 실수를 방지하도록 도와줍니다. 1.6에서는 컴파일 수행시 Non-exhaustive 'when' statements on sealed class/interface will be prohibited in 1.7. Add an 'is InstantMessage' branch or 'else' branch instead. 라는 경고를 보여주고, 1.7부터는 아예 컴파일타임에 에러가 발생하도록 변경될 예정입니다.

슈퍼타입으로의 Suspending functions

기존 코틀린에서는 다음과 같이 함수를 클래스의 상위타입으로 지정할 수 있었습니다.

class MyCompletionHandler : () -> Unit {
   override fun invoke() { doSomething() }
}

코틀린 1.6부터는 suspend 함수도 클래스의 상위타입으로 지정할 수 있습니다.

class MyClickAction : suspend () -> Unit {
   override suspend fun invoke() { doSomething() }
}

suspend 변환

코틀린 1.6에서는 일반 함수 타입을 suspend 함수 타입으로 자동으로 변환하도록 지원합니다. 이제 suspend 함수타입의 인자를 넘겨야 하는 부분에서 일반 함수타입의 구문을 넘겨도 컴파일러가 자동으로 인지하여 바꿔줍니다. 이는 큰 변경사항은 아니지만, 일반 함수타입과 suspend 함수타입 사이의 성가신 불일치를 해결한 수정입니다.

예를들어 Flow.collect() 함수는 suspend 함수타입을 인자로 받는데, 1.6부터는 일반 함수타입을 인자로 넘길 수 있습니다.

fun processItem(item: Item) { /* ... */ }

flow.collect { processItem(it) }
flow.collect(::processItem)

재귀적 제네릭 타입에서의 타입 추론 개선

1.6부터 코틀린 컴파일러는 기본적으로 재귀적인 제네릭이라면, 타입 상한에 해당하는 타입파라미터를 기반으로 타입 매개변수를 유추할 수 있습니다. 이는 자바에서 빌더 API를 만들 때 종종 사용하는 것처럼, 재귀적인 제네릭을 이용한 다양한 패턴의 코드를 작성할 수 있도록 도와줍니다.

// Before 1.5.30
val containerA = PostgreSQLContainer<nothing>(DockerImageName.parse("postgres:13-alpine")).apply {
    withDatabaseName("db")
    withUsername("user")
    withPassword("password")
    withInitScript("sql/schema.sql")
}

// With compiler option in 1.5.30 or by default starting with 1.6.0
val containerB = PostgreSQLContainer(DockerImageName.parse("postgres:13-alpine"))
    .withDatabaseName("db")
    .withUsername("user")
    .withPassword("password")
    .withInitScript("sql/schema.sql")

Note 재귀적 제네릭 타입은 타입상한을 이용하여 해당 클래스/인터페이스로 제한하는 방법

interface Animal<T: Animal<T>> {
    val name: String
    fun compareName(other: T): Boolean {
        return name == other.name
    }
}

data class Dog(override val name: String): Animal<Dog>
data class Cat(override val name: String): Animal<Cat>

Builder inference 개선

코틀린 1.5.30에서 -Xunrestricted-builder-inference라는 컴파일러 옵션이 도입되었습니다. 이 옵션을 통해 빌더 호출에 대한 타입 정보를 빌더 람다의 내부로 가져오는게 가능해지도록 합니다. 이름 그대로, buildList() 내부에서 get()처럼 아직 유추되지 않은 유형의 인스턴스를 반환하는 호출을 만드는 기능을 제공하는 것입니다.

코틀린 1.6부터는 더이상 -Xunrestricted-builder-inference 컴파일 옵션을 명시할 필요가 없습니다. -Xenable-builder-inference 컴파일 옵션을 이용하면, @BuilderInterface 어노테이션을 사용하지 않고도 제네릭 빌더를 작성할 수 있고, 일반 타입 추론이 타입의 정보를 해결할 수 없다면 빌더 추론을 자동으로 활성화 할 수 있습니다.

표준 라이브러리

표준 입력을 위한 새로운 함수

새로 코틀린을 접하는 이들에게 더 나은 경험을 제공하기 위해, 코틀린 1.6에서는 표준입력으로 라인을 읽은 후에 !! 연산자로 null이 아님을 명시해주어야 했던 필요를 삭제했습니다. 다음과 같이 동작하는 새로운 함수를 도입했습니다.

  • readIn()은 EOF에 도달하면 예외를 발생시킵니다. readLine() 함수로 매 라인을 null체크하는 대신 readIn() 함수를 사용하길 권장합니다.
  • 기존의 readLine()함수와 동일한 동작을 하는 readInOrNull() 함수를 추가했습니다.

readln 함수의 네이밍 컨벤션은 println 으로부터 가져와서, 코틀린을 처음 접하는 이들도 친숙할 수 있습니다. 이 함수는 JVM, Native에서 사용가능합니다.

fun main() {
    println("Input two integer numbers each on a separate line")
    val num1 = readln().toInt()
    val num2 = readln().toInt()
    println("The sum of $num1 and $num2 is ${num1 + num2}")
}

Duration API 안정화

많은 분들의 피드백 덕분에 Duration API는 드디어 안정화되어 배포되었습니다. 1.5.30에서는 Duration.toString() 의 가독성을 향상시키고, 문자열로부터 Duration을 파싱하는 새로운 함수를 도입했었고, 이번 1.6에서의 변경사항은 다음과 같습니다.

  • toComponents() 함수의 days 인자의 타입이 Int에서 Long으로 변경되었습니다.
  • DurationUtil enum은 이제 타입엘리어스가 아닙니다. JVM에서 java.util.concurrent.TimeUnit을 위해 타입엘리어스로 사용한 경우는 없습니다.
  • Int.seconds 같은 확장 프로퍼티들을 다시 가져왔습니다. 이 프로퍼티의 적용을 제한하기 위해, Duration의 컴패니언 클래스에서만 사용가능합니다.

typeOf() 안정화

코틀린 1.6에서 typeOf() 함수가 안정화 되었습니다. 이 함수는 1.3.40에서부터 JVM환경에서 실험적 API로 사용가능했고, 1.6부터는 JVM뿐 아니라 어떤 코틀린 플랫폼에서도 사용가능하며, 플랫폼의 컴파일러에서 유추가능한 코틀린의 타입을 KType으로 반환합니다.

Collection 빌더 안정화

코틀린 1.6부터 buildMap()buildList()buildSet()이 안정화되었습니다. 빌더로부터 반환되는 Collection은 읽기전용 상태에서 serializable입니다.

정수의 bit 회전연산 안정화

코틀린 1.6에서 rotateLeft()rotateRight() 함수가 안정화되었습니다.

  • rotateLeft : 인자로 주어진 카운트만큼 bit를 왼쪽으로 shift연산하고, 왼쪽에서 넘치는 bit를 오른쪽으로 추가함
  • rotateRight : 인자로 주어진 카운트만큼 bit를 오른쪽으로 shift연산하고, 오른쪽에서 넘치는 bit를 왼쪽으로 추가함
val number: Short = 0b10001
println(number.rotateRight(2).toString(radix = 2)) // 100000000000100
println(number.rotateLeft(2).toString(radix = 2))  // 1000100

문자열을 시퀀스로 변환하는 정규식 함수 안정화

코틀린 1.6에서 문자열을 시퀀스로 변환해주는 splitToSequence() 함수가 안정화되었습니다.

val colorsText = "green, red , brown&blue, orange, pink&green"
val regex = "[,\\s]+".toRegex()
val mixedColor = regex.splitToSequence(colorsText)
    .onEach { println(it) }
    .firstOrNull { it.contains('&') }
println(mixedColor) // "brown&blue"

compareTo 함수에 중위연산(infix) 키워드 추가

Comparable.compareTo 함수에 infix 키워드를 추가하여 중위연산자로 사용할 수 있게 되었습니다.

class WrappedText(val text: String) : Comparable<WrappedText> {
    override fun compareTo(other: WrappedText): Int =
        this.text compareTo other.text
}

JVM, JS에서의 replace(), replaceFirst()의 동작 일원화

코틀린 1.6 전에는 group 레퍼런스를 포함한 문자열의 경우에 replace()와 replaceFirst() 정규식 함수가 JVM과 JS환경에서 서로 다르게 동작했었습니다. 1.6부터는 JS환경의 결과가 JVM과 동일해지도록 수정했습니다.

원문

https://blog.jetbrains.com/kotlin/2021/11/kotlin-1-6-0-is-released/

댓글 없음:

댓글 쓰기