如何从函数 return first-class 模块的嵌套类型的实例?
How to return the instance of first-class module's nested type from a function?
上下文:
我正在尝试使用 first-class 模块在 OCaml 中实现类似 OOP 可观察模式的东西。我有一个包含模块列表的项目,并希望通过观察而不更改地扩展它们。为了尽量减少代码重复,我创建了 Subject 模块,并计划将其用作此扩展的通用方式(在项目上下文中)的一部分。我声明了三种模块类型:
观察者:
module type OBSERVER = sig
type event
type t
val send : event -> t -> t
end
观察:
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
and SUBJECT 正在合并 OBSERVER 和 OBSERVABLE:
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 的第一个 class 模块的实例,并且能够添加和删除(以任何顺序)它们,我使用 Map with int as key (即 subscr).
...
type subscr = int
module SMap = Map.Make (Int)
...
正如我们从 OBSERVER (val send : event -> t -> t
) 中的 send 签名中看到的那样,不仅需要存储实例OBSERVER 的第一个 class 模块,还有 states 个(“[=315= 的实例]").由于类型不同,我无法将所有 states 存储在一个集合中。所以我声明了模块类型 PACK 来将 OBSERVER 的第一个-class 模块的实例和它的状态实例打包到实例中包.
...
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 的 send 函数在新的 state 中重新打包每个 pack 并在旧的 Observer 模块中。
...
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
为了测试 Subject 并查看在没有更改的情况下进行观察扩展的模块的外观 - 我创建了一些模块 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
并在模块 OAcc 中使用观察功能扩展它,并使用以下签名合并 OBSERVABLE 和原始模块类型 加速度
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 ();;
我有以下输出
# print_operations ();;
1.Add(1);
2.Add(1);
3.Add(1);
1.Multiply(2);
2.Multiply(2);
3.Multiply(2);
1.Multiply(3);
3.Multiply(3);
1.Add(4);
3.Add(4);
1.Add(5);
- : 整数 = 90
当我们的第一个class模块observer的逻辑完全基于副作用并且我们不需要它的状态时,一切正常主题 之外。但是对于相反的情况,我没有找到任何关于如何从 Subject[ 中提取订阅 observer 的 state =233=].
例如,我有以下“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
我可以订阅 History 的第一个 class 实例和它的一些初始状态 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中更改了unsubscribe的签名。在它之前 return 是“OBSERVABLE”的状态,没有“OBSERVER”与提供的订阅关联,现在它是 return 此状态的三元组,首先取消订阅-class 模块,以及取消订阅模块的状态。
之前:
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.state
^^^^^^^^^^
Error: This expression has type Pack.Observer.t
but an expression was expected of type 'a
The type constructor Pack.Observer.t would escape its scope
问题 1:
是否可以用这个签名实现取消订阅?
没用。我尝试了另一种解决方案。
它基于 取消订阅 可以 return PACK 的第一个 class 模块的实例的想法。
我更喜欢前面的想法,因为它使 PACK 的声明在 Subject 中保持私有。但是当前的解决方案在寻找解决方案方面取得了更好的进步。
我将 PACK 模块类型添加到 OBSERVABLE 并将 unsubscribe 签名更改为以下内容。
module type OBSERVABLE = sig
...
module type PACK = sig
module Observer : OBSERVER
val state : Observer.t
end
...
val unsubscribe : subscr -> t -> (t * (module PACK))
end
将 PACK 添加到 OAcc 实现中,因为它的签名包括 OBSERVABLE。另外,我重新实现了 unsubscribe of 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,因此无需添加。
仅 unsubscribe 被重新实现。
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
^^^^^^^^^^
Error: This expression has type Pack.Observer.t
but an expression was expected of type 'a
The type constructor Pack.Observer.t would escape its scope
另外,我试过了
let history_of_operations () =
...
History.to_list Pack.state
但是
History.to_list Pack.state
^^^^^^^^^^
Error: This expression has type Pack.Observer.t
but an expression was expected of type History.t
问题 2:
如何从类型为 List.t?
的 Pack 中提取状态
我更改了退订
的签名
module type OBSERVABLE = sig
...
val unsubscribe : subscr -> t -> (t * (module PACK with type Observer.t = 't))
end
并尝试在 Subject
中重新实现 unsubscribe
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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: This expression has type (module PACK)
but an expression was expected of type
(module PACK with type Observer.t = t)
看起来 OCaml 有 3 个类型抽象级别
1.具体module A : sig type t = int end = struct ...
2.摘要module A : sig type t end = struct ...
3.打包到first-class模块
问题 3:
是否可以使用(2)抽象级别[=252=存储第一个class模块的嵌套实例类型] 或者能够将其恢复到 (2) 抽象级别 ?
题目中的问题:
如何从函数return第一个class模块的嵌套类型的实例?
备注:
当然,可以通过使用可变状态来解决这个问题,但问题不在于此。
初始可编译源代码here。
关于您的问题 1,您期望一个带有签名的函数:
val unsubscribe : subscr -> t -> (t * (module OBSERVER with type t = 't) * 't))
模块的存在在这里是一个转移注意力的问题。你的签名和
没有区别
val unsubscribe : subscr -> t -> 'a
换句话说,它是一个神奇地 returns 调用者可能需要的任何类型的值的函数。如果调用者想要一个整数,函数 returns 一个整数。如果调用者想要一个字符串,函数 returns 一个字符串。等等。因此,只有一种具有这种签名的安全函数,它是一种永远不会 returns 任何东西的函数。
因此,您需要将量化转移到其他地方的类型上,例如在构造函数下:
type 'u unsubscribe_result = UResult: 'u * (module OBSERVER with type t = 't) * 't -> 'u unsubscribe_result
val unsubscribe : subscr -> t -> t unsubscribe_result
简短的回答是,打包模块的内部类型永远不能被提升到它们的第一个 class 模块之外。
当您将 packed observer 定义为:
module type PACK = sig
module Observer: sig
type t
val send: event -> t -> t
end
val state: Observer.t
end
类型 Observer.t
在第一个 class 模块中存在量化:通过将初始实现打包在 (module PACK)
中,我忘记了我对初始模块的所有了解,除了模块内部的类型相等。
这意味着对于 (module PACK)
类型的值 (module M)
,我唯一可用的操作是调用 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
可以return观察者的当前状态,因为我们知道这个状态的类型:它是订阅存储的类型:
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
免责声明:我不会假装我完全理解你的问题,这是迄今为止我在 SO 上看到的最大的 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
是纯粹抽象的,而且确实是 existential types are abstract types,例如
module Int = struct
type t = int
let int x = x
let add = (+)
let sub = (-)
let out = print_int
end
let zero = Object {
intf = (module Int);
self = 0;
}
let incr (Object {intf=(module T); self}) = Object {
intf = (module T);
self = T.add self (T.int 1)
}
let out (Object {intf=(module T); self}) = T.out self
可恢复的存在(又名动态类型)
但是,如果想要恢复抽象类型的原始类型,以便我们可以应用适用于该类型值的其他函数,该怎么办。为此,我们需要存储类型 x
属于所需类型 y
的证据,我们可以使用可扩展的 GADT
来做到这一点
type 'a witness = ..
为了创建新的见证人,我们将首先使用 class 个模块,
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)
其中模块类型 Witness
及其打包类型是,
module type Witness = sig
type t
type _ witness += Id : t witness
end
type 'a typeid = (module Witness with type t = 'a)
每次调用 newtype
时,它都会向见证类型添加一个新的构造函数,保证不等于任何其他构造函数。为了证明两个 witness 实际上是用相同的构造函数创建的,我们将使用以下函数,
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
其中returns定义为的等式证明,
type ('a,'b) eq = Equal : ('a,'a) eq
在我们可以构建类型 (x,y) eq
的对象的环境中,类型检查器将处理类型 x
与 y
具有相同类型的值。有时,当您确实确定转换必须成功时,您可以使用 cast
函数,
let cast x y = match try_cast x y with
| None -> failwith "Type error"
| Some Equal -> Equal
作为,
let Equal = cast t1 t2 in
(* here we have proved that types witnessed by t1 and t2 are the same *)
好的,现在当我们有了动态类型时,我们可以使用它们来使我们的对象类型可恢复和状态可转义。我们需要的只是将运行时信息添加到我们的对象表示中,
type obj = Object : {
intf : (module T with type t = 'a);
self : 'a;
rtti : 'a typeid;
} -> obj
现在让我们定义类型 int
的运行时表示(请注意,通常我们可以在 rtti 中放置更多信息,除了 witness,我们还可以将其设为排序类型并在运行时扩展动态类型使用新的操作,并实现临时多态性),
let int : int typeid = newtype ()
所以现在我们的 zero
对象定义为,
let zero = Object {
intf = (module Int);
self = 0;
rtti = int;
}
incr
函数仍然相同(模对象表示中的一个额外字段),因为它不需要转义。但是现在我们可以编写 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
和
# cast_object int zero;;
- : int option = Some 0
# cast_object int (incr zero);;
- : int option = Some 1
另一个例子,
let print_if_int (Object {self; rtti}) =
match try_cast int rtti with
| Some Equal -> print_int self
| None -> ()
您可以阅读有关动态类型的更多信息here。 OCaml 中也有很多库提供动态类型和异构字典等。
上下文:
我正在尝试使用 first-class 模块在 OCaml 中实现类似 OOP 可观察模式的东西。我有一个包含模块列表的项目,并希望通过观察而不更改地扩展它们。为了尽量减少代码重复,我创建了 Subject 模块,并计划将其用作此扩展的通用方式(在项目上下文中)的一部分。我声明了三种模块类型:
观察者:
module type OBSERVER = sig
type event
type t
val send : event -> t -> t
end
观察:
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
and SUBJECT 正在合并 OBSERVER 和 OBSERVABLE:
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 的第一个 class 模块的实例,并且能够添加和删除(以任何顺序)它们,我使用 Map with int as key (即 subscr).
...
type subscr = int
module SMap = Map.Make (Int)
...
正如我们从 OBSERVER (val send : event -> t -> t
) 中的 send 签名中看到的那样,不仅需要存储实例OBSERVER 的第一个 class 模块,还有 states 个(“[=315= 的实例]").由于类型不同,我无法将所有 states 存储在一个集合中。所以我声明了模块类型 PACK 来将 OBSERVER 的第一个-class 模块的实例和它的状态实例打包到实例中包.
...
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 的 send 函数在新的 state 中重新打包每个 pack 并在旧的 Observer 模块中。
...
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
为了测试 Subject 并查看在没有更改的情况下进行观察扩展的模块的外观 - 我创建了一些模块 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
并在模块 OAcc 中使用观察功能扩展它,并使用以下签名合并 OBSERVABLE 和原始模块类型 加速度
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 ();;
我有以下输出
# print_operations ();;
1.Add(1);
2.Add(1);
3.Add(1);
1.Multiply(2);
2.Multiply(2);
3.Multiply(2);
1.Multiply(3);
3.Multiply(3);
1.Add(4);
3.Add(4);
1.Add(5);- : 整数 = 90
当我们的第一个class模块observer的逻辑完全基于副作用并且我们不需要它的状态时,一切正常主题 之外。但是对于相反的情况,我没有找到任何关于如何从 Subject[ 中提取订阅 observer 的 state =233=].
例如,我有以下“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
我可以订阅 History 的第一个 class 实例和它的一些初始状态 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中更改了unsubscribe的签名。在它之前 return 是“OBSERVABLE”的状态,没有“OBSERVER”与提供的订阅关联,现在它是 return 此状态的三元组,首先取消订阅-class 模块,以及取消订阅模块的状态。
之前:
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.state ^^^^^^^^^^
Error: This expression has type Pack.Observer.t
but an expression was expected of type 'a
The type constructor Pack.Observer.t would escape its scope
问题 1:
是否可以用这个签名实现取消订阅?
没用。我尝试了另一种解决方案。 它基于 取消订阅 可以 return PACK 的第一个 class 模块的实例的想法。 我更喜欢前面的想法,因为它使 PACK 的声明在 Subject 中保持私有。但是当前的解决方案在寻找解决方案方面取得了更好的进步。
我将 PACK 模块类型添加到 OBSERVABLE 并将 unsubscribe 签名更改为以下内容。
module type OBSERVABLE = sig
...
module type PACK = sig
module Observer : OBSERVER
val state : Observer.t
end
...
val unsubscribe : subscr -> t -> (t * (module PACK))
end
将 PACK 添加到 OAcc 实现中,因为它的签名包括 OBSERVABLE。另外,我重新实现了 unsubscribe of 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,因此无需添加。 仅 unsubscribe 被重新实现。
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 ^^^^^^^^^^
Error: This expression has type Pack.Observer.t
but an expression was expected of type 'a
The type constructor Pack.Observer.t would escape its scope
另外,我试过了
let history_of_operations () =
...
History.to_list Pack.state
但是
History.to_list Pack.state ^^^^^^^^^^
Error: This expression has type Pack.Observer.t
but an expression was expected of type History.t
问题 2:
如何从类型为 List.t?
的 Pack 中提取状态我更改了退订
的签名module type OBSERVABLE = sig
...
val unsubscribe : subscr -> t -> (t * (module PACK with type Observer.t = 't))
end
并尝试在 Subject
中重新实现 unsubscribemodule 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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: This expression has type (module PACK)
but an expression was expected of type
(module PACK with type Observer.t = t)
看起来 OCaml 有 3 个类型抽象级别
1.具体module A : sig type t = int end = struct ...
2.摘要module A : sig type t end = struct ...
3.打包到first-class模块
问题 3:
是否可以使用(2)抽象级别[=252=存储第一个class模块的嵌套实例类型] 或者能够将其恢复到 (2) 抽象级别 ?
题目中的问题:
如何从函数return第一个class模块的嵌套类型的实例?
备注:
当然,可以通过使用可变状态来解决这个问题,但问题不在于此。
初始可编译源代码here。
关于您的问题 1,您期望一个带有签名的函数:
val unsubscribe : subscr -> t -> (t * (module OBSERVER with type t = 't) * 't))
模块的存在在这里是一个转移注意力的问题。你的签名和
没有区别val unsubscribe : subscr -> t -> 'a
换句话说,它是一个神奇地 returns 调用者可能需要的任何类型的值的函数。如果调用者想要一个整数,函数 returns 一个整数。如果调用者想要一个字符串,函数 returns 一个字符串。等等。因此,只有一种具有这种签名的安全函数,它是一种永远不会 returns 任何东西的函数。
因此,您需要将量化转移到其他地方的类型上,例如在构造函数下:
type 'u unsubscribe_result = UResult: 'u * (module OBSERVER with type t = 't) * 't -> 'u unsubscribe_result
val unsubscribe : subscr -> t -> t unsubscribe_result
简短的回答是,打包模块的内部类型永远不能被提升到它们的第一个 class 模块之外。
当您将 packed observer 定义为:
module type PACK = sig
module Observer: sig
type t
val send: event -> t -> t
end
val state: Observer.t
end
类型 Observer.t
在第一个 class 模块中存在量化:通过将初始实现打包在 (module PACK)
中,我忘记了我对初始模块的所有了解,除了模块内部的类型相等。
这意味着对于 (module PACK)
类型的值 (module M)
,我唯一可用的操作是调用 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
可以return观察者的当前状态,因为我们知道这个状态的类型:它是订阅存储的类型:
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
免责声明:我不会假装我完全理解你的问题,这是迄今为止我在 SO 上看到的最大的 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
是纯粹抽象的,而且确实是 existential types are abstract types,例如
module Int = struct
type t = int
let int x = x
let add = (+)
let sub = (-)
let out = print_int
end
let zero = Object {
intf = (module Int);
self = 0;
}
let incr (Object {intf=(module T); self}) = Object {
intf = (module T);
self = T.add self (T.int 1)
}
let out (Object {intf=(module T); self}) = T.out self
可恢复的存在(又名动态类型)
但是,如果想要恢复抽象类型的原始类型,以便我们可以应用适用于该类型值的其他函数,该怎么办。为此,我们需要存储类型 x
属于所需类型 y
的证据,我们可以使用可扩展的 GADT
type 'a witness = ..
为了创建新的见证人,我们将首先使用 class 个模块,
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)
其中模块类型 Witness
及其打包类型是,
module type Witness = sig
type t
type _ witness += Id : t witness
end
type 'a typeid = (module Witness with type t = 'a)
每次调用 newtype
时,它都会向见证类型添加一个新的构造函数,保证不等于任何其他构造函数。为了证明两个 witness 实际上是用相同的构造函数创建的,我们将使用以下函数,
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
其中returns定义为的等式证明,
type ('a,'b) eq = Equal : ('a,'a) eq
在我们可以构建类型 (x,y) eq
的对象的环境中,类型检查器将处理类型 x
与 y
具有相同类型的值。有时,当您确实确定转换必须成功时,您可以使用 cast
函数,
let cast x y = match try_cast x y with
| None -> failwith "Type error"
| Some Equal -> Equal
作为,
let Equal = cast t1 t2 in
(* here we have proved that types witnessed by t1 and t2 are the same *)
好的,现在当我们有了动态类型时,我们可以使用它们来使我们的对象类型可恢复和状态可转义。我们需要的只是将运行时信息添加到我们的对象表示中,
type obj = Object : {
intf : (module T with type t = 'a);
self : 'a;
rtti : 'a typeid;
} -> obj
现在让我们定义类型 int
的运行时表示(请注意,通常我们可以在 rtti 中放置更多信息,除了 witness,我们还可以将其设为排序类型并在运行时扩展动态类型使用新的操作,并实现临时多态性),
let int : int typeid = newtype ()
所以现在我们的 zero
对象定义为,
let zero = Object {
intf = (module Int);
self = 0;
rtti = int;
}
incr
函数仍然相同(模对象表示中的一个额外字段),因为它不需要转义。但是现在我们可以编写 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
和
# cast_object int zero;;
- : int option = Some 0
# cast_object int (incr zero);;
- : int option = Some 1
另一个例子,
let print_if_int (Object {self; rtti}) =
match try_cast int rtti with
| Some Equal -> print_int self
| None -> ()
您可以阅读有关动态类型的更多信息here。 OCaml 中也有很多库提供动态类型和异构字典等。