Sök…
Vad är bra med en 0-tuple?
En 2-tuple eller en 3-tuple representerar en grupp relaterade objekt. (Pekar i 2D-utrymme, RGB-värden för en färg, etc.) En 1-tupel är inte särskilt användbar eftersom den lätt kan ersättas med en enda int
.
En 0-tuple verkar ännu mer värdelös eftersom den innehåller absolut ingenting . Ändå har det egenskaper som gör det mycket användbart på funktionella språk som F #. Till exempel har typen 0-tuple exakt ett värde, vanligtvis representerat som ()
. Alla 0-tuples har detta värde så det är i huvudsak en singleton typ. I de flesta funktionella programmeringsspråk, inklusive F #, kallas detta unit
.
Funktioner som returnerar void
i C # kommer att returnera unit
i F #:
let printResult = printfn "Hello"
Kör det i F # interaktiv tolk, så ser du:
val printResult : unit = ()
Detta innebär att värdet printResult
är av typen unit
, och har värdet ()
(den tomma tuppeln, varvid en och endast värdet på unit
Funktioner kan också ta unit
som en parameter. I F # kan funktioner se ut som om de inte tar några parametrar. Men faktiskt tar de en enda parameter av typen unit
. Denna funktion:
let doMath() = 2 + 4
motsvarar faktiskt:
let doMath () = 2 + 4
Det vill säga en funktion som tar en parameter av unit
och returnerar int
värdet 6. Om du tittar på typsignaturen som den interaktiva F # -utskriften skriver ut när du definierar den här funktionen ser du:
val doMath : unit -> int
Det faktum att alla funktioner tar åtminstone en parameter och returnerar ett värde, även om det värdet ibland är ett "värdelöst" värde som ()
, betyder att funktionskompositionen är mycket lättare i F # än på språk som inte har unit
. Men det är ett mer avancerat ämne som vi kommer till senare. För tillfället, kom ihåg att när du ser unit
i en funktionssignatur, eller ()
i en funktions parametrar, det är 0-tupeltypen som fungerar som sättet att säga "Denna funktion tar, eller returnerar, inga meningsfulla värden."
Uppskjuta körning av kod
Vi kan använda unit
som ett funktionsargument för att definiera funktioner som vi inte vill utföra förr senare. Detta är ofta användbart i asynkrona bakgrundsuppgifter, när huvudtråden kanske vill utlösa viss fördefinierad funktionalitet i bakgrundstråden, som att kanske flytta den till en ny fil, eller om en låtbindning inte ska köras omedelbart:
module Time =
let now = System.DateTime.Now // value is set and fixed for duration of program
let now() = System.DateTime.Now // value is calculated when function is called (each time)
I följande kod definierar vi kod för att starta en "arbetare" som helt enkelt skriver ut värdet den arbetar med varannan sekund. Arbetaren returnerar sedan två funktioner som kan användas för att styra den - en som flyttar den till nästa värde att arbeta med, och en som hindrar den från att fungera. Dessa måste vara funktioner, eftersom vi inte vill att deras kroppar ska köras förrän vi väljer att, annars skulle arbetaren omedelbart flytta till det andra värdet och stänga av utan att ha gjort något.
let startWorker value =
let current = ref value
let stop = ref false
let nextValue () = current := !current + 1
let stopOnNextTick () = stop := true
let rec loop () = async {
if !stop then
printfn "Stopping work."
return ()
else
printfn "Working on %d." !current
do! Async.Sleep 2000
return! loop () }
Async.Start (loop ())
nextValue, stopOnNextTick
Vi kan sedan starta en arbetare genom att göra
let nextValue, stopOnNextTick = startWorker 12
och arbetet kommer att börja - om vi är i F # interaktiva, kommer vi att se meddelandena som skrivs ut i konsolen varannan sekund. Vi kan sedan springa
nextValue ()
och vi kommer att se meddelandena som indikerar att värdet som bearbetas har flyttat till nästa.
När det är dags att slutföra arbetet kan vi köra
stopOnNextTick ()
funktion, som kommer att skriva ut stängningsmeddelandet och sedan avsluta.
unit
är här viktig för att beteckna "ingen ingång" - funktionerna har redan all information de behöver för att arbeta inbyggd i dem, och den som ringer får inte ändra det.