In primo luogo, iniziamo con un semplice problema. Diciamo che abbiamo bisogno di ottenere una somma di due numeri interi entrambi avvolti in un Future
e Option
. Supponiamo che usiamo la libreria cats
.
Se usiamo approccio monade (aka flatMap
), abbiamo bisogno di:
- sia
Future
e Option
dovrebbe avere Monad
casi definiti su di loro
- abbiamo anche bisogno di trasformatore monadica
OptionT
che funzionerà solo per Option
(precisamente F[Option[T]]
)
Quindi, ecco il codice (dimentichiamoci per la comprensione e il sollevamento per rendere più semplice):
val fa = OptionT[Future, Int](Future(Some(1)))
val fb = OptionT[Future, Int](Future(Some(2)))
fa.flatMap(a => fb.map(b => a + b)) //note that a and b are already Int's not Future's
se si guarda al OptionT.flatMap
fonti:
def flatMap[B](f: A => OptionT[F, B])(implicit F: Monad[F]): OptionT[F, B] =
flatMapF(a => f(a).value)
def flatMapF[B](f: A => F[Option[B]])(implicit F: Monad[F]): OptionT[F, B] =
OptionT(F.flatMap(value)(_.fold(F.pure[Option[B]](None))(f)))
Si noterà che il codice è abbastanza specifico per la logica s' Option
interna e della struttura (fold
, None
). Stesso problema per EitherT
, StateT
ecc
cosa importante è che non v'è alcun FutureT
definito nei gatti, in modo da poter comporre Future[Option[T]]
, ma non può farlo con Option[Future[T]]
(in seguito vi mostrerò che questo problema è ancora più più generico).
D'altra parte, se si sceglie la composizione utilizzando Applicative
, dovrete soddisfare solo requisito:
- sia
Future
e Option
dovrebbe avere Applicative
casi definiti su di loro
È non sono necessari trasformatori speciali per Option
, in pratica la libreria cats fornisce la classe Nested
che funziona per qualsiasi Applicative
(dimentichiamo lo zucchero del builder applicativo per semplificare comprensione):
val fa = Nested[Future, Option, Int](Future(Some(1)))
val fb = Nested[Future, Option, Int](Future(Some(1)))
fa.map(x => (y: Int) => y + x).ap(fb)
Diamo loro swap:
val fa = Nested[Option, Future, Int](Some(Future(1)))
val fb = Nested[Option, Future, Int](Some(Future(1)))
fa.map(x => (y: Int) => y + x).ap(fb)
Works!
Quindi sì Monade è applicativo, Option[Future[T]]
è ancora una monade (su Future[T]
ma non su T
in sé), ma consente di operare solo con Future[T]
non T
. Per "unire" Option
con i livelli Future
- devi definire il trasformatore monadico FutureT
, per unire Future
con Option
- devi definire OptionT
. E, OptionT
è definito in cats/scalaz, ma non FutureT
.
In generale (da here):
Purtroppo, il nostro vero obiettivo, la composizione di monadi, è un po 'più difficile. .. In realtà, possiamo effettivamente dimostrare che, in un certo senso, non c'è modo di costruire una funzione di join con il tipo sopra usando solo le operazioni delle due monadi (vedi l'appendice per un profilo della dimostrazione). Ne consegue che l'unico modo che si potrebbe sperare per formare una composizione è se ci sono alcune costruzioni aggiuntive che collegano i due componenti
E questa composizione non è nemmeno commutativa come ho mostrato per Option
e Future
.
Come esercizio, si può provare a definire la 's FutureT
flatMap:
def flatMapF[B](f: A => F[Future[B]])(implicit F: Monad[F]): FutureT[F, B] =
FutureT(F.flatMap(value){ x: Future[A] =>
val r: Future[F[Future[B]] = x.map(f)
//you have to return F[Future[B]] here using only f and F.pure,
//where F can be List, Option whatever
})
fondamentalmente il problema con tale implementazione è che si deve 'estrarre' valore dal r che è impossibile qui, supponendo che può 'estrai il valore da Future
(non è stato definito alcun comando da parte di esso), che è vero se stiamo parlando di API "non bloccanti". Questo in pratica significa che non è possibile "scambiare" Future
e F
, come Future[F[Future[B]] => F[Future[Future[B]
, che è a proposito di trasformazione naturale (morfismo tra i funtori).In modo che spiega il primo commento su this general answer:
è possibile comporre monadi se è possibile fornire una trasformazione di swap naturale: NM a -> MN un
Applicative
s tuttavia non hanno questi problemi - puoi facilmente comporli, ma tieni presente che il risultato della composizione di due Applicatives
potrebbe non essere una monade (ma sarà sempre un applicativo). Nested[Future, Option, T]
non è una monade su T
, indipendentemente dal fatto che sia Option
sia Future
siano monadi su T
. Mettere in parole semplici Nested as a class non ha flatMap
.
Sarebbe anche utile leggere:
Mettendo tutto insieme (F
e G
sono monadi)
F[G[T]]
è una monade sul G[T]
, ma non su T
G_TRANSFORMER[F, T]
necessaria al fine di ottenere una monade sul T
da F[G[T]]
.
- non esiste in quanto tale
MEGA_TRANSFORMER[G, F, T]
trasformatore non può essere costruito sopra monade - richiede ulteriori operazioni definite su G
(sembra che comonad su G
dovrebbe essere sufficiente)
- ogni monade (compresi
G
e F
) è applicativo, ma non ogni applicativo è una monade
- teoricamente
F[G[T]]
è un applicativo su entrambi G[T]
e T
. Tuttavia scala richiede di creare NESTED[F, G, T]
per ottenere l'applicativo composto su T
(che è implementato nella libreria di gatti).
NESTED[F, G, T]
è applicativa, ma non una monade
Questo significa che è possibile comporre Future x Option
(aka Option[Future[T]]
) ad un unico monade, ma non è possibile comporre Option x Future
(aka Future[Option[T]]
) senza sapere che sono qualcosa altro oltre a essere monadi. E puoi comporre qualsiasi due applicativi in un unico applicativo, puoi anche comporre due monadi in un solo singolo applicativo (ma non in una singola monade).
Possibile duplicato di [Applicatives compose, monads do not] (http://stackoverflow.com/questions/7040844/applicatives-compose-monads-dont) – ziggystar
Questa è la risposta haskell non per scala – Jay
Controlla la [risposta di Conal] (http://stackoverflow.com/a/7070339/108915). È un linguaggio agnostico. Le monadi sono un concetto matematico e non differiscono tra le lingue. – ziggystar