我正在尝试在 OCaml 中使用 Writer monad。
module Writer : Monad = struct
type 'a t = 'a * string
let return x = (x, "")
let (>>=) m f =
let (x, s1) = m in
let (y, s2) = f x in
(y, s1 ^ s2)
end
以下语句有效。
Writer.(>>=) (Writer.return 2) (fun x -> Writer.return 1);;
但是下面的语句没有。
Writer.(>>=) (Writer.return 2) (fun x -> (x, "inc"));;
Error: This expression has type 'a * 'b but an expression was expected of type
'c Writer.t
我试过了
Writer.(>>=) (Writer.return 2) (fun x -> ((x, "inc") : int Writer.t))
Error: This expression has type 'a * 'b but an expression was expected of type
int Writer.t
我在这里做错了什么?如何将值提升到 OCaml 中的 monad 中?
答案 0 :(得分:3)
如前所述,代码适用于:
Writer.(>>=) (Writer.return 2) (fun x -> Writer.return 1);;
这是因为描述 monad 的签名,此处为 Monad
,可能具有以下形式:
module type Monad = sig
type 'a t
val return : 'a -> 'a t
val ( >>= ) : 'a t -> ('a -> 'b t) -> 'b t
end
由于 'a t
的具体实现尚不清楚(类型模块可能服务于多个实现),'a t
是“抽象的”。
因此,一种方法是“去抽象化”它。
这可以通过在 Writer
模块的签名中反映出我们想要实现 Monad
模块而不是使类型 'a t
抽象,如下所示:
module Writer : Monad with type 'a t = 'a * string = struct
type 'a t = 'a * string
let return x = (x, "")
let (>>=) m f =
let (x, s1) = m in
let (y, s2) = f x in
(y, s1 ^ s2)
end
这样,类型“a t”将不再是抽象的。
作为旁注,我个人认为 Writer(或 Reader 或 State)类型是抽象的并不是特别糟糕。在我看来,这是一对夫妇的事实是一个实现细节,一些不需要泄漏的管道。
答案 1 :(得分:3)
一个更基本的问题是签名约束
module Any_monad : Monad
在 OCaml 中几乎总是一个错误。
签名约束删除了签名之外的信息。因此,在约束之后,Any_monad
可以被满足 monad 签名的任何其他 monad 替换(如果我们暂时排除有副作用的 monad)。例如,您可以用这个非常有用的 monad 替换 Writer
monad:
module Nil = struct
type 'a t = unit
let bind x f = ()
let (>>=) = bind
let return x = ()
end
换句话说,一个有用的 monad 需要具有不属于 monad 接口的函数。如果 Writer
monad 是一个写函数:
module Writer: sig
include Monad
val write: string -> unit t
val read: 'a t -> string
end = struct
...
end
请注意,我们在将签名用作约束之前对其进行了扩展。 一旦这些功能可用,您的原始代码就可以重写为:
Writer.(return 2 >>= fun x -> write "inc" >>= fun () -> return x)
答案 2 :(得分:2)
您将 monad 实现限制为 Monad
,但实际上,您希望其类型具有 Writer
签名。换句话说,您正在升级您的实现并忘记它也必须实现特定于 Writer monad 的操作。显然,这会使你的 Writer monad 变得无用,因为你不能写任何东西。
为了修复它,我们首先需要定义 Writer 模块类型,一个通用的定义可能看起来像这样,
module type Writer = sig
type state
val write : state -> unit t
val read : 'a t -> state t
val listen : 'a t -> ('a * state) t
val exec : unit t -> state
include Monad with type 'a t := 'a t
end
现在您需要实现新添加的操作,并将 state
类型设置为 string
,然后您将能够在不破坏其抽象的情况下使用您的 Writer monad,
module Writer : Writer with type state = string = struct
type state = string
type 'a t = 'a * string
let return x = (x, "")
let (>>=) m f =
let (x, s1) = m in
let (y, s2) = f x in
(y, s1 ^ s2)
let write x = (),x
let read (_,x) = x
let listen (x,s) = ((x,s),s)
let exec (_,x) = x
end
在 OCaml 中实现 monad 的库很少,因此您无需自己重新发明它。您可以尝试我们在 CMU 开发的 monads 库。它可以安装
opam install monads