본문 바로가기
Kotlin/Release Notes

[Kotlin Release Notes] M10 is out

by 노력남자 2023. 8. 28.
반응형

2014년 12월 17일

 

축제가 시작되기 바로 직전에, 우리는 Kotlin의 다음 이정표인 M10을 릴리스하였습니다. 동적 타입과 더 많은 기능을 추가했습니다. 어떤 변화가 M10에서 우리에게 오는지 살펴보겠습니다.


언어 개선 사항

 

언어의 몇 가지 개선 사항 중에서 특히 다음과 같은 것들이 있습니다:


인라인 함수의 타입 인자 재구성


M10 이전에 우리는 때때로 다음과 같은 코드를 작성했습니다:

 

fun TreeNode.findParentOfType(clazz: Class): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p?.parent
    }
    return p as T
}

 

여기서는 트리를 올라가며 특정 유형을 가진 노드를 확인하기 위해 리플렉션을 사용했습니다. 모두 괜찮지만, 호출 위치는 약간 어지럽게 보입니다:

 

myTree.findParentOfType(javaClass())

 

실제로 원하는 것은 이 함수에 단순히 유형을 전달하고 싶은 것입니다. 즉, 다음과 같이 호출하고 싶습니다:

 

myTree.findParentOfType()

 

그러나 그러려면 함수 내부에서 유형에 액세스하기 위해 reified 제네릭이 필요하며, JVM에서 reified 제네릭은 비용이 많이 듭니다...

다행히 Kotlin은 인라인 함수를 지원하며, 이제 reified 타입 매개변수를 지원하므로 다음과 같이 작성할 수 있습니다:

 

inline fun TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p?.parent
    }
    return p as T
}

 

타입 매개변수를 reified 한정자로 지정하였으므로 함수 내에서 액세스할 수 있습니다. 마치 일반 클래스인 것처럼 작동합니다. 함수가 인라인화되기 때문에 리플렉션이 필요하지 않으며, !is와 같은 일반 연산자가 작동합니다. 또한 위에서 언급한 대로 다음과 같이 호출할 수 있습니다: myTree.findParentOfType<MyTreeNodeType>().

많은 경우에는 리플렉션이 필요하지 않을 수 있지만, reified 타입 매개변수와 함께 리플렉션을 사용할 수도 있습니다: javaClass()를 사용하여 액세스할 수 있습니다:

 

inline fun methodsOf() = javaClass().getMethods()

fun main(s: Array<String>) {
    println(methodsOf().joinToString("\n"))
}

 

(인라인 처리되지 않은) 일반 함수에는 reified 매개변수를 가질 수 없습니다. 실행 시점 표현이 없는 타입 (예: reified 타입 매개변수나 Nothing과 같은 가상 타입)은 reified 타입 매개변수의 인수로 사용할 수 없습니다.

이 기능은 리플렉션을 기반으로 하는 프레임워크의 코드를 단순화하기 위해 고안되었으며, 내부 실험 결과 이 기능이 잘 작동한다는 것을 보여줍니다.


선언 지점 변화에 대한 확인


Kotlin은 처음부터 선언 지점 변위를 지원하고 있었지만, 해당 확인 사항은 오랫동안 컴파일러에 없었습니다. 이제 해당 확인 사항이 컴파일러에 추가되었습니다. 따라서 우리가 타입 매개변수를 in이나 out으로 선언하고 클래스 본문에서 잘못 사용하면 컴파일러가 경고합니다:

 

class C {
    fun foo(t: T) {} // 오류
}

 

이 예에서 T가 out으로 선언되었기 때문에 (즉, 클래스는 T에서 공변적입니다), foo() 함수에 매개변수로 가져올 수 없습니다. T를 반환할 수만 있습니다.

참고로 private 선언은 변위 제한을 위반할 수 있습니다. 예를 들어:

 

class C(t: T) {
    private var foo: T = t
}

 

foo의 setter가 인수로 T를 사용하며, 따라서 T에 대한 out 제한을 위반하지만, 컴파일러는 이를 허용하고 foo에 대한 액세스 권한을 같은 인스턴스의 C에만 부여합니다. 따라서 C의 다음 함수는 컴파일되지 않습니다:

 

// 클래스 C 내부
private fun copyTo(other: C) {
    other.foo = foo // 오류: 다른 C의 인스턴스에서 foo에 액세스할 수 없음
}

 

이는 변경 사항이며, 이전에 컴파일된 일부 코드가 깨질 수 있습니다. 그러나 수정하지 않으면 런타임 예외가 발생할 가능성이 높기 때문에 컴파일러 오류가 유용할 것입니다 :)


타입 추론은 사용 지점 변위를 지원합니다


타입 인자 추론이 사용 지점 변위를 보다 편리하게 수용할 수 있도록 개선되었습니다. 이제 제네릭 함수, 예를 들어 reverseInPlace()를 Array<out Number>와 같은 프로젝션된 타입에서 호출할 수 있습니다.

 

fun example(a: Array) {
    a.reverseInPlace()
}

 

여기서 reverseInPlace은 다음과 같이 정의되어 있습니다:

 

fun  Array.reverseInPlace() {
    fun (i in 0..size() / 2) {
        val tmp = this[i]
        this[i] = this[size - i - 1]
        this[size - i - 1] = tmp
    }
}

 

기반 메커니즘은 초기에 Ross Tate의 "Mixed-Site Variance" 논문에서 제안되었습니다.

 

Varargs가 프로젝션된 배열로 변환됨


다른 하나의 변경 사항은 조금 어려운 문제의 수정으로 인해 발생하였습니다. 이 문제는 때로는 꽤 귀찮은 문제일 수 있습니다. String?의 vararg를 가진 함수를 가지고 있을 때, String 배열을 전달할 수 있어야 합니다. M10 이전에는 불가능했지만, T의 vararg는 Array<T>로 컴파일되었지만, 이제 Array<out T>로 컴파일되므로 다음 코드가 작동합니다:

 

fun takeVararg(vararg strings: String?) { ... }

val strs = arrayOf("a", "b", "c")
takeVararg(*strs)

 

JavaScript 개선 사항과 변경 사항


이 버전에서는 동적 타입 지원과 함께 JavaScript에 중요한 업데이트가 있습니다.


동적 지원


동적 언어와 대화하는 가장 좋은 방법은 동적으로 대화하는 것입니다. 이것이 우리가 동적 타입으로 선언할 수 있게 해주는 soft 키워드 dynamic을 도입한 이유입니다. 현재 이 기능은 JVM이 아닌 JavaScript를 타겟팅할 때만 지원됩니다.

JavaScript와 상호 운용할 때 이제 함수가 매개변수로 동적 타입을 취하거나 반환할 수 있습니다.

 

fun interopJS(obj: dynamic): dynamic {
   ...
   return "any type"
}

 

더 많은 세부 정보와 사용 시나리오 및 제한 사항은 별도의 블로그 게시물에서 동적에 대해 더 자세히 다룰 것입니다. 기술적인 세부 정보는 사양 문서를 참조하십시오.

 

새로운 어노테이션

 

JavaScript 상호 운용성을 더 쉽게하기 위해 일련의 어노테이션을 추가했습니다. 특히 nativeInvoke, nativeGetter 및 nativeSetter가 있습니다.

함수 bar에 nativeInvoke 어노테이션이 붙어 있다면 foo.bar() 호출은 JavaScript에서 foo()로 변환됩니다. 예를 들어:

 

class Foo {
    nativeInvoke
    fun invoke(a: Int) {}
}

val f = Foo() 
f(1) // f(1)로 변환, f.invoke(1)이 아님
f.invoke(1) // 또한 f(1)로 변환

 

비슷한 방식으로 nativeGetter 및 nativeSetter를 사용하여 JavaScript에서 사용 가능한 인덱스 접근을 할 수 있습니다:

 

native("Array")
class JsArray {
    nativeGetter
    fun get(i: Int): T = noImpl
    nativeSetter
    fun set(i: Int, v: T): Unit = noImpl
}

 

native* 어노테이션 없이 get 및 set 호출 (a[i] = j와 같은 관례를 따르는 것도 포함)은 a.get(...) 및 a.set(...)으로 변환되지만, 위와 같이 어노테이션이 있는 경우에는 JavaScript에서 대괄호 연산자로 변환됩니다:

 

a[0] // a[0]로 변환, a.get(0)이 아님
a.get("first") // a["first"]로 변환
a[2] = 3 // a[2] = 3으로 변환

 

이러한 어노테이션은 다음 경우에 사용할 수 있습니다:

- 네이티브 선언의 비확장 멤버 함수


- 최상위 확장 함수


Kotlin.js 출력 - 변경 사항


이전에 새 프로젝트를 생성할 때 kotlin.js 런타임이 scripts라는 폴더에 생성되었습니다. M10부터는 이 파일이 처음 컴파일될 때 생성되며 출력 폴더 (기본값은 out)에 배치됩니다. 이로써 라이브러리 및 프로젝트 출력이 이제 동일한 루트 폴더 아래에 위치하므로 배포 시나리오가 훨씬 쉬워졌습니다.


kotlin-js 컴파일러에 대한 새로운 no-stdlib 옵션 - 변경 사항


이제 kotlin-js 컴파일러에 no-stdlib라는 명령 줄 옵션을 제공합니다. 이 옵션을 지정하지 않으면 컴파일러는 번들화된 표준 라이브러리를 사용합니다. 이것은 M9부터의 동작과 변경된 것입니다.

 

js code

 

Kotlin 코드에서 직접 JavaScript 코드 출력 가능

 

js("var value = document.getElementById('item')")

 

이렇게 하면 매개변수로 제공된 코드가 컴파일러가 생성하는 AST JavaScript 코드에 직접 주입됩니다.

보시다시피, 이번 릴리스에서 JavaScript에 대한 많은 새로운 개선 사항이 추가되었으며, 이러한 내용은 별도의 게시물에서 더 자세히 다룰 것입니다.

 

자바 상호 운용성


[propertyStatic]을 사용한 속성

 

이제 속성을 [propertyStatic]으로 표시하여 접근자를 정적 메서드로서 Java에서 볼 수 있도록 할 수 있습니다.


객체의 정적 필드


이제 어떤 객체의 속성은 정적 필드를 생성하여 platformStatic 주석을 사용하지 않고도 Java에서 쉽게 사용할 수 있도록 합니다.


JNI 및 [native]


Kotlin은 이제 kotlin.jvm 패키지에 정의된 [native] 주석을 통해 JNI를 지원합니다. 네이티브 메서드를 선언하려면 해당 주석을 메서드에 단순히 적용하면 됩니다:

 

import kotlin.jvm.*
import kotlin.platform.*

class NativeExample {
    native fun foo() // 네이티브 메서드

    class object {
        platformStatic native fun bar() // 정적 네이티브 메서드
    }
}

 

다음은 Android 및 NDK와 함께 네이티브 선언을 사용하는 예제입니다.

 

IntelliJ IDEA 개선 사항


IntelliJ IDEA 영역에서 몇 가지 더 개선 사항이 있습니다. 이에는 다음이 포함됩니다:


혼합 프로젝트에서의 증분 컴파일


증분 컴파일을 향상시켜 M10에서는 이제 Kotlin 및 Java 코드 간의 종속성을 지원합니다. 이제 Java 코드를 변경할 때 Kotlin 코드의 관련 부분이 다시 컴파일됩니다. 증분 컴파일은 Kotlin 컴파일러 옵션에서 활성화됩니다.


디버거에서 HotSwap 수정됨


이제 디버깅 중에 Kotlin 코드를 다시 컴파일하면 해당 코드가 원활하게 디버그 중인 프로세스에 다시 로드됩니다.


식 평가: 완성 개선


디버그 세션 중에 식을 평가할 때 캐스트가 필요한 경우 자동으로 추가됩니다. 예를 들어 Any에서 특정 타입으로 다운캐스트할 때입니다.

 

 

참조 복사


이제 Kotlin 심볼의 완전한 참조를 얻을 수 있습니다. 마치 Java 코드에서 IntelliJ IDEA와 같이 할 때처럼요.

 

 

클래스 및 패키지용 사용에서 생성


이제 클래스와 패키지에 대한 사용에서 생성할 수 있으며, 이는 TDD 작업 흐름에 큰 기여를 합니다. TDD를 수행하지 않더라도 새 요소를 생성하는 데 거리감을 최소화합니다.

 

 

change signature에서의 제네릭


Change Signature 리팩터링은 이제 기본 클래스 함수가 제네릭을 사용하도록 업그레이드되는 경우 제네릭을 지원합니다. 본질적으로 다음 시나리오에서

 

open class Base {
    open fun baseMethod(t: T, k: K) {}
}

class Derived: Base> {
    override fun baseMethod(a: List, b: Y) {}
}

 

Base.baseMethod의 시그니처가 baseMethod<T>(t: List<T>, k: K?)로 변경되면 Derived.baseMethod의 시그니처도 >baseMethod<Y>(a: List<Y>, b: List<X>?)로 적절하게 변경됩니다.

 

완성 개선


완성 항목의 순서가 개선되었으며, 즉시 멤버가 강조되었습니다. 스마트 완성은 기대되는 유형의 하위 클래스를 찾습니다. 완성 성능이 크게 향상되었습니다.


실행 가능한 객체


이제 [platformStatic] main 함수를 선언한 객체를 IDE에서 실행할 수 있습니다:

 

import kotlin.platform.*

object Hello {
    platformStatic fun main(args: Array) {
        println("Hello")
    }
}

 

객체를 마우스 오른쪽 버튼으로 클릭하고 실행을 선택하면 됩니다.

 

에디터에서 코드 커버리지 강조 표시


코드 커버리지가 있는 Kotlin 코드를 실행하면 에디터에서 커버된 줄과 커버되지 않은 줄을 표시해 줍니다 (IntelliJ IDEA 14에서만 사용 가능).

 

JavaScript 프로젝트 구성


IDE 플러그인은 이제 Maven 프로젝트를 Kotlin/JS와 함께 작동하도록 자동으로 구성할 수 있습니다. 또한 Kotlin의 런타임 라이브러리 버전이 오래되었을 경우 IDE에서 업데이트하도록 요청하며, 플러그인 배포에서 라이브러리를 복사하는 대신 해당 라이브러리를 사용할지 여부를 선택할 수 있습니다.

요약


M10을 설치하려면 IntelliJ IDEA 14(또는 이전 버전)에서 플러그인을 업데이트하면 됩니다. 항상 플러그인 리포지토리에서 플러그인을 찾을 수 있습니다. 릴리스 페이지에서 독립형 컴파일러를 다운로드할 수도 있습니다.

 

원문

 

https://blog.jetbrains.com/kotlin/2014/12/m10-is-out/

 

반응형

댓글