我试图理解以下代码,特别是StringConstant:
type StringConstant = StringConstant of string * string
[<EntryPoint>]
let main argv =
let x = StringConstant("little", "shack")
printfn "%A" x
0 // return an integer exit code
(通过上下文,在FParsec tutorial中使用StringConstant,但此示例不使用FParsec。)
我想知道的是:
类型语句到底在做什么?
一旦我实例化了x,我将如何访问各个“部分” (“小”或“房子”)
答案 0 :(得分:7)
正如其他人已经注意到的那样,从技术上讲,StringConstant
是一个只有一个案例的歧视联盟,你可以使用模式匹配来提取值。
在谈论F#中的domain modelling时,我喜欢使用另一种有用的类比。通常,您可以通过说某些数据类型是元组来开始:
type Person = string * int
这是表示数据的简单方法,但问题是当您编写"Tomas", 42
时,编译器不知道您的意思是Person
,而是将其理解为string * int
元组。一个案例歧视的工会是命名你的元组的一种非常好的方式:
type Person = Person of string * int
使用名称Person
两次可能有点令人困惑 - 首先是类型名称,第二个是案例名称。这没有特殊意义 - 它只是意味着该类型将与案例具有相同的名称。
现在您可以编写Person("Tomas", 42)
来创建一个值,它的类型为Person
。您可以使用match
或let
对其进行分解,但您也可以轻松编写带Person
的函数。例如,要返回名称,您可以写:
let getName (Person(name, _)) =
name
我认为单一案例歧视联盟经常被使用,主要是因为它们很容易定义并且非常容易使用。但是,我不会在作为公共API公开的代码中使用它们,因为它们有点不寻常并且可能令人困惑。
PS:另请注意,在提取值时需要使用括号:
// Correct. Defines symbols 'name' and 'age'
let (Person(name, age)) = tomas
// Incorrect! Defines a function `Person` that takes a tuple
// (and hides the `Person` case of the discriminated union)
let Person(name, age) = tomas
答案 1 :(得分:2)
StringConstant
是一种有区别的联合类型,只有一个案例(也称为StringConstant
)。您可以使用match
/ function
或仅let
通过模式匹配来提取零件,因为只有一个案例:
let (StringConstant(firstPart, secondPart)) = x
答案 2 :(得分:1)
type StringConstant = StringConstant of string * string
导致一种类型的歧视联合。
如果您在F#interactive中执行它,请 type StringConstant = | StringConstant of string * string
。
您可以在here上看到msdn文档。
您可以像这样获得价值:
let printValue opt =
match opt with
| StringConstant( x, y) -> printfn "%A%A" x y
答案 3 :(得分:1)
其他人已经提到你如何从一个受歧视的联盟中提取数据,但是为了更多地阐述歧视联盟,人们可以说它们就像类固醇的枚举一样。它们在幕后实现为类型层次结构,其中类型是基类,案例是该基类的子类,它们可能具有作为只读公共变量的任何参数。
在Scala中,类似的数据结构称为案例类,可以帮助您说服自己这种实现方法。
有区别的联合的一个不错的属性是它们是可自引用的,因此非常适合定义像树一样的递归结构。以下是仅在三行代码中的霍夫曼编码树的定义。在C#中执行此操作可能需要5到10倍的代码行。
type CodeTree =
| Branch of CodeTree * CodeTree * list<char> * int
| Leaf of char * int
有关歧视联盟的信息,请参阅msdn documentation
有关使用Discriminated Unions作为树结构的示例,请参阅此gist,它是大约60行F#中的霍夫曼解码器的实现。