2015-09-24 17 views
20

Sto imparando la monade libera in Scala, e ho messo insieme un semplice esempio di algebra che posso sollevare in una monade libera usando i gatti.Impilare effetti monadici in una Monade libera in Scala

Ecco la mia algebra

sealed trait ConsultationOp[A] 
object consultation { 
    case class Create(c: Consultation) extends ConsultationOp[Unit] 
    case class Get(s: ConsultationId) extends ConsultationOp[Option[Consultation]] 
} 

E io sono in grado di usarlo come

def app = for { 
    c <- consultation.Create(Consultation("123", "A consultation")) 
    _ <- consultation.Get(c._id) 
} yield() 

def interpreters = ConsultationInterpreter or UserInterpreter 
app.foldMap(interpreters) 

Quando è effettuata implicitamente il sollevamento ConsultationOp-Free.

(c'è un sacco di dettagli mancanti, l'implementazione di lavoro completo è qui: https://github.com/gabro/free-api)

Fin qui tutto bene, ma se ho bisogno di estrarre il valore opzionale restituito da consultation.Get.

La prima cosa che viene in mente è un trasformatore Monade, vale a dire qualcosa come

def app = for { 
    c <- consultation.Create(Consultation("123", "A consultation")).liftM[OptionT] 
    d <- OptionT(consultation.Get(c._id)) 
    _ <- doSomethingAConsultation(d) 
} yield() 

ma sembra brutto, e non mi sembra giusto.

Qual è il modo glorificato, se esiste, di impilare effetti monadici quando si utilizza una monade libera?

+1

C'è una discussione relativa a questo [qui] (https://www.reddit.com/r/scala/comments/5p3fc3/free_monads_in_scala_web_stack_part_i/dco5yqy/). L'essenziale è che l'utilizzo di Free non ti libera dalla gestione del valore 'A' in' ConsultationOp'. Esistono librerie come [freek] (https://github.com/ProjectSeptemberInc/freek) e [eff] (https://github.com/atnos-org/eff) che risolvono il problema in modo più elegante. –

risposta

3

Il modo più comune che vedo ricorrenti in questi casi è quello di utilizzare traversata, così si potrebbe modificare il codice lungo le linee di:

import cats.syntax.traverse._ 
import cats.instances.option._ 

// ... 

def app = for { 
    c <- consultation.Create(Consultation("123", "A consultation")) 
    d <- consultation.Get(c._id) 
    _ <- d.traverseU(doSomethingAConsultation(_)) 
} yield() 

Il che, secondo me, è molto più pulita rispetto al trasformatore Monade alternativa . Nota che potresti aver bisogno di altri import e modificare leggermente il codice, non l'ho provato, ma il concetto è: usa la traversa.

+0

grazie! È questa pratica comune (per la tua esperienza) o è forse più comune spostare l'impilamento di monad nell'interprete e avere una trasformazione naturale da 'ConsultationOp' a (ad es.' Future [Option] 'lì? –

+0

Nella mia, piccola verità, esperienza ho sempre risolto questo tipo di problemi usando 'traverse'. Tuttavia spostare l'impilamento di monad all'interprete è un'opzione e alcune librerie fanno proprio questo, ad es. stile libero: https://github.com/47deg/freestyle/blob/master/freestyle-effects/shared/src/main/scala/effects/option.scala. Sfortunatamente, non avevo ancora avuto il tempo di guardarlo a fondo. – lambdista

+0

grazie! Ciò ha senso. –