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

View file

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

View file

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

View file

@ -13,7 +13,7 @@ object UserName:
type Age = Age.T
object Age:
opaque type T <: Byte = Byte
val Adult: Age = 18.toByte
val Adult: Age = 18.toByte
def apply(v: Byte): Age = v
final case class User(id: UserId, name: UserName, age: Age, friends: Set[UserId]):

View file

@ -2,4 +2,4 @@ package hw.user
object UserErrors:
case class UserAlreadyExists(name: UserName) extends Throwable
case class UserDoesNotExists(id: UserId) extends Throwable
case class UserDoesNotExists(id: UserId) extends Throwable

View file

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

View file

@ -8,7 +8,9 @@ import scala.util.{Success, Try}
class Tests extends AnyFlatSpec with Matchers:
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 {
val logger = Logger[Try]()

View file

@ -25,10 +25,11 @@ object TestsData:
lastConfig = Right(config)
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)
if (usersList.exists(_.name == name))
Left(UserAlreadyExists(name))
if (usersList.exists(_.name == name)) Left(UserAlreadyExists(name))
else
val user = User(UserId(usersList.size), name, age, friends)
usersList += user
@ -39,8 +40,7 @@ object TestsData:
if (usersList.exists(_.id == userId))
usersList -= usersList.find(_.id == userId).get
Right(())
else
Left(UserDoesNotExists(userId))
else Left(UserDoesNotExists(userId))
override def update(user: User)(config: Config): Either[UserDoesNotExists, Unit] =
lastConfig = Right(config)
@ -48,24 +48,25 @@ object TestsData:
usersList -= usersList.find(_.id == user.id).get
usersList += user
Right(())
else
Left(UserDoesNotExists(user.id))
else Left(UserDoesNotExists(user.id))
type M = [A] =>> Config => Either[Throwable, A]
object M:
given MonadThrow[M] = new MonadError[M, Throwable]:
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 tailRecM[A, B](a: A)(f: A => M[Either[A, B]]): M[B] =
c => f(a)(c) match
case Left(e) => Left(e)
case Right(Left(a)) => tailRecM(a)(f)(c)
case Right(Right(b)) => Right(b)
c =>
f(a)(c) match
case Left(e) => Left(e)
case Right(Left(a)) => tailRecM(a)(f)(c)
case Right(Right(b)) => Right(b)
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
case Left(e) => f(e)(c)
case Right(a) => Right(a)
override def handleErrorWith[A](fa: M[A])(f: Throwable => M[A]): M[A] = c =>
fa(c) match
case Left(e) => f(e)(c)
case Right(a) => Right(a)
given Ask[M, Config] = new Ask[M, Config]:
override def applicative: Applicative[M] = MonadThrow[M]
@ -78,41 +79,41 @@ class Tests extends AnyFlatSpec with Matchers:
it should "correct findAll config propagation" in {
val repository = UserRepositoryDao[M](dao(List.empty))
val config = Config(42)
val config = Config(42)
repository.findAll(config)
lastConfig shouldBe Right(Config(42))
}
it should "correct create config propagation" in {
val repository = UserRepositoryDao[M](dao(List.empty))
val config = Config(43)
val config = Config(43)
repository.create(UserName("Subject #0"), Age(0), Set.empty)(config)
lastConfig shouldBe Right(Config(43))
}
it should "correct delete config propagation" in {
val repository = UserRepositoryDao[M](dao(List.empty))
val config = Config(44)
val config = Config(44)
repository.delete(UserId(0))(config)
lastConfig shouldBe Right(Config(44))
}
it should "correct update config propagation" in {
val repository = UserRepositoryDao[M](dao(List.empty))
val config = Config(45)
val config = Config(45)
repository.update(User(UserId(0), UserName("Subject #0"), Age(0), Set.empty))(config)
lastConfig shouldBe Right(Config(45))
}
it should "correct return all users" in {
val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46)
val config = Config(46)
repository.findAll(config) shouldBe Right(sampleUsers.toSet.toList)
}
it should "correct create new user" in {
val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46)
val config = Config(46)
repository.create(UserName("New subject"), Age(0), Set.empty)(config) shouldBe Right(
User(UserId(5), UserName("New subject"), Age(0), Set.empty)
)
@ -120,30 +121,31 @@ class Tests extends AnyFlatSpec with Matchers:
it should "not create user more than once" in {
val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46)
repository.create(UserName("Subject #2"), Age(0), Set.empty)(config) shouldBe a [Left[Throwable, User]]
val config = Config(46)
repository.create(UserName("Subject #2"), Age(0), Set.empty)(config) shouldBe a[Left[Throwable, User]]
}
it should "delete existing user" in {
val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46)
val config = Config(46)
repository.delete(UserId(2))(config) shouldBe Right(())
}
it should "not delete nonexistent user" in {
val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46)
repository.delete(UserId(42))(config) shouldBe a [Left[Throwable, Unit]]
val config = Config(46)
repository.delete(UserId(42))(config) shouldBe a[Left[Throwable, Unit]]
}
it should "update existing user" in {
val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46)
val config = Config(46)
repository.update(User(UserId(2), UserName("Subject #2"), Age(42), Set.empty))(config) shouldBe Right(())
}
it should "not update nonexistent user" in {
val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46)
repository.update(User(UserId(22), UserName("Subject #22"), Age(42), Set.empty))(config) shouldBe a [Left[Throwable, Unit]]
val config = Config(46)
repository
.update(User(UserId(22), UserName("Subject #22"), Age(42), Set.empty))(config) shouldBe a[Left[Throwable, Unit]]
}