【发布时间】:2020-06-08 22:28:17
【问题描述】:
上下文:
我正在尝试使用一流的模块在 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
和 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)实现为函子的原因。 p>
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' 的实例s 一流的模块,还有它们的状态(“OBSERVER.t”的实例)。由于类型不同,我无法将所有 states 存储在一个集合中。所以我声明了模块类型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 的 函数 send 将每个 pack 重新打包到新的 state 和旧的 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 和原始 Acc 的模块类型
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,将观察职责委托给 Subject,将主要职责委托给原始 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.添加(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
我可以将 History 的第一类实例及其一些初始状态订阅到 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 的签名。在返回没有与提供的订阅关联的“OBSERVER”的“OBSERVABLE”状态之前,现在它返回此状态的三倍,未订阅的第一类模块和状态未订阅的模块。
之前:
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 ^^^^^^^^^^错误:此表达式的类型为 Pack.Observer.t
但是需要一个类型为 'a
的表达式 类型构造函数 Pack.Observer.t 将逃脱其范围
问题一:
是否可以使用此签名实现取消订阅?
它不起作用。我尝试了另一种解决方案。 它基于 unsubscribe 可以返回 PACK 的一流模块的实例的想法。 我更喜欢前面的想法,因为它在 Subject 中将 PACK 的声明保持为私有。但目前的解决方案在寻找解决方案方面取得了更好的进展。
我在 OBSERVABLE 中添加了 PACK 模块类型并将 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
在 OAcc 实现中添加了 PACK,因为它的签名包含 OBSERVABLE。另外,我重新实现了OAcc的unsubscribe。
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 ^^^^^^^^^^错误:此表达式的类型为 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:
如何从 Pack 中提取 List.t 类型的状态?
我更改了退订的签名
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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^错误:此表达式具有类型(模块 PACK)
但是表达式应该是类型
(Observer.t = t 类型的模块 PACK)
看起来 OCaml 有 3 个类型抽象级别
1.混凝土module A : sig type t = int end = struct ...
2. 摘要module A : sig type t end = struct ...
3.打包成一流的模块
问题3:
是否可以使用 (2) 抽象级别 或将其恢复到 的能力来存储第一类模块的嵌套类型的实例(2) 抽象级别?
题目中的问题:
如何从函数中返回一等模块嵌套类型的实例?
备注:
当然,可以通过使用可变状态来解决这个问题,但问题不在于。
初始可编译源代码here。
【问题讨论】:
-
解决方案:gist.github.com/vzakh/017cc4314404dc089fa5a3116148c052
标签: generics ocaml observer-pattern first-class-modules