F#
Związki dyskryminowane
Szukaj…
Nazywanie elementów krotek w ramach dyskryminowanych związków
Podczas definiowania dyskryminowanych związków możesz nazwać elementy krotek i używać ich podczas dopasowywania wzorców.
type Shape =
| Circle of diameter:int
| Rectangle of width:int * height:int
let shapeIsTenWide = function
| Circle(diameter=10)
| Rectangle(width=10) -> true
| _ -> false
Dodatkowo nazewnictwo elementów dyskryminowanych związków poprawia czytelność kodu i interoperacyjność z C # - podane nazwy będą używane do nazw właściwości i parametrów konstruktorów. Domyślnie generowane nazwy w kodzie interop to „Item”, „Item1”, „Item2” ...
Podstawowe dyskryminowane użycie w Unii
Zróżnicowane związki w F # oferują sposób definiowania typów, które mogą zawierać dowolną liczbę różnych typów danych. Ich funkcjonalność jest podobna do związków C ++ lub wariantów VB, ale ma dodatkową zaletę bezpieczeństwa typu.
// define a discriminated union that can hold either a float or a string
type numOrString =
| F of float
| S of string
let str = S "hi" // use the S constructor to create a string
let fl = F 3.5 // use the F constructor to create a float
// you can use pattern matching to deconstruct each type
let whatType x =
match x with
| F f -> printfn "%f is a float" f
| S s -> printfn "%s is a string" s
whatType str // hi is a string
whatType fl // 3.500000 is a float
Związki w stylu enum
Informacje o typie nie muszą być dołączane w przypadkach dyskryminowanego związku. Pomijając informacje o typie, możesz utworzyć związek, który po prostu reprezentuje zestaw wyborów, podobny do wyliczenia.
// This union can represent any one day of the week but none of
// them are tied to a specific underlying F# type
type DayOfWeek = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
Konwertowanie na i z łańcuchów za pomocą Reflection
Czasami konieczne jest przekonwertowanie Unii dyskryminowanej na ciąg i z niego:
module UnionConversion
open Microsoft.FSharp.Reflection
let toString (x: 'a) =
match FSharpValue.GetUnionFields(x, typeof<'a>) with
| case, _ -> case.Name
let fromString<'a> (s : string) =
match FSharpType.GetUnionCases typeof<'a> |> Array.filter (fun case -> case.Name = s) with
| [|case|] -> Some(FSharpValue.MakeUnion(case, [||])) :?> 'a)
| _ -> None
Związek dyskryminowany w jednym przypadku
Pojedynczy związek dyskryminowany jest jak każdy inny związek dyskryminowany, z tym wyjątkiem, że ma tylko jeden przypadek.
// Define single-case discriminated union type.
type OrderId = OrderId of int
// Construct OrderId type.
let order = OrderId 123
// Deconstruct using pattern matching.
// Parentheses used so compiler doesn't think it is a function definition.
let (OrderId id) = order
Jest przydatny do egzekwowania bezpieczeństwa typów i jest powszechnie używany w języku F # w przeciwieństwie do C # i Java, gdzie tworzenie nowych typów wiąże się z większym obciążeniem.
Następujące dwie alternatywne definicje typów powodują zadeklarowanie tej samej pojedynczej dyskryminowanej unii:
type OrderId = | OrderId of int
type OrderId =
| OrderId of int
Używanie pojedynczych dyskryminowanych związków jako rekordów
Czasami przydatne jest tworzenie typów unii z tylko jednym przypadkiem, aby zaimplementować typy podobne do rekordów:
type Point = Point of float * float
let point1 = Point(0.0, 3.0)
let point2 = Point(-2.5, -4.0)
Stają się one bardzo przydatne, ponieważ można je rozłożyć za pomocą dopasowania wzorca w taki sam sposób, jak argumenty krotkowe:
let (Point(x1, y1)) = point1
// val x1 : float = 0.0
// val y1 : float = 3.0
let distance (Point(x1,y1)) (Point(x2,y2)) =
pown (x2-x1) 2 + pown (y2-y1) 2 |> sqrt
// val distance : Point -> Point -> float
distance point1 point2
// val it : float = 7.433034374
RequireQualifiedAccess
Dzięki atrybutowi RequireQualifiedAccess
przypadki unii muszą być nazywane MyUnion.MyCase
zamiast tylko MyCase
. Zapobiega to kolizjom nazw w otaczającej przestrzeni nazw lub module:
type [<RequireQualifiedAccess>] Requirements =
None | Single | All
// Uses the DU with qualified access
let noRequirements = Requirements.None
// Here, None still refers to the standard F# option case
let getNothing () = None
// Compiler error unless All has been defined elsewhere
let invalid = All
Jeśli na przykład System
został otwarty, Single
oznacza System.Single
. Nie ma kolizji ze sprawą Case Union Requirements.Single
.
Rekurencyjne dyskryminowane związki
Typ rekurencyjny
Zróżnicowane związki mogą być rekurencyjne, to znaczy mogą odnosić się do siebie w swojej definicji. Pierwszym przykładem jest tutaj drzewo:
type Tree =
| Branch of int * Tree list
| Leaf of int
Jako przykład zdefiniujmy następujące drzewo:
1
2 5
3 4
Możemy zdefiniować to drzewo za pomocą naszej rekurencyjnej dyskryminacyjnej unii w następujący sposób:
let leaf1 = Leaf 3
let leaf2 = Leaf 4
let leaf3 = Leaf 5
let branch1 = Branch (2, [leaf1; leaf2])
let tip = Branch (1, [branch1; leaf3])
Iteracja po drzewie jest wtedy kwestią dopasowania wzoru:
let rec toList tree =
match tree with
| Leaf x -> [x]
| Branch (x, xs) -> x :: (List.collect toList xs)
let treeAsList = toList tip // [1; 2; 3; 4; 5]
Wzajemnie zależne typy rekurencyjne
Jednym ze sposobów osiągnięcia rekurencji jest zagnieżdżenie wzajemnie zależnych typów.
// BAD
type Arithmetic = {left: Expression; op:string; right: Expression}
// illegal because until this point, Expression is undefined
type Expression =
| LiteralExpr of obj
| ArithmeticExpr of Arithmetic
Definiowanie typu rekordu bezpośrednio w dyskryminowanym związku jest przestarzałe:
// BAD
type Expression =
| LiteralExpr of obj
| ArithmeticExpr of {left: Expression; op:string; right: Expression}
// illegal in recent F# versions
Możesz użyć słowa kluczowego and
do połączenia wzajemnie zależnych definicji:
// GOOD
type Arithmetic = {left: Expression; op:string; right: Expression}
and Expression =
| LiteralExpr of obj
| ArithmeticExpr of Arithmetic