带有 Coproduct 和 monad 转换器的 Scala Free Monads [英] Scala Free Monads with Coproduct and monad transformer

查看:66
本文介绍了带有 Coproduct 和 monad 转换器的 Scala Free Monads的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试开始在我的项目中使用免费的 monad,并且我正在努力使其优雅.
假设我有两个上下文(实际上我有更多) - ReceiptUser - 两者都对数据库进行操作,我想将它们的解释器分开并组合它们在最后一刻.
为此,我需要为每个操作定义不同的操作,并使用 Coproduct 将它们组合成一种类型.
这是我经过几天的谷歌搜索和阅读后的结果:

//收据密封性状 ReceiptOp[A]case class GetReceipt(id: String) extends ReceiptOp[Either[Error, ReceiptEntity]]类 ReceiptOps[F[_]](隐式 I: Inject[ReceiptOp, F]) {def getReceipt(id: String): Free[F,either[Error, ReceiptEntity]] = Free.inject[ReceiptOp, F](GetReceipt(id))}对象 ReceiptOps {隐式 def receiveOps[F[_]](implicit I: Inject[ReceiptOp, F]): ReceiptOps[F] = new ReceiptOps[F]}//用户密封特征 UserOp[A]case class GetUser(id: String) extends UserOp[Either[Error, User]]类 UserOps[F[_]](隐式 I: Inject[UserOp, F]) {def getUser(id: String): Free[F, Each[Error, User]] = Free.inject[UserOp, F](GetUser(id))}对象用户操作{隐式 def userOps[F[_]](隐式 I: Inject[UserOp, F]): UserOps[F] = new UserOps[F]}

当我想写一个程序时,我可以这样做:

type ReceiptsApp[A] = Coproduct[ReceiptOp, UserOp, A]输入程序[A] = 免费[ReceiptsApp, A]def program(隐式 RO: ReceiptOps[ReceiptsApp], UO: UserOps[ReceiptsApp]): Program[String] = {进口 RO._, UO._为了 {//想要在此处输入用户"类型用户 <- getUser("user_id")收据 <- getReceipt("test " + user.isLeft)//用户类型是`Either[Error, User]`产生一些结果"}

这里的问题是,例如用于理解的 userEither[Error, User] 类型,查看 getUser 是可以理解的> 签名.

我想要的是 User 类型或停止计算.
我知道我需要以某种方式使用 EitherT monad 转换器或 FreeT,但经过数小时的尝试后,我不知道如何组合这些类型以使其工作.>

有人可以帮忙吗?如果需要更多详细信息,请告诉我.

我还在这里创建了一个最小的 sbt 项目,所以任何愿意帮助的人都可以运行它:https://github.com/Leonti/free-monad-experiment/blob/master/src/main/scala/example/FreeMonads.scala

干杯,莱昂蒂

Freek 库 实现了所有解决您的问题所需的机器:

type ReceiptsApp = ReceiptOp :|: UserOp :|: NilDSLval PRG = DSL.Make[PRG]定义程序:程序[字符串] =为了 {用户 <- getUser("user_id").freek[PRG]收据 <- getReceipt("test" + user.isLeft).freek[PRG]产生一些结果"

当你重新发现自己时,Free monads 和类似的东西如果不经历副产品的复杂性是不可扩展的.如果您正在寻找一个优雅的解决方案,我建议您查看 Tagless最终口译员.

I'm trying to start using free monads in my project and I'm struggling to make it elegant.
Let's say I have two contexts (in reality I have more) - Receipt and User - both have operations on a database and I would like to keep their interpreters separate and compose them at the last moment.
For this I need to define different operations for each and combine them into one type using Coproduct.
This is what I have after days of googling and reading:

  // Receipts
sealed trait ReceiptOp[A]
case class GetReceipt(id: String) extends ReceiptOp[Either[Error, ReceiptEntity]]

class ReceiptOps[F[_]](implicit I: Inject[ReceiptOp, F]) {
  def getReceipt(id: String): Free[F, Either[Error, ReceiptEntity]] = Free.inject[ReceiptOp, F](GetReceipt(id))
}

object ReceiptOps {
  implicit def receiptOps[F[_]](implicit I: Inject[ReceiptOp, F]): ReceiptOps[F] = new ReceiptOps[F]
}

// Users
sealed trait UserOp[A]
case class GetUser(id: String) extends UserOp[Either[Error, User]]

class UserOps[F[_]](implicit I: Inject[UserOp, F]) {
  def getUser(id: String): Free[F, Either[Error, User]] = Free.inject[UserOp, F](GetUser(id))
}

object UserOps {
  implicit def userOps[F[_]](implicit I: Inject[UserOp, F]): UserOps[F] = new UserOps[F]
}

When I want to write a program I can do this:

type ReceiptsApp[A] = Coproduct[ReceiptOp, UserOp, A]
type Program[A] = Free[ReceiptsApp, A]

def program(implicit RO: ReceiptOps[ReceiptsApp], UO: UserOps[ReceiptsApp]): Program[String] = {

  import RO._, UO._

  for {
    // would like to have 'User' type here
    user <- getUser("user_id")
    receipt <- getReceipt("test " + user.isLeft) // user type is `Either[Error, User]`
  } yield "some result"
}  

The problem here is that for example user in for comprehension is of type Either[Error, User] which is understandable looking at the getUser signature.

What I would like to have is User type or stopped computation.
I know I need to somehow use an EitherT monad transformer or FreeT, but after hours of trying I don't know how to combine the types to make it work.

Can someone help? Please let me know if more details are needed.

I've also created a minimal sbt project here, so anyone willing to help could run it: https://github.com/Leonti/free-monad-experiment/blob/master/src/main/scala/example/FreeMonads.scala

Cheers, Leonti

解决方案

The Freek library implements all the machinery required to solve your problem:

type ReceiptsApp = ReceiptOp :|: UserOp :|: NilDSL
val PRG = DSL.Make[PRG]

def program: Program[String] = 
  for {
    user    <- getUser("user_id").freek[PRG]
    receipt <- getReceipt("test " + user.isLeft).freek[PRG]
  } yield "some result"

As you rediscovered yourself, Free monads and the likes are not extensible without going through the complexity of coproducts. If you are looking for an elegant solution, I would suggest you have a look at Tagless Final Interpreters.

这篇关于带有 Coproduct 和 monad 转换器的 Scala Free Monads的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