F# 编程/区分联合
F#:区分联合 |
区分联合,也称为标记联合,表示一组有限且定义明确的选择。区分联合通常是构建更复杂数据结构(包括链接列表和各种树)的首选工具。
区分联合使用以下语法定义
type unionName =
| Case1
| Case2 of datatype
| ...
按照惯例,联合名称以小写字母开头,联合情况用帕斯卡大小写书写。
假设我们有一个电灯开关。对于大多数人来说,电灯开关只有两种可能的状态:电灯开关可以打开,也可以关闭。我们可以使用 F# 联合来模拟我们的电灯开关的状态,如下所示
type switchstate =
| On
| Off
我们定义了一个名为 switchstate
的联合,它有两个情况,On
和 Off
。您可以创建和使用 switchstate
的实例,如下所示
type switchstate =
| On
| Off
let x = On (* creates an instance of switchstate *)
let y = Off (* creates another instance of switchstate *)
let main() =
printfn "x: %A" x
printfn "y: %A" y
main()
此程序具有以下类型
type switchstate = On | Off
val x : switchstate
val y : switchstate
它输出以下内容
x: On y: Off
请注意,我们通过使用其情况的名称来创建一个 switchstate
实例;这是因为,从字面上讲,联合的情况是构造函数。您可能已经猜到,由于我们使用相同的语法来构建对象和匹配对象,我们可以通过以下方式对联合进行模式匹配
type switchstate =
| On
| Off
let toggle = function (* pattern matching input *)
| On -> Off
| Off -> On
let main() =
let x = On
let y = Off
let z = toggle y
printfn "x: %A" x
printfn "y: %A" y
printfn "z: %A" z
printfn "toggle z: %A" (toggle z)
main()
函数 toggle
的类型为 val toggle : switchstate -> switchstate
。
此程序的输出如下
x: On y: Off z: On toggle z: Off
上面的示例故意保持简单。事实上,在许多方面,上面定义的区分联合与枚举值并没有太大区别。但是,假设我们想将我们的电灯开关模型更改为调光开关的模型,或者换句话说,是一个允许用户将灯泡的功率输出从 0% 调整到 100% 的电灯开关。
我们可以修改上面的联合来适应三种可能的状态:打开、关闭和一个在 0 到 100 之间的可调值
type switchstate =
| On
| Off
| Adjustable of float
我们添加了一个新的情况,Adjustable of float
。此情况从根本上与其他情况相同,只是它在构造函数中接受一个 float
值
open System
type switchstate =
| On
| Off
| Adjustable of float
let toggle = function
| On -> Off
| Off -> On
| Adjustable(brightness) ->
(* Matches any switchstate of type Adjustable. Binds
the value passed into the constructor to the variable
'brightness'. Toggles dimness around the halfway point. *)
let pivot = 0.5
if brightness <= pivot then
Adjustable(brightness + pivot)
else
Adjustable(brightness - pivot)
let main() =
let x = On
let y = Off
let z = Adjustable(0.25) (* takes a float in constructor *)
printfn "x: %A" x
printfn "y: %A" y
printfn "z: %A" z
printfn "toggle z: %A" (toggle z)
Console.ReadKey(true) |> ignore
main()
此程序输出
x: On y: Off z: Adjustable 0.25 toggle z: Adjustable 0.75
区分联合可以轻松地模拟各种树和层次数据结构。
例如,让我们考虑以下二叉树
我们树的每个节点都包含正好两个分支,每个分支可以是整数或另一棵树。我们可以用以下方式表示这棵树
type tree =
| Leaf of int
| Node of tree * tree
我们可以使用以下代码创建上面树的实例
open System
type tree =
| Leaf of int
| Node of tree * tree
let simpleTree =
Node(
Leaf 1,
Node(
Leaf 2,
Node(
Node(
Leaf 4,
Leaf 5
),
Leaf 3
)
)
)
let main() =
printfn "%A" simpleTree
Console.ReadKey(true) |> ignore
main()
此程序输出以下内容
Node (Leaf 1,Node (Leaf 2,Node (Node (Leaf 4,Leaf 5),Leaf 3)))
通常,我们希望递归地枚举树结构中的所有节点。例如,如果我们想计算树中 Leaf
节点的总数,我们可以使用
open System
type tree =
| Leaf of int
| Node of tree * tree
let simpleTree =
Node (Leaf 1, Node (Leaf 2, Node (Node (Leaf 4, Leaf 5), Leaf 3)))
let rec countLeaves = function
| Leaf(_) -> 1
| Node(tree1, tree2) ->
(countLeaves tree1) + (countLeaves tree2)
let main() =
printfn "countLeaves simpleTree: %i" (countLeaves simpleTree)
Console.ReadKey(true) |> ignore
main()
此程序输出
countLeaves simpleTree: 5
请注意,我们上面的二叉树只对整数起作用。可以构造对所有可能的数据类型起作用的联合。我们可以将我们联合的定义修改为以下内容
type 'a tree =
| Leaf of 'a
| Node of 'a tree * 'a tree
(* The syntax above is "OCaml" style. It's common to see
unions defined using the ".NET" style as follows which
surrounds the type parameter with <'s and >'s after the
type name:
type tree<'a> =
| Leaf of 'a
| Node of tree<'a> * tree<'a>
*)
我们可以使用上面定义的联合来定义任何数据类型的二叉树
open System
type 'a tree =
| Leaf of 'a
| Node of 'a tree * 'a tree
let firstTree =
Node(
Leaf 1,
Node(
Leaf 2,
Node(
Node(
Leaf 4,
Leaf 5
),
Leaf 3
)
)
)
let secondTree =
Node(
Node(
Node(
Leaf "Red",
Leaf "Orange"
),
Node(
Leaf "Yellow",
Leaf "Green"
)
),
Node(
Leaf "Blue",
Leaf "Violet"
)
)
let prettyPrint tree =
let rec loop depth tree =
let spacer = new String(' ', depth)
match tree with
| Leaf(value) ->
printfn "%s |- %A" spacer value
| Node(tree1, tree2) ->
printfn "%s |" spacer
loop (depth + 1) tree1
loop (depth + 1) tree2
loop 0 tree
let main() =
printfn "firstTree:"
prettyPrint firstTree
printfn "secondTree:"
prettyPrint secondTree
Console.ReadKey(true) |> ignore
main()
上面的函数具有以下类型
type 'a tree =
| Leaf of 'a
| Node of 'a tree * 'a tree
val firstTree : int tree
val secondTree : string tree
val prettyPrint : 'a tree -> unit
此程序输出
firstTree: | |- 1 | |- 2 | | |- 4 |- 5 |- 3 secondTree: | | | |- "Red" |- "Orange" | |- "Yellow" |- "Green" | |- "Blue" |- "Violet"
F# 有几种从区分联合派生的内置类型,其中一些已经在本教程中介绍过。这些类型包括
type 'a list =
| Cons of 'a * 'a list
| Nil
type 'a option =
| Some of 'a
| None
ML 语言家族(包括 F# 及其母语 OCaml)最初是为开发自动定理证明器而设计的。联合类型允许 F# 程序员以非常简洁的方式表示命题逻辑。为了简单起见,让我们将命题限制为四种可能的情况
type proposition =
| True
| Not of proposition
| And of proposition * proposition
| Or of proposition * proposition
假设我们有一系列命题,并希望确定它们是否计算为真或假。我们可以通过以下方式递归地枚举命题语句来轻松编写 eval
函数
let rec eval = function
| True -> true
| Not(prop) -> not (eval(prop))
| And(prop1, prop2) -> eval(prop1) && eval(prop2)
| Or(prop1, prop2) -> eval(prop1) || eval(prop2)
eval
函数的类型为 val eval : proposition -> bool
。
以下是用 eval
函数的完整程序
open System
type proposition =
| True
| Not of proposition
| And of proposition * proposition
| Or of proposition * proposition
let prop1 =
(* ~t || ~~t *)
Or(
Not True,
Not (Not True)
)
let prop2 =
(* ~(t && ~t) || ( (t || t) || ~t) *)
Or(
Not(
And(
True,
Not True
)
),
Or(
Or(
True,
True
),
Not True
)
)
let prop3 =
(* ~~~~~~~t *)
Not(Not(Not(Not(Not(Not(Not True))))))
let rec eval = function
| True -> true
| Not(prop) -> not (eval(prop))
| And(prop1, prop2) -> eval(prop1) && eval(prop2)
| Or(prop1, prop2) -> eval(prop1) || eval(prop2)
let main() =
let testProp name prop = printfn "%s: %b" name (eval prop)
testProp "prop1" prop1
testProp "prop2" prop2
testProp "prop3" prop3
Console.ReadKey(true) |> ignore
main()
此程序输出以下内容
prop1: true prop2: true prop3: false
定理证明示例(OCaml)