Scala, коты - как создать окончательную реализацию без тегов с IO (или другой монадой) и 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
чтобы "ловить" ошибки, но я бы тоже хотел использовать IO
или Future
как основная монада. В моем основном классе я создал вызов этой реализации:
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
и я не понимаю почему. я создалPointer
класс с F[_]
как 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, *]]]
, но мне это не помогло.
РЕДАКТИРОВАТЬ: я узнал, как вернуть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
}
Но у меня все еще проблема с основной функцией, потому что я не могу получить Right
ценность Either
:
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)
и пройти, но это тоже не помогает. Как мне принять это значение?
1 ответ
F[_]
описывает ваш главный эффект. Теоретически вы можете использовать любую монаду (или даже любой более высокий тип), но на практике лучший выбор - это монада, которая позволяет вам приостанавливать выполнение, напримерcats-effect
или Future
.
Ваша проблема в том, что вы пытаетесь использовать IO
в качестве основного эффекта, но для UserRepositoryInterpreter
твой набор Either
Как ваш F
.
Что вам нужно сделать, это просто параметризовать UserRepositoryInterpreter
, чтобы вы могли выбрать свою монаду эффекта. Если вы хотите использовать обаEither
для обработки ошибок и F
для приостановки эффектов вы должны использовать стек монад 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
}