This commit is contained in:
Карпышев Сергей Вадимович 2023-10-26 13:44:21 +03:00
parent 310b3961ad
commit c1099c97f9
10 changed files with 114 additions and 142 deletions

View file

@ -13,35 +13,27 @@ case class Error(error: String)
type WriterTF[F[_], L] = [A] =>> WriterT[F, L, A] type WriterTF[F[_], L] = [A] =>> WriterT[F, L, A]
case class LogEmbed[F[_], A](value: WriterT[WriterTF[WriterTF[F, Vector[Debug]], Vector[Info]], Vector[Error], A]) case class LogEmbed[F[_], A](value: WriterT[WriterTF[WriterTF[F, Vector[Debug]], Vector[Info]], Vector[Error], A])
/** /** I. Продвинутый логгер
* I. Продвинутый логгер
*
* В этом задании вам предстоит реализовать логгер, имеющий 3 уровня записей - Debug, Info и Error.
* Структура лога LogEmbed описана выше, от вас же требуется реализовать функции для удобного создания логов
* *
* В этом задании вам предстоит реализовать логгер, имеющий 3 уровня записей - Debug, Info и Error. Структура лога
* LogEmbed описана выше, от вас же требуется реализовать функции для удобного создания логов
*/ */
final case class Logger[F[_]: Monad](): final case class Logger[F[_]: Monad]():
/** /** I.1) Реализовать функцию debug
* I.1) Реализовать функцию debug
*
* Функция должна принимать сообщение уровня debug и возвращать лог,
* содержащий только это сообщение на правильном уровне
* *
* Функция должна принимать сообщение уровня debug и возвращать лог, содержащий только это сообщение на правильном
* уровне
*/ */
def debug(debug: String): LogEmbed[F, Unit] = ??? def debug(debug: String): LogEmbed[F, Unit] = ???
/** /** I.1) Реализовать функцию info
* I.1) Реализовать функцию info
* *
* Функция аналогична предыдущей за исключением того, что уровень лога меняется на Info * Функция аналогична предыдущей за исключением того, что уровень лога меняется на Info
*
*/ */
def info(info: String): LogEmbed[F, Unit] = ??? def info(info: String): LogEmbed[F, Unit] = ???
/** /** I.1) Реализовать функцию error
* I.1) Реализовать функцию error
* *
* Функция-аналог предыдущих двух для уровня Error * Функция-аналог предыдущих двух для уровня Error
*
*/ */
def error(error: String): LogEmbed[F, Unit] = ??? def error(error: String): LogEmbed[F, Unit] = ???

View file

@ -4,14 +4,11 @@ import cats.Monad
import cats.data.{Reader, ReaderT} import cats.data.{Reader, ReaderT}
import cats.syntax.applicative.* import cats.syntax.applicative.*
/** /** II. Интерфейс Ask
* II. Интерфейс Ask
*
* Теперь от вас требуется реализовать несколько интерфейсов Ask для некоторых монад.
* Интерфейс служит для получения контекстной информации, хранящейся в монаде
* (подробнее о данной монаде можно прочить на сайте библиотеки cats,
* а в одном из следующих заданий будут примеры применения)
* *
* Теперь от вас требуется реализовать несколько интерфейсов Ask для некоторых монад. Интерфейс служит для получения
* контекстной информации, хранящейся в монаде (подробнее о данной монаде можно прочить на сайте библиотеки cats, а в
* одном из следующих заданий будут примеры применения)
*/ */
trait Ask[F[_], R]: trait Ask[F[_], R]:
def ask: F[R] def ask: F[R]
@ -19,18 +16,14 @@ trait Ask[F[_], R]:
object Ask: object Ask:
def apply[F[_], R](using ask: Ask[F, R]): Ask[F, R] = ask def apply[F[_], R](using ask: Ask[F, R]): Ask[F, R] = ask
/** /** II.1) Интерфейс Ask для монады Reader
* II.1) Интерфейс Ask для монады Reader
* *
* Реализуйте описанный выше интерфейс, возвращающий контекст, содержащийся в монаде Reader * Реализуйте описанный выше интерфейс, возвращающий контекст, содержащийся в монаде Reader
*
*/ */
given [R]: Ask[Reader[R, *], R] = ??? given [R]: Ask[Reader[R, *], R] = ???
/** /** II.2) Интерфейс Ask для трансформера ReaderT
* II.2) Интерфейс Ask для трансформера ReaderT
* *
* Реализуйте описанный выше интерфейс, возвращающий контекст, содержащийся в трансформере ReaderT * Реализуйте описанный выше интерфейс, возвращающий контекст, содержащийся в трансформере ReaderT
*
*/ */
given [F[_]: Monad, R]: Ask[ReaderT[F, R, *], R] = ??? given [F[_]: Monad, R]: Ask[ReaderT[F, R, *], R] = ???

