2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

継続モナド (Contモナド) をF# のコンピュテーション式で書く

Last updated at Posted at 2025-06-22

Haskellの「モナドのすべて (All About Monads)」チュートリアルの、継続モナドの部分を参考に、F# で Contモナドを書いてみます。

F# の標準機能にはモナド型クラスがなく、モナドトランスフォーマー関連の便利関数もないので、今回は ContT を使わない定義としました。

type Cont<'a, 'r> = { runCont: ('a -> 'r) -> 'r }

HaskellのContモナド関連関数で使いそうなものをモジュールに定義しておきます。

module Cont =
    let toCont c = { runCont = c }
    let runCont { runCont = c } = c
    let return' a = toCont (fun k -> k a)

    let (>>=) { runCont = c } f =
        toCont (fun k -> c (fun a -> runCont (f a) k))

    let when' b s = if b then s () else return' ()

    let bind m f = m >>= f

    let callCC f =
        toCont (fun k -> runCont (f (fun a -> toCont (fun _ -> k a))) k)

callCC を定義しました。さて、期待通りに動くでしょうか。

その前に、せっかく F# で書くわけですから、コンピュテーション式のビルダーも書いておきましょう。

module Cont =
    // ...

    type Builder() =
        member _.Bind(m, f) = m >>= f
        member _.Return(v) = return' v
        member _.ReturnFrom(m) = m
        member _.Zero() = return' ()
        member _.Delay(f) = fun () -> f ()
        member _.Run(funcToRun) = funcToRun ()
        member _.Combine(m1, f) = m1 >>= fun _ -> f ()

    let cont = Builder()
  • ReturnFrom をサポートしておくと、callCC から脱出するコードが書きやすくなります。
  • DelayRun に対応させて、 ZeroCombine をサポートしておくと、when' 関数の代わりに if が使えます。

では、チュートリアルのexample18.hsをコンピュテーション式で書いてみましょう。こうなりました。

open Cont

let toIntList (s: string) =
    s |> Seq.toList |> List.map (fun c -> int c - int '0')

let fromIntList (ns: int list) =
    ns
    |> List.skipWhile (fun n -> n = 0)
    |> List.map (fun n -> '0' + char n)
    |> List.toArray
    |> System.String

(*
   Continuation モナドを使って,コードブロックからの「脱出」を行います
   この関数は以下のような処理の複雑な制御構造を実装します

   Input (n)       Output                       List Shown
   =========       ======                       ==========
   0-9             n                            none
   10-199          number of digits in (n/2)    digits of (n/2)
   200-19999       n                            digits of (n/2)
   20000-1999999   (n/2) backwards              none
   >= 2000000      sum of digits of (n/2)       digits of (n/2)
*)

let f (n: int) =
    let c: Cont<string, string> =
        cont {
            let! str =
                callCC (fun exit1 ->
                    cont {
                        if n < 10 then
                            return! exit1 $"%d{n}"

                        let ns = toIntList (string (n / 2))

                        let! n' =
                            callCC (fun exit2 ->
                                cont {
                                    let len = List.length ns

                                    if len < 3 then
                                        return! exit2 len

                                    if len < 5 then
                                        return! exit2 n

                                    if len < 7 then
                                        let ns' = List.rev ns
                                        return! exit1 (fromIntList ns')

                                    return List.sum ns
                                })

                        return $"(ns = %A{ns}) %d{n'}"
                    })

            return $"Answer: {str}"
        }

    runCont c id

printfn "%s" (f 2)       // Answer: 2
printfn "%s" (f 24)      // Answer: (ns = [1; 2]) 2
printfn "%s" (f 2468)    // Answer: (ns = [1; 2; 3; 4]) 2468
printfn "%s" (f 24680)   // Answer: 4321
printfn "%s" (f 2468024) // Answer: (ns = [1; 2; 3; 4; 0; 1; 2]) 13
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?