[英]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"
}
這里的問題是,例如,用於理解的user
是Either[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.