Aktív minták
Aktív minták lehetővé teszik a bemeneti adatok felosztását lehetővé tevő elnevezett partíciók meghatározását, így ezeket a neveket ugyanúgy használhatja egy mintaegyező kifejezésben, mint a diszkriminált egyesítés esetében. Az aktív mintákkal testre szabott módon bonthatja le az adatokat az egyes partíciókhoz.
Szintaxis
// Active pattern of one choice.
let (|identifier|) [arguments] valueToMatch = expression
// Active Pattern with multiple choices.
// Uses a FSharp.Core.Choice<_,...,_> based on the number of case names. In F#, the limitation n <= 7 applies.
let (|identifier1|identifier2|...|) valueToMatch = expression
// Partial active pattern definition.
// Can use FSharp.Core.option<_>, FSharp.Core.voption<_> or bool to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression
Megjegyzések
Az előző szintaxisban az azonosítók a bemeneti adatok partícióinak nevei, amelyeket argumentumok, más szóval az argumentumok összes lehetséges értékének halmazához tartozó alhalmazok nevei. Egy aktív mintadefiníció legfeljebb hét partíciót tartalmazhat. A kifejezés azt az alakot írja le, amelyre az adatokat le kell bontani. Egy aktív mintadefinícióval meghatározhatja azokat a szabályokat, amelyek meghatározzák, hogy az elnevezett partíciók közül melyikhez tartoznak az argumentumként megadott értékek. A (| és |) szimbólumokat banánklipek nevezik, és az ilyen típusú let kötés által létrehozott függvényt aktív felismerőnevezik.
Vegyük például az alábbi aktív mintát egy argumentummal.
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd
Az aktív mintát egy mintaegyező kifejezésben használhatja, ahogyan az alábbi példában is látható.
let TestNumber input =
match input with
| Even -> printfn "%d is even" input
| Odd -> printfn "%d is odd" input
TestNumber 7
TestNumber 11
TestNumber 32
A program kimenete a következő:
7 is odd
11 is odd
32 is even
Az aktív minták másik használata az adattípusok több módon történő lebontása, például ha ugyanazok a mögöttes adatok különböző lehetséges ábrázolásokkal rendelkeznek. Egy Color
objektum például RGB- vagy HSB-reprezentációra bontható.
open System.Drawing
let (|RGB|) (col : System.Drawing.Color) =
( col.R, col.G, col.B )
let (|HSB|) (col : System.Drawing.Color) =
( col.GetHue(), col.GetSaturation(), col.GetBrightness() )
let printRGB (col: System.Drawing.Color) =
match col with
| RGB(r, g, b) -> printfn " Red: %d Green: %d Blue: %d" r g b
let printHSB (col: System.Drawing.Color) =
match col with
| HSB(h, s, b) -> printfn " Hue: %f Saturation: %f Brightness: %f" h s b
let printAll col colorString =
printfn "%s" colorString
printRGB col
printHSB col
printAll Color.Red "Red"
printAll Color.Black "Black"
printAll Color.White "White"
printAll Color.Gray "Gray"
printAll Color.BlanchedAlmond "BlanchedAlmond"
A fenti program kimenete a következő:
Red
Red: 255 Green: 0 Blue: 0
Hue: 360.000000 Saturation: 1.000000 Brightness: 0.500000
Black
Red: 0 Green: 0 Blue: 0
Hue: 0.000000 Saturation: 0.000000 Brightness: 0.000000
White
Red: 255 Green: 255 Blue: 255
Hue: 0.000000 Saturation: 0.000000 Brightness: 1.000000
Gray
Red: 128 Green: 128 Blue: 128
Hue: 0.000000 Saturation: 0.000000 Brightness: 0.501961
BlanchedAlmond
Red: 255 Green: 235 Blue: 205
Hue: 36.000000 Saturation: 1.000000 Brightness: 0.901961
Az aktív minták ezen két módja együttesen lehetővé teszi az adatok megfelelő formában való particionálását és felbontását, valamint a megfelelő számítások elvégzését a számításhoz legkényelmesebb formában.
Az eredményül kapott mintamegfeleltetési kifejezések lehetővé teszik az adatok kényelmes, nagyon olvasható módon történő megírását, ami jelentősen leegyszerűsíti a potenciálisan összetett elágaztatási és adatelemzési kódot.
Részleges aktív minták
Előfordulhat, hogy a bemeneti területnek csak egy részét kell particionolnia. Ebben az esetben részleges mintákat kell írnia, amelyek mindegyike megfelel bizonyos bemeneteknek, de nem egyezik más bemenetekkel. Azokat az aktív mintákat, amelyek nem mindig hoznak létre értéket, részleges aktív mintáknak nevezzük; olyan visszatérési értékkel rendelkeznek, amely egy beállítástípus. Részleges aktív minta definiálásához egy helyettesítő karaktert (_) használ a banánklipeken belüli minták listájának végén. Az alábbi kód egy részleges aktív minta használatát mutatja be.
let (|Integer|_|) (str: string) =
let mutable intvalue = 0
if System.Int32.TryParse(str, &intvalue) then Some(intvalue)
else None
let (|Float|_|) (str: string) =
let mutable floatvalue = 0.0
if System.Double.TryParse(str, &floatvalue) then Some(floatvalue)
else None
let parseNumeric str =
match str with
| Integer i -> printfn "%d : Integer" i
| Float f -> printfn "%f : Floating point" f
| _ -> printfn "%s : Not matched." str
parseNumeric "1.1"
parseNumeric "0"
parseNumeric "0.0"
parseNumeric "10"
parseNumeric "Something else"
Az előző példa kimenete a következő:
1.100000 : Floating point
0 : Integer
0.000000 : Floating point
10 : Integer
Something else : Not matched.
Részleges aktív minták használata esetén előfordulhat, hogy az egyes döntések különállóak vagy kölcsönösen kizárhatók, de ez nem szükségszerű. Az alábbi példában a négyzet és a minta kocka nem különálló, mert egyes számok négyzetek és kockák, például 64. Az alábbi program az AND mintát használja a Négyzet és a Kocka minták kombinálásához. Az 1000-ig összes egész számot kinyomtatja, amelyek négyzetek és kockák, valamint azokat is, amelyek csak kockák.
let err = 1.e-10
let isNearlyIntegral (x:float) = abs (x - round(x)) < err
let (|Square|_|) (x : int) =
if isNearlyIntegral (sqrt (float x)) then Some(x)
else None
let (|Cube|_|) (x : int) =
if isNearlyIntegral ((float x) ** ( 1.0 / 3.0)) then Some(x)
else None
let findSquareCubes x =
match x with
| Cube x & Square _ -> printfn "%d is a cube and a square" x
| Cube x -> printfn "%d is a cube" x
| _ -> ()
[ 1 .. 1000 ] |> List.iter (fun elem -> findSquareCubes elem)
A kimenet a következő:
1 is a cube and a square
8 is a cube
27 is a cube
64 is a cube and a square
125 is a cube
216 is a cube
343 is a cube
512 is a cube
729 is a cube and a square
1000 is a cube
Paraméteres aktív minták
Az aktív mintáknak mindig van legalább egy argumentumuk a vizsgált elemhez, de további argumentumokat is vehetnek, ebben az esetben az úgynevezett paraméterezett aktív mintát vonatkozik. A további argumentumok lehetővé teszik egy általános minta specializált használatát. A sztringek elemzéséhez reguláris kifejezéseket használó aktív minták például gyakran extra paraméterként használják a reguláris kifejezést, mint az alábbi kódban, amely az előző kód példában definiált részleges aktív mintát is használja Integer
. Ebben a példában a különböző dátumformátumokhoz reguláris kifejezéseket használó sztringek az általános ParseRegex aktív minta testreszabására használhatók. Az aktív egész szám mintával a megfeleltethető sztringek egész számokká alakíthatók, amelyek átadhatók a DateTime konstruktornak.
open System.Text.RegularExpressions
// ParseRegex parses a regular expression and returns a list of the strings that match each group in
// the regular expression.
// List.tail is called to eliminate the first element in the list, which is the full matched expression,
// since only the matches for each group are wanted.
let (|ParseRegex|_|) regex str =
let m = Regex(regex).Match(str)
if m.Success
then Some (List.tail [ for x in m.Groups -> x.Value ])
else None
// Three different date formats are demonstrated here. The first matches two-
// digit dates and the second matches full dates. This code assumes that if a two-digit
// date is provided, it is an abbreviation, not a year in the first century.
let parseDate str =
match str with
| ParseRegex "(\d{1,2})/(\d{1,2})/(\d{1,2})$" [Integer m; Integer d; Integer y]
-> new System.DateTime(y + 2000, m, d)
| ParseRegex "(\d{1,2})/(\d{1,2})/(\d{3,4})" [Integer m; Integer d; Integer y]
-> new System.DateTime(y, m, d)
| ParseRegex "(\d{1,4})-(\d{1,2})-(\d{1,2})" [Integer y; Integer m; Integer d]
-> new System.DateTime(y, m, d)
| _ -> new System.DateTime()
let dt1 = parseDate "12/22/08"
let dt2 = parseDate "1/1/2009"
let dt3 = parseDate "2008-1-15"
let dt4 = parseDate "1995-12-28"
printfn "%s %s %s %s" (dt1.ToString()) (dt2.ToString()) (dt3.ToString()) (dt4.ToString())
Az előző kód kimenete a következő:
12/22/2008 12:00:00 AM 1/1/2009 12:00:00 AM 1/15/2008 12:00:00 AM 12/28/1995 12:00:00 AM
Az aktív minták nem csak a mintamegfeleltetési kifejezésekre korlátozódnak, hanem let-bindingek esetén is használhatók.
let (|Default|) onNone value =
match value with
| None -> onNone
| Some e -> e
let greet (Default "random citizen" name) =
printfn "Hello, %s!" name
greet None
greet (Some "George")
Az előző kód kimenete a következő:
Hello, random citizen!
Hello, George!
Vegye figyelembe azonban, hogy csak az egy esetből álló aktív minták paraméterezhetők.
// A single-case partial active pattern can be parameterized
let (| Foo|_|) s x = if x = s then Some Foo else None
// A multi-case active patterns cannot be parameterized
// let (| Even|Odd|Special |) (s: int) (x: int) = if x = s then Special elif x % 2 = 0 then Even else Odd
Részleges aktív minták visszatérési típusa
A részleges aktív minták Some ()
-t adnak vissza egyezés esetén, különben None
-et.
Fontolja meg ezt a mérkőzést:
match key with
| CaseInsensitive "foo" -> ...
| CaseInsensitive "bar" -> ...
A részleges aktív minta a következő lenne:
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
if String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase) then
Some ()
else
None
Az F# 9-től kezdve az ilyen minták is visszaadhatják a bool
-t:
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase)
Strukturált ábrázolások részleges aktív mintákhoz
Alapértelmezés szerint, ha egy részleges aktív minta egy option
-t ad vissza, ez magában foglalja a Some
érték lefoglalását, ha a találat sikeres. Ennek elkerülése érdekében a
open System
[<return: Struct>]
let (|Int|_|) str =
match Int32.TryParse(str) with
| (true, n) -> ValueSome n
| _ -> ValueNone
Az attribútumot meg kell adni, mert a strukturált visszatérés használata nem következik pusztán a visszatérési típus ValueOption
-ra való megváltoztatásából. További információ: RFC FS-1039.
Null aktív minták
Az F# 9-ben nullabilitással kapcsolatos aktív minták lettek hozzáadva.
Az első a | Null | NonNull x |
, amely ajánlott módszer a lehetséges null értékek kezelésére. Az alábbi példában a s
paraméter az aktív mintahasználattal null értékű:
let len s =
match s with
| Null -> -1
| NonNull s -> String.length s
Ha inkább NullReferenceException
szeretne automatikusan dobni, használja a | NonNullQuick |
mintát:
let len (NonNullQuick str) = // throws if the argument is null
String.length str