risposta di @gcanti su italiajs.slack.com in #fp
ambedue servono per risolvere un problema di composizione di funzioni, ma in due situazioni diverse. Partiamo dal caso più semplice, diciamo che hai due funzioni:
f: (a: A) => B
g: (b: B) => C
e vuoi calcolarne la composizione h: (a: A) => C
, come fai? in fp-ts puoi usare flow (che implementa appunto la composizione di funzioni)
import { flow } from 'fp-ts/lib/function'
// questo programma prende in input due funzioni:
// f: (a: A) => B
// g: (b: B) => C
// e restituisce come risultato la loro composizione:
// (a: A) => C
function program1<A, B, C>(f: (a: A) => B, g: (b: B) => C): (a: A) => C {
return flow(f, g)
}
E fino a qui è tutto semplice. Ora però supponiamo che la prima abbia un "effetto", per esempio Option
f: (a: A) => Option<B>
g: (b: B) => C
come fai a comporle per ottenere una funzione (a: A) => Option<C>
? Se provi a fare come prima TypeScript si lamenta e non compila
import { Option } from 'fp-ts/lib/Option'
function program2<A, B, C>(f: (a: A) => Option<B>, g: (b: B) => C): (a: A) => Option<C> {
return flow(f, g) // error: Argument of type '(a: A) => Option<B>' is not assignable to parameter of type '(a: A) => B'
}
Il che è comprensibile dato che l'output di f (cioè Option<B>
) non coincide con l'input di g
(cioè B
).
Quindi che si fa? Si usa map
import { Option, map } from 'fp-ts/lib/Option'
function program2<A, B, C>(f: (a: A) => Option<B>, g: (b: B) => C): (a: A) => Option<C> {
return flow(f, map(g)) // ora compila
}
Ora supponiamo che anche la seconda abbia un effetto (lo stesso effetto però, quindi ancora Option)
f: (a: A) => Option<B>
g: (b: B) => Option<C> // <= lo stesso effetto di `f`, cioè `Option`
ancora una volta, come fai a comporle per ottenere una funzione (a: A) => Option<C>
? Si usa chain
import { Option, chain } from 'fp-ts/lib/Option'
function program3<A, B, C>(f: (a: A) => Option<B>, g: (b: B) => Option<C>): (a: A) => Option<C> {
return flow(f, chain(g))
}
Se nel codice qui sopra sostituisci Option con un qualsiasi altro effetto (Either, Task, Array, ecc..) le soluzioni valgono ancora, amesso che usi le loro corrispondenti map e chain. Per esempio usiamo Task invece che Optione due funzioni con effetto
import { Task, chain } from 'fp-ts/lib/Task'
function program4<A, B, C>(f: (a: A) => Task<B>, g: (b: B) => Task<C>): (a: A) => Task<C> {
return flow(f, chain(g))
}
in sostanza map e chain servono per comporre funzioni che per loro natura non lo farebbero in modo naturale
qual è la differenza tra pipe e flow?
sostanzialmente fanno la stessa cosa, pipe è più comodo quando parti da un valore. Qui sotto f e g sono la stessa funzione