Настало время монады cont и страшного оператора callCC. Понимать смысл cont очень важно не только потому, что это мама всех монад, но и для того, чтобы дружить с continuation passing style, не редко встречающимся в функциональном программировании. Более того, async из состава стандартной бибилиотеки F# тоже представляет собой монаду cont, только не с одним продолжением k, а тремя (обычное продолжение, продолжение при возникновение исключения и продолжение при запросе отмены async workflow), а так же инфраструктурой для управления потоками и контекстами синхронизации.

Итак, сигнатура:

namespace FSharp.Monads

type Cont<'a, 'result> =
  Cont of (('a -> 'result) -> 'result)

[<RequireQualifiedAccess>]
module Cont =
  val run: Cont<'a,'r> -> ('a -> 'r) -> 'r
  val callCC: (('a -> Cont<'b,'r>) -> Cont<'a,'r>) -> Cont<'a,'r>

type ContBuilder =
  new: unit -> ContBuilder
  member Bind: Cont<'a,'r> * ('a -> Cont<'b,'r>) -> Cont<'b,'r>
  member Zero: unit -> Cont<unit,'r>
  member Return: 'a -> Cont<'a,'r>
  member ReturnFrom: Cont<'a,'r> -> Cont<'a,'r>

Реализация:

namespace FSharp.Monads

type Cont<'a, 'result> =
  Cont of (('a -> 'result) -> 'result)

[<RequireQualifiedAccess>]
module Cont =
  let run (Cont c) k = c k
  let callCC f =
    Cont(fun c -> let g a = Cont(fun _ -> c a)
                  let (Cont m) = f g in m c)

type ContBuilder() =
  member b.Bind(Cont m, f) =
    Cont(fun k ->
      m (fun r -> let (Cont c) = f r in c k))
  member b.Zero() = Cont(fun k -> k ())
  member b.Return x = Cont(fun k -> k x)
  member b.ReturnFrom x = x : Cont<_,_>

В качестве примера использования перепишем на F# хрестоматийный пример использования монады cont вместе с функцией callCC. Функция осуществляет проверку строки с именем пользователя и осуществляет немедленный выход в случае указания пустого имени - с помощью вызова функции exit внутри Cont<_,_>-вычисления, переданного в callCC:

open FSharp.Monads

let cont = ContBuilder()

/// Проверка строки имени на пустоту
let validateName name exit =
  cont { if System.String.IsNullOrEmpty name then
           return! exit "Вы забыли указать своё имя!" }

/// Проверка имени пользователя
let whatsYourName name =
  Cont.run (cont {
    let! responce =
      Cont.callCC <| fun exit -> cont {
        do! validateName name exit
        return sprintf "Добро пожаловать, %s!" name }
    return responce
  }) (printfn "%s")

whatsYourName ""
whatsYourName "Alex"

Функция validateName разворачивается компилятором следующим образом:

/// Проверка строки имени на пустоту
let validateName' name exit =
  if System.String.IsNullOrEmpty name
    then cont.ReturnFrom(exit "Вы забыли указать своё имя!")
    else cont.Zero()

Функция whatsYourName выглядит немного сложнее:

/// Проверка имени пользователя
let whatsYourName' name =
  Cont.run
    (cont.Bind(
      Cont.callCC (fun exit ->
        cont.Bind(
          validateName' name exit,
          fun _ -> cont.Return (sprintf "Добро пожаловать, %s!" name))),
      fun responce -> cont.Return responce))
    (printfn "%s")

Если вы не разбирались с callCC, то очень советую взять этот код и аккуратно заинлайнить/применить все вызовы, это не сложно, увлекательно и позволит понять как именно осуществляется контроль потока управления с помощью оператора callCC.