我正在尝试在我的项目中使用自由单子,但我很难做到优雅。
假设我有两个上下文(实际上我有更多) -
为此,我需要为每个上下文定义不同的操作,并使用
这是我经过数天的搜索和阅读所得到的结果:
当我想要编写程序时,我可以这样做:
问题在于,例如,在 for 推导中的
我想要的是
有人可以帮忙吗? 如果需要更多细节,请告诉我。
我还创建了一个最小的 sbt 项目,这样任何愿意帮忙的人都可以运行它: https://github.com/Leonti/free-monad-experiment/blob/master/src/main/scala/example/FreeMonads.scala 干杯, Leonti
假设我有两个上下文(实际上我有更多) -
Receipt
和 User
- 两者都在数据库上进行操作,我想保持它们的解释器分开,并在最后一刻组合它们。为此,我需要为每个上下文定义不同的操作,并使用
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"
}
问题在于,例如,在 for 推导中的
user
的类型为 Either[Error, User]
,这个类型可以从 getUser
的签名中理解。我想要的是
User
类型或停止计算。
我知道我需要以某种方式使用 EitherT
monad transformer 或 FreeT
,但尝试了几个小时后,我不知道如何组合类型使其正常工作。有人可以帮忙吗? 如果需要更多细节,请告诉我。
我还创建了一个最小的 sbt 项目,这样任何愿意帮忙的人都可以运行它: https://github.com/Leonti/free-monad-experiment/blob/master/src/main/scala/example/FreeMonads.scala 干杯, Leonti
GetUser
定义为case class GetUser(id: String) extends UserOp[User]
,并让解释器处理错误。对于GetReceipt
同样如此。 - Tomas MikulaAction
返回Either
,然后有一个解释器Action~>Id
。相反,它可以只返回成功的结果,并且有一个解释器Action~>Either[Error,?]
。用户端至少不需要使用EitherT
。这也使得错误类型由解释器决定。 - Tomas Mikula