如何在不知道元素类型的情况下创建一组元素?

如何解决如何在不知道元素类型的情况下创建一组元素?

我在尝试使用 Caml 的 Map/Set 内容时遇到了有关递归/相互引用模块定义的问题。我真的想要那些只适用于类型而不是模块的。我觉得应该可以用一流的模块来做到这一点,但我没有让语法正常工作。

我想要的签名是:

module type NonFunctorSet = sig
  type 'a t
  val create : ('a -> 'a -> int) -> 'a t
  val add : 'a t -> 'a -> 'a t
  val remove : 'a t -> 'a -> 'a t
  val elements : 'a t -> 'a list
end

可能包含其他 Caml.Set 函数。我对这将如何工作的想法是这样的:

type 'a t = {
 m : (module Caml.Set.S with type elt = 'a);
 set : m.t
}

let create (compare : 'a -> 'a -> t) =
    module m = Caml.Set.Make(struct type t = 'a let compare = compare end) in
    let set = m.empty in
    {m = m; set = set;}
end

但由于多种原因,这不起作用; 'a 未在正确的位置公开,我无法在定义 m 的同一记录中引用 m.t,等等。

有这样的版本吗?

添加更多关于我的用例的上下文:

我有两个模块,RegionTribeTribe 需要访问 Region 的很多接口,所以我目前正在创建 Tribe 作为函子 MakeTribe(Region : RegionT)Region 大多不需要知道 Tribe,但它确实需要能够存储代表居住在该地区的部落的 Tribe.t 的可变集合。

所以,不知何故,我需要一个 RegionT 之类的

module type RegionT = sig
  type <region>
  val get_local_tribes : <region> -> <tribes>
  val add_tribe : <region> -> <tribe> -> unit
  ...
end

我不太关心这里面<tribe><tribes><region>的具体语法,只要完整构建的Tribe模块可以知道{ {1}} 等将产生实际的 Region.get_local_tribes 循环依赖问题是类型 Tribe.t 在创建模块 <tribe> 之前不存在。到目前为止,我的想法是让 Tribe 实际上是 RegionT.t,然后 'a RegionT.t 可以简单地引用 Tribe。如果我对在 Tribe.t Region.t 中保留一个 <tribe> list 感到满意,这一切都很好,但我希望它是一个集合。

根据以下示例代码,我觉得这应该是可能的:

Region

module Example : sig type t val compare : t -> t -> int end = struct type t = int let compare = Int.compare end module ExampleSet = Caml.Set.Make(struct type t = Example.t let compare = Example.compare end) 在其接口中公开的只是一个类型和一个从该类型的两个实例到一个 int 的函数;为什么这不仅仅是拥有一个具有相同内容的 Example

解决方法

使用基础库中的多态集和映射

在来自 Jane Street 的 Base 和 Core 库中,有序数据结构(例如映射、集合、哈希表和哈希集)都实现为多态数据结构,而不是像普通 OCaml 标准库中的函子化版本。

您可以在 Real World OCaml Maps and Hashtbales 一章中阅读有关它们的更多信息。但这里有快速食谱。当您在函数接口中看到比较器时,例如,在 Map.empty 中,它真正想要的是给您一个实现比较器接口的模块。好消息是 Base/Core 中的大多数模块都在实现它,因此您不必担心或了解它的任何信息即可使用它,例如,

# open Base;;
# let empty = Map.empty (module Int);;
val empty : (Base.Int.t,'a,Base.Int.comparator_witness) Base.Map.t =
  <abstr>
# Map.add empty 1 "one";;
- : (Base.Int.t,string,Base.Int.comparator_witness) Base.Map.t
    Base.Map.Or_duplicate.t
= `Ok <abstr>

所以简单的规则,如果你想要一个 set、map、hashtable、hashset,其中键元素的类型为 foo,只需将 (module Foo) 作为比较器传递即可。

现在,如果您想从自定义类型进行映射怎么办?例如,您想按字典顺序比较的一对整数。

首先,我们需要定义sexp_of和compare函数。对于我们的类型。我们将使用 ppx 派生器,但如果您需要,也可以轻松手动制作。

 module Pair = struct
   type t = int * int [@@deriving compare,sexp_of]
 end

现在,要创建一个比较器,我们只需要使用 Base.Comparator.Make 函子,例如,

 module Lexicographical_order = struct 
    include Pair
    include Base.Comparator.Make(Pair)
 end

现在我们可以这样做了,


# let empty = Set.empty (module Lexicographical_order);;
val empty :
  (Lexicographical_order.t,Lexicographical_order.comparator_witness)
  Base.Set.t = <abstr>
# Set.add empty (1,2);;
- : (Lexicographical_order.t,Lexicographical_order.comparator_witness)
    Base.Set.t
= <abstr>

尽管 Base 的数据结构是多态的,但它们严格要求提供比较器的模块已实例化且已知。您可以只使用 compare 函数来创建多态数据结构,因为 Base 将为每个定义的比较函数实例化一个见证类型,并在数据结构类型中捕获它以启用二进制方法。无论如何,这是一个复杂的问题,请继续阅读更简单(和更难)的解决方案。

在相互依赖的模块上实例化集合

事实上,OCaml 支持相互递归的仿函数,虽然我建议您通过引入 Region 和 Tribe 都依赖的公共抽象来打破递归,但您仍然可以在 OCaml 中编码您的问题,例如,

module rec Tribe : sig
  type t
  val create : string -> t
  val compare : t -> t -> int
  val regions : t -> Region.t list
end = struct
  type t = string * Region.t list
  let create name = name,[]
  let compare (x,_) (y,_) = String.compare x y
  let regions (_,r) = r
end
and Region : sig
  type t
  val empty : t
  val add_tribe : Tribe.t -> t -> t
  val tribes : t -> Tribe.t list
end = struct
  module Tribes = Set.Make(Tribe)
  type t = Tribes.t
  let empty = Tribes.empty
  let add_tribe = Tribes.add
  let tribes = Tribes.elements
end

打破依赖循环

一个更好的解决方案是重新设计你的模块并打破依赖循环。最简单的方法就是选择一些用于比较部落的标识符,例如,通过它们的唯一名称,

module Region : sig
  type 'a t
  val empty : 'a t
  val add_tribe : string -> 'a -> 'a t -> 'a t
  val tribes : 'a t -> 'a list
end = struct
  module Tribes = Map.Make(String)
  type 'a t = 'a Tribes.t
  let empty = Tribes.empty
  let add_tribe = Tribes.add
  let tribes r = Tribes.bindings r |> List.map snd

end

module Tribe : sig
  type t
  val create : string -> t
  val name : t -> string
  val regions : t -> t Region.t list
  val conquer : t Region.t -> t -> t Region.t
end = struct
  type t = Tribe of string * t Region.t list
  let create name = Tribe (name,[])
  let name (Tribe (name,_)) = name
  let regions (Tribe (_,r)) = r
  let conquer region tribe =
    Region.add_tribe (name tribe) tribe region
end

还有很多其他选项,一般来说,当您有相互依赖关系时,它实际上表明您的设计存在问题。所以,我仍然会重新审视设计阶段并避免循环依赖。

使用 Vanilla OCaml 标准库创建多态集

这不是一件容易的事,尤其是当您需要处理涉及多个集合的操作时,例如 Set.union。问题是 Set.Make 为每个 compare 函数的集合生成一个新类型,所以当我们需要联合两个集合时,我们很难向 OCaml 编译器证明它们是从同类型。这是可能的,但真的很痛苦,我展示了如何做到这一点只是为了阻止你这样做(并展示 OCaml 的动态类型功能)。

首先,我们需要一个见证类型,它将集合的 OCaml 类型具体化为一个具体的值。

type _ witness = ..


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

现在我们可以将我们的多态集合定义为一个存在集合,它包含集合本身和带有操作的模块。它还包含 tid(用于类型标识符),我们稍后将使用它来恢复集合的类型 's

type 'a set = Set : {
    set : 's;
    ops : (module Set.S with type elt = 'a and type t = 's);
    tid : (module Witness with type t = 's);
  } -> 'a set

现在我们可以编写 create 函数来获取比较函数并将其转换为集合,

let create : type a s. (a -> a -> int) -> a set =
  fun compare ->
  let module S = Set.Make(struct
      type t = a
      let compare = compare
    end) in
  let module W = struct
    type t = S.t
    type _ witness += Id : t witness
  end in
  Set {
    set = S.empty;
    ops = (module S);
    tid = (module W);
  }

这里需要注意的是,每次调用 create 都会生成一个集合类型 's 的新实例,因此我们可以比较/联合/等使用相同 {{1} 创建的两个集合} 功能。换句话说,我们实现中的所有集合都应该共享同一个祖先。但在此之前,让我们痛苦地实现至少两个操作,createadd

union

现在,我们可以玩一下了,

let add : type a. a -> a set -> a set =
  fun elt (Set {set; tid; ops=(module Set)}) -> Set {
      set = Set.add elt set;
      ops = (module Set);
      tid;
    }

let union : type a. a set -> a set -> a set =
  fun (Set {set=s1; tid=(module W1); ops=(module Set)})
    (Set {set=s2; tid=(module W2)}) ->
    match W1.Id with
    | W2.Id -> Set {
        set = Set.union s1 s2;
        tid = (module W1);
        ops = (module Set);
      }
    | _ -> failwith "sets are potentially using different types"

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res