F# Come comporre valori e funzioni utilizzando operatori comuni

Esempio

Nella programmazione orientata agli oggetti un compito comune è comporre oggetti (valori). Nella programmazione funzionale è compito comune comporre valori e funzioni.

Siamo abituati a comporre valori dalla nostra esperienza di altri linguaggi di programmazione usando operatori come + , - , * , / e così via.

Composizione del valore

let x = 1 + 2 + 3 * 2

Poiché la programmazione funzionale compone funzioni e valori, non sorprende che esistano operatori comuni per la composizione di funzioni come >> , << , |> e <| .

Composizione funzionale

// 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# tubazioni in avanti sono preferite rispetto alle tubazioni inverse perché:

  1. Digitare un'inferenza (generalmente) scorre da sinistra a destra, quindi è naturale che valori e funzioni scorrano anche da sinistra a destra
  2. Perché <| e << dovrebbe avere la giusta associatività, ma in F# sono lasciati associativi che ci costringono a inserire ()
  3. La miscelazione delle tubazioni avanti e indietro generalmente non funziona perché hanno la stessa precedenza.

Composizione di Monad

Dato che Monads (come Option<'T> o List<'T> ) sono comunemente usati nella programmazione funzionale ci sono anche operatori comuni ma meno conosciuti per comporre funzioni che funzionano con Monads come >>= , >=> , <|> e <*> .

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

Conclusione

Per i nuovi programmatori funzionali la composizione delle funzioni utilizzando gli operatori potrebbe sembrare opaca e oscura, ma è perché il significato di questi operatori non è comunemente noto come operatori che lavorano sui valori. Tuttavia, con un po 'di allenamento usando |> , >> , >>= e >=> diventa naturale come usare + , - , * e / .