首页 > 解决方案 > 在有区别的联合案例中使用元组时,括号的作用是什么?

问题描述

Microsoft 文档定义的可区分联合语法不包括括号。以下定义允许我在类型定义中使用字段名称,并且与文档中的语法兼容。

type Node<'T> = L of value:'T | N of value:'T * children:Node<'T> list

这个定义:

type Node<'T> = L of value:'T | N of (value:'T * children:Node<'T> list)

给我错误:

Anonymous type variables are not permitted in this declaration

但是,如果我不使用字段名称,括号很好:

type Node<'T> = L of 'T | N of ('T * Node<'T> list)

此更改请求中的评论提到了括号使用之间的差异,但坦率地说,整个讨论都超出了我的想象。

我的印象是,无论我是否使用括号,我都在定义一个元组,即我希望编译器将它们视为冗余,但显然存在差异。它是什么?

标签: f#tuplesdiscriminated-union

解决方案


这只是重载的语法。尽管句法相似,但这两种情况在语义上是不同的。

混淆来自这样一个事实,即定义 DU 案例字段定义元组类型使用相同的语法。

考虑这两种类型:

type A = A of int * string
type B = B of (int * string)

在这里,构造函数A不包装元组。相反,它有两个字段 - 第一个字段 type int,第二个字段 type string。DU 案例字段可以选择具有名称,这也是它起作用的原因:

type A1 = A1 of x: int * y: string

B另一方面,构造函数没有两个字段。它只有一个字段,该字段的类型是int * string. 由于 DU 案例字段可能有名称,我们也可以为该单个字段命名:

type B1 = B1 of t: (int * string)

在构造Aor类型的值时A1,您在括号中指定所有字段:

let a = A (42, "foo")

就像在类型声明中一样,这看起来像一个元组,但事实并非如此。这是两个独立的字段。由于 DU 案例字段可能有名称,因此您可以在构造值时使用这些名称。有时它有助于不混淆它们或只是为了代码可读性:

let a1 = A1 (x = 42, y = "foo")

事实上,DU 案例字段总是有名称,即使您没有明确指定它们。省略时,编译器将分配看起来像的名称ItemN(或仅Item当只有一个字段时)。是的,您可以在构造值时使用它们:

let a = A (Item1 = 42, Item2 = "foo")

另一方面,在构造 的值时B,不能为元组元素使用名称,因为元组元素没有名称:

let b = B (42, "foo") // works
let b = B (Item1 = 42, Item2 = "foo") // doesn't compile

但是您可以使用该单个字段的名称,其类型为int * string

let b = B (Item = (42, "foo"))
let b1 = B1 (t = (42, "foo"))

它与模式匹配类似。考虑表达式:

match a with
| A (p, q) -> ...

再说一次,尽管(p, q)看起来像一个元组,但它不是。它与构造函数内的元组不匹配A。相反,它匹配构造函数的两个字段。没有一个元组。两个领域。

与构造值类似,您可以使用字段名称:

match a with
| A1 (x = p, y = q) -> ...

使用字段名称还可以让您部分匹配:

match a with
| A1 (x = p) -> ...

但是当你匹配时B,你有两个选择:

(1) match b with B (x, y) -> ...
(2) match b with B t -> ...

第二个选项的工作原理与 match 相同A:构造函数B有一个字段,我们正在匹配该字段。

第一个选项有效,因为模式可以嵌套:我们匹配构造函数的单个字段B,然后我们匹配同一模式中该字段的各个元素。

当然,您也可以使用字段名称:

match b with B (Item = (x, y)) -> ...
match b1 with B1 (t = (x, y)) -> ...

也许不幸的是,DU case 字段的语法与元组的语法如此相似,以至于造成了如此多的混乱,但我们在这里。要记住的底线是:这些不是元组,它只是一个类似的语法。


推荐阅读