Scala,cats - 如何使用 IO(或其他 monad)和 Either 创建无标记最终实现?
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
}
这里我想用Either
或EitherT
来"catch"错误,但我也想用IO
或Future
作为主要单子。在我的主要 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, *]]]
之类的东西,但它对我没有帮助。
编辑:
我找到了如何 return F[Either[Error,User]]
通过使用 Applicative[F]
转换 A => F[A]
:
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
}
但是我的main函数还是有问题,因为我无法得到Either
的Right
值:
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))
user
是Either[Error, User]
,不过我只需要传User
。所以我试着做类似的事情:
Right(user).map(u=>u)
并通过它,但它也无济于事。我应该如何取这个值?
F[_]
描述了你的主要作用。理论上,您可以使用任何 monad(甚至任何更高类型的类型),但实际上,最好的选择是 monad,它允许您暂停执行,如 cats-effect
或 Future
.
你的问题是你试图使用 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
}
我创建了一个简单的 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
}
这里我想用Either
或EitherT
来"catch"错误,但我也想用IO
或Future
作为主要单子。在我的主要 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, *]]]
之类的东西,但它对我没有帮助。
编辑:
我找到了如何 return F[Either[Error,User]]
通过使用 Applicative[F]
转换 A => F[A]
:
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
}
但是我的main函数还是有问题,因为我无法得到Either
的Right
值:
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))
user
是Either[Error, User]
,不过我只需要传User
。所以我试着做类似的事情:
Right(user).map(u=>u)
并通过它,但它也无济于事。我应该如何取这个值?
F[_]
描述了你的主要作用。理论上,您可以使用任何 monad(甚至任何更高类型的类型),但实际上,最好的选择是 monad,它允许您暂停执行,如 cats-effect
或 Future
.
你的问题是你试图使用 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
}