Som jeg tidligere har nævnt er en af de helt store styrker ved F# at det giver mulighed for at kombinere forskellige stilarter, og jeg skylder derfor at runde imperativ programmering, som nok i virkeligheden er den programmeringsform de fleste udviklere er bekendt med.
Imperativ betyder påbud og inden for grammatik er det den form at et verb vi på dansk kalder bydeform (eks. "syng!"). Imperativ programmering vil derfor sige programmering hvor ens statements går på "hvordan" en opgave løses, hvorimod funktionel programmering handler mere om "hvad" der skal gøres.
Selvom F# i udgangspunkt er et funktionelt sprog der søger at få folk til at bruge immutable datastrukturer, kan det nogen gange være mere intuitivt at arbejde med imperative statements som loop strukturer og mutable data. Der kan selvfølgelig også nogen gange være en masse performance at hente ved ikke at skabe en masse objekter men istedet blot ændre en værdi.
Looping and iterating over data
Et helt basalt loop ligner noget man kender fra andre sprog og ser ud som herunder.
let PrintValues1To10 =
for i = 0 to 10 do
printfn "Value: %d" i
PrintValues1To10
På samme måde kan man desuden skrive et while loop sådan her.
let PrintNamesAndCities =
for (name, city) in [("Christian", "Odense"); ("Henrik", "Kerteminde")] do
printfn "%s %s" name city
PrintNamesAndCities
Mutable data og while loops
While loops som man kender dem findes naturligvis også, men disse bruges oftest sammen med mutable data hvis man tænker over det. Et typisk scenarie er at man laver en udregning og gentager denne indtil en given variabel har en værdi der tilfredsstiller et eller andet boolsk udtryk. Et eksempel på anvendelse at et while loop og mutable data kunne eksempelvis være.
let PrintWhileLessThan10 =
let mutable i = 0
while (i<10) do
printfn "Value: %d" i
i <- i+1
PrintWhileLessThan10
Det ovenstående eksempel viser den mest brugte måde at arbejde med mutable typer, men ligesom man i andre sprog kan arbejde med mutable reference typer kan man også i F# ved hjælp af keywordet ref og operatorerne ! og :=. På den måde kunne ovenstående stump kode have været skrevet sådan her, med samme overordnede resultat.
let PrintWhileLessThan10 =
let i = ref 0
while (!i<10) do
printfn "Value: %d" !i
i := !i+1
PrintWhileLessThan10
Imperative collections
Udover de datastrukturer jeg har set på tidligere her på bloggen kan man desuden også arbejde med de collection klasser der findes i .NET frameworket, som er imperative af natur. Disse collections bruges på samme vis som du sikkert er vant til men System.Collections.Generic.List<'a> bruges normalvis via aliaset ResizeArray, som er standard i F# fordi det er mere sigende for hvad strukturen er beregnet til.
let names = new ResizeArray<string>()
for name in ["Jens"; "Frans"; "Erik"] do
names.Add(name)
printfn "Navn: %s" names.[1]
Exception raising og handling
En anden karakteristisk egenskab det går igen i imperative sprog er exception handling. Selvom det bygger på den måde exception handling fungerer på i .NET frameworket fungerer det en smugle anderledes i F#.
Hvis man vil raise en exception kan det gøres sådan her:
raise (System.InvalidOperationException("Man kan jo ikke dividere med 0 vel!"))
Der findes dog også en generel måde at raise exceptions på ved at skrive eksempelvis sådan her.
failwith "Man kan jo ikke dividere med 0 vel!"
Som medfører at en Microsoft.FSharp.Core.FailureException kastes. Den findes også i en failwithf der tager stringformat argumenter ligesom printf.
Specifikt til argument exceptions er der en forkortet udgave der kaster en InvalidArgumentException.
invalid_arg "argument må ikke have negativ værdi"
På den anden side af hegnet skal man jo også kunne håndtere exceptions. Dette fungerer som en kombination imellem en try catch som de fleste kender den og et udtryk til pattern matching.
try
raise (System.InvalidOperationException("Fejl nu bare"))
with
| :? System.InvalidOperationException as err -> printfn "Grebet ud: %s" err.Message
Ellers hvis man ønsker at gribe alle exceptions kan det gøres sådan her.
try
raise (System.InvalidOperationException("Fejl nu bare"))
with
| err -> printfn "Message: %s" err.Message
Og finally findes naturligvis også til at sørge for at ressourcer disposes korrekt - og den ligner sig selv som man kender den.
try
raise (System.InvalidOperationException("Fejl nu bare"))
finally
printfn "Finally kaldt"
Man kan dog ikke kombinere disse som i andre .NET sprog da en try-with-finally konstruktion ikke er understøttet, idet det ikke findes i OCaml. Det man normalt gør istedet er at bruge enten en "use" binding eller et "using" scope istedet.
let WriteToFile() =
use output = File.CreateText(@"data.txt")
output.WriteLine("This is nice")
eller som using
let WriteToFile() =
using (File.CreateText(@"data.txt")) (fun output ->
output.WriteLine("This is nice"))