代码之家  ›  专栏  ›  技术社区  ›  Alexandr Antonov

基于无猫单子的代数合成

  •  3
  • Alexandr Antonov  · 技术社区  · 6 年前

    假设我有以下处理文件系统的代数:

    sealed trait Fs[A]
    case class Ls(path: String) extends Fs[Seq[String]]
    case class Cp(from: String, to: String) extends Fs[Unit]
    
    def ls(path: String) = Free.liftF(Ls(path))
    def cp(from: String, to: String) = Free.liftF(Cp(from, to))
    

    以及以下代数解释器:

    def fsInterpreter = new (Fs ~> IO) {
      def apply[A](fa: Fs[A]) = fa match {
        case Ls(path) => IO(Seq(path))
        case Cp(from, to) => IO(())
      }
    }
    

    现在假设我想构建另一个使用第一个代数的代数。E、 g.:

    sealed trait PathOps[A]
    case class SourcePath(template: String) extends PathOps[String]
    
    def sourcePath(template: String) = Free.liftF(SourcePath(template))
    

    接下来我想写一个解释器 PathOps ~> IO 这样做:

    for {
      paths <- ls(template)
    } yield paths.head
    

    换句话说,我的翻译 PathOps 应调用 Fs 代数

    我该怎么做?

    1 回复  |  直到 6 年前
        1
  •  2
  •   Andrey Tyukin    6 年前

    我想你应该写两个口译员 PathOps ~> Free[Fs, ?] Fs ~> IO ,然后将它们组合成单个解释器 PathOps ~> IO

    下面是一个可编译的示例。以下是我在本例中使用的所有导入:

    import cats.~>
    import cats.free.Free
    import cats.free.Free.liftF
    

    下面是的模拟实现 IO 还有你的代数:

    // just for this example
    type IO[X] = X 
    object IO {
      def apply[A](a: A): IO[A] = a
    }
    
    sealed trait Fs[A]
    case class Ls(path: String) extends Fs[Seq[String]]
    case class Cp(from: String, to: String) extends Fs[Unit]
    type FreeFs[A] = Free[Fs, A]
    
    def ls(path: String) = Free.liftF(Ls(path))
    def cp(from: String, to: String) = Free.liftF(Cp(from, to))
    

    这是翻译 Fs ~>IO 从代码中复制:

    def fsToIoInterpreter = new (Fs ~> IO) {
      def apply[A](fa: Fs[A]) = fa match {
        case Ls(path) => IO(Seq(path))
        case Cp(from, to) => IO(())
      }
    }
    
    sealed trait PathOps[A]
    case class SourcePath(template: String) extends PathOps[String]
    
    def sourcePath(template: String) = Free.liftF(SourcePath(template))
    

    这是你的 for -理解转换为 PathOps ~>免费[Fs,?] -口译员:

    val pathToFsInterpreter = new (PathOps ~> FreeFs) {
      def apply[A](p: PathOps[A]): FreeFs[A] = p match {
        case SourcePath(template) => {
          for {
            paths <- ls(template)
          } yield paths.head
        }
      }
    }
    

    现在您可以提起 Fs ~>IO 变成一个 Free[Fs, ?] ~> IO 使用 Free.foldMap ,并用 PathOps ~>免费[Fs,?] -解释器使用 andThen :

    val pathToIo: PathOps ~> IO = 
      pathToFsInterpreter andThen 
      Free.foldMap(fsToIoInterpreter)
    

    这将为您提供一个来自 PathOps ~>IO 由两个可单独测试的独立层组成。