带有 Coproduct 和 monad 转换器的 Scala Free Monads
Scala Free Monads with Coproduct and monad transformer
我正在尝试开始在我的项目中使用免费的 monad,并且正在努力使其变得优雅。
假设我有两个上下文(实际上我有更多)- 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 comprehension 中的 user
是 Either[Error, User]
类型,从 getUser
签名来看是可以理解的。
我想要的是User
类型或停止计算。
我知道我需要以某种方式使用 EitherT
monad 转换器或 FreeT
,但经过数小时的尝试我不知道如何组合这些类型以使其工作。
有人可以帮忙吗?
如果需要更多详细信息,请告诉我。
我还在这里创建了一个最小的 sbt 项目,所以任何愿意提供帮助的人都可以 运行 它:https://github.com/Leonti/free-monad-experiment/blob/master/src/main/scala/example/FreeMonads.scala
干杯,
莱昂蒂
Freek library 实现了解决您的问题所需的所有机制:
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 转换器,函数将 return:
Free[F, A]
一旦我们将运算的余积添加到图片中,return 类型变为:
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)))
如果需要,我们可以将 return 类型简化为:
type ResultEitherT[F[_], A] = EitherT[Free[F, ?], Error, A]
并在以下函数中使用它:
def getReceipt(id: String): ResultEitherT[F[_], ReceiptEntity] = liftFE(GetReceipt(id))
我正在尝试开始在我的项目中使用免费的 monad,并且正在努力使其变得优雅。
假设我有两个上下文(实际上我有更多)- 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 comprehension 中的 user
是 Either[Error, User]
类型,从 getUser
签名来看是可以理解的。
我想要的是User
类型或停止计算。
我知道我需要以某种方式使用 EitherT
monad 转换器或 FreeT
,但经过数小时的尝试我不知道如何组合这些类型以使其工作。
有人可以帮忙吗? 如果需要更多详细信息,请告诉我。
我还在这里创建了一个最小的 sbt 项目,所以任何愿意提供帮助的人都可以 运行 它:https://github.com/Leonti/free-monad-experiment/blob/master/src/main/scala/example/FreeMonads.scala
干杯, 莱昂蒂
Freek library 实现了解决您的问题所需的所有机制:
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 转换器,函数将 return:
Free[F, A]
一旦我们将运算的余积添加到图片中,return 类型变为:
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)))
如果需要,我们可以将 return 类型简化为:
type ResultEitherT[F[_], A] = EitherT[Free[F, ?], Error, A]
并在以下函数中使用它:
def getReceipt(id: String): ResultEitherT[F[_], ReceiptEntity] = liftFE(GetReceipt(id))