F# interaktiv, pattern matching og active patterns

by DotNetNerd 23. February 2008 17:46

Interaktiv eksekvering

Interaktiv eksekvering er en feature som F# tilbyder der giver mulighed for at afvikle kode som scripts. Dette giver mulighed for at skrive kode på en behagelig måde, hvor man undgår hele tiden at skulle igennem en kode-compile-run cyklus. En almindeligt anvendt teknik er at anvende en scriptfil som "scratch pad", hvor man kan teste små stykker kode løbende.

For at udnytte dette skal man blot åbne F# interactive igennem Tools -> Add-in Manager og lave en fsx fil. Her skriver man så sin kode og når der er en del man ønsker at eksekvere markerer man den og trykker Alt + Enter. Dette gør at man meget hurtigt kan teste sin kode, uden at skulle compile, sætte et breakpoint og debugge indtil man er fremme til det sted i koden man er interesseret i.

Pattern matching

En interessant ting man kan starte ud med at prøve af - f.eks i F# interactive - er pattern matching, som er en feature der ikke er set før i .NET sprog, men som findes i andre miljøer. Pattern matching giver en meget kompakt måde at styre program flow på, lidt i stil med at bruge case sætninger i struktur, men langt mere anvendeligt og kraftfuldt.  Et super simpelt eksempel kan være at man gerne vil afvikle noget kode ud fra hvad værdi en streng har:

let value = "a"

match value with
| "a" -> printfn "The value was an a"
| "b" -> printfn "The value was a b"
| _ -> failwith "Unknown value"

Som det fremgår er syntaksen meget kompakt, og let at læse. Underscoren i den sidste linie betyder at hvilken som helst anden værdi vil blive håndteret, lidt ligesom default i en case hvis man vil sammenligne det med noget man kender fra C#/VB. En vigtig ting man skal vide er at et pattern match altid skal være udtømmende. Det vil sige at det samlet set skal kunne håndtere alle mulige værdier for at det validerer. Desuden afvikles koden ved at værdien matches imod mønstret linie for linie indtil der findes et match. Så hvis man laver et match der udelukker et efterfølgende vil man få en warning idet koden aldrig vil kunne blive ramt.

En anden anvendelse af pattern matching er til at opløst tubler som ses herunder. Nu begynder vi at komme ud i nogen scenarier der viser at pattern matching er mere anvendelige end case sætninger, og at de er stærke i samspil med funktionelle data strukturer.

let value = ("b", "a")

match value with
| ("a", "b") -> printfn "The values were a and b"
| ("b", _) -> printfn "The first value was b"
| _ -> failwith "Unknown value"

Der kan matches på mange forskellige måder men en anden meget anvendt operator :? gør det muligt at matche på typer. Her er det dog vigtigt også at bemærke at dette kræver en box operation, som der fremgår.

let value = "2"

match box value with
| :? System.Int32 -> printfn "The values was an integer"
| :? System.String -> printfn "The value was a string"
| _ -> failwith "Unknown value"

Active patterns

Active patterns er en teknik der gør det muligt at skrive mere advancerede patterns til brug sammen med mere komplekse datastrukturer eller til at lave mere komplek matching. I eksemplet herunder ses et active pattern der matcher ud fra et regular expression og returnerer en option sequence af de værdier der blev matchet ved at eksekvere det angivne regex.

let (|ParseRegex|_|) regex str =
  let regex = new System.Text.RegularExpressions.Regex(regex)    
  let matches = regex.Matches(str)    
  if matches.Count > 0 then        
    Some { for x in matches -> x.Value }    
  else        
    None

Dette active pattern kan derefter bruges på følgende måde sammen med pattern matching til eksempelvis at udskrive alle tal i en streng. Underscoren i deklerationen af dette active pattern betyder at mønstret er ufuldkomment, hvilket vil sige at det kan returnere None.

let PrintNumbers str =
  match str with
  | ParseRegex "\d" st -> Seq.iter(fun x -> printfn "Number found: %s" x) st
  | _ -> printfn "No numbers"

PrintNumbers "b123"

Alternativt kan active patterns bruges over eksempelvis discriminated unions. Et meget anvendt eksempel rundt omkring på nettet har været et discriminated union over et binært træ. Eksemplet kan ses herunder og det representerer helt enkelt et træ, hvor hvert element kan være enten en knude eller et blad - hvor knuder består af 2 elementer.

type BinaryTree<'a> =    
  | Leaf of 'a    
  | Node of BinaryTree<'a> * BinaryTree<'a>

Udfra dette kunne et active pattern eksempelvis bruges til at returnere om en XmlNode er en node eller et blad.

let (|Node|Leaf|) (node : #System.Xml.XmlNode) =    
  if node.HasChildNodes then        
    Node (node.Name, { for x in node.ChildNodes -> x })    
  else        
    Leaf (node.InnerText)

Dette kunne så bruges til at lave forskellige matches, og eksempelvis rekursivt udskrive noderne indenteret ligesom i xml dokumentet.

let PrintXml node =    
  let rec PrintXml indent node =        
    match node with        
    | Leaf (text) -> printfn "%s%s" indent text        
    | Node (name, nodes) ->            
      printfn "%s%s:" indent name            
      nodes |> Seq.iter (PrintXml (indent + "    "))    
  PrintXml "" node

Givet et xml dokument over ansatte i en lille IT virksomhed kunne man så benytte funktionen til at udskrive indholdet pænt sådan her.

let doc =    
  let xmlDoc = new System.Xml.XmlDocument()    
  let text = "
<employees>    
  <programmers>
    <junior>10</junior>
    <senior>8</senior>
  </programmers>
  <designers>3</designers> 
</employees>"
  xmlDoc.LoadXml(text)    
  xmlDoc
PrintXml (doc.DocumentElement :> System.Xml.XmlNode)

Tags:

Who am I?

My name is Christian Holm Diget, and I work as an independent consultant, in Denmark, where I write code, give advice on architecture and help with training. On the side I get to do a bit of speaking and help with miscellaneous community events.

Some of my primary focus areas are code quality, programming languages and using new technologies to provide value.

Microsoft Certified Professional Developer

Microsoft Most Valuable Professional

Month List