Составление алгебр на основе Cats Free Monad

Предположим, у меня есть следующая алгебра для работы с файловой системой:

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

Теперь предположим, что я хочу построить другую алгебру, использующую первую. Например.:

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 алгебре.

Как мне это сделать?


person Alexandr Antonov    schedule 12.03.2018    source источник
comment
Полный пример этой точной проблемы можно найти в документации по кошкам: typelevel.org/cats/datatypes/freemonad .html (раздел: Создание бесплатных ADT монад.)   -  person erdeszt    schedule 12.03.2018
comment
К сожалению, я не думаю, что этот раздел отвечает на мой вопрос. Это объясняет, как составить 2 взаимно независимых ADT при построении программы. Однако это не распространяется на случай, когда интерпретатор одного ADT полагается на другой ADT.   -  person Alexandr Antonov    schedule 12.03.2018
comment
@AlexandrAntonov Может быть, вы хотите составить два _1 _-_ 2_-nat trafos PathOps ~> Free[Fs, ?] и Fs ~> IO? То есть: ваш желаемый естественный трафарет PathOps ~> IO каким-то образом учитывается Free[Fs, ?]-монадой?   -  person Andrey Tyukin    schedule 12.03.2018
comment
Спасибо, Андрей, это именно то, что я искал   -  person Alexandr Antonov    schedule 13.03.2018


Ответы (1)


Я предполагаю, что вы хотите написать два интерпретатора 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 ~> Free[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 ~> Free[Fs, ?]-интерпретатором, используя andThen:

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

Это дает вам интерпретатор из PathOps ~> IO, который состоит из двух отдельных уровней, которые можно тестировать отдельно.

person Andrey Tyukin    schedule 12.03.2018