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

}
Другие вопросы по тегам