2

Я узнаю о свободных монадах, и я собрал простой пример в Scala, где я использую их для определения двух доменных языков.Stacking Free Monads

Первая монада имеет дело с побочными эффектами репозитория. Я применил интерпретатор, который использует государственную монаду для управления состоянием, но в реальной программе я бы использовал базу данных.

Вторая монада имеет дело с IO.

import cats.data.State 
import cats.{Id, ~>} 
import cats.free.Free 
import cats.free.Free.liftF 

final case class Todo(title: String, body: String) 
def represent(todo: Todo) = s"${todo.title}: ${todo.body}" 


sealed trait CRUDActionA[T] 
final case class Find(key: String) extends CRUDActionA[Option[Todo]] 
final case class Add(data: Todo) extends CRUDActionA[Unit] 

type CRUDAction[T] = Free[CRUDActionA, T] 
def find(key: String): CRUDAction[Option[Todo]] = liftF[CRUDActionA, Option[Todo]](Find(key)) 
def add(data: Todo): CRUDAction[Unit] = liftF[CRUDActionA, Unit](Add(data)) 

type TodosState[A] = State[List[Todo], A] 
val repository: CRUDActionA ~> TodosState = new (CRUDActionA ~> TodosState) { 
    def apply[T](fa: CRUDActionA[T]): TodosState[T] = fa match { 
    case Add(todo) => State.modify(todos => todos :+ todo) 
    case Find(title) => State.inspect(todos => todos find (_.title == title)) 
    } 
} 


sealed trait IOActionA[T] 
final case class Out(str: String) extends IOActionA[Unit] 

type IOAction[T] = Free[IOActionA, T] 
def out(str: String): IOAction[Unit] = liftF[IOActionA, Unit](Out(str)) 

val io: IOActionA ~> Id = new (IOActionA ~> Id) { 
    override def apply[A](fa: IOActionA[A]): Id[A] = fa match { 
    case Out(todo) => println(todo) 
    } 
} 

Тогда я могу соединить эти две «программы»

def addNewTodo: Free[CRUDActionA, Option[Todo]] = for { 
    _ <- add(Todo(title = "Must do", body = "Must do something")) 
    todo <- find("Must do") 
} yield todo 

def outProgram(todo: Todo): IOAction[Unit] = for { 
    _ <- out(represent(todo)) 
} yield() 

И запустить их делают

val (_, mayBeTodo) = (addNewTodo foldMap repository run List()).value 
outProgram(mayBeTodo.get).foldMap(io) 

Я понимаю, что это далеко от идеала, и я хотел бы напишите программу как и переводчик, который поддерживает:

def fullProgram = for { 
    _ <- add(Todo(title = "Must do", body = "Must do something")) 
    todo <- find("Must do")  // This is an option!!! 
    _ <- out(represent(todo)) // But represent expects a Todo 
} yield() 

Итак вопросы:

  1. Как я могу сложить два монады вместе в «fullProgram»
  2. Как я строю двух переводчиков в новый интерпретатор?
  3. Как справиться с опцией [Todo] возвращенного find, а затем перешел к represent
+0

там уже некоторые проекты пытаются генерировать эти вещи без написания шаблона. например, FreeDSL -> https://github.com/ISCPIF/freedsl – jopasserat

ответ

1

Ответ на вопросы 1 & 2:

type TodoApp[A] = Coproduct[IOActionA, CRUDActionA, A] 


    class CRUDActions[F[_]](implicit I: Inject[CRUDActionA, F]) { 
    def find(key: String): Free[F, Option[Todo]] = Free.inject[CRUDActionA, F](Find(key)) 
    def add(data: Todo): Free[F, Unit] = Free.inject[CRUDActionA, F](Add(data)) 
    } 

    object CRUDActions { 
    implicit def crudActions[F[_]](implicit I: Inject[CRUDActionA, F]): CRUDActions[F] = new CRUDActions[F] 
    } 

    class IOActions[F[_]](implicit I: Inject[IOActionA, F]) { 
    def out(str: String): Free[F, Unit] = Free.inject[IOActionA, F](Out(str)) 
    } 

    object IOActions { 
    implicit def ioActions[F[_]](implicit I: Inject[IOActionA, F]): IOActions[F] = new IOActions[F] 
    } 

    def fullProgram(implicit C : CRUDActions[TodoApp], I : IOActions[TodoApp]): Free[TodoApp, Unit] = { 
    for { 
     _ <- C.add(Todo(title = "Must do", body = "Must do something")) 
     todo <- C.find("Must do") 
     _ <- I.out(represent(todo.get)) 
    } yield() 
    } 

    object ConsoleCatsInterpreter extends (IOActionA ~> Id) { 
    def apply[A](i: IOActionA[A]) = i match { 
     case Out(prompt) => println(prompt).asInstanceOf[A] 
    } 
    } 

    object MutableListCrudInterpreter extends (CRUDActionA ~> Id) { 
    val data = new ListBuffer[Todo] 

    override def apply[A](fa: CRUDActionA[A]): Id[A] = fa match { 
     case Add(todo) => data.append(todo).asInstanceOf[A] 
     case Find(title) => data.find(_.title == title).asInstanceOf[A] 
    } 
    } 

    val interpreter: TodoApp ~> Id = ConsoleCatsInterpreter or MutableListCrudInterpreter 

    fullProgram.foldMap(interpreter)