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)
