Untitled

mail@pastecode.io avatar
unknown
plain_text
a year ago
5.5 kB
2
Indexable
package mipt.monads

import cats.MonadThrow
import cats.data.OptionT
import cats.syntax.flatMap.toFlatMapOps
import cats.syntax.functor.toFunctorOps
import cats.syntax.traverse.toTraverseOps

import cats.syntax.applicativeError
import scala.concurrent.Future
import scala.util.control.NoStackTrace

trait UserRepository[F[_]]:
  def findAll: F[List[User]]
  def create(name: UserName, age: Age, friends: Set[UserId] = Set.empty): F[User]
  def delete(userId: UserId): F[Unit]
  def update(user: User): F[Unit]

object UserRepository:
  case class UserNotFoundError(id: UserId) extends Throwable

  type Op[F[_], T] = UserRepository[F] => F[T]

  given [F[_]: MonadThrow]: MonadThrow[Op[F, *]] with
    override def pure[A](a: A): Op[F, A] =
      repo => MonadThrow[F].pure(a)

    override def handleErrorWith[A](fa: Op[F, A])(f: Throwable => Op[F, A]): Op[F, A] =
      repo => MonadThrow[F].handleErrorWith(fa(repo))(e => f(e)(repo))

    override def raiseError[A](e: Throwable): Op[F, A] =
      repo => MonadThrow[F].raiseError(e)

    override def flatMap[A, B](fa: Op[F, A])(f: A => Op[F, B]): Op[F, B] =
      repo => MonadThrow[F].flatMap(fa(repo))(a => f(a)(repo))

    override def tailRecM[A, B](a: A)(f: A => Op[F, Either[A, B]]): Op[F, B] =
      repo => MonadThrow[F].tailRecM(a)(a => f(a)(repo))

  object Operations:
    def findAll[F[_]]: Op[F, List[User]] =
      _.findAll

    def create[F[_]](name: UserName, age: Age, friends: Set[UserId] = Set.empty): Op[F, User] =
      _.create(name, age, friends)

    def delete[F[_]](userId: UserId): Op[F, Unit] = _.delete(userId)

    def update[F[_]](user: User): Op[F, Unit] = _.update(user)

    /** реализуйте композитные методы, используя базовые выше
      *
      * для работы с ошибками можно использовать синтаксис из cats.syntax.applicativeError val err: Op[User] =
      * UserNotFoundError(UserId(1)).raiseError[Op, User]
      */

    /** Метод опционального поиска пользователя */
    def findMaybe[F[_]](userId: UserId)(using me: MonadThrow[F]): Op[F, Option[User]] =
      repo => OptionT(repo.findAll.map(_.find(_.id == userId))).value

    /** Метод поиска пользователя. Если пользователь не найден, должна генерироваться ошибка UserNotFound */
    def find[F[_]](userId: UserId)(using me: MonadThrow[F]): Op[F, User] = repo =>
      findMaybe[F](userId).map(_.getOrElse(throw UserNotFoundError(userId)))(repo)

    /** Метод добавления друга к пользователю. */
    def addFriend[F[_]](currentUserId: UserId, friendId: UserId)(using
        me: MonadThrow[F]
    ): Op[F, User] = repo =>
      for {
        curUser <- find(currentUserId)(using me)(repo)
        friend  <- find(friendId)(using me)(repo)
        curUserWithFriend = curUser.copy(friends = curUser.friends + friendId)
        _ <- update(curUserWithFriend)(repo)
      } yield curUserWithFriend

    /** Метод удаления друга у пользователя */
    def deleteFriend[F[_]](currentUserId: UserId, friendId: UserId)(using
        me: MonadThrow[F]
    ): Op[F, User] = repo =>
      for {
        curUser <- find(currentUserId)(using me)(repo)
        curUserWithoutFriend = curUser.copy(friends = curUser.friends - friendId)
        _ <- update(curUserWithoutFriend)(repo)
      } yield curUserWithoutFriend

    /** Метод получения всех друзей пользователя */
    def getUserFriends[F[_]](userId: UserId)(using
        me: MonadThrow[F]
    ): Op[F, List[User]] = repo =>
      for {
        user    <- find(userId)(using me)(repo)
        friends <- user.friends.toList.traverse(find(_)(using me)(repo))
      } yield friends

    /** Метод получения пользователей, у которых в друзьях только взрослые пользователи */
    def getUsersWithAdultOnlyFriends[F[_]]()(using
        me: MonadThrow[F]
    ): Op[F, List[User]] = repo =>
      repo.findAll.map(users =>
        users.filter(u => u.friends.nonEmpty && u.friends.forall(fid => users.find(_.id == fid).get.isAdult))
      )

    /** Метод удаления всех молодых пользователей */
    def deleteAllJuniorUsers[F[_]]()(using
        me: MonadThrow[F]
    ): Op[F, Unit] = repo =>
      for {
        users <- repo.findAll
        juniorUsers = users.filter(!_.isAdult)
        _ <- juniorUsers.traverse(user => delete(user.id)(repo))
      } yield ()

    /** Метод создания сообщества, где все являются друзьями друг для друга. На вход подается список атрибутов
      * пользователей из сообщества
      */
    def createCommunity[F[_]](community: List[(UserName, Age)])(using
        me: MonadThrow[F]
    ): Op[F, List[User]] = repo =>
      for {
        communityUsers <- community.traverse { case (name, age) => create(name, age)(repo) }
        updatedCommunityUsers <- communityUsers.traverse { currentUser =>
          val curUserWithFriend =
            currentUser.copy(friends = communityUsers.filterNot(_ == currentUser).map(_.id).toSet)
          update(curUserWithFriend)(repo).map(_ => curUserWithFriend)
        }
      } yield updatedCommunityUsers