是的,这基本上是可行的,这就是 Haskell 做事的方式。但是,有一个问题:您必须确保引用永远不会“逃脱” monad。伪代码:
module MutMonad : sig
(* This is all sound: *)
type 'a t
type 'a ref
val mkref : 'a -> ('a ref) t
val read_ref : 'a ref -> 'a t
val write_ref : 'a -> 'a ref -> unit t
(* This breaks soundness: *)
val run : 'a t -> 'a
end
run 的存在让我们回到了我们开始的地方:
let x = run (mkref []) in (* x is of type ('a list) ref *)
run (read_ref x >>= fun list -> write_ref (1::list) x);
run (read_ref x >>= fun list -> write_ref (true::list) x)
Haskell 通过两种方式解决这个问题:
- 由于
main
已经是一元类型(IO),它不能有一个 runIO 或类似的。
- ST monad 使用 rank 2 类型的技巧来确保引用在 monad 退出后不可用,从而在保持声音的同时允许局部可变状态。
对于第二种情况,你有类似的东西:
module MutMonad : sig
(* The types now take an extra type parameter 's,
which is a phantom type. Otherwise, the first
bit is the same: *)
type 'a 's t
type 'a 's ref
val mkref : 'a -> ('a 's ref) 's t
val read_ref : 'a 's ref -> 'a 's t
val write_ref : 'a -> 'a 's ref -> unit 's t
(* This bit is the key. *)
val run : (forall 's. 'a 's t) -> 'a
end
类型级别的forall 's. ...
类似于fun x -> ...
。's 是本地绑定变量,因此 run 的参数不能假设任何关于 's. 特别是,这不会进行类型检查:
let old_ref = run (mkref 3) in
run (read_ref old_ref)
因为要运行的参数不能假定它们传递的类型相同's
。
这需要 ocaml 中不存在的类型系统的功能,并且需要 Haskell 中的语言扩展(Rank2Types)。