Sumy rozłączne (F#)
Połączenia rozróżniane zapewniają obsługę wartości, które mogą być jednym z wielu przypadków nazwanych, prawdopodobnie każdy z różnych wartości i typów.Rozróżniane połączenia są przydatne w przypadku heterogenicznych danych; danych, które mogą mieć specjalne przypadki, włączając przypadku prawidłowe i błędy; danych, których typ zmienia się w kolejnych wystąpieniach; i jako alternatywa dla małych obiektów hierarchii.Ponadto rekursywne sumy rozłączne są używane do reprezentowania struktur drzew danych.
type type-name =
| case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
| case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]
...
Uwagi
Połączenia rozróżniane są podobne do typów połączeń w innych językach, ale istnieją różnice.Jako typu jednostki w języku C++ lub typ wariantu w języku Visual Basic, dane przechowywane w wartości nie zostały ustalone; może to być jedna z kilku opcji distinct.W przeciwieństwie do złożeń w tych innych językach, każda z możliwych opcji otrzymuje identyfikator przypadku.Identyfikatory przypadków to nazwy dla różnych możliwych typów wartości, którymi obiekty tego typu mogą być; wartości są opcjonalne.Jeśli wartości nie są obecne, wielkość liter jest równoważna do wielkości liter w wyliczeniu.Jeśli wartości są obecne, każda wartość może być jedną wartością z określonego typu lub spójną kolekcją, która agreguje wiele pól tych samych lub różnych typów.Jak dla F# 3.1 poszczególnym polom można nadać nazwę, ale nazwa jest opcjonalna, nawet jeśli inne pola w tym samym przypadku są nazwane.
Na przykład rozważmy następującą deklarację typu Kształt.
type Shape =
| Rectangle of width : float * length : float
| Circle of radius : float
| Prism of width : float * float * height : float
Poprzedni kod deklaruje Kształt złożenia dyskryminowanego, który może zawierać wartości dowolnego z trzech przypadków: Prostokąt, Okrąg i Pryzmat.Każdy przypadek ma inny zestaw pól.Przypadek Prostokąt ma dwa o nazwane pola, oba typu float, o szerokości i długości nazwy.Przypadek Okręg ma tylko jedno nazwane pole, promień.Przypadek Pryzmat ma trzy pola, dwa z nich noszą nazwy Pola nienazwane i są określane jako pola anonimowe.
Konstruujesz obiekty podając wartości dla pól nazwanych i anonimowych według poniższych przykładów.
let rect = Rectangle(length = 1.3, width = 10.0)
let circ = Circle (1.0)
let prism = Prism(5., 2.0, height = 3.0)
Ten kod wskazuje, że możesz użyć nazwanych pól podczas inicjowania, lub możesz polegać na określeniu kolejności pól w deklaracji i tylko podawać wartości dla każdego pola.Wywołanie konstruktora dla rect w poprzednim kodzie używa pól nazwanych, ale wywołanie konstruktora dla circ używa kolejności.Możesz mieszać pola zamówione i pola nazwane, tak jak podczas budowy prism.
Typ option jest prostym złożeniem dyskryminowanym w bibliotece głównej F#.Typ option jest opisany następująco.
// The option type is a discriminated union.
type Option<'a> =
| Some of 'a
| None
Powyższy kod określa, że typ Option jest złożeniem dyskryminowanym, które posiada dwa przypadki, Some i None.Przypadek Some ma skojarzoną wartość, która składa się z jednego pola anonimowego, którego typ jest reprezentowany przez parametr typu 'a.Przypadek None nie ma przypisanej wartości.Zatem typ option określa typ ogólny, który ma wartość pewnego typu, lub nie ma wartości.Typ Option ma również alias z małej litery, option, stosowany znacznie częściej.
Identyfikatory przypadków mogą służyć jako konstruktory dla dyskryminowanego typu złożenia.Na przykład poniższy kod służy do tworzenia wartości typu option.
let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None
Identyfikatory przypadków są również używane w wyrażeniach dopasowania wzorca.W wyrażeniu dopasowania do wzorca identyfikatory są dostarczane dla wartości skojarzonych z indywidualnych przypadkami.Na przykład w poniższym kodzie x jest identyfikatorem otrzymującym wartość, która jest skojarzona z przypadkiem Some typu option.
let printValue opt =
match opt with
| Some x -> printfn "%A" x
| None -> printfn "No value."
W wyrażeniach dopasowania do wzorca można użyć nazwanych pól do określenia dopasowań do sum rozłącznych.Dla typu Kształt, który został uprzednio zadeklarowany, można użyć nazwanych pól, jak w poniższym kodzie pokazano, do wyodrębnienia wartości pól.
let getShapeHeight shape =
match shape with
| Rectangle(height = h) -> h
| Circle(radius = r) -> 2. * r
| Prism(height = h) -> h
Normalnie identyfikatorów wielkości liter można używać bez kwalifikowania ich nazwą złączenia.Jeśli chcesz, aby nazwa zawsze była kwalifikowana nazwą złożenia, można zastosować atrybut RequireQualifiedAccess do definicji typu złożenia.
Używanie dyskryminowanych związków zamiast Hierarchii obiektu
Często możesz użyć złożenia dyskryminowanego jako prostszej alternatywy dla hierarchii małych obiektów.Na przykład następującej sumy rozłączonej można użyć zamiast klasy podstawowej Shape, która ma pochodne typy dla okręgu, kwadratu i tak dalej.
type Shape =
// The value here is the radius.
| Circle of float
// The value here is the side length.
| EquilateralTriangle of double
// The value here is the side length.
| Square of double
// The values here are the height and width.
| Rectangle of double * double
Zamiast metody wirtualnej do obliczenia powierzchni lub obwodu, która zostałaby użyta w implementacji zorientowanej na obiekty, można używać dopasowania do wzorca w celu odgałęzienia odpowiednich formuł do obliczania tych ilości.W poniższym przykładzie różne formuły służą do obliczania powierzchni, w zależności od kształtu.
let pi = 3.141592654
let area myShape =
match myShape with
| Circle radius -> pi * radius * radius
| EquilateralTriangle s -> (sqrt 3.0) / 4.0 * s * s
| Square s -> s * s
| Rectangle (h, w) -> h * w
let radius = 15.0
let myCircle = Circle(radius)
printfn "Area of circle that has radius %f: %f" radius (area myCircle)
let squareSide = 10.0
let mySquare = Square(squareSide)
printfn "Area of square that has side %f: %f" squareSide (area mySquare)
let height, width = 5.0, 10.0
let myRectangle = Rectangle(height, width)
printfn "Area of rectangle that has height %f and width %f is %f" height width (area myRectangle)
Dane wyjściowe są następujące:
Area of circle that has radius 15.000000: 706.858347
Area of square that has side 10.000000: 100.000000
Area of rectangle that has height 5.000000 and width 10.000000 is 50.000000
Używanie dyskryminowanych związków dla struktur drzewa danych
Połączenia rozróżniane mogą być cykliczne, co oznacza, że samo połączenie może być uwzględnione w jednym typie przypadku lub kilku.Rekurencyjne związki dyskryminowane mogą służyć do tworzenia struktur drzewa, które służą do modelowania wyrażeń w językach programowania.W poniższym kodzie cykliczna suma rozłączna jest używana do tworzenia struktury drzewa danych binarnych.Złożenie składa się z dwóch przypadków, Node, który jest węzłem o wartości całkowitej oraz poddrzew lewego i prawego, i Tip, który kończy drzewo.
type Tree =
| Tip
| Node of int * Tree * Tree
let rec sumTree tree =
match tree with
| Tip -> 0
| Node(value, left, right) ->
value + sumTree(left) + sumTree(right)
let myTree = Node(0, Node(1, Node(2, Tip, Tip), Node(3, Tip, Tip)), Node(4, Tip, Tip))
let resultSumTree = sumTree myTree
W poprzednim kodzie resultSumTree ma wartość 10.Poniższa ilustracja przedstawia strukturę drzewa dla myTree.
Struktura drzewa dla myTree
Połączenia rozróżniane działają dobrze, jeśli węzły w drzewie są heterogeniczne.W poniższym kodzie typ Expression reprezentuje drzewo abstrakcyjnej składni wyrażenia w prostym języku programowania, który obsługuje dodawanie i mnożenie liczb i zmiennych.Niektórych przypadki nie są cykliczne i reprezentują liczby (Number) lub zmienne (Variable).Inne przypadki są cykliczne i reprezentują operacje (Add i Multiply), gdzie argumenty operacji są również wyrażeniami.Funkcja Evaluate używa wyrażenia dopasowania, aby procesu cyklicznie przetwarzać drzewo składni.
type Expression =
| Number of int
| Add of Expression * Expression
| Multiply of Expression * Expression
| Variable of string
let rec Evaluate (env:Map<string,int>) exp =
match exp with
| Number n -> n
| Add (x, y) -> Evaluate env x + Evaluate env y
| Multiply (x, y) -> Evaluate env x * Evaluate env y
| Variable id -> env.[id]
let environment = Map.ofList [ "a", 1 ;
"b", 2 ;
"c", 3 ]
// Create an expression tree that represents
// the expression: a + 2 * b.
let expressionTree1 = Add(Variable "a", Multiply(Number 2, Variable "b"))
// Evaluate the expression a + 2 * b, given the
// table of values for the variables.
let result = Evaluate environment expressionTree1
Kiedy ten kod jest wykonywany, wartość result wynosi 5.