Zoeken…
Waarden en functies samenstellen met behulp van algemene operatoren
In Object Oriented Programming is een veel voorkomende taak het samenstellen van objecten (waarden). In functionele programmering is het een veel voorkomende taak om zowel waarden als functies samen te stellen.
We worden gebruikt om waarden samen te stellen op basis van onze ervaring met andere programmeertalen met operators zoals +
, -
, *
, /
enzovoort.
Waarde samenstelling
let x = 1 + 2 + 3 * 2
Aangezien functioneel programmeren functies en waarden samenstelt, is het niet verwonderlijk dat er gemeenschappelijke operatoren zijn voor functiesamenstelling zoals >>
, <<
, |>
en <|
.
Functie samenstelling
// val f : int -> int
let f v = v + 1
// val g : int -> int
let g v = v * 2
// Different ways to compose f and g
// val h : int -> int
let h1 v = g (f v)
let h2 v = v |> f |> g // Forward piping of 'v'
let h3 v = g <| (f <| v) // Reverse piping of 'v' (because <| has left associcativity we need ())
let h4 = f >> g // Forward functional composition
let h5 = g << f // Reverse functional composition (closer to math notation of 'g o f')
In F#
voorwaartse leidingen de voorkeur boven achterwaartse leidingen omdat:
- Type-inferentie stroomt (meestal) van links naar rechts, dus het is natuurlijk dat waarden en functies ook van links naar rechts stromen
- Omdat
<|
en<<
moeten de juiste associativiteit hebben maar inF#
ze links associatief waardoor we moeten invoegen () - Het mixen van voorwaartse en achterwaartse leidingen werkt over het algemeen niet omdat ze dezelfde prioriteit hebben.
Monade samenstelling
Aangezien Monaden (zoals Option<'T>
of List<'T>
) vaak worden gebruikt in functioneel programmeren, zijn er ook veel voorkomende maar minder bekende operatoren om functies samen te stellen die werken met Monaden zoals >>=
, >=>
, <|>
en <*>
.
let (>>=) t uf = Option.bind uf t
let (>=>) tf uf = fun v -> tf v >>= uf
// val oinc : int -> int option
let oinc v = Some (v + 1) // Increment v
// val ofloat : int -> float option
let ofloat v = Some (float v) // Map v to float
// Different ways to compose functions working with Option Monad
// val m : int option -> float option
let m1 v = Option.bind (fun v -> Some (float (v + 1))) v
let m2 v = v |> Option.bind oinc |> Option.bind ofloat
let m3 v = v >>= oinc >>= ofloat
let m4 = oinc >=> ofloat
// Other common operators are <|> (orElse) and <*> (andAlso)
// If 't' has Some value then return t otherwise return u
let (<|>) t u =
match t with
| Some _ -> t
| None -> u
// If 't' and 'u' has Some values then return Some (tv*uv) otherwise return None
let (<*>) t u =
match t, u with
| Some tv, Some tu -> Some (tv, tu)
| _ -> None
// val pickOne : 'a option -> 'a option -> 'a option
let pickOne t u v = t <|> u <|> v
// val combine : 'a option -> 'b option -> 'c option -> (('a*'b)*'c) option
let combine t u v = t <*> u <*> v
Conclusie
Voor nieuwe functionele programmeurs lijkt functiesamenstelling met operatoren ondoorzichtig en onduidelijk, maar dat komt omdat de betekenis van deze operatoren niet zo algemeen bekend is als operatoren die aan waarden werken. Bij sommige trainingen wordt het gebruik van |>
, >>
, >>=
en >=>
echter net zo natuurlijk als het gebruik van +
, -
, *
en /
.
Latebinding in F # gebruiken? operator
In een statisch getypte taal zoals F#
werken we met types die bekend zijn tijdens het compileren. We gebruiken externe gegevensbronnen op een typeveilige manier met typeproviders.
Af en toe is er echter behoefte aan late binding (zoals dynamic
in C#
). Bijvoorbeeld bij het werken met JSON
documenten die geen goed gedefinieerd schema hebben.
Om het werken met late binding te vereenvoudigen F#
biedt F#
ondersteuning voor dynamische lookup-operators ?
en ?<-
.
Voorbeeld:
// (?) allows us to lookup values in a map like this: map?MyKey
let inline (?) m k = Map.tryFind k m
// (?<-) allows us to update values in a map like this: map?MyKey <- 123
let inline (?<-) m k v = Map.add k v m
let getAndUpdate (map : Map<string, int>) : int option*Map<string, int> =
let i = map?Hello // Equivalent to map |> Map.tryFind "Hello"
let m = map?Hello <- 3 // Equivalent to map |> Map.add "Hello" 3
i, m
Het blijkt dat de F#
-ondersteuning voor late binding eenvoudig maar flexibel is.