2013-06-12 22 views
11

Mam następujący kod, który używa monady Reader do konfiguracji, a także ma do czynienia z IO[Option[String]] i skończyłem z kodem, że kroki schodowe w mojej funkcji encode.Jak uniknąć schodkowych z Monad Transformers w scala?

Jak mogę sformułować transformator monada dla Reader i OptionT aby uniknąć brzydkich zagnieżdżone for listowych w moim encode funkcji?

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

Dziękuję!

Odpowiedz

13

Jeśli spojrzeć na definition of Reader in the Scalaz source, zobaczysz to:

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

który mówi nam, że monada Reader używasz tylko specjalizacja transformatora monada gdzie monada jest owinięty jest trywialny Id Monada. Możesz użyć ReaderT bezpośrednio, ale owijając swoją monadę OptionT[IO, _] zamiast po prostu owijać wszystko w Reader. Na przykład, co następuje powinien robić to, co chcesz:

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) 

W zasadzie powinno być możliwe, aby zastąpić część po stream <- z poniższym:

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

Ale z jakiegoś powodu maszyny Unapply że liftReaderT polega na tym, że nie działa w tym przypadku. Na szczęście napisanie fragmentu Kleisli nie jest wcale takie straszne.


jako przypis: miła liftReaderT składnia wspominałem staje się dostępna, jeśli zdefiniować instancję UnapplyCo takiego:

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 
} 

nie jestem pewien z góry na głowie, czy istnieje powód Scalaz 7 obecnie nie udostępnia tej instancji, ale prawdopodobnie warto się jej przyjrzeć.

+1

Byłem w trakcie pisania prawie identycznej odpowiedzi, kiedy pojawiło się twoje. Zmieniłem górną część Twojej odpowiedzi na coś, co było w moim, ale nie w twoim o wyświetleniu aliasu Reader = ReaderT, proszę go usunąć, jeśli uważasz, że nie dodaje się do Twojej odpowiedzi :) – stew

+0

@stew: Podziękowania ! Właśnie dodałem link do źródła, o którym wspomniałeś. –

+0

Travis i @stew są niezwykle pomocne! Wypróbuj to teraz. – cwmyers