簡體   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