type 'u unsubscribe_result = UResult: 'u * (module OBSERVER with type t = 't) * 't -> 'u unsubscribe_result
val unsubscribe : subscr -> t -> t unsubscribe_result
我正在尝试使用一流的模块在OCaml中实现类似OOP的可观察模式。我有一个包含模块列表的项目,并希望通过观察扩展它们而无需更改。为了最大程度地减少代码重复,我创建了[[Subject模块,并计划将其用作此扩展的通用方式的一部分(在项目上下文中)。我声明了三种模块类型:
OBSERVER:
module type OBSERVER = sig
type event
type t
val send : event -> t -> t
end
OBSERVABLE:
module type OBSERVABLE = sig
type event
type subscr
type t
module type OBSERVER = OBSERVER with type event = event
val subscribe : (module OBSERVER with type t = 't) -> 't -> t -> (subscr * t)
val unsubscribe : subscr -> t -> t
end
和合并为[[OBSERVER和OBSERVABLE:]]SUBJECT
module type SUBJECT = sig
include OBSERVER
include OBSERVABLE
with type event := event
and type t := t
end
我接下来要做的是Subject模块。该模块的职责是将许多
OBSERVER
聚合为一个。当然,它们应该处理相同的event类型,这就是为什么我将“ Subject”(Subject.Make)用作函子的原因。module Subject = struct
module Make (Event : sig type t end) : sig
include SUBJECT with type event = Event.t
val empty : t
end =
struct
type event = Event.t
module type OBSERVER = OBSERVER with type event = event
...
为了存储OBSERVER一流模块的实例并能够添加和删除(以任何顺序)它们,我将
Map
与int用作key(这是subscr)。...
type subscr = int
module SMap = Map.Make (Int)
...
正如我们从OBSERVER(
val send : event -> t -> t
)中的send
签名所看到的,不仅需要存储OBSERVER一流模块的实例,而且还需要存储states (实例为“ OBSERVER.t”)。由于类型不同,我无法将所有状态存储在一个集合中。因此,我声明了模块类型PACK,将OBSERVER的一流模块的实例及其状态的实例打包在PACK的实例中。...
module type PACK = sig
module Observer : OBSERVER
val state : Observer.t
end
type t =
{ next_subscr : subscr;
observers : (module PACK) SMap.t
}
let empty =
{ next_subscr = 0;
observers = SMap.empty
}
let subscribe (type t)
(module Obs : OBSERVER with type t = t) init o =
o.next_subscr,
{ next_subscr = succ o.next_subscr;
observers = o.observers |> SMap.add
o.next_subscr
( module struct
module Observer = Obs
let state = init
end : PACK
)
}
let unsubscribe subscription o =
{ o with
observers = o.observers |> SMap.remove subscription
}
...
Subject的Function重新包装了新的state和旧的Observer模块中的每个pack。send
...
let send event o =
let send (module Pack : PACK) =
( module struct
module Observer = Pack.Observer
let state = Observer.send event Pack.state
end : PACK
) in
{ o with
observers = SMap.map send o.observers
}
end
end
测试主题并查看在不进行更改的情况下扩展模块的外观-我创建了一些模块
Acc
module Acc : sig
type t
val zero : t
val add : int -> t -> t
val multiply : int -> t -> t
val value : t -> int
end = struct
type t = int
let zero = 0
let add x o = o + x
let multiply x o = o * x
let value o = o
end
并使用<< OBSERVABLE和原始Acc的模块类型合并的以下签名在模块
OAcc
中使用观察功能对其进行了扩展。module OAcc : sig
type event = Add of int | Multiply of int
include module type of Acc
include OBSERVABLE with type event := event
and type t := t
end =
...
我实施了OAcc,将观察责任委托给了[[主题,而主要责任则委托给了原始的[[Acc。]]...
struct
type event = Add of int | Multiply of int
module Subject = Subject.Make (struct type t = event end)
module type OBSERVER = Subject.OBSERVER
type subscr = Subject.subscr
type t =
{ subject : Subject.t;
acc : Acc.t
}
let zero =
{ subject = Subject.empty;
acc = Acc.zero
}
let add x o =
{ subject = Subject.send (Add x) o.subject;
acc = Acc.add x o.acc
}
let multiply x o =
{ subject = Subject.send (Multiply x) o.subject;
acc = Acc.multiply x o.acc
}
let value o = Acc.value o.acc
let subscribe (type t) (module Obs : Subject.OBSERVER with type t = t) init o =
let subscription, subject =
Subject.subscribe (module Obs) init o.subject in
subscription, { o with subject }
let unsubscribe subscription o =
{ o with subject = Subject.unsubscribe subscription o.subject
}
end
创建了一些“ OBSERVER模块”,该模块仅将操作打印到控制台中
module Printer : sig
include OAcc.OBSERVER
val make : string -> t
end = struct
type event = OAcc.event
type t = string
let make prefix = prefix
let send event o =
let () =
[ o;
( match event with
| OAcc.Add x -> "Add(" ^ (string_of_int x)
| OAcc.Multiply x -> "Multiply(" ^ (string_of_int x)
);
");\n"
]
|> String.concat ""
|> print_string in
o
end
最后,我创建了函数print_operations,并测试了所有功能均按预期工作
let print_operations () =
let p = (module Printer : OAcc.OBSERVER with type t = Printer.t) in
let acc = OAcc.zero in
let s1, acc = acc |> OAcc.subscribe p (Printer.make "1.") in
let s2, acc = acc |> OAcc.subscribe p (Printer.make "2.") in
let s3, acc = acc |> OAcc.subscribe p (Printer.make "3.") in
acc |> OAcc.add 1
|> OAcc.multiply 2
|> OAcc.unsubscribe s2
|> OAcc.multiply 3
|> OAcc.add 4
|> OAcc.unsubscribe s3
|> OAcc.add 5
|> OAcc.unsubscribe s1
|> OAcc.multiply 6
|> OAcc.value
调用print_operations ();;
之后,我得到以下输出
我可以将History的一等实例及其某些初始状态预订给#print_operations();;
1.Add(1);2.添加(1);3.添加(1);1.乘(2);2.乘法(2);3.乘法(2);1.乘(3);3.乘(3);1.添加(4);3.添加(4);1.添加(5);-:int = 90
如果我们的一流模块的逻辑完全基于副作用,并且我们不需要observer
Subject之外的状态,则一切正常。但是对于相反的情况,我没有找到关于如何从中提取已订阅Subject
observer的
state
的任何解决方案。例如,我有以下“ OBSERVER”(在这种情况下,访问者多于观察者)module History : sig include OAcc.OBSERVER val empty : t val to_list : t -> event list end = struct type event = OAcc.event type t = event list let empty = [] let send event o = event :: o let to_list = List.rev end
OAcc
,但我不知道如何将其提取回来。let history_of_operations () =
let h = (module History : OAcc.OBSERVER with type t = History.t) in
let acc = OAcc.zero in
let s, acc = acc |> OAcc.subscribe h History.empty in
let history : History.t =
acc |> OAcc.add 1
|> OAcc.multiply 2
|> failwith "implement extraction of History.t from OAcc.t" in
history
我试图做的。我在OBSERVABLE中更改了退订的签名。在返回状态“
OBSERVABLE”而没有与提供的订阅相关联的“”之前,现在返回此状态的三倍,未订阅的一流模块和未订阅的模块状态。
OBSERVER
之前:module type OBSERVABLE = sig
...
val unsubscribe : subscr -> t -> t
end
之后:module type OBSERVABLE = sig
...
val unsubscribe : subscr -> t -> (t * (module OBSERVER with type t = 't) * 't))
end
OBSERVABLE
是可编译的,但我无法实现它。以下示例显示了我的尝试之一。module Subject = struct module Make (Event : sig type t end) : sig ... end = struct ... let unsubscribe subscription o = let (module Pack : PACK) = o.observers |> SMap.find subscription and observers = o.observers |> SMap.remove subscription in { o with observers }, (module Pack.Observer : OBSERVER), Pack.state ... end end
结果,我有:
错误:此表达式的类型为Pack.Observer.t但是应该使用'a类型的表达式类型构造函数Pack.Observer.t将逃避其范围
Pack.state ^^^^^^^^^^
问题1:?是否可以使用此签名实现
取消订阅
这不起作用。我尝试了另一种解决方案。它基于以下想法:
unsubscribe可以返回
PACK的一流模块的实例。我更喜欢前面的想法,因为它在Subject中将
PACK的声明保留为私有。但是当前的解决方案在解决方案查找方面提供了更好的进展。
我在OBSERVABLE中添加了PACK模块类型,并将unsubscribe签名更改为以下内容。
module type OBSERVABLE = sig
...
module type PACK = Subject.PACK
...
val unsubscribe : subscr -> t -> (t * (module PACK))
end
将PACK添加到OAcc实现中,因为其签名包括OBSERVABLE
。另外,我重新实现了OAcc的
退订。module OAcc : sig
...
end = struct
...
module type PACK = Subject.PACK
...
let unsubscribe subscription o =
let subject, ((module Pack : PACK) as p) =
Subject.unsubscribe subscription o.subject in
{ o with subject }, p
end
Subject的实现已包含PACK,因此无需添加它。仅重新实现退订
module Subject = struct module Make (Event : sig type t end) : sig ... end = struct ... let unsubscribe subscription o = let ((module Pack : PACK) as p) = o.observers |> SMap.find subscription and observers = o.observers |> SMap.remove subscription in { o with observers }, p ... end end
最后,我创建了我更改的history_of_operations以测试解决方案
致电let history_of_operations () = let h = (module History : OAcc.OBSERVER with type t = History.t) in let acc = OAcc.zero in let s, acc = acc |> OAcc.subscribe h History.empty in let acc, (module Pack : OAcc.PACK) = acc |> OAcc.add 1 |> OAcc.multiply 2 |> OAcc.unsubscribe s in Pack.state ;;
history_of_operations ();;
后出现错误
Pack.state ^^^^^^^^^^
错误:此表达式的类型为Pack.Observer.t但是应该使用'a类型的表达式类型构造函数Pack.Observer.t将逃避其范围
也,我尝试过
let history_of_operations () = ... History.to_list Pack.state
但是
History.to_list Pack.state ^^^^^^^^^^
错误:此表达式的类型为Pack.Observer.t但是期望使用History.t
类型的表达式问题2:的如何从类型为[[List.t
Pack中提取状态?
我更改了[[退订的签名]
module type OBSERVABLE = sig ... val unsubscribe : subscr -> t -> (t * (module PACK with type Observer.t = 't)) end
并尝试在主题]中重新实现取消订阅
module Subject = struct module Make (Event : sig type t end) : sig ... end = struct ... let unsubscribe (type t) subscription o = let ((module Pack : PACK with type Observer.t = t) as p) = o.observers |> SMap.find subscription and observers = o.observers |> SMap.remove subscription in { o with observers }, p ... end end
但是
o.observers |> SMap.find subscription ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
错误:此表达式具有类型(模块PACK)但是期望一个类型的表达式(类型为Observer.t = t的模块PACK)
看来OCaml具有3个类型抽象级别1.混凝土module A : sig type = int end = struct ...
2.摘要module A : sig type = int end = struct ...
3.打包到一流的模块中问题3:是否可以用
(2)?抽象级别存储一流的模块实例的嵌套类型,或者可以将其还原为(2)
level的抽象
标题中的问题:
如何从函数返回一流模块的嵌套类型的实例?备注:当然,可以通过使用可变状态来解决此问题,但问题不关乎。
初始可编译源代码here。
[上下文:我正在尝试使用一流的模块在OCaml中实现类似OOP可观察模式的功能。我有一个包含模块列表的项目,并希望通过观察来扩展它们...关于问题1,您希望具有签名的功能:
val unsubscribe : subscr -> t -> (t * (module OBSERVER with type t = 't) * 't))
这里存在模块是一个红色的鲱鱼。您的签名与
没有什么不同
val unsubscribe : subscr -> t -> 'a
换句话说,它是一个神奇地返回调用者可能需要的任何类型的值的函数。如果调用方想要一个整数,则该函数返回一个整数。如果调用方需要一个字符串,则该函数返回一个字符串。等等。因此,只有一种具有这种签名的安全功能,它是永远不会返回任何东西的功能。因此,您需要将量化移到其他类型上,例如在构造函数下:type 'u unsubscribe_result = UResult: 'u * (module OBSERVER with type t = 't) * 't -> 'u unsubscribe_result val unsubscribe : subscr -> t -> t unsubscribe_result
简短的回答是,打包模块的内部类型永远不能被提升到其一流的模块之外。
当您将压缩的观察者定义为:
module type PACK = sig module Observer: sig type t val send: event -> t -> t end val state: Observer.t end
类型
Observer.t
在第一类模块中存在地量化:通过将初始实现打包在(module PACK)
中,我忘记了关于初始模块的所有知识,除了模块内部的类型相等性之外。这意味着对于类型为(module M)
的值(module PACK)
,我唯一可用的操作是调用M.Observer.send event M.state
。换句话说,(module PACK)
实际上等效于以下类型type send = { send: event -> send }
Observer
的状态在视觉上更不可访问。因此,当您将观察员汇入其中时,您的问题就开始了>
let subscribe (type t) (module Obs : OBSERVER with type t = t) init o = o.next_subscr, { next_subscr = succ o.next_subscr; observers = o.observers |> SMap.add o.next_subscr ( module struct module Observer = Obs let state = init end : PACK ) }
这里,当您打包模块
Obs
时,实际上是在忘记Obs
的类型,而放弃了对该类型的任何进一步使用。如果要获取观察者的状态,则必须保留类型信息。一个很好的起点是查看OBSERVABLE签名:
module type OBSERVABLE = sig type event type subscr type t module type OBSERVER = OBSERVER with type event = event val subscribe : (module OBSERVER with type t = 't) -> 't -> t -> (subscr * t) val unsubscribe : subscr -> t -> t end
并且请注意,由于我无法将特定的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
然后,通过此更改,
unsubscribe
可以返回观察者的当前状态,因为我们知道该状态的类型:它是订阅存储的类型:val unsubscribe : 't subscr -> t -> t * 't
因此,剩下的问题是将观察者存储在地图中,其类型取决于插入他们的键的类型。此约束指向异构图。使用hmap库,可以通过以下方法完成:
module Subject = struct module Make (Event : sig type t end) : sig include SUBJECT with type event = Event.t val empty : t end = struct type event = Event.t module type OBSERVER = OBSERVER with type event = event (* we need to keep the module implementation with the key for map *) module HM = Hmap.Make(struct type 'a t = (module OBSERVER with type t = 'a) end) type t = HM.t type 'a subscr = 'a HM.key let empty = HM.empty let subscribe (type t) (((module Obs) : (module OBSERVER with type t = t) ) as vt) (init:t) o = let key: t subscr = HM.Key.create vt in key, HM.add key init o let unsubscribe subscription o = HM.rem subscription o, HM.get subscription o let send event o = let send_and_readd (HM.B(k,s)) o = let module Obs = (val HM.Key.info k) in let s = Obs.send event s in HM.add k s o in HM.fold send_and_readd o empty end end
type 'u unsubscribe_result = UResult: 'u * (module OBSERVER with type t = 't) * 't -> 'u unsubscribe_result
val unsubscribe : subscr -> t -> t unsubscribe_result