如何在Reason ML中声明一个map类型?

14

Reason ML 相对于 JavaScript 的一个优势是它提供了一种使用结构等价而不是引用等价的 Map 类型。

然而,我找不到这方面的用例。

例如,如何声明一个名为 scores 的类型,它是字符串到整数的映射?

/* Something like this */
type scores = Map<string, int>; 

我应该如何构建一个实例?

/* Something like this */
let myMap = scores();

let myMap2 = myMap.set('x', 100);
2个回答

21
标准库中的Map在编程语言领域中实际上是非常独特的,因为它是一个模块函子,您必须使用它来构建适用于特定键类型的映射模块(因此API参考文档可以在 Map.Make下找到):
module StringMap = Map.Make({
  type t = string;
  let compare = compare
});

type scores = StringMap.t(int);

let myMap = StringMap.empty;
let myMap2 = StringMap.add("x", 100, myMap);

除了使用常规数据结构来构建类似于映射的功能之外,如果你需要专门的字符串键,还可以使用其他数据结构。在BuckleScript Cookbook中,有不同方法的比较。除了Js.Dict外,其他方法都可在BuckleScript之外使用。此外,BuckleScript beta标准库中也内置了一个新的Map数据结构,可通过此链接查看,不过我还没有尝试过。


所以键类型是在“模块函数”中定义的(这对我来说是一个新概念),而值类型则被定义为t函数的类型参数?你能分享一下为什么要分两个阶段实现吗?作为来自F#的人,这看起来非常奇怪。 - sdgfsdh
1
StringMap.t 是一个带有类型参数的类型,严格来说不是一个函数,但从概念上讲,你可以(而且应该!)将其视为一种类型函数。同样地,模块函子只是模块之间的函数,它接受一个模块(或多个模块),并返回一个模块。 - glennsl
1
我不完全确定为什么它被实现为一个函数对象,但可能的动机之一就是因为你可以这样做。在 F# 中,我认为你不能这样做,因为模块系统不够强大。对于一个不可变的数据结构,这可能也使得实现更加清晰、稍微更高效,并且占用的内存更少,因为如果没有“硬编码”比较函数,你将不得不在每次添加或删除映射中的节点时携带它并进行复制。 - glennsl
2
另一个重要的点是,地图的类型应该取决于键类型和比较函数。否则,可能会尝试合并使用不同比较函数构建的两个地图,导致完全不可预测的行为。Functor提供了一种很好的方法,使地图类型依赖于整个参数模块,包括比较函数在内。实现此结果的其他清晰方法更加复杂(参见Janestreet的Base.Map模块以获取良好的示例)。 - octachron

6
如果你只涉及到一个 Map<string, int>,Belt 的 Map.String 就能解决问题。
module MS = Belt.Map.String;

let foo: MS.t(int) = [|("a", 1), ("b", 2), ("c", 3)|]->MS.fromArray;

关于“Belt”版本的人机工程学比较友好,而且还有不可变映射!此外,在“Belt”中还有Map.Int。对于其他键类型,您将需要定义自己的比较器。这又回到了@glennsl之前详细描述的类似于两步过程的方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接