View file

@ -4,12 +4,10 @@ import cats.{Monad, Monoid}
import cats.data.{Writer, WriterT} import cats.data.{Writer, WriterT}
import cats.syntax.applicative.* import cats.syntax.applicative.*
/** /** III. Интерфейс Tell
* III. Интерфейс Tell
*
* Данное задание очень похоже на предыдущее, только наш интерфейс будет не возвращать контекст,
* а, наоборот, отдавать дополнительную информацию во вне
* *
* Данное задание очень похоже на предыдущее, только наш интерфейс будет не возвращать контекст, а, наоборот, отдавать
* дополнительную информацию во вне
*/ */
trait Tell[F[_], W]: trait Tell[F[_], W]:
def tell(log: W): F[Unit] def tell(log: W): F[Unit]
@ -17,18 +15,14 @@ trait Tell[F[_], W]:
object Tell: object Tell:
def apply[F[_], W](using tell: Tell[F, W]): Tell[F, W] = tell def apply[F[_], W](using tell: Tell[F, W]): Tell[F, W] = tell
/** /** III.1) Интерфейс Tell для Writer
* III.1) Интерфейс Tell для Writer
* *
* Реализуйте интерфейс Tell так, чтобы полученное сообщение добавлялось в лог монады * Реализуйте интерфейс Tell так, чтобы полученное сообщение добавлялось в лог монады
*
*/ */
given [W: Monoid]: Tell[Writer[W, *], W] = ??? given [W: Monoid]: Tell[Writer[W, *], W] = ???
/** /** III.2) Интерфейс Tell для WriterT
* III.2) Интерфейс Tell для WriterT
* *
* Реализуйте интерфейс Tell так, чтобы полученное сообщение добавлялось в лог трансформера * Реализуйте интерфейс Tell так, чтобы полученное сообщение добавлялось в лог трансформера
*
*/ */
given [F[_]: Monad, W: Monoid]: Tell[WriterT[F, W, *], W] = ??? given [F[_]: Monad, W: Monoid]: Tell[WriterT[F, W, *], W] = ???

View file

@ -12,7 +12,9 @@ case class Config(chunkSize: Int)
trait UserRepositoryDao: trait UserRepositoryDao:
def findAll(config: Config): List[User] def findAll(config: Config): List[User]
def create(name: UserName, age: Age, friends: Set[UserId] = Set.empty)(config: Config): Either[UserAlreadyExists, User] def create(name: UserName, age: Age, friends: Set[UserId] = Set.empty)(
config: Config
): Either[UserAlreadyExists, User]
def delete(userId: UserId)(config: Config): Either[UserDoesNotExists, Unit] def delete(userId: UserId)(config: Config): Either[UserDoesNotExists, Unit]
def update(user: User)(config: Config): Either[UserDoesNotExists, Unit] def update(user: User)(config: Config): Either[UserDoesNotExists, Unit]
@ -22,51 +24,38 @@ trait UserRepository[F[_]]:
def delete(userId: UserId): F[Unit] def delete(userId: UserId): F[Unit]
def update(user: User): F[Unit] def update(user: User): F[Unit]
/** /** IV. Обёртка для работы с базой данных
* IV. Обёртка для работы с базой данных
*
* В последнем задании рассмотрим более практичный пример:
* пусть есть интерфейс UserRepositoryDao, который обеспечивает работу с базой данных, обновляя данные о пользователях
* (модель пользователей и операции, проводимые с ними, описываются кодом в данном файле и в User.scala, UserErrors.scala).
* От вас требуется сделать обёртку над данным интерфейсом, чтобы его "грязные" функции можно было бы использовать
* в коде в функциональном стиле.
* При написании используйте интерфейсы MonadThrow и Ask - такой подход часто называется Tagless final
* *
* В последнем задании рассмотрим более практичный пример: пусть есть интерфейс UserRepositoryDao, который обеспечивает
* работу с базой данных, обновляя данные о пользователях (модель пользователей и операции, проводимые с ними,
* описываются кодом в данном файле и в User.scala, UserErrors.scala). От вас требуется сделать обёртку над данным
* интерфейсом, чтобы его "грязные" функции можно было бы использовать в коде в функциональном стиле. При написании
* используйте интерфейсы MonadThrow и Ask - такой подход часто называется Tagless final
*/ */
object UserRepositoryDao: object UserRepositoryDao:
def apply[F[_]: MonadThrow](dao: UserRepositoryDao)(using Ask[F, Config]): UserRepository[F] = new UserRepository[F]: def apply[F[_]: MonadThrow](dao: UserRepositoryDao)(using Ask[F, Config]): UserRepository[F] = new UserRepository[F]:
/** /** IV.1) Фукнция findAll
* IV.1) Фукнция findAll
*
* Для данной функции требуется, используя интерфейс Ask из библиотеки Cats
* (полностью аналогичный варианту из предыдущего задания), получить конфиг, хранящийся в монаде F,
* и вернуть значение, обёрнутое в монаду
* *
* Для данной функции требуется, используя интерфейс Ask из библиотеки Cats (полностью аналогичный варианту из
* предыдущего задания), получить конфиг, хранящийся в монаде F, и вернуть значение, обёрнутое в монаду
*/ */
override def findAll: F[List[User]] = ??? override def findAll: F[List[User]] = ???
/** /** IV.2) Фукнция create
* IV.2) Фукнция create
*
* Для этой функции аналогично нужно получить конфиг,
* а так же дополнительно обработать возможные ошибки при помощи интерфейса MonadThrow
* (примеры использования можно найти на сайте cats)
* *
* Для этой функции аналогично нужно получить конфиг, а так же дополнительно обработать возможные ошибки при помощи
* интерфейса MonadThrow (примеры использования можно найти на сайте cats)
*/ */
override def create(name: UserName, age: Age, friends: Set[UserId]): F[User] = ??? override def create(name: UserName, age: Age, friends: Set[UserId]): F[User] = ???
/** /** IV.3) Функция delete
* IV.3) Функция delete
* *
* Данная функция аналогично требует получения конфига и обработки ошибки * Данная функция аналогично требует получения конфига и обработки ошибки
*
*/ */
override def delete(userId: UserId): F[Unit] = ??? override def delete(userId: UserId): F[Unit] = ???
/** /** IV.4) Функция update
* IV.4) Функция update
* *
* Для последней функции задание аналогично * Для последней функции задание аналогично
*
*/ */
override def update(user: User): F[Unit] = ??? override def update(user: User): F[Unit] = ???

View file

@ -8,7 +8,9 @@ import scala.util.{Success, Try}
class Tests extends AnyFlatSpec with Matchers: class Tests extends AnyFlatSpec with Matchers:
def logEmbed(debug: Vector[String], info: Vector[String], error: Vector[String]): LogEmbed[Try, Unit] = def logEmbed(debug: Vector[String], info: Vector[String], error: Vector[String]): LogEmbed[Try, Unit] =
LogEmbed(WriterT(WriterT(WriterT(Success((debug.map(Debug.apply), (info.map(Info.apply), (error.map(Error.apply), ())))))))) LogEmbed(
WriterT(WriterT(WriterT(Success((debug.map(Debug.apply), (info.map(Info.apply), (error.map(Error.apply), ())))))))
)
it should "info log" in { it should "info log" in {
val logger = Logger[Try]() val logger = Logger[Try]()

View file

@ -25,10 +25,11 @@ object TestsData:
lastConfig = Right(config) lastConfig = Right(config)
usersList.toList usersList.toList
override def create(name: UserName, age: Age, friends: Set[UserId])(config: Config): Either[UserAlreadyExists, User] = override def create(name: UserName, age: Age, friends: Set[UserId])(
config: Config
): Either[UserAlreadyExists, User] =
lastConfig = Right(config) lastConfig = Right(config)
if (usersList.exists(_.name == name)) if (usersList.exists(_.name == name)) Left(UserAlreadyExists(name))
Left(UserAlreadyExists(name))
else else
val user = User(UserId(usersList.size), name, age, friends) val user = User(UserId(usersList.size), name, age, friends)
usersList += user usersList += user
@ -39,8 +40,7 @@ object TestsData:
if (usersList.exists(_.id == userId)) if (usersList.exists(_.id == userId))
usersList -= usersList.find(_.id == userId).get usersList -= usersList.find(_.id == userId).get
Right(()) Right(())
else else Left(UserDoesNotExists(userId))
Left(UserDoesNotExists(userId))
override def update(user: User)(config: Config): Either[UserDoesNotExists, Unit] = override def update(user: User)(config: Config): Either[UserDoesNotExists, Unit] =
lastConfig = Right(config) lastConfig = Right(config)
@ -48,8 +48,7 @@ object TestsData:
usersList -= usersList.find(_.id == user.id).get usersList -= usersList.find(_.id == user.id).get
usersList += user usersList += user
Right(()) Right(())
else else Left(UserDoesNotExists(user.id))
Left(UserDoesNotExists(user.id))
type M = [A] =>> Config => Either[Throwable, A] type M = [A] =>> Config => Either[Throwable, A]
@ -58,12 +57,14 @@ object TestsData:
override def pure[A](x: A): M[A] = _ => Right(x) override def pure[A](x: A): M[A] = _ => Right(x)
override def flatMap[A, B](fa: M[A])(f: A => M[B]): M[B] = c => fa(c).flatMap(a => f(a)(c)) override def flatMap[A, B](fa: M[A])(f: A => M[B]): M[B] = c => fa(c).flatMap(a => f(a)(c))
override def tailRecM[A, B](a: A)(f: A => M[Either[A, B]]): M[B] = override def tailRecM[A, B](a: A)(f: A => M[Either[A, B]]): M[B] =
c => f(a)(c) match c =>
f(a)(c) match
case Left(e) => Left(e) case Left(e) => Left(e)
case Right(Left(a)) => tailRecM(a)(f)(c) case Right(Left(a)) => tailRecM(a)(f)(c)
case Right(Right(b)) => Right(b) case Right(Right(b)) => Right(b)
override def raiseError[A](e: Throwable): M[A] = _ => Left(e) override def raiseError[A](e: Throwable): M[A] = _ => Left(e)
override def handleErrorWith[A](fa: M[A])(f: Throwable => M[A]): M[A] = c => fa(c) match override def handleErrorWith[A](fa: M[A])(f: Throwable => M[A]): M[A] = c =>
fa(c) match
case Left(e) => f(e)(c) case Left(e) => f(e)(c)
case Right(a) => Right(a) case Right(a) => Right(a)
@ -145,5 +146,6 @@ class Tests extends AnyFlatSpec with Matchers:
it should "not update nonexistent user" in { it should "not update nonexistent user" in {
val repository = UserRepositoryDao[M](dao(sampleUsers)) val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46) val config = Config(46)
repository.update(User(UserId(22), UserName("Subject #22"), Age(42), Set.empty))(config) shouldBe a [Left[Throwable, Unit]] repository
.update(User(UserId(22), UserName("Subject #22"), Age(42), Set.empty))(config) shouldBe a[Left[Throwable, Unit]]
} }