Skip to content

Instantly share code, notes, and snippets.

@mosugi
Last active May 12, 2023 03:17
Show Gist options
  • Save mosugi/fe4b750309297dcb0b4ae15671521404 to your computer and use it in GitHub Desktop.
Save mosugi/fe4b750309297dcb0b4ae15671521404 to your computer and use it in GitHub Desktop.
KotlinでTypeScriptのようなUnion型を実現するには?

KotlinはTypeScriptのような直接的なUnion型をサポートしていません。

しかし、シーリングクラス(Sealed Class)、またはジェネリックを使用することで、類似の機能を実現することが可能です。

シーリングクラス(Sealed Class)

Sealedクラスは限定的なクラス階層を定義するために使用されます。

Union型のような振る舞いを実現するためには、

各種類の値を表す独自のクラスを定義することができます。以下に一例を示します:

sealed class UnionType {
    data class Type1(val value: Int) : UnionType()
    data class Type2(val value: String) : UnionType()
    // Add more types as needed
}

fun useUnionType(value: UnionType) {
    when(value) {
        is UnionType.Type1 -> println(value.value + 1)
        is UnionType.Type2 -> println(value.value.toUpperCase())
        // Don't forget to handle all possible types
    }
}

この方法では、UnionTypeのインスタンスがType1Type2のどちらかであることがコンパイル時に確定します。

これにより、when式を使って各型を安全に処理することができます。

インターフェース

Kotlinでは、interfaceとenumを組み合わせることで、ある程度Union型のような機能を模倣することが可能です。

interfaceを実装した複数のenumを用いることで、それらのenumをまとめて扱うことができます。以下に一例を示します:

interface Animal {
    val sound: String
}

enum class Dog : Animal {
    BULLDOG, BEAGLE, RETRIEVER;

    override val sound: String
        get() = "Woof"
}

enum class Cat : Animal {
    PERSIAN, SIAMESE, MAINE_COON;

    override val sound: String
        get() = "Meow"
}

fun makeSound(animal: Animal) {
    println(animal.sound)
}

fun main() {
    makeSound(Dog.BULLDOG)
    makeSound(Cat.PERSIAN)
}

この例では、Animalというinterfaceを定義し、そのinterfaceを実装したDogCatというenumを定義しています。

Animalsoundプロパティをオーバーライドして、それぞれの動物の鳴き声を定義しています。

makeSound関数では、Animal型の引数を受け取り、その動物の鳴き声を表示します。

この関数にはDogCatも渡すことができます。これにより、異なるenumをまとめて扱うことができ、ある程度Union型のような振る舞いを模倣することが可能です。

ただし、この方法では異なるenumの値が同じ名前を持つことは許されていません。

例えば、DogCatの両方でPERSIANという値を定義することはできません。これはUnion型では許されることであるため、完全なUnion型の代替とは言えません。

ジェネリック

別の方法として、ジェネリックを使用してUnion型のような機能を模倣することもできます。

この方法の欠点は、ジェネリックには型消去(Type Erasure)が存在するため、実行時に具体的な型情報が利用できない場合があることです。

以下に一例を示します:

class UnionType<A, B>(val asA: A? = null, val asB: B? = null) {
    fun <R> whenType(onA: (A) -> R, onB: (B) -> R): R = 
        asA?.let(onA) ?: asB?.let(onB) ?: throw Exception("Neither A or B")
}

typealias

typealiasenumを組み合わせて使用する

enum class Color {
    RED, GREEN, BLUE
}

typealias RGB = Color

fun printColor(color: RGB) {
    when (color) {
        RGB.RED -> println("Red color")
        RGB.GREEN -> println("Green color")
        RGB.BLUE -> println("Blue color")
    }
}

fun main() {
    printColor(RGB.RED)  // Prints "Red color"
}

この例では、Colorというenumに対してRGBというtypealiasを定義しています。その結果、Colorの値をRGBという名前で参照することができます。これにより、例えばColorが複雑な型であった場合や、特定の文脈でより明確な名前を使いたい場合などに、コードの可読性を向上させることができます。

https://discuss.kotlinlang.org/t/union-types/77/14

提供されたリンクはKotlinの公式フォーラムで、"Union types"というトピックに関するディスカッションが行われています。以下に要約を示します。

ディスカッションの中で、ユーザーはKotlinにおけるUnion型のサポートに関する質問をしています。Union型は、複数の型を1つの型として表現するもので、他の言語(例:TypeScriptやScala)では一般的に使用されています。

Kotlinの言語設計者からの回答によると、Kotlinでは直接的なUnion型のサポートは提供されていません。代わりに、他の方法を使用してUnion型の代替を実現する必要があります。

いくつかの代替手段が提案されました。例えば、Nullable型を使用することで、値が存在しないことを表現することができます。また、sealed classやenum classを使用して、特定の制限付きのセット内でのみ値を受け入れるような型を定義することもできます。

また、Kotlin 1.5以降では、パターンマッチングの強化として「Pattern Matching for Kotlin」(Kotlinのパターンマッチング)が導入されました。これにより、より柔軟なデータのマッチングと操作が可能になり、Union型の一部の用途をエレガントに表現できるようになりました。

最後に、ディスカッションではUnion型がKotlinに直接組み込まれる可能性についても議論されていますが、公式の提案や計画はまだ存在していません。

要約すると、Kotlinは直接的なUnion型のサポートを提供していませんが、Nullable型、sealed class、enum class、およびKotlin 1.5以降のパターンマッチングを使用することで、Union型の代替手段を実現することができます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment