Монады в OCaml
В блогах уже есть, конечно, рассказы о монадах в OCaml, например, A Monad Tutorial for OCaml и Syntax Extension for Monads in OCaml. Однако меня интересовало воссоздание соответствующих классов типов Haskell при помощи параметрических модулей (вот пост про соответствие между ними).
Вот «иерархия» модулей, расширяющих функтор до монады. Полезные комментарии к соответствующим классам Haskell есть в статье The Typeclassopedia из The Monad.Reader.
module type FunctorType =
sig
type 'a t
val map : ('a -> 'b) -> 'a t -> 'b t
end
module type PointedType =
sig
include FunctorType
val pure : 'a -> 'a t
end
module type ApplicativeType =
sig
include PointedType
val (<*>) : ('a -> 'b) t -> 'a t -> 'b t
end
module type MonadType =
sig
include ApplicativeType
val join : 'a t t -> 'a t
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
val (>>) : 'a t -> 'b t -> 'b t
end
Здесь уже видно, что не всё получается как в Haskell. Сигнатуры модулей не позволяют привести значение терма по умолчанию.
Upd: Выложил полный исходник с примерами в pastebin.
Мы можем определить структуру модуля, соответствующего сигнатуре. Например, вот тип 'a option как монада:
module OptionMonad =
struct
type 'a t = 'a option
let map f x =
match x with
| Some x' -> Some (f x')
| None -> None
let pure x = Some x
let (<*>) f x =
match (f, x) with
| (Some f', Some x') -> Some (f' x')
| _ -> None
let join x =
match x with
| Some x' -> x'
| None -> None
let (>>=) x f = join (pure f <*> x)
let (>>) x y = x >>= fun _ -> y
end
Здесь пока что всё хорошо. Однако при использовании модуля возникают проблемы, т. к. ограничения на полиморфный код здесь вводятся явно, через параметризацию модулей (модуль с параметром является функцией из модуля в модуль и называется в OCaml функтором, не путать с классом Functor!).
Итак, вот простенький модуль, параметризованный классом монады, работающий со значениями типа 'a Monad.t:
module MakeSumInMonad (Monad : MonadType) =
struct
let (>>=) = Monad.(>>=)
let return = Monad.pure
let maybe_div a b =
match (a, b) with
| (Some a', Some b') ->
if b' = 0 then
None
else
Some (a' / b')
| _ -> None
let (+) a b =
a >>= fun a' ->
b >>= fun b' ->
return (Pervasives.(+) a' b')
end
Прежде чем работать с модулем, нужно создать его, применив наш параметризованный модуль-«функтор» к модулю со структурой монады:
module SumInMaybe = MakeSumInMonad (OptionMonad)
Это далеко не так приятно, как в Haskell. Там это выглядит более естественным. Здесь же приходится оборачивать любой код, работающий с типами некоторого класса, в технический модуль. Также я пока не смотрел, что будет с модулями с несколькими параметрами.
В конце небольшой тест:
let main () =
let (/?) = SumInMaybe.maybe_div in
let (+?) = SumInMaybe.(+) in
begin
assert ((Some 4) /? (Some 2) +? (Some 3) /? (Some 1) = (Some 5));
assert ((Some 4) /? (Some 0) +? (Some 3) /? (Some 1) = None);
end




