簡體   English   中英

Scala,貓 - 如何使用 IO(或其他單子)和任何一個創建無標簽最終實現?

[英]Scala, cats - how to create tagless-final implementation with IO (or other monad) and Either?

我創建了一個簡單的trait和他的實現:

trait UserRepositoryAlg[F[_]] {

  def find(nick: String): F[User]

  def update(user: User): F[User]
}

class UserRepositoryInterpreter extends UserRepositoryAlg[Either[Error, *]] {
  override def find(nick: String): Either[Error, User] = for {
    res <- users.find(user => user.nick == nick).toRight(UserError)
  } yield res

  override def update(user: User): Either[Error, User] = for {
    found <- users.find(u => u.nick == user.nick).toRight(UserError)
    updated = found.copy(points = found.points + user.points)
  } yield updated
}

在這里,我想使用EitherEitherT來“捕獲”錯誤,但我也想使用IOFuture作為主要單子。 在我的主要 class 中,我創建了對此實現的調用:

 object Main extends App {

  class Pointer[F[_] : Monad](repo: UserRepositoryAlg[F]) {
    def addPoints(nick: String): EitherT[F, Error, User] = {
      for {
        user <- EitherT.right(repo.find(nick))
        updated <- EitherT.right(repo.update(user))
      } yield Right(updated)
    }
  }
  val pointer = new Pointer[IO](new UserRepositoryInterpreter{}).addPoints("nick")
}

但是在創建pointer的行中,IntelliJ 向我顯示錯誤: Type mismatch - required: UserRepositoryAlg[F], found: UserRepositoryInterpreter ,我不明白為什么。 我用F[_]創建了Pointer class 作為IO並想使用UserRepositoryAlg[F]的實現。 我該如何解決這個問題或者在這種情況下有什么好的做法? 如果我想實現這樣的目標: IO[Either[Error, User]]EitherT[IO, Error, User]

我試圖將class UserRepositoryInterpreter extends UserRepositoryAlg[Either[Error, *]]更改為class UserRepositoryInterpreter[F[_]] extends UserRepositoryAlg[F[Either[Error, *]]]之類的東西,但它沒有幫助。

編輯:我發現了如何通過使用變換A => F[A]Applicative[F]返回F[Either[Error,User]] ] :

class UserRepositoryInterpreter[F[_] : Applicative] extends UserRepositoryAlg[F[Either[Error, *]]] {
  override def find(nick: String): F[Either[Error, User]] = for {
    res <- Applicative[F].pure(users.find(user => user.nick == nick).toRight(UserError))
  } yield res

  override def update(user: User): F[Either[Error, User]] = for {
    found <- Applicative[F].pure(users.find(u => u.nick == user.nick).toRight(UserError))
    updated = Applicative[F].pure(found.map(u => u.copy(points = u.points + user.points)))
  } yield updated
}

但是我在主要的 function 中仍然存在問題,因為我無法獲得EitherRight值:

 def addPoints(nick: String): EitherT[F, Error, User] = {
      for {
        user <- EitherT.liftF(repo.find(nick))
        updated <- EitherT.rightT(repo.update(user))
      } yield Right(updated)
    }

這里updated <- EitherT.rightT(repo.update(user)) userEither[Error, User] ,但我只需要傳遞User 所以我嘗試做類似的事情: Right(user).map(u=>u)並通過它,但它也無濟於事。 我應該如何取這個值?

F[_]描述了你的主要影響。 理論上,您可以使用任何 monad(甚至任何更高種類的類型),但在實踐中,最好的選擇是 monad,它允許您像cats-effectFuture一樣暫停執行。

你的問題是你試圖使用IO作為你的主要效果,但是對於UserRepositoryInterpreter你的設置Either作為你的F

你應該做的只是參數化UserRepositoryInterpreter ,你可以選擇你的效果單子。 如果你想同時使用Either來處理錯誤和F來暫停效果,你應該使用 monad stack F[Either[Error, User]]

示例解決方案:

import cats.Monad
import cats.data.EitherT
import cats.effect.{IO, Sync}
import cats.implicits._

case class User(nick: String, points: Int)

trait UserRepositoryAlg[F[_]] {

  def find(nick: String): F[Either[Error, User]]

  def update(user: User): F[Either[Error, User]]
}

//UserRepositoryInterpreter is parametrized, but we require that F has typeclass Sync,
//which would allow us to delay effects with `Sync[F].delay`.
//Sync extends Monad, so we don't need to request is explicitly to be able to use for-comprehension
class UserRepositoryInterpreter[F[_]: Sync] extends UserRepositoryAlg[F] {

  val users: mutable.ListBuffer[User] = ListBuffer()

  override def find(nick: String): F[Either[Error, User]] = for {
    //Finding user will be delayed, until we interpret and run our program. Delaying execution is useful for side-effecting effects,
    //like requesting data from database, writting to console etc.
    res <- Sync[F].delay(Either.fromOption(users.find(user => user.nick == nick), new Error("Couldn't find user")))
  } yield res


  //we can reuse find method from UserRepositoryInterpreter, but we have to wrap find in EitherT to access returned user
  override def update(user: User): F[Either[Error, User]] = (for {
    found <- EitherT(find(user.nick))
    updated = found.copy(points = found.points + user.points)
  } yield updated).value
}

object Main extends App {

  class Pointer[F[_] : Monad](repo: UserRepositoryAlg[F]) {
    def addPoints(nick: String): EitherT[F, Error, User] = {
      for {
        user <- EitherT(repo.find(nick))
        updated <- EitherT(repo.update(user))
      } yield updated
    }
  }

  //at this point we define, that we want to use IO as our effect monad
  val pointer = new Pointer[IO](new UserRepositoryInterpreter[IO]).addPoints("nick")

  pointer.value.unsafeRunSync() //at the end of the world we run our program

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM