Home About
Kotlin , Maybe , Monad

Kotlin で Maybe その4

Kotlin で Maybe その3をベースにさらに修正した。 Maybe を monad にする覚え書き。

環境

$ kotlin -version
Kotlin version 2.0.20-release-360 (JRE 17.0.12+7-Ubuntu-1ubuntu222.04)

Maybe<T>

// main.kts

sealed class Maybe<T>(val value: T?) {
    fun <R> bind(transform: (T)->Maybe<R>): Maybe<R> {
        return if( value!=null ){
            transform(value)
        } else {
            Maybe<R>.Nothing()
        }
    }

    override fun toString(): String {
        return if( value!=null ){ "Just($value)" } else { "Nothing" }
    }

    companion object {
        fun <T> `return`(value: T?): Maybe<T> {
            return if( value!=null ){
                Maybe<T>.Just(value)
            } else {
                Maybe<T>.Nothing()
            }
        }
    }

    class Just<T>(value: T): Maybe<T>(value)
    class Nothing<T>(): Maybe<T>(null)
}

bind の実装について補足: bind はその内部の処理で value が null かどうかで分岐するよりは、 when を使って Just か Nothing かで分岐した方が(気分が)よいのだが、 そうすると value の型が T? のままなので、 value!! と記述しなければいけない。 これが気持悪い。 もそれだったら、普通に if を使えばいいかな。

bind を when で実装した場合:

    fun <R> bind(transform: (T)->Maybe<R>): Maybe<R> {
        return when(this){
            is Maybe.Just -> {
                transform(value!!)
            }
            is Maybe.Nothing -> {
                Maybe<R>.Nothing()
            }
        }
    }

まあ、どっちでもいい。

この Maybe<T> を使うコード例。

二つの Maybe<Int> を足し合わせる関数 add :

val add: (Maybe<Int>, Maybe<Int>)-> Maybe<Int> = { maybeA, maybeB->
    maybeA.bind( {a: Int->
        maybeB.bind( {b: Int->
            Maybe.Just(a+b)
            //Maybe.`return`(a+b)
        } )
    } )
}

こんな風に使う:

val one: Maybe<Int> = Maybe.Just(1)
val two: Maybe<Int> = Maybe.Just(2)
val result: Maybe<Int> = add(one, two)
println(result)

実行:

$ kotlin main.kts
Just(3)

Maybe.Nothing() と足し合わせようとする場合:

val one: Maybe<Int> = Maybe.Just(1)
val nothing: Maybe<Int> = Maybe.Nothing()
val result: Maybe<Int> = add(one, nothing)
println(result)

実行:

$ kotlin main.kts
Nothing

型の異なる2つの Maybe ( Maybe<Int>Maybe<Float> ) を足す関数 add2 :

val add2: (Maybe<Int>, Maybe<Float>)-> Maybe<Float> = { maybeA, maybeB->
    maybeA.bind( {a: Int->
        maybeB.bind( {b: Float->
            Maybe.Just(a.toFloat() + b)
        } )
    } )
}

使ってみる。

val one: Maybe<Int> = Maybe.Just(1)
val two: Maybe<Float> = Maybe.Just(2f)
val result: Maybe<Float> = add2(one, two)
println(result)

実行:

$ kotlin main.kts
3f

型が (Int)->Maybe<Int> のような incdec 関数を用意しておけば...このように:

val inc:(Int)->Maybe<Int> = { v->
    Maybe.Just( v+1 )
}

val dec:(Int)->Maybe<Int> = { v->
    Maybe.Just( v-1 )
}

次のように(失敗の可能性ある計算を)記述できる。

val result: Maybe<Int> = Maybe.Just(1).bind( inc ).bind( inc ).bind( dec )
println(result)

実行:

$ kotlin main.kts
Just(2)

必ず失敗する関数 failure をつくり、計算途中に入れる例:

val failure:(Int)->Maybe<Int> = { v->
    Maybe.Nothing()
}

val result: Maybe<Int> = Maybe.Just(1).bind( inc ).bind( failure ).bind( inc ).bind( dec )
println(result)

実行:

$ kotlin main.kts
Nothing

まとめ

ここで書いたコード全体を掲載します。

// main.kts

sealed class Maybe<T>(val value: T?) {
    fun <R> bind(f: (T)->Maybe<R>): Maybe<R> {
        return when(this){
            is Maybe.Just -> {
                f(value!!)
            }
            is Maybe.Nothing -> {
                Maybe<R>.Nothing()
            }
        }

        /*
        return if( value!=null ){
            f(value)
        } else {
            Maybe<R>.Nothing()
        }
        */
    }

    override fun toString(): String {
        return if( value!=null ){ "Just($value)" } else { "Nothing" }
    }

    companion object {
        fun <T> `return`(value: T?): Maybe<T> {
            return if( value!=null ){
                Maybe<T>.Just(value)
            } else {
                Maybe<T>.Nothing()
            }
        }
    }

    class Just<T>(value: T): Maybe<T>(value)
    class Nothing<T>(): Maybe<T>(null)
}


val add: (Maybe<Int>, Maybe<Int>)-> Maybe<Int> = { maybeA, maybeB->
    maybeA.bind( {a: Int->
        maybeB.bind( {b: Int->
            Maybe.Just(a+b)
        } )
    } )
}

val add2: (Maybe<Int>, Maybe<Float>)-> Maybe<Float> = { maybeA, maybeB->
    maybeA.bind( {a: Int->
        maybeB.bind( {b: Float->
            Maybe.Just(a.toFloat() + b)
        } )
    } )
}


/*
// Example-1:
val one: Maybe<Int> = Maybe.Just(1)
val two: Maybe<Int> = Maybe.Just(2)
val result: Maybe<Int> = add(one, two)
println(result)
*/

/*
// Example-2:
val one: Maybe<Int> = Maybe.Just(1)
val nothing: Maybe<Int> = Maybe.Nothing()
val result: Maybe<Int> = add(one, nothing)
println(result)
*/

/*
// Example-3:
val one: Maybe<Int> = Maybe.Just(1)
val two: Maybe<Float> = Maybe.Just(2f)
val result: Maybe<Float> = add2(one, two)
println(result)
*/

// Example-4:

val inc:(Int)->Maybe<Int> = { v->
    Maybe.Just( v+1 )
}

val dec:(Int)->Maybe<Int> = { v->
    Maybe.Just( v-1 )
}

val failure:(Int)->Maybe<Int> = { v->
    Maybe.Nothing()
}

val result: Maybe<Int> = Maybe.Just(1).bind( inc ).bind( failure ).bind( inc ).bind( dec )
println(result)

以上です。

Liked some of this entry? Buy me a coffee, please.