Home About
Kotlin , Haskell

Kotlin Sealed クラスを使った Maybe の実装

Kotlin の Sealed クラスを使えば、 代数データ型の直和型を表現できる、という情報を得たので、MaybePokemon クラスをつかって試してみた。

main.kt にコードを書いていきます。

ポケモンデータクラスを用意:

data class Pokemon(val name: String, val kind: String)

ポケモンデータベースを作成:

val createPokemonDb: ()->List<Pokemon> = {
    val db = arrayListOf<Pokemon>()
    db.add(Pokemon("Eevee",      "normal"))
    db.add(Pokemon("Pidgeot",    "normal"))
    db.add(Pokemon("Pikachu",    "electric"))
    db.add(Pokemon("Squirtle",   "water"))
    db
}

このポケモンデータベースを使ってポケモンをその名前から引けるように findByName 関数を追加:

val findByName: (List<Pokemon>, String)->Pokemon? = {pokemonDb, name->
    val pokemons = pokemonDb.filter { it.name==name }
    if( pokemons.size>0 ){ pokemons.first() } else { null }
}

データベースに存在しないポケモン名が指定された場合に備えて、戻り値は Pokemon? にしています。(Pokemon または null)

これらを使用する main 関数を定義:

fun main(){
    val db = createPokemonDb()
    val result = findByName(db, "Pikachu")
    println(result)
}

Pikachu で検索しています。

実行してみましょう。

$ kotlinc main.kt -include-runtime -d main.jar
$ java -jar main.jar
Pokemon(name=Pikachu, kind=electric)

うまくピカチュを検索できました。

もし存在しないポケモン名で検索すると...

val result = findByName(db, "Golduck")
println(result)

結果は当然 null になります。

一旦、ここまでを整理。 現在の main.kt は以下の通り。

data class Pokemon(val name: String, val kind: String)

val createPokemonDb: ()->List<Pokemon> = {
    val db = arrayListOf<Pokemon>()
    db.add(Pokemon("Eevee",      "normal"))
    db.add(Pokemon("Pidgeot",    "normal"))
    db.add(Pokemon("Pikachu",    "electric"))
    db.add(Pokemon("Squirtle",   "water"))
    db
}

val findByName: (List<Pokemon>, String)->Pokemon? = {pokemonDb, name->
    val pokemons = pokemonDb.filter { it.name==name }
    if( pokemons.size>0 ){ pokemons.first() } else { null }
}

fun main(){
    val db = createPokemonDb()
    //val result = findByName(db, "Pikachu")
    val result = findByName(db, "Golduck")
    println( result )
}

findByName が null を返すかわりに MaybePokemon を返すようにしてみましょう。 Sealed クラスを使った MaybePokemon は以下の通り:

sealed class MaybePokemon {
    data class Just(val value: Pokemon): MaybePokemon()
    object Nothing: MaybePokemon()
}

つまり MaybePokemon 型は Nothing | Just Pokemon というような意味の型になります。

これを使うように findByName 関数を修正:

val findByName: (List<Pokemon>, String)-> MaybePokemon = {pokemonDb, name->
    val pokemons = pokemonDb.filter { it.name==name }
    if( pokemons.size>0 ){ MaybePokemon.Just(pokemons.first()) } else { MaybePokemon.Nothing }
}

実行してみましょう。

Pikachu で検索した場合:

$ kotlinc main.kt -include-runtime -d main.jar
$ java -jar main.jar
Just(value=Pokemon(name=Pikachu, kind=electric))

Just が返る。

Golduck で検索した場合:

$ kotlinc main.kt -include-runtime -d main.jar
$ java -jar main.jar
MaybePokemon$Nothing@610455d6

Nothing になる。

ちなみに、戻ってきた MaybePokemon が Just か Nothing かで処理を分岐する場合は、 たとえば when を使えば以下のように書ける。

val resultMaybe = findByName(db, "Pikachu")

val result: Any = when(resultMaybe) {
    is MaybePokemon.Just -> resultMaybe.value
    is MaybePokemon.Nothing -> ""
}

Just の場合は Pokemon 型、Nothing の場合は 文字列型が来るというちょっと気持ち悪いコードですが。

コード全体 main.kt:

data class Pokemon(val name: String, val kind: String)

sealed class MaybePokemon {
    data class Just(val value: Pokemon): MaybePokemon()
    object Nothing: MaybePokemon()
}

val createPokemonDb: ()->List<Pokemon> = {
    val db = arrayListOf<Pokemon>()
    db.add(Pokemon("Eevee",      "normal"))
    db.add(Pokemon("Pidgeot",    "normal"))
    db.add(Pokemon("Pikachu",    "electric"))
    db.add(Pokemon("Squirtle",   "water"))
    db
}

val findByName: (List<Pokemon>, String)-> MaybePokemon = {pokemonDb, name->
    val pokemons = pokemonDb.filter { it.name==name }
    if( pokemons.size>0 ){ MaybePokemon.Just(pokemons.first()) } else { MaybePokemon.Nothing }
}

fun main(){
    val db = createPokemonDb()

    val resultMaybe = findByName(db, "Pikachu")
    //val resultMaybe: MaybePokemon = findByName(db, "Golduck")

    val result: Any = when(resultMaybe) {
        is MaybePokemon.Just -> resultMaybe.value
        is MaybePokemon.Nothing -> ""
    }
    println( result )
}

まとめ

Sealed クラスを使うとたとえば、Haskell の Maybe 的なものを表現できる。 とはいえ、この実装では Pokemon 型以外を Maybe にしようとしたら MaybeFoo, MaybeBar, MaybeHoge のように 次々実装していかなければいけない。 このままではあまり実用性はない。