繁体   English   中英

Scala免费Monads与Coproduct和monad变压器

[英]Scala Free Monads with Coproduct and monad transformer

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

  // 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]
}

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

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"
}  

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

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

有人可以帮忙吗? 如果需要更多详细信息,请与我们联系。

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

干杯,莱昂蒂

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

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"

当你重新发现自己时,如果不经历副产品的复杂性,免费的monad和类似的东西是不可扩展的。 如果您正在寻找一个优雅的解决方案,我建议您看看Tagless Final Interpreters

经过与猫的长期战斗:

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

class ReceiptOps[F[_]](implicit I: Inject[ReceiptOp, F]) {
  private[this] def liftFE[A, B](f: ReceiptOp[Either[A, B]]) = EitherT[Free[F, ?], A, B](Free.liftF(I.inj(f)))

  def getReceipt(id: String): EitherT[Free[F, ?], Error, ReceiptEntity] = liftFE(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]) {
  private[this] def liftFE[A, B](f: UserOp[Either[A, B]]) = EitherT[Free[F, ?], A, B](Free.liftF(I.inj(f)))

  def getUser(id: String): EitherT[Free[F, ?], Error, User] = Free.inject[UserOp, F](GetUser(id))
}

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

然后你可以根据需要编写程序:

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

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

  import RO._, UO._

  (for {
    // would like to have 'User' type here
    user <- getUser("user_id")
    receipt <- getReceipt("test " + user.isLeft) // user type is `User` now
  } yield "some result").value // you have to get Free value from EitherT, or change return signature of program 
}  

一点解释。 没有Coproduct转换器,函数将返回:

Free[F, A]

一旦我们将操作的Coproduct添加到图片中,返回类型就变为:

Free[F[_], A]

,在我们尝试将其转换为EitherT之前,它可以正常工作。 如果没有Coproduct,EitherT看起来像:

EitherT[F, ERROR, A]

其中F,是自由[F,A]。 但如果F是Coproduct和Injection,则直觉导致:

EitherT[F[_], ERROR, A]

显然这是错误的,这里我们必须提取Coproduct的类型。 这会引导我们使用kind-projector插件:

EitherT[Free[F, ?], ERROR, A]

或者使用lambda表达式:

EitherT[({type L[a] = Free[F, a]})#L, ERROR, A]

现在它是我们可以解除的正确类型:

EitherT[Free[F, ?], A, B](Free.liftF(I.inj(f)))

如果需要,我们可以将返回类型简化为:

type ResultEitherT[F[_], A] = EitherT[Free[F, ?], Error, A]

并在以下功能中使用它:

def getReceipt(id: String): ResultEitherT[F[_], ReceiptEntity] = liftFE(GetReceipt(id))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM