Как избежать ступенчатого шага с Monad Transformers в scala?

У меня есть следующий код, который использует конфигурацию Reader для конфигурации, а также должен иметь дело с IO[Option[String]], и я получил код, который является этапом в моей функции encode.

Как я могу сформулировать трансформатор монады для 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)}

Благодарственные!

Ответ 1

Если вы посмотрите на определение Reader в источнике Scalaz, вы увидите следующее:

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

Что говорит нам о том, что монада Reader, которую вы используете, - это просто специализация монадного трансформатора, где монада обертывается тривиальной монадой Id. Вы можете напрямую использовать ReaderT, но обертываете свою монаду OptionT[IO, _] вместо того, чтобы просто обернуть все в 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 явно не такая ужасная.


В качестве сноски: синтаксис nice 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 в настоящее время не предоставляет этот экземпляр, но, вероятно, стоит посмотреть.