2013-06-12 36 views
11

我有以下代码使用Reader monad进行配置,并且还必须处理IO[Option[String]],并且我已经使用我的encode函数中的阶梯代码结束了代码。如何避免scala中使用Monad变形金刚阶梯式步进?

我怎样才能制定一个单子转换为ReaderOptionT避免我encode功能丑陋的嵌套for内涵?

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

谢谢!

回答

13

如果你看一下definition of Reader in the Scalaz source,你会看到这一点:

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

它告诉我们您所使用的Reader单子只是一个单子转换,其中被包裹的单子的专业化是微不足道Id monad。您可以直接使用ReaderT,但是会包装您的OptionT[IO, _] monad,而不是将所有内容都包装在Reader中。例如,下面应该做你想要什么:

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) 

原则,你应该可以用下面的stream <-后更换部分:

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

但由于某些原因,Unapply机械是liftReaderT依赖似乎并没有在这种情况下工作。幸运的是,明确写出Kleisli的部分并不是那么可怕。


作为一个注脚:

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 
} 

我不知道把我的头顶部是否有一个原因:如果你定义一个UnapplyCo例如像这样漂亮的liftReaderT语法我提到可用Scalaz 7目前不提供这个实例,但它可能值得研究。

+1

当你出现时,我正在写一个几乎相同的答案。我修改了你的答案的顶部,这些东西是在我的,但没有在你的显示Reader = ReaderT类型别名,请随时删除它,如果你认为它不会添加到您的答案:) – stew

+0

@stew:谢谢!我刚刚添加了一个链接到您提到的来源。 –

+0

特拉维斯和@stew这非常有帮助!现在就试试吧。 – cwmyers