Андрей Власовских

Блог Андрея Власовских

Монады в OCaml

leave a comment »

В блогах уже есть, конечно, рассказы о монадах в 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

Реклама

Written by vlan

2009-07-13 в 22:17

Опубликовано в Uncategorized

Tagged with , , , ,

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: