我正在写一篇博客文章,介绍如何使用OCaml的模块系统而不是Java的OO系统(一个有趣的视角)。我遇到过一些我不明白强迫的事情。下面是一个基本模块和两个包含它的模块:
module M = struct
type t = int
let make () = 1
end
module A = struct
include M
end
module B = struct
include M
end
现在A.t和B.t是同一类型!为什么?如果你这样做,那就显而易见了
let a = A.make();;
let b = B.make();;
[a;b] --> A.t list (* ? *)
我知道可以使用私有类型缩写来阻止这种情况,然后如果要将它们放在同一个列表中则使用强制。我的问题是:为什么不这样做?编译器如何知道A.t
和B.t
来自相同的基类型?
问候
欧莱
答案 0 :(得分:9)
在很多情况下,您希望这两个模块兼容。一个更简单的用例如下:
module Hashtbl = struct ... (* definition in the stdlib *) end
module ExtHashtbl = struct ... (* my own layer on top of it *) end
我希望ExtHashtbl.t
与Hashtbl.t
兼容,以便我可以使用ExtHashtbl
在代码中间使用Hashtbl.t
的函数,或者对具有Hashtbl
的值进行操作由其他只知道module A (U : sig end) = struct include M end
module B (U : sig end) = struct include M end
库的人建造,而不是我自己的东西。
在ML模块理论中,有一个称为“强化”的操作,它使用尽可能多的方程式丰富模块定义,将它们暴露在签名中。我们的想法是,如果你想要更多的抽象(更少的方程式),你总是可以使用类型签名来限制它,所以拥有方程式更为通用。
仿函数的情况有点不同。考虑到不是将A和B定义为简单模块,而是在空签名上将它们作为仿函数:
module type S = sig
type t
val x : t
end;;
module M : S = struct
type t = int
let x = 1
end;;
(* definitions below are compatible, the test type-checks *)
module A1 = M;;
module B1 = M;;
let _ = (A1.x = B1.x);;
(* definitions below are each independently sealed with an abstract
signature, so incompatible; the test doesn't type-check *)
module A2 : S = M;;
module B2 : S = M;;
let _ = (A2.x = B2.x);;
(*This expression has type B2.t but an expression was expected of type A2.t*)
(* note: if you don't seal Make with the S module type, all functor
applications will be transparently equal to M, and all examples below
then have compatible types. *)
module Make (U : sig end) : S = M;;
(* same functor applied to same argument:
compatible (applicative behavior) *)
module U = struct end;;
module A3 = Make(U);;
module B3 = Make(U);;
let _ = (A3.x = B3.x);;
(* same functor applied to different argument:
incompatible (applicative behavior) *)
module V = struct end;;
module A4 = Make(U);;
module B4 = Make(V);;
let _ = (A4.x = B4.x);;
(* This expression has type B4.t = Make(V).t
but an expression was expected of type A4.t = Make(U).t *)
(* same functor applied to non-path (~unnamed) arguments:
incompatible (generative behavior) *)
module A5 = Make(struct end);;
module B5 = Make(struct end);;
let _ = (A5.x = B5.x);;
(* This expression has type B5.t but an expression was expected
of type A5.t *)
有那么在ML模块系统函子的两个不同的概念,被称为一“生成”(各算符的调用产生“新鲜的”类型,可与其他调用不相容)调用,和那些“应用性“(对于相等参数的仿函数的所有调用都具有兼容的类型)。 OCaml的系统行为中的应用性的方式,如果你有名为的模块参数实例化(更一般地路径),并在如果使用一个模块参数为未命名实例化该生成方法。
您可以在Xavier Leroy的2000论文A Modular Module System (PDF)(来自网页A few papers on Caml)中了解有关OCaml模块系统的知识。您还可以找到我上面描述的所有情况的以下代码示例。
在ML模块系统,特别是通过Anreas Rossberg,德里克·德雷尔和克劳迪奥·鲁索,最近的工作都带来了不同的观点,以“应用性”和“生成”仿函数之间的区别经典的倾向。他们声称,他们应该对应于“纯”与“不纯”函子:仿函数,其应用程序执行的副作用应始终生成,而仿函数只有把纯条款应该是默认应用性(有一些密封结构,迫使不兼容,特此提供抽象)。
{{1}}
答案 1 :(得分:4)
我不明白还没有做什么,但是: