如何表示非空列表类型

Dan*_*sky 7 ocaml list reason

我非常喜欢创建可以表示无效状态的数据结构,所以我想问一下如何在reasonml 中表示非空列表?

由于可以在列表上进行模式匹配[][head, ...rest]我认为表示非空列表很容易,但我还没有找到方法。

更新:多亏了下面有启发性的答案,我才能想出一些真正打动我的东西:

module List = {
  include List;
  type nonEmpty('a) = ::('a, list('a));
  let foldNonEmpty = (type a, fn, l: nonEmpty(a)) => switch(l) {
    | [head, ...tail] => fold_left(fn, head, tail)
  };
}

module Number = {
  let min = List.foldNonEmpty(Pervasives.min);
  let max = List.foldNonEmpty(Pervasives.max);
}

Number.min([]); // illegal :D
Number.min([1]); // legal
Run Code Online (Sandbox Code Playgroud)

不知道大家怎么看,我觉得挺好看的。谢谢!

oct*_*ron 5

您还可以将不带 GADT 的新列表类型定义为:

type nonempty('a) =
  | First('a)
  | ::('a,nonempty('a))
Run Code Online (Sandbox Code Playgroud)

与 GADT 解决方案相比,您失去了一些语法糖,因为语法

let l = [1,2,3,4]
Run Code Online (Sandbox Code Playgroud)

隐式添加了一个终端,[][x, ...y]语法仍然有效

let x = [1, 2, 3, 4, ...First(5)];
let head =
  fun
  | [a, ...q] => a
  | First(a) => a;

let tail =
  fun
  | [a, ...q] => Some(q)
  | First(a) => None;
Run Code Online (Sandbox Code Playgroud)

否则,编码

type nonempty_2('a) = { head:'a, more:list('a) };
let x = { head:1, more:[2,3,4,5 ] };
let head = (x) => x.head;
let tail = fun
| {more:[head,...more],_} => Some({head, more})
| {more:[],_} => None;
Run Code Online (Sandbox Code Playgroud)

甚至更简单,并且不依赖于可能令人惊讶的句法结构。

编辑: ::,中缀变体构造函数

如果定义中的部分::看起来很奇怪,那是因为它是 OCaml 语法的边角案例的遗留物。在 Ocaml 中,

[x, ... l ]
Run Code Online (Sandbox Code Playgroud)

写了

x :: l
Run Code Online (Sandbox Code Playgroud)

这本身就是中缀形式

(::)(x,l)
Run Code Online (Sandbox Code Playgroud)

(这与标准运算符的前缀形式相同:1 + 2也可以写成 (+)(1,2)(in Reason) )最后一种形式也是[x,...l]in reason的前缀形式。简而言之,在理性中,我们有

[x, ... l ] ? (::)(x,l) 
Run Code Online (Sandbox Code Playgroud)

使用 OCaml 语法作为两个符号之间的缺失链接。

换句话说,::是一个中缀构造函数(也是唯一的一个)。使用最新版本的 OCaml,可以使用以下命令定义您自己的中缀构造函数版本

type t = (::) of int * int list
Run Code Online (Sandbox Code Playgroud)

相同的结构在 Reason 中延续

type t = ::(int, list(int))
Run Code Online (Sandbox Code Playgroud)

然后,如果您编写[a, ...b]它,则将其转换为(::)(a,b)with::作为您新定义的运算符。相似地,

[1,2,3]
Run Code Online (Sandbox Code Playgroud)

实际上是一个捷径

[1,2,3, ...[]]
Run Code Online (Sandbox Code Playgroud)

所以如果你同时定义[]and ::,例如在这个愚蠢的例子中

type alternating('a,'b) =
| []
| ::('a, alternating('b,'a) )
/* here the element of the list can alternate between `'a` and `'b`:*/
let l = [1,"one",2,"two"]
Run Code Online (Sandbox Code Playgroud)

您最终会得到一个异国列表的语法,它的工作原理与标准列表完全相同。

  • 符号`fun ... => ... | ... => ...` 确实只是 `(x)=>switch(x){ | 的快捷方式 ... => ... | ... => ... }`。 (2认同)
  • 你确实可以定义 `nonEmpty('a)=::('a,list('a))` ,它可能工作得更好,因为你得到了 `[1,2,3]` 语法,并且可以轻松提取一个列表。 (2认同)
  • 但是,是的,这种定义的一个问题是,您正在隐藏列表类型中的原始变体构造函数“::”。有两种方法可以和谐地混合两种类型的列表:或者使用类型引导消歧:“([1]:list(_))”选择正确的构造函数。正交地,您可以在模块/子模块内定义外来列表并使用本地开放语法:“Nonempty.[1,2,3]” (2认同)