2015-09-24 14 views
20

Ich lerne über die Freie Monade in Scala, und ich habe ein einfaches Beispiel für Algebra zusammengestellt, das ich mit Katzen in eine Freie Monade erheben kann.Monadische Effekte in einer Freien Monade in Scala

Hier ist meine Algebra

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

Und ich bin in der Lage, es zu benutzen wie

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

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

Wo die Hebe ConsultationOp-Free implizit durchgeführt wird.

(es gibt eine Menge von Details fehlen, die voll funktionsfähige Implementierung ist hier: https://github.com/gabro/free-api)

So weit so gut, aber was ist, wenn ich ein optionalen Wert von consultation.Get zurück extrahieren.

Das erste, was in den Sinn kommt, ist eine Monade Transformator, das heißt so etwas wie

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

aber es sieht hässlich, und es fühlt sich nicht richtig.

Was ist der verherrlichte Weg - falls vorhanden - monadische Effekte zu stapeln, wenn eine Free Monade verwendet wird?

+1

Es gibt eine Diskussion zu diesem [hier] (https://www.reddit.com/r/scala/comments/5p3fc3/free_monads_in_scala_web_stack_part_i/dco5yqy/). Das Wesentliche ist, dass die Verwendung von Free Sie nicht davon befreit, den Wert "A" in "ConsultationOp" zu behandeln. Es gibt Bibliotheken wie [freek] (https://github.com/ProjectSeptemberInc/freek) und [eff] (https://github.com/atnos-org/eff), die dieses Problem eleganter lösen. –

Antwort

3

Der gemeinsame Weg ich in diesen Fällen sehen Wiederholungs zu verwenden ist Traverse, so dass Sie Ihren Code entlang der Linien ändern könnte:

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() 

Which, imho, ist viel sauberer als die Monade Transformator Alternative . Beachten Sie, dass Sie eine andere import benötigen und den Code leicht ändern können, ich habe es nicht versucht, aber das Konzept ist: Verwenden Sie Traverse.

+0

danke! Ist das (nach Ihrer Erfahrung) die übliche Praxis oder ist es vielleicht üblicher, die Monadenstapelung auf den Interpreter zu verschieben und eine natürliche Transformation von "ConsultationOp" zu (z. B.) "Future [Option]" durchzuführen? –

+0

In meiner, um die Wahrheit zu sagen, Erfahrung habe ich immer diese Art von Problemen mit 'traverse' gelöst. Es ist jedoch eine Option, die Monadenstapelung auf den Interpreter zu verschieben, und einige Bibliotheken tun genau das, z. Freestyle: https://github.com/47deg/freestyle/blob/master/freestyle-effects/shared/src/main/scala/effects/option.scala. Unglücklicherweise hatte ich nicht die Zeit, es gründlich zu betrachten. – lambdista

+0

danke! Das macht Sinn. –