2013-06-12 9 views
11

Ich habe den folgenden Code, der die Reader Monade für die Konfiguration verwendet und hat auch mit IO[Option[String]] zu tun, und ich habe mit Code, der Treppenstufen in meinem encode Funktion endete.Wie vermeidet man Tread-Stepping mit Monad Transformers in Scala?

Wie kann ich eine Monade Transformator für Reader und OptionT formulieren die hässlichen verschachtelten for Comprehensions in meiner encode Funktion zu vermeiden?

def encode(fileName: String): Reader[Config, IO[Unit]] = for { 
    ffmpegWrapper <- findFfmpegWrapper 
    ffmpegBin <- findFfmpeg 
} yield (for { 
    w <- ffmpegWrapper 
    b <- ffmpegBin 
    stream <- callFfmpeg(getCommand(w, b, fileName)).liftM[OptionT] 
} yield stream) map (_ foreach (println)) getOrElse Unit.box {} 


def getCommand(ffmpegWrapper: String, ffmpegBin: String, 
      videoFile: String) = s"$ffmpegWrapper $ffmpegBin $videoFile '-vcodec libx264 -s 1024x576' /tmp/out.mp4" 

def callFfmpeg(command: String): IO[Stream[String]] = IO { 
    Process(command).lines_! 
} 

def findFile(path:List[String]): OptionT[IO,String] = OptionT[IO,String](IO{path.find(new File(_).exists)}) 

def findFfmpeg:Reader[Config, OptionT[IO,String]] = Reader {c=>findFile(c.ffmpegLocations)} 

def findFfmpegWrapper:Reader[Config, OptionT[IO,String]] = Reader {c=>findFile(c.ffmpegWrapperLocations)} 

Vielen Dank!

Antwort

13

Wenn man sich die definition of Reader in the Scalaz source anschauen, werden Sie sehen:

type Reader[-E, +A] = ReaderT[Id, E, A] 

, die uns sagt, dass die Reader Monade Sie verwenden, ist nur eine Spezialisierung einer Monade Transformator, wo die Monade eingewickelt wird, ist die triviale Id Monad. Sie können ReaderT direkt verwenden, aber Ihre OptionT[IO, _] Monade einwickeln, anstatt alles einfach in eine Reader einzupacken. Zum Beispiel soll die folgende tun, was Sie wollen:

type OptionIO[+A] = OptionT[IO, A] 

def findFfmpeg: ReaderT[OptionIO, Config, String] = 
    Kleisli[OptionIO, Config, String](c => findFile(c.ffmpegLocations)) 

def findFfmpegWrapper: ReaderT[OptionIO, Config, String] = 
    Kleisli[OptionIO, Config, String](c => findFile(c.ffmpegWrapperLocations)) 

def encode(fileName: String): ReaderT[OptionIO, Config, Unit] = (for { 
    w <- findFfmpegWrapper 
    b <- findFfmpeg 
    stream <- Kleisli[OptionIO, Config, Stream[String]](
    _ => callFfmpeg(getCommand(w, b, fileName)).liftM[OptionT] 
    ) 
} yield stream).map(_ foreach println) 

Grundsätzlich sollten Sie in der Lage, den Teil nach stream <- mit den folgenden ersetzen:

callFfmpeg(getCommand(w, b, fileName)).liftM[OptionT].liftReaderT[Config] 

Aber aus irgendeinem Grunde der Unapply Maschinen, die liftReaderT vertraut scheint in diesem Fall nicht zu funktionieren. Den Teil Kleisli explizit zu schreiben, ist glücklicherweise nicht so schrecklich.


Als Fußnote: die nette liftReaderT Syntax habe ich erwähnt, wird zur Verfügung, wenn Sie eine UnapplyCo weise wie folgt definieren:

implicit def unapplyMFA1[TC[_[_]], F[+_], M0[F[+_], +_], A0](
    implicit TC0: TC[({ type L[x] = M0[F, x] })#L] 
): UnapplyCo[TC, M0[F, A0]] { 
    type M[+X] = M0[F, X] 
    type A = A0 
} = new UnapplyCo[TC, M0[F, A0]] { 
    type M[+X] = M0[F, X] 
    type A = A0 
    def TC = TC0 
    def leibniz = Leibniz.refl 
} 

ich aus der Spitze von meinem Kopf bin nicht sicher, ob es einen Grund Scalaz 7 stellt diese Instanz derzeit nicht zur Verfügung, aber es lohnt sich, sie genauer zu betrachten.

+1

Ich war dabei, eine fast identische Antwort zu schreiben, als Ihre erschien. Ich habe den oberen Teil Ihrer Antwort mit etwas, das in meinem, aber nicht in Ihrem war, über den Reader = ReaderT-Typ-Alias ​​ergänzt, bitte zögern Sie nicht, es zu entfernen, wenn Sie denken, es nicht zu Ihrer Antwort hinzuzufügen :) – stew

+0

@stew: Danke ! Ich habe gerade einen Link zu der Quelle hinzugefügt, die Sie erwähnt haben. –

+0

Travis und @stew das ist unglaublich hilfreich! Probier es einfach aus. – cwmyers