为什么在F#类型声明中括号很重要?

6

我对观察到的F#代码行为感到非常困惑,以下内容来自交互式会话:

Microsoft (R) F# 2.0 Interactive build 4.0.40219.1
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> type foo = Foo of (string * int);;
  type foo = | Foo of (string * int)

> let f = Foo ("bar",42);;
  val f : foo = Foo ("bar", 42)

> match f with Foo x -> x;;
  val it : string * int = ("bar", 42)

> type bar = Bar of string * int;;
  type bar = | Bar of string * int

> let b = Bar ("baz",21);;
  val b : bar = Bar ("baz",21)

> match b with Bar x -> x;;
  match b with Bar x -> x;;
  -------------^^^^^

stdin(7,14): error FS0019: This constructor is applied to 1 argument(s) but expects 2
> 

我认为使用单个变量对Foo和Bar进行模式匹配应该是有效的,这似乎是显而易见的-因此,我想知道是否有人知道这种奇怪行为的原因,或者像我一样认为这是一个错误。

更新: 仅澄清,构造函数 Foo Bar 的报告类型如下:

> Foo;;
val it : string * int -> foo = <fun:clo@14-1>
> Bar;;
val it : string * int -> bar = <fun:clo@13>

因此,他们应该接受相同的有效模式集。

1个回答

6
我同意这看起来相当混乱。正如pad所解释的那样,两个声明之间的区别不仅仅是语法上的 - 你实际上正在定义由不同类型组成的判别式联合情况。
对于Foo,情况包含一个类型为int * string的元素
对于Bar,情况包含两个类型为int和string的元素
这两个选项非常相似,但它们实际上是不同的。如果您查看F#规范中的类型定义,就会发现这一点。以下是描述判别式联合类型定义的部分: union-type-defn := type-name '=' union-type-cases type-extension-elementsopt union-type-cases := |opt union-type-case '|' ... '|' union-type-case union-type-case := attributesopt union-type-case-data union-type-case-data := ident -- null union case ident of type * ... * type -- n-ary union case 请注意,“n元联合情况”由多个元素(type * ... * type)组成。类型定义如下(毫不奇怪,它可以是一个元组):
type := (type) type -> type -- 函数类型 type * ... * type -- 元组类型 ... -- 许多其他类型
我不知道为什么union-type-case-data不仅使用一元联合情况(而不是n元),并始终将元素视为元组。我认为这完全有道理,但它可能是F#从OCaml或ML继承的东西。但是,至少规范解释了这一点!
事实上,我认为规范有点模棱两可,因为您可以将Foo of int * int视为n元联合情况和带有元组(但没有括号类型(type))的一元情况。

非常感谢您提供如此详细的回复,甚至指出了相关语言规范的部分!但是我恐怕现在认为它更像是一种严重的畸形了,因为我知道这是有意为之的! :-) - plc
1
@plc - 我猜测差异的原因是为了控制与其他语言(如C#)的交互边界。在这种情况下,您可能会关心是否需要通过传递预构造的元组或单独传递组成部分来创建DU实例。 - kvb

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