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


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow