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. Структура лога
* В этом задании вам предстоит реализовать логгер, имеющий 3 уровня записей - Debug, Info и Error. * LogEmbed описана выше, от вас же требуется реализовать функции для удобного создания логов
* Структура лога 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,33 +4,26 @@ 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 для некоторых монад. Интерфейс служит для получения
* Теперь от вас требуется реализовать несколько интерфейсов Ask для некоторых монад. * контекстной информации, хранящейся в монаде (подробнее о данной монаде можно прочить на сайте библиотеки cats, а в
* Интерфейс служит для получения контекстной информации, хранящейся в монаде * одном из следующих заданий будут примеры применения)
* (подробнее о данной монаде можно прочить на сайте библиотеки cats, */
* а в одном из следующих заданий будут примеры применения)
*
*/
trait Ask[F[_], R]: trait Ask[F[_], R]:
def ask: F[R] def 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,31 +4,25 @@ 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]
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

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

View file

@ -2,4 +2,4 @@ package hw.user
object UserErrors: object UserErrors:
case class UserAlreadyExists(name: UserName) extends Throwable 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: 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, который обеспечивает
* В последнем задании рассмотрим более практичный пример: * работу с базой данных, обновляя данные о пользователях (модель пользователей и операции, проводимые с ними,
* пусть есть интерфейс UserRepositoryDao, который обеспечивает работу с базой данных, обновляя данные о пользователях * описываются кодом в данном файле и в User.scala, UserErrors.scala). От вас требуется сделать обёртку над данным
* (модель пользователей и операции, проводимые с ними, описываются кодом в данном файле и в User.scala, UserErrors.scala). * интерфейсом, чтобы его "грязные" функции можно было бы использовать в коде в функциональном стиле. При написании
* От вас требуется сделать обёртку над данным интерфейсом, чтобы его "грязные" функции можно было бы использовать * используйте интерфейсы MonadThrow и Ask - такой подход часто называется Tagless final
* в коде в функциональном стиле. */
* При написании используйте интерфейсы 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 (полностью аналогичный варианту из
* Для данной функции требуется, используя интерфейс Ask из библиотеки Cats * предыдущего задания), получить конфиг, хранящийся в монаде F, и вернуть значение, обёрнутое в монаду
* (полностью аналогичный варианту из предыдущего задания), получить конфиг, хранящийся в монаде 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,24 +48,25 @@ 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]
object M: object M:
given MonadThrow[M] = new MonadError[M, Throwable]: 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 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 =>
case Left(e) => Left(e) f(a)(c) match
case Right(Left(a)) => tailRecM(a)(f)(c) case Left(e) => Left(e)
case Right(Right(b)) => Right(b) 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 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 =>
case Left(e) => f(e)(c) fa(c) match
case Right(a) => Right(a) case Left(e) => f(e)(c)
case Right(a) => Right(a)
given Ask[M, Config] = new Ask[M, Config]: given Ask[M, Config] = new Ask[M, Config]:
override def applicative: Applicative[M] = MonadThrow[M] override def applicative: Applicative[M] = MonadThrow[M]
@ -78,41 +79,41 @@ class Tests extends AnyFlatSpec with Matchers:
it should "correct findAll config propagation" in { it should "correct findAll config propagation" in {
val repository = UserRepositoryDao[M](dao(List.empty)) val repository = UserRepositoryDao[M](dao(List.empty))
val config = Config(42) val config = Config(42)
repository.findAll(config) repository.findAll(config)
lastConfig shouldBe Right(Config(42)) lastConfig shouldBe Right(Config(42))
} }
it should "correct create config propagation" in { it should "correct create config propagation" in {
val repository = UserRepositoryDao[M](dao(List.empty)) 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) repository.create(UserName("Subject #0"), Age(0), Set.empty)(config)
lastConfig shouldBe Right(Config(43)) lastConfig shouldBe Right(Config(43))
} }
it should "correct delete config propagation" in { it should "correct delete config propagation" in {
val repository = UserRepositoryDao[M](dao(List.empty)) val repository = UserRepositoryDao[M](dao(List.empty))
val config = Config(44) val config = Config(44)
repository.delete(UserId(0))(config) repository.delete(UserId(0))(config)
lastConfig shouldBe Right(Config(44)) lastConfig shouldBe Right(Config(44))
} }
it should "correct update config propagation" in { it should "correct update config propagation" in {
val repository = UserRepositoryDao[M](dao(List.empty)) 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) repository.update(User(UserId(0), UserName("Subject #0"), Age(0), Set.empty))(config)
lastConfig shouldBe Right(Config(45)) lastConfig shouldBe Right(Config(45))
} }
it should "correct return all users" in { it should "correct return all users" in {
val repository = UserRepositoryDao[M](dao(sampleUsers)) val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46) val config = Config(46)
repository.findAll(config) shouldBe Right(sampleUsers.toSet.toList) repository.findAll(config) shouldBe Right(sampleUsers.toSet.toList)
} }
it should "correct create new user" in { it should "correct create new user" in {
val repository = UserRepositoryDao[M](dao(sampleUsers)) 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( repository.create(UserName("New subject"), Age(0), Set.empty)(config) shouldBe Right(
User(UserId(5), UserName("New subject"), Age(0), Set.empty) 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 { it should "not create user more than once" in {
val repository = UserRepositoryDao[M](dao(sampleUsers)) val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46) val config = Config(46)
repository.create(UserName("Subject #2"), Age(0), Set.empty)(config) shouldBe a [Left[Throwable, User]] repository.create(UserName("Subject #2"), Age(0), Set.empty)(config) shouldBe a[Left[Throwable, User]]
} }
it should "delete existing user" in { it should "delete existing user" in {
val repository = UserRepositoryDao[M](dao(sampleUsers)) val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46) val config = Config(46)
repository.delete(UserId(2))(config) shouldBe Right(()) repository.delete(UserId(2))(config) shouldBe Right(())
} }
it should "not delete nonexistent user" in { it should "not delete nonexistent user" in {
val repository = UserRepositoryDao[M](dao(sampleUsers)) val repository = UserRepositoryDao[M](dao(sampleUsers))
val config = Config(46) val config = Config(46)
repository.delete(UserId(42))(config) shouldBe a [Left[Throwable, Unit]] repository.delete(UserId(42))(config) shouldBe a[Left[Throwable, Unit]]
} }
it should "update existing user" in { it should "update existing 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(2), UserName("Subject #2"), Age(42), Set.empty))(config) shouldBe Right(()) repository.update(User(UserId(2), UserName("Subject #2"), Age(42), Set.empty))(config) shouldBe Right(())
} }
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]]
} }