Generics 如何从函数返回一级模块嵌套类型的实例? 背景:

Generics 如何从函数返回一级模块嵌套类型的实例? 背景:,generics,ocaml,observer-pattern,first-class-modules,Generics,Ocaml,Observer Pattern,First Class Modules,我正试图使用一流的模块在OCaml中实现类似OOP可观察模式的东西。我有一个包含模块列表的项目,希望在不改变的情况下通过观察来扩展它们。为了最大限度地减少代码重复,我创建了主题模块,并计划将其作为此扩展的项目上下文中通用方式的一部分。我声明了三种模块类型: 观察员: 模块类型观测器=sig 类型事件 t型 val发送:事件->t->t 终止 可观察到: 可观测的模块类型=sig 类型事件 类型subscr t型 模块类型观察员=观察员,类型事件=事件 val subscribe:类型为t='t-

我正试图使用一流的模块在OCaml中实现类似OOP可观察模式的东西。我有一个包含模块列表的项目,希望在不改变的情况下通过观察来扩展它们。为了最大限度地减少代码重复,我创建了主题模块,并计划将其作为此扩展的项目上下文中通用方式的一部分。我声明了三种模块类型:

观察员:

模块类型观测器=sig 类型事件 t型 val发送:事件->t->t 终止 可观察到:

可观测的模块类型=sig 类型事件 类型subscr t型 模块类型观察员=观察员,类型事件=事件 val subscribe:类型为t='t->'t->t->subscr*t的模块观察者 val取消订阅:subscr->t->t 终止 以及观察者和可观察者融合的主体:

模块类型主题=sig 包括观察员 包括可观察的 类型为event:=事件 和类型t:=t 终止 接下来我实现的是主题模块。 本模块的职责是将多个观察员聚合为一个。 当然,它们应该处理相同的事件类型,这就是我实现Subject Subject.Make作为functor的原因

模块主题=结构 模块制造事件:sig类型t结束:sig 包含类型为event=event.t的主题 val空:t end=struct 类型event=event.t 模块类型观察员=观察员,类型事件=事件 ... 为了存储观测者的一级模块的实例,并且能够以任意顺序添加和删除它们,我使用带有int的Map作为键,它是subscr

... 类型subscr=int 模块SMap=Map.Make Int ... 正如我们从OBSERVER val send:event->t->t中的send签名所看到的,不仅需要存储OBSERVER的一级模块的实例,而且还需要存储它们的状态OBSERVER.t的实例。由于类型不同,我无法将所有状态存储在一个集合中。所以我在PACK实例中声明了module-type-PACK-to-PACK观察器的第一类模块的实例及其状态的实例

... 模块类型PACK=sig 模块观察者:观察者 瓦尔州:观察员 终止 t型= {next_subscr:subscr; 观察员:模块包SMap.t } 让空= {next_subscr=0; 观察员=SMap.empty } 让我们订阅类型t Obs模块:类型为t=t init o的观察者= o、 下一步, {next_subscr=succo.next_subscr; 观察员=o.Observators |>SMap.add o、 下一步 模块结构 模块观察员=Obs 让state=init 完:包 } 让我们取消订阅o= {o与 Observators=o.Observators |>SMap.remove订阅 } ... 受试者发送功能在新状态和旧观察者模块内重新包装每个包装

... 让我们发送事件o= 让我们发送模块包:包= 模块结构 模块观察者=组件观察者 让状态=Observer.send event Pack.state 完:包 在里面 {o与 观察员=SMap.map发送o.observators } 终止 终止 为了测试这个主题,看看在没有变化的情况下通过观察扩展模块会是什么样子——我创建了一些模块Acc

模块Acc:sig t型 val零:t val添加:int->t->t val乘法:int->t->t val值:t->int end=struct 类型t=int 设0=0 让我们加上xo=o+x 让x乘以o=o*x 设值o=o 终止 并在模块OAcc中使用观察功能对其进行了扩展,该模块具有以下特征,即合并原始Acc的可观察和模块类型

模块OAcc:sig 类型事件=整数的加法|整数的乘法 包括Acc的模块类型 包含类型为event:=event的可观察事件 和类型t:=t 结束= ... 我实施OAcc时,将观察责任委托给主体,主要责任委托给原Acc

... 结构 类型事件=整数的加法|整数的乘法 模块主题=主题。使结构类型t=事件结束 模块类型观察员=Subject.OBSERVER 类型subscr=Subject.subscr 类型t= {主题:subject.t; acc:acc.t } 设零= {subject=subject.empty; acc=acc.0 } 让我们加上xo= {subject=subject.send添加x o.subject; acc=acc.add x o.acc } 让x乘以o= {subject=subject.send乘以x o.subject; acc=acc乘以o.acc } 让值o=附件值o.Acc 让订阅类型为t的模块Obs:Subject.OBSERVER,类型为t=t init o= 让订阅,主题= Subject.subscribe模块Obs init o.Subject in 订阅,{o带主题} 允许 取消订阅= {o with subject=subject.unsubscribe订阅o.subject } 终止 创建了一些只将操作打印到控制台的观察者模块

模块打印机:sig 包括OAcc.OBSERVER val make:string->t end=struct 类型event=OAcc.event 类型t=字符串 让make prefix=前缀 让发送事件o= 让= [o; 比赛 |OAcc.Add x->Add^string\u of\u int x |OAcc.Multiply x->Multiply^string\u of_int x ; ;\n ] |>String.concat |>打印字符串 o 终止 最后,我创建了函数print_操作,并测试了所有操作是否按预期工作

让我们打印您的操作= 设p=模块打印机:OAcc.OBSERVER,类型为t=Printer.t in 设acc=OAcc.0英寸 让s1,acc=acc |>OAcc.subscribe p Printer.make 1。在里面 设s2,acc=acc |>OAcc.subscribe p Printer.make 2。在里面 让s3,acc=acc |>OAcc.subscribe p Printer.make 3。在里面 acc |>OAcc.add 1 |>OAcc.2 |>OAcc.unsubscribe s2 |>OAcc.3 |>OAcc.add.4 |>取消订阅s3 |>OAcc.add.5 |>OAcc.取消订阅s1 |>OAcc.6 |>OAcc值 调用print_操作之后;;我有以下输出

打印操作

1.Add1; 2.1; 3.1; 1.2; 2.2.2; 3.2; 1.3倍; 3.倍数3; 1.4; 3.4; 1.5

-:int=90

当我们的第一类模块观察器的逻辑完全基于副作用,并且我们不需要它在主体之外的状态时,所有这些都可以正常工作。但对于相反的情况,我并没有找到任何解决方案,如何从主体中提取订阅观测者的状态

例如,我有以下观察者 在这种情况下,它更多的是访问者,而不是观察者

模块历史:sig 包括OAcc.OBSERVER val空:t val to_列表:t->事件列表 end=struct 类型event=OAcc.event 类型t=事件列表 设为空=[] 让发送事件o=事件::o let to_list=list.rev 终止 我可以将历史的一级实例和它的一些初始状态订阅给OAcc,但我不知道如何将其提取回来

让\u操作的历史\u= 设h=模块历史:类型为t=History.t的OAcc.OBSERVER in 设acc=OAcc.0英寸 让我们,acc=acc |>OAcc.subscribe h History.empty in 让历史:历史。t= acc |>OAcc.add 1 |>OAcc.2 |>failwith在中实现从OAcc.t中提取历史记录.t 历史 我想做的事。我在OBSERVABLE中更改了退订签名。在返回与所提供订阅相关联的不带观察者的可观察状态之前,现在返回此状态的三倍、未订阅的一级模块和未订阅模块的状态

之前:

可观测的模块类型=sig ... val取消订阅:subscr->t->t 终止 之后:

可观测的模块类型=sig ... val取消订阅:subscr->t->t*类型为t='t*'t的模块观察者 终止 OBSERVABLE是可编译的,但我无法实现它。 下面的示例显示了我的一次尝试

模块主题=结构 模块制造事件:sig类型t结束:sig ... end=struct ... 让我们取消订阅o= 让模块打包:打包= o、 观察员|>SMap.find订阅 和观察员= o、 观察员|>SMap.remove订阅 {o有观察员}, 模块包。观察者:观察者, 包装状态 ... 终止 终止 因此,我:

错误:此表达式的类型为Pack.Observer.t 但应为“a”类型的表达式 类型构造函数Pack.Observer.t将脱离其作用域

问题1: 是否可以使用此签名实施取消订阅

它不起作用。我尝试了另一种解决办法。 它基于这样一种思想:取消订阅可以返回PACK的一级模块的实例。 我更喜欢上一个想法,因为它在主题中保持了包装声明的私密性。但目前的方案在寻找解决方案方面取得了更好的进展

我将包模块类型添加到OBSERVABLE中,并将unsubscribe签名更改为以下内容

可观测的模块类型=sig ... 模块类型PACK=sig 模块观察者:观察者 瓦尔州:观察员 终止 ... val取消订阅:subscr->t->t*模块包 终止 将包添加到OAcc实现中,因为其签名包括可观察的。另外,我重新实现了取消OAcc的订阅

模块OAcc:sig ... end=struct ... 模块类型PACK=Subject.PACK ... 让我们取消订阅o= let主题,模块包:包为p= Subject.unsubscribe订阅o.Subject in {o带主语},p 终止 主题的实现已经包含包,所以不需要添加它。 只重新执行了取消订阅

模块主题=结构 模块制造事件:sig类型t结束:sig ... end=struct ... 让我们取消订阅o= 让模块打包:打包为p= o、 观察员|>SMap.find订阅 和观察员= o、 观察员|>SMap.remove订阅 {o有观察员},p ... 终止 终止 最后,我创建并更改了操作的历史,以测试解决方案

让\u操作的历史\u= 设h=模块历史:类型为t=History.t的OAcc.OBSERVER in 设acc=OAcc.0英寸 让我们,acc=acc |>OAcc.subscribe h History.empty in 让acc,模块组:OAcc.Pack= 行政协调会 |>OAcc.add.1 |>OAcc.2 |>OAcc.在中取消订阅 国家;; 调用_操作的历史_之后;;我有错误

包装状态 ^^^^^^^^^^ 错误:此表达式的类型为Pack.Observer.t 但应为“a”类型的表达式 类型构造函数Pack.Observer.t将脱离其作用域

还有,我试过了

让\u操作的历史\u= ... History.to_list Pack.state 但是

History.to_list Pack.state ^^^^^^^^^^ 错误:此表达式的类型为Pack.Observer.t 但是需要一个类型为History.t的表达式

问题2: 如何从List.t类型的包中提取状态

我更改了退订的签名

可观测的模块类型=sig ... val取消订阅:subscr->t->t*类型为Observer的模块包。t='t 终止 并试图在主题中重新实现取消订阅

模块主题=结构 模块制造事件:sig类型t结束:sig ... end=struct ... 让我们取消订阅类型t订阅o= 让模块封装:封装类型为Observer.t=t作为p= o、 观察员|>SMap.find订阅 和观察员= o、 观察员|>SMap.remove订阅 {o有观察员},p ... 终止 终止 但是

o、 观察员|>SMap.find订阅 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 错误:此表达式具有模块包类型 但需要类型为的表达式 带观察者类型的模块组件。t=t

看起来OCaml有3个级别的类型抽象 1.混凝土模块A:sig type t=int end=struct。。。 2.抽象模块A:sig type t end=struct。。。 3.打包到一等舱

问题3: 是否可以使用2个抽象级别存储一级模块的嵌套类型实例,或者能够将其恢复到2个抽象级别

标题中的问题: 如何从函数返回一级模块嵌套类型的实例

备注: 当然,通过使用可变状态解决这个问题是可能的,但问题不在于此


最初的可编译源代码

关于您的问题1,您希望函数具有签名:

val unsubscribe : subscr -> t -> (t * (module OBSERVER with type t = 't) * 't))
在这里,模块的存在是一个危险因素。你的签名和我的一样

val unsubscribe : subscr -> t -> 'a
换句话说,它是一个神奇地返回调用者可能想要的任何类型的值的函数。如果调用方需要整数,则函数返回整数。如果调用方需要字符串,则函数返回字符串。等等因此,只有一种安全函数具有这种签名,它是一种从不返回任何内容的函数

因此,您需要将量化移到其他位置的类型上,例如在构造函数下:

输入'u unsubscribe\u result=UResult:'u*模块观察者,类型为t='t*'t->'u unsubscribe\u result val退订:subscr->t->t退订结果
关于您的问题1,您希望函数具有签名:

val unsubscribe : subscr -> t -> (t * (module OBSERVER with type t = 't) * 't))
在这里,模块的存在是一个危险因素。你的签名和我的一样

val unsubscribe : subscr -> t -> 'a
换句话说,它是一个神奇地返回调用者可能想要的任何类型的值的函数。如果调用方需要整数,则函数返回整数。如果调用方需要字符串,则函数返回字符串。等等因此,只有一种安全函数具有这种签名,它是一种从不返回任何内容的函数

因此,您需要将量化移到其他位置的类型上,例如在构造函数下:

输入'u unsubscribe\u result=UResult:'u*模块观察者,类型为t='t*'t->'u unsubscribe\u result val退订:subscr->t->t退订结果
简单的回答是,封装模块的内部类型永远不能提升到其一级模块之外

将压缩观察者定义为:

模块类型PACK=sig 模块观察员:sig t型 val发送:事件->t->t 终止 瓦尔州:观察员 终止 类型Observer.t在一级模块中被存在量化:通过将初始实现打包到模块包中,我忘记了关于初始模块的所有知识,除了模块中的类型等式。 这意味着对于mod类型的值模块M 在ule PACK中,我唯一可用的操作是调用M.Observer.send event M.state。 换句话说,模块包实际上相当于以下类型

键入send={send:event->send} 观察者的状态更明显地不可接近

因此,当你把你的观察者打包进来时,你的问题就开始了

让我们订阅类型t Obs模块:类型为t=t init o的观察者= o、 下一步, {next_subscr=succo.next_subscr; 观察员=o.Observators |>SMap.add o、 下一步 模块结构 模块观察员=Obs 让state=init 完:包 } 在这里,当您打包模块Obs时,实际上您忘记了Obs的类型,并且放弃了进一步使用这种类型

如果要恢复观察者的状态,必须保留类型信息。一个好的起点是观察可观察的特征:

val unsubscribe : subscr -> t -> (t * (module OBSERVER with type t = 't) * 't))
可观测的模块类型=sig 类型事件 类型subscr t型 模块类型观察员=观察员,类型事件=事件 val subscribe:类型为t='t->'t->t->subscr*t的模块观察者 val取消订阅:subscr->t->t 终止 请注意,我们在subscribe中开始丢失类型信息,因为我无法将特定的subscr与可观察的类型相关联。因此,一种解决方案是通过使用订阅的观察者类型参数化subscr来保留此信息:

module type OBSERVABLE = sig
  type event
  type 'a subscr
  type t

  module type OBSERVER = OBSERVER with type event = event
  val subscribe : (module OBSERVER  with type t = 't) -> 't -> t -> ('t subscr * t)
  val unsubscribe : 't subscr -> t -> t
end
然后,通过此更改,“取消订阅”可以返回观察者的当前状态,因为我们知道此状态的类型:它是订阅存储的类型:

val取消订阅:'t subscr->t->t*'t 因此,剩下的问题是将观察者存储在一个映射中,该映射的类型取决于插入它们的键的类型。此约束指向异构映射。使用库,可以通过以下方式完成此操作:

模块主题=结构 模块制造事件:sig类型t结束:sig 包含类型为event=event.t的主题 val空:t end=struct 类型event=event.t 模型观测器= 类型为event=event的观察者 *我们需要使用map的键来保持模块的实现* 模块HM=Hmap.Makestruct type'a t=类型为t='a end的模块观察者 t型=HM.t 键入'a subscr='一个HM键 设为空=HM.empty 让我们订阅类型t 模块Obs:t=t类型为vt init:t o的模块观察者= 让key:t subscr=HM.key.create vt in 密钥,HM.add密钥初始化o 让我们取消订阅o= HM.rem订阅o,HM.get订阅o 让我们发送事件o= 让我们发送您和您的阅读HM.Bk,s o= 让模块Obs=val HM.Key.info k输入 设s=Obs.send事件s in 嗯,加入k s o HM.折叠发送和读取空 终止 终止
简单的回答是,封装模块的内部类型永远不能提升到其一级模块之外

将压缩观察者定义为:

模块类型PACK=sig 模块观察员:sig t型 val发送:事件->t->t 终止 瓦尔州:观察员 终止 类型Observer.t在一级模块中被存在量化:通过将初始实现打包到模块包中,我忘记了关于初始模块的所有知识,除了模块中的类型等式。 这意味着对于模块包类型的值模块M,我唯一可用的操作是调用M.Observer.send event M.state。 换句话说,模块包实际上相当于以下类型

键入send={send:event->send} 观察者的状态更明显地不可接近

因此,当你把你的观察者打包进来时,你的问题就开始了

让我们订阅类型t Obs模块:类型为t=t init o的观察者= o、 下一步, {next_subscr=succo.next_subscr; 观察员=o.Observators |>SMap.add o、 下一步 模块结构 模块观察员=Obs 让state=init 完:包 } 在这里,当您打包模块Obs时,实际上您忘记了Obs的类型,并且放弃了进一步使用这种类型

如果要恢复观察者的状态,必须保留类型信息。一个好的起点是观察可观察的特征:

val unsubscribe : subscr -> t -> (t * (module OBSERVER with type t = 't) * 't))
可观测的模块类型=sig 类型事件 类型subscr t型 模块类型观察员=观察员,类型事件=事件 val subscribe:类型为t='t->'t->t->subscr*t的模块观察者 val取消订阅:subscr->t->t 终止 请注意,我们在subscribe中开始丢失类型信息,因为我无法将特定的subscr与可观察的类型相关联。因此,一种解决方案是通过使用 订阅观察员的类型:

module type OBSERVABLE = sig
  type event
  type 'a subscr
  type t

  module type OBSERVER = OBSERVER with type event = event
  val subscribe : (module OBSERVER  with type t = 't) -> 't -> t -> ('t subscr * t)
  val unsubscribe : 't subscr -> t -> t
end
然后,通过此更改,“取消订阅”可以返回观察者的当前状态,因为我们知道此状态的类型:它是订阅存储的类型:

val取消订阅:'t subscr->t->t*'t 因此,剩下的问题是将观察者存储在一个映射中,该映射的类型取决于插入它们的键的类型。此约束指向异构映射。使用库,可以通过以下方式完成此操作:

模块主题=结构 模块制造事件:sig类型t结束:sig 包含类型为event=event.t的主题 val空:t end=struct 类型event=event.t 模型观测器= 类型为event=event的观察者 *我们需要使用map的键来保持模块的实现* 模块HM=Hmap.Makestruct type'a t=类型为t='a end的模块观察者 t型=HM.t 键入'a subscr='一个HM键 设为空=HM.empty 让我们订阅类型t 模块Obs:t=t类型为vt init:t o的模块观察者= 让key:t subscr=HM.key.create vt in 密钥,HM.add密钥初始化o 让我们取消订阅o= HM.rem订阅o,HM.get订阅o 让我们发送事件o= 让我们发送您和您的阅读HM.Bk,s o= 让模块Obs=val HM.Key.info k输入 设s=Obs.send事件s in 嗯,加入k s o HM.折叠发送和读取空 终止 终止
免责声明:我不会假装完全理解您的问题,这是迄今为止我所看到的与OCaml相关的最大问题。但我的直觉告诉我你在寻找存在主义

没有类型相等的简单存在句 在这种方法中,我们可以将对象接口及其状态打包到一个存在GADT中。我们将能够使用状态,只要它不超出其定义的范围,这将是解压我们的存在论的函数。有时,这是我们想要的,但我们将在下一节中扩展此方法

让我们从一些初步定义开始,让我们定义要打包的对象的接口,例如,类似这样的东西:

module type T = sig
  type t
  val int : int -> t
  val add : t -> t -> t
  val sub : t -> t -> t
  val out : t -> unit
end
现在,我们可以将这个接口与状态打包在一起,状态类型为t,存在形式为

type obj = Object : {
    intf : (module T with type t = 'a);
    self : 'a
  } -> obj
然后,我们可以轻松地解包接口和状态,并将任何函数从接口应用到状态。因此,我们的t型完全是抽象的,事实上,例如

可恢复存在动态类型 但是如果我们希望恢复抽象类型的原始类型,以便我们可以应用适用于此类型的值的其他函数,该怎么办呢。为此,我们需要存储类型x属于所需类型y的见证,我们可以使用可扩展GADT

 type 'a witness = ..
为了创造新的证人,我们将采用一流的模块

let newtype (type u) () =
  let module Witness = struct
    type t = u
    type _ witness += Id : t witness
  end in
  (module Witness : Witness with type t = u)
其中模块类型见证及其包装类型为:

module type Witness = sig 
     type t 
     type _ witness += Id : t witness
end

type 'a typeid = (module Witness with type t = 'a)
每次调用newtype时,它都会向见证类型添加一个新的构造函数,该构造函数保证不等于任何其他构造函数。为了证明两个见证人实际上是用同一个构造函数创建的,我们将使用以下函数

let try_cast : type a b. a typeid -> b typeid -> (a,b) eq option =
  fun x y ->
  let module X : Witness with type t = a = (val x) in
  let module Y : Witness with type t = b = (val y) in
  match X.Id with
  | Y.Id -> Some Equal
  | _ -> None
let cast x y = match try_cast x y with
  | None -> failwith "Type error"
  | Some Equal -> Equal
返回定义为的等式证明

type ('a,'b) eq = Equal : ('a,'a) eq
let zero = Object {
    intf = (module Int);
    self = 0;
    rtti = int;
  }
在可以构造类型为x、y eq的对象的环境中,typechecker将处理类型为x且类型与y相同的值。有时候,当你真的确定演员必须成功时,你可以使用演员功能

let try_cast : type a b. a typeid -> b typeid -> (a,b) eq option =
  fun x y ->
  let module X : Witness with type t = a = (val x) in
  let module Y : Witness with type t = b = (val y) in
  match X.Id with
  | Y.Id -> Some Equal
  | _ -> None
let cast x y = match try_cast x y with
  | None -> failwith "Type error"
  | Some Equal -> Equal
作为

好的,现在当我们有了动态类型,我们可以利用它们使我们的对象类型可恢复和状态可转义。我们需要的只是将运行时信息添加到对象表示中

type obj = Object : {
    intf : (module T with type t = 'a);
    self : 'a;
    rtti : 'a typeid;
  } -> obj
现在让我们定义int类型的运行时表示注意,一般来说,我们可以在rtti中放入更多信息,而不仅仅是见证,我们还可以将其设置为oredered类型,并在运行时使用新操作扩展动态类型,并实现即席多态性

let int : int typeid = newtype ()
现在我们的零物体被定义为

type ('a,'b) eq = Equal : ('a,'a) eq
let zero = Object {
    intf = (module Int);
    self = 0;
    rtti = int;
  }
incr函数在对象表示中仍然是一个额外字段的模,因为它不需要转义。但是现在我们可以编写cast_object函数,它将接受所需的类型和cast object

let cast_object (type a) (t : a typeid) (Object {self; rtti}) : a option =
  match try_cast t rtti with
  | Some Equal -> Some self
  | None -> None

再比如,

let print_if_int (Object {self; rtti}) =
  match try_cast int rtti with
  | Some Equal -> print_int self
  | None -> ()

您可以阅读有关动态类型的更多信息。OCaml中还有许多库提供动态类型和异构字典,等等

免责声明:我不会假装完全理解您的问题,这是迄今为止我所看到的与OCaml相关的最大问题。但我的直觉告诉我你在寻找存在主义

没有类型相等的简单存在句 在这种方法中,我们可以将对象接口及其状态打包到一个存在GADT中。我们将能够使用状态,只要它不超出其定义的范围,这将是解压我们的存在论的函数。有时,这是我们想要的,但我们将在下一节中扩展此方法

让我们从一些初步定义开始,让我们定义要打包的对象的接口,例如,类似这样的东西:

module type T = sig
  type t
  val int : int -> t
  val add : t -> t -> t
  val sub : t -> t -> t
  val out : t -> unit
end
现在,我们可以将这个接口与状态打包在一起,状态类型为t,存在形式为

type obj = Object : {
    intf : (module T with type t = 'a);
    self : 'a
  } -> obj
然后,我们可以轻松地解包接口和状态,并将任何函数从接口应用到状态。因此,我们的t型完全是抽象的,事实上,例如

可恢复存在动态类型 但是如果我们希望恢复抽象类型的原始类型,以便我们可以应用适用于此类型的值的其他函数,该怎么办呢。为此,我们需要存储类型x属于所需类型y的见证,我们可以使用可扩展GADT

 type 'a witness = ..
为了创造新的证人,我们将采用一流的模块

let newtype (type u) () =
  let module Witness = struct
    type t = u
    type _ witness += Id : t witness
  end in
  (module Witness : Witness with type t = u)
其中模块类型见证及其包装类型为:

module type Witness = sig 
     type t 
     type _ witness += Id : t witness
end

type 'a typeid = (module Witness with type t = 'a)
每次调用newtype时,它都会向见证类型添加一个新的构造函数,该构造函数保证不等于任何其他构造函数。为了证明两个见证人实际上是用同一个构造函数创建的,我们将使用以下函数

let try_cast : type a b. a typeid -> b typeid -> (a,b) eq option =
  fun x y ->
  let module X : Witness with type t = a = (val x) in
  let module Y : Witness with type t = b = (val y) in
  match X.Id with
  | Y.Id -> Some Equal
  | _ -> None
let cast x y = match try_cast x y with
  | None -> failwith "Type error"
  | Some Equal -> Equal
返回定义为的等式证明

type ('a,'b) eq = Equal : ('a,'a) eq
let zero = Object {
    intf = (module Int);
    self = 0;
    rtti = int;
  }
在可以构造类型为x、y eq的对象的环境中,typechecker将处理类型为x且类型与y相同的值。有时候,当你真的确定演员必须成功时,你可以使用演员功能

let try_cast : type a b. a typeid -> b typeid -> (a,b) eq option =
  fun x y ->
  let module X : Witness with type t = a = (val x) in
  let module Y : Witness with type t = b = (val y) in
  match X.Id with
  | Y.Id -> Some Equal
  | _ -> None
let cast x y = match try_cast x y with
  | None -> failwith "Type error"
  | Some Equal -> Equal
作为

好的,现在当我们有了动态类型,我们可以利用它们使我们的对象类型可恢复和状态可转义。我们需要的只是将运行时信息添加到对象表示中

type obj = Object : {
    intf : (module T with type t = 'a);
    self : 'a;
    rtti : 'a typeid;
  } -> obj
现在让我们定义int类型的运行时表示注意,一般来说,我们可以在rtti中放入更多信息,而不仅仅是见证,我们还可以将其设置为oredered类型,并在运行时使用新操作扩展动态类型,并实现即席多态性

let int : int typeid = newtype ()
现在我们的零物体被定义为

type ('a,'b) eq = Equal : ('a,'a) eq
let zero = Object {
    intf = (module Int);
    self = 0;
    rtti = int;
  }
incr函数在对象表示中仍然是一个额外字段的模,因为它不需要转义。但是现在我们可以编写cast_object函数,它将接受所需的类型和cast object

let cast_object (type a) (t : a typeid) (Object {self; rtti}) : a option =
  match try_cast t rtti with
  | Some Equal -> Some self
  | None -> None

再比如,

let print_if_int (Object {self; rtti}) =
  match try_cast int rtti with
  | Some Equal -> print_int self
  | None -> ()

您可以阅读有关动态类型的更多信息。OCaml中还有许多库提供动态类型和异构字典,等等

请注意,您的取消订阅结果类型相当于简单地返回一对t*模块包,并且观察者状态的类型仍然未知。请注意,您的取消订阅结果类型相当于简单地返回一对t*模块包,并且观察者状态的类型仍然未知。异构映射-是我从未做过的事情我以前听过。据我所知,我可以将hmap安装为带有Opam的黑盒,并使用它,但对我来说,它的工作原理很有趣。我考虑过类似结构的实现,对我来说,这看起来是不可能的。如果您有一些相关信息的有用链接,请与我们分享好吗?在幕后,hmap和其他异构映射实现正在使用基于扩展构造函数的类型平等见证,如@ivg answer.heterogeneous map中所述,这是我以前从未听说过的。据我所知,我可以将hmap安装为带有Opam的黑盒,并使用它,但对我来说,它的工作原理很有趣。我考虑过类似结构的实现,对我来说,这看起来是不可能的。如果您有一些相关信息的有用链接,请与我们分享好吗?在幕后,hmap和其他异构映射实现正在使用基于扩展构造函数的类型平等见证,如@ivg answer所述。这并不容易,但我想我理解存在类型和动态类型。我的示例解决方案的源代码如下:这并不容易,但我认为我理解存在类型和动态类型。我的示例的解决方案源代码如下:解决方案:gist.github.com/vzakh/017cc4314404dc089fa5a3116148c052解决方案:gist.github.com/vzakh/017cc4314404dc089fa5a3116148c052