Skip to content

Instantly share code, notes, and snippets.

@zanza00
Last active December 2, 2019 10:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zanza00/f1d089b802d0455b9d2f142f8431aca5 to your computer and use it in GitHub Desktop.
Save zanza00/f1d089b802d0455b9d2f142f8431aca5 to your computer and use it in GitHub Desktop.

map e chain rispettivamente in cosa si differenziano?

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

@zanza00
Copy link
Author

zanza00 commented Dec 2, 2019

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

import { flow } from 'fp-ts/lib/function'
import { pipe } from 'fp-ts/lib/pipeable'
const len = (s: string): number => s.length
const double = (n: number): number => n * 2
const lt2 = (n: number): boolean => n < 2
const f = flow(len, double, lt2)
const g = (s: string): boolean => pipe(s, len, double, lt2)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment