Я узнаю о свободных монадах, и я собрал простой пример в 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()
Итак вопросы:
- Как я могу сложить два монады вместе в «fullProgram»
- Как я строю двух переводчиков в новый интерпретатор?
- Как справиться с опцией [Todo] возвращенного
find
, а затем перешел кrepresent
там уже некоторые проекты пытаются генерировать эти вещи без написания шаблона. например, FreeDSL -> https://github.com/ISCPIF/freedsl – jopasserat