2015-09-24 34 views
20

Uczę się o Darmowej Monadzie w Scali, a ja stworzyłem prosty przykład algebry, którą mogę podnieść do wolnej monady używając kotów.Układanie monadycznych efektów w Free Monadzie w Scali

Oto mój algebra

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

I jestem w stanie używać go jak

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

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

Gdzie zniesienie od ConsultationOp do Free jest wykonywana w sposób dorozumiany.

(tam jest dużo informacji brakuje, pełne wdrożenie pracy jest tutaj: https://github.com/gabro/free-api)

Jak na razie dobrze, ale co jeśli trzeba wyodrębnić opcjonalnego wartość zwracana przez consultation.Get.

Pierwszą rzeczą, która przychodzi do głowy, to transformator monada, czyli coś jak

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

ale wygląda brzydki, i to nie w porządku.

Jaki jest uwielbiony sposób - jeśli w ogóle - ułożenia monadycznych efektów podczas używania monady Free?

+1

Istnieje dyskusja związana z tym [tutaj] (https://www.reddit.com/r/scala/comments/5p3fc3/free_monads_in_scala_web_stack_part_i/dco5yqy/). Istotą jest to, że użycie opcji Wolny nie zwalnia cię z obsługi wartości 'A' w' ConsultationOp'. Istnieją biblioteki takie jak [freek] (https://github.com/ProjectSeptemberInc/freek) i [eff] (https://github.com/atnos-org/eff), które rozwiązują ten problem bardziej elegancko. –

Odpowiedz

3

Wspólna droga widzę powtarzające się w tych przypadkach jest użycie trawers, więc można zmienić swój kod wzdłuż linii:

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

, który IMHO jest znacznie czystsze niż alternatywa transformatora monada . Zauważ, że możesz potrzebować innego import i nieznacznie zmodyfikować kod, nie wypróbowałem go, ale koncepcja jest: użyj przechodzenia.

+0

dziękuję! Czy jest to powszechna praktyka (do twojego doświadczenia), czy może bardziej powszechne jest przenoszenie układania monad do interpretera i naturalna transformacja z 'ConsultationOp' na (np.)' Future [Option] 'there? –

+0

W moim, małym, aby powiedzieć prawdę, doświadczeniu, zawsze rozwiązywałem ten typ problemów za pomocą 'trawersu '. Jednak przenoszenie stosu monad do interpretera jest opcją i niektóre biblioteki właśnie to robią, np. freestyle: https://github.com/47deg/freestyle/blob/master/freestyle-effects/shared/src/main/scala/effects/option.scala. Pechowo, nie miałem czasu jeszcze na to spojrzeć. – lambdista

+0

dzięki! To ma sens. –