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)