如何从函数返回一流模块的嵌套类型的实例?

问题描述 投票:1回答:2

上下文:

我正在尝试使用一流的模块在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

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
一流模块的实例并能够添加和删除(以任何顺序)它们,我将

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 

send

重新包装了新的state和旧的Observer模块中的每个pack... 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 ();;之后,我得到以下输出

#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
我可以将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中更改了退订的签名。在返回状态“

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.state ^^^^^^^^^^

错误:此表达式的类型为Pack.Observer.t但是应该使用'a类型的表达式类型构造函数Pack.Observer.t将逃避其范围
问题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

generics ocaml observer-pattern first-class-modules
2个回答
0
投票
type 'u unsubscribe_result = UResult: 'u * (module OBSERVER with type t = 't) * 't -> 'u unsubscribe_result val unsubscribe : subscr -> t -> t unsubscribe_result

0
投票
© www.soinside.com 2019 - 2024. All rights reserved.