问题 如何避免在Scala中使用Monad变形金刚踩踏板?


我有以下代码使用 Reader monad用于配置并且还必须处理 IO[Option[String]] 我最终得到的代码是我的 encode 功能。

我怎样才能制作一个monad变换器 Reader 和 OptionT 避免丑陋的嵌套 for 在我的理解中 encode 功能?

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

谢谢!


3221
2018-06-12 09:33


起源



答案:


如果你看看 的定义 Reader 在Scalaz源代码中,你会看到这个:

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

哪个告诉我们了 Reader 你正在使用的monad只是一个monad变换器的特化,其中monad被包裹是微不足道的 Id 单子。您可以使用 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 幸运的是,明确表示并不是那么可怕。


作为一个脚注:很好 liftReaderT 如果你定义一个,我提到的语法变得可用 UnapplyCo 像这样的实例:

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
}

我不确定Scalaz 7目前是否提供此实例是否有原因,但它可能值得研究。


13
2018-06-12 13:10



当你的出现时,我正在写一个完全相同的答案。我修改了你的答案的顶部,但是在我的内容中却没有关于显示Reader = ReaderT类型的别名,如果您认为它没有添加到您的答案中,请随意将其删除:) - stew
@stew:谢谢!我刚刚添加了你提到的来源的链接。 - Travis Brown
特拉维斯和@stew这是非常有帮助的!现在就试试吧。 - cwmyers
是的,它的效果非常好。我想我将不得不花时间了解不适用的机制。非常感谢。 - cwmyers
@cwmyers:The Unapply 业务并不是真正意图面向用户 - 这是一个旨在补充Scala(相当弱)类型推断的技巧。你在机制上遇到了一个缺口,最合理的做法可能是跳过更好的语法,特别是因为(可能)即将发生的变化会使问题无关紧要 - 请参阅评论 这里 作者Lars Hupel。 - Travis Brown


答案:


如果你看看 的定义 Reader 在Scalaz源代码中,你会看到这个:

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

哪个告诉我们了 Reader 你正在使用的monad只是一个monad变换器的特化,其中monad被包裹是微不足道的 Id 单子。您可以使用 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 幸运的是,明确表示并不是那么可怕。


作为一个脚注:很好 liftReaderT 如果你定义一个,我提到的语法变得可用 UnapplyCo 像这样的实例:

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
}

我不确定Scalaz 7目前是否提供此实例是否有原因,但它可能值得研究。


13
2018-06-12 13:10



当你的出现时,我正在写一个完全相同的答案。我修改了你的答案的顶部,但是在我的内容中却没有关于显示Reader = ReaderT类型的别名,如果您认为它没有添加到您的答案中,请随意将其删除:) - stew
@stew:谢谢!我刚刚添加了你提到的来源的链接。 - Travis Brown
特拉维斯和@stew这是非常有帮助的!现在就试试吧。 - cwmyers
是的,它的效果非常好。我想我将不得不花时间了解不适用的机制。非常感谢。 - cwmyers
@cwmyers:The Unapply 业务并不是真正意图面向用户 - 这是一个旨在补充Scala(相当弱)类型推断的技巧。你在机制上遇到了一个缺口,最合理的做法可能是跳过更好的语法,特别是因为(可能)即将发生的变化会使问题无关紧要 - 请参阅评论 这里 作者Lars Hupel。 - Travis Brown