Рахимжанов Тимур
trait DataBaseService[T] {
def find(id: String): T
def insert(entity: T): Unit
}
object SideEffect {
case class Person(id: String, name: String, age: Int)
def addPersonIfNotExists(person: Person): Unit = ???
}
trait DataBaseService[T] {
def find(id: String): Either[Throwable, Option[T]]
def insert(entity: T): Either[Throwable, Unit]
}
object SideEffect {
case class Person(id: String, name: String, age: Int)
def addPersonIfNotExists(person: Person): Unit = ???
}
trait DataBaseService[T] {
def find(id: String): Either[Throwable, Option[T]]
def insert(entity: T): Either[Throwable, Unit]
}
object SideEffect {
case class Person(id: String, name: String, age: Int)
def addPersonIfNotExists(person: Person)(dbService: DataBaseService[Person]): Unit = {
val personQueryResult = dbService.find(person.id)
// Проверяем, что запрос прошел без ошибок
val result: Either[Throwable, Unit] =
if (personQueryResult.isRight) {
// Извлекаем объект из контейнера Either
val existsPersonOpt = personQueryResult.getOrElse(None)
// Проверяем, что запрос вернул не пустой объект
val insertQueryResult =
if (existsPersonOpt.isEmpty)
dbService.insert(person)
else
Right(()) // Наличие сущ. записи - не ошибка. Значит, ставим заглушку
insertQueryResult
} else {
personQueryResult.map(_ => ()) // Возвращаем наш запрос с ошибкой,
// но приводим к нужному типу
}
result.fold(err => println(err.getMessage()), res => res)
}
}
trait DataBaseService[T] {
def find(id: String): Either[Throwable, Option[T]]
def insert(entity: T): Either[Throwable, Unit]
}
object SideEffect {
case class Person(id: String, name: String, age: Int)
def addPersonIfNotExists(person: Person)(dbService: DataBaseService[Person]): Unit = {
dbService.find(person.id)
.flatMap { existsPersonOpt =>
existsPersonOpt
.map(person => dbService.insert(person))
.getOrElse(Right(()))
}.fold(err => println(err.getMessage()), res => res)
}
}
trait DataBaseService[T] {
def find(id: String): Either[Throwable, Option[T]]
def insert(entity: T): Either[Throwable, Unit]
}
object SideEffect {
case class Person(id: String, name: String, age: Int)
def addPersonIfNotExists(person: Person)(dbService: DataBaseService[Person]): Unit = {
for {
existsPersonOpt <- dbService.find(person.id)
_ <- existsPersonOpt.map(dbService.insert).getOrElse(Rigth(()))
} yield ()
}.fold(err => println(err.getMessage()), res => res)
}
case class IO[A](unsafeFunction: Function[Unit, A]) {
def unsafeRun(): A = unsafeFunction()
def unit[B](x: B): IO[B] = IO(_ => x)
def map[B](f: A => B): IO[B] = IO[B](unsafeFunction andThen f)
def flatMap[B](f: A => IO[B]): IO[B] = (unsafeFunction andThen f)()
}
val randomFirst: IO[Int] = IO(_ => Random.nextInt(25))
val randomSecond: IO[Int] = IO(_ => Random.nextInt(25))
lazy val printOfTwoRandomNumsIO =
randomFirst
.flatMap(f => randomSecond.map(s => (f, s)))
.map { case (f, s) => f + s }
.map { result => println(result) }
printOfTwoRandomNumsIO.unsafeRun()
val randomFirst: IO[Int] = IO(_ => Random.nextInt(25))
val randomSecond: IO[Int] = IO(_ => Random.nextInt(25))
lazy val printOfTwoRandomNumsIO =
for {
f <- randomFirst
s <- randomSecond
sum <- IO(_ => f + s)
_ <- IO(_ => println(sum))
} yield ()
printOfTwoRandomNumsIO.unsafeRun()
ZIO[-R, +E, +A]
R - тип зависимостей
E - тип ошибки
A - тип результата
ZIO[R, E, A] ⇔ R => Either[E, A]
ZIO[Clock with Random with DBService, Throwable, String]
Создание ZIO объектов
Элементарные конструкторы
val ok: ZIO[Any, Nothing, Int] = ZIO.succeed(42)
val fail: IO[Throwable, Nothing] = ZIO.fail(new Throwable("Error!"))
Конструкторы из Scala объектов
val fromOption: IO[Option[Nothing], String] = ZIO.fromOption(Some("Hello"))
// Если Option - Some, то возвращает его содержимое
// если Option - None, то возвращает пустую ошибку
val fromEither: IO[String, Int] = ZIO.fromEither(Left[String, Int]("Not good"))
// Right - возвращает содержимое как результат
// Left - возвращает содержимое как ошибку
val fromTry: Task[Int] = ZIO.fromTry(Try("24".toInt))
// Если небыло исключения - возвращает результат
// в случае исключения - возвращает Throwable
Map
Преобразует результат успешного выполнения эффекта, применяя к нему заданную функцию
// ZIO[Any, Nothing, Int] => ZIO[Any, Nothing, String]
val map: ZIO[Any, Nothing, String] = ZIO.succeed(42).map(_.toString)
FlatMap
Комбинирует эффекты, применяя функцию к результату одного эффекта и возвращая новый эффект. Если хотя бы один из эффектов завершается ошибкой, вся цепочка завершается ошибкой
// В данном случае вернет 48
val flatMapSuccess: ZIO[Any, Nothing, Int] =
ZIO.succeed(25).flatMap(f => ZIO.succeed(23 + f))
// Если один из ZIO возвращает ошибку, то и их комбинация через flatMap
// также будет возвращать ошибку
val flatMapFail: ZIO[Any, String, Int] =
ZIO.succeed.flatMap(f => ZIO.fail("Error"))
For-yield
val forYield: ZIO[Any, Serializable, String] =
for {
f <- ZIO.fromOption("42".toIntOption) // f = 42
s <- ZIO.fromOption("0".toIntOption) // s = 0
div <- ZIO.fromTry(Try(f / s)) // zio перейдет в состояние ошибки
result <- ZIO.succeed("ok") // эта часть не будет выполнена
} yield result
// в forYield будет ZIO с ошибкой типа Throwable
ZIP
val helloWorld: ZIO[Any, Nothing, (String, String)] =
(ZIO.succeed("Hello") zip ZIO.succeed("World"))
Zip действует аналогично методу zip у объекта Option
Если хоть один из zio возвращает ошибку, то и их zip возвращает ошибку.
Если же оба возвращают значения, то zip вернет кортеж из них.
MapError
val errorWithType: ZIO[Any, SpecialError, Unit] =
ZIO.fail(new Throwable("Error!"))
.mapError(err => SpecialError(err.getMessage))
Работает аналогично map, только применяет заданную функцию к объекту ошибки
MapError
Ошибки можно комбинировать
val zio1 = ZIO.fail("First zio error;")
val zio2 = ZIO.succeed("hello")
.flatMap(_ => zio1).mapError(err => err + " Second zio error;")
val zio3 = ZIO.succeed("world")
.flatMap(_ => zio2).mapError(err => err + " Third zio error;")
zio3.catchAll(err => Console.printLine(err))
В результате на экран будет выведено:
First zio error; Second zio error; Third zio error;
CatchAll
val errorCatchedAll: ZIO[Any, Nothing, String] = {
for {
_ <- ZIO.fromOption(None)
_ <- ZIO.fail(new Throwable("F Error"))
_ <- ZIO.fail(SpecialError("Special"))
} yield "ok"
}.catchAll { err: Serializable =>
ZIO.succeed("Failed with error " + err.toString)
}
CatchSome
val errorCatchedSome: ZIO[Any, Serializable, String] = {
for {
_ <- ZIO.fromOption(None)
_ <- ZIO.fail(new Throwable("F Error"))
_ <- ZIO.fail(SpecialError("Special"))
} yield "ok"
}.catchSome {
case err: Option[Nothing] => ZIO.succeed("Not found option")
case SpecialError(errMsg) => ZIO.succeed("Special error " + errMsg)
}
OrElse
val orElse = ZIO.fromOption(None).orElse(ZIO.succeed(25))
val orElseSucceed = ZIO.fromOption(None).orElseSucceed(25)
val orElseFail =
ZIO.fromOption(None).orElseFail(new Throwable("Объект не найден"))
ZIO[-R, +E, +A]
R - тип зависимостей
E - тип ошибки
A - тип результата
ZIO.service
trait NameService {
def name: String
}
trait AgeService {
def age: Int
}
val nameWithAge: ZIO[NameService with AgeService, Nothing, String] =
for {
nameServ <- ZIO.service[NameService]
ageServ <- ZIO.service[AgeService]
res = nameServ.name + ageServ.age
} yield res
B with A ⇔ A with B
type PersonInfo = NameService with AgeService
val useAandB: ZIO[PersonInfo, Nothing, String] =
for {
nameServ <- ZIO.service[NameService]
ageServ <- ZIO.service[AgeService]
res = nameServ.name + ageServ.age
} yield res
trait Logger {
def log(str: String): Task[Unit] // ZIO[Any, Throwable, Unit]
}
val zioWithLogAge: ZIO[Logger with AgeService, Throwable, Int] =
for {
ageServ <- ZIO.service[AgeService]
logger <- ZIO.service[Logger]
age = ageServ.age
_ <- logger.log(age.toString)
} yield age
val zioWithAwithNwithLogger: ZIO[Logger with AgeService with NameService,
Throwable, String] =
for {
nameServ <- ZIO.service[NameService]
name = nameServ.name
age <- zioWithLogAge
res = name + age
} yield res
serviceWith
object AgeZio {
def ageZ: ZIO[AgeService, Nothing, Int] = ZIO.serviceWith[AgeService](_.age)
}
object LoggerZio {
def log(str: String): ZIO[Logger, Throwable, Unit] =
ZIO.serviceWithZIO[Logger](_.log(str))
}
val zioWithLogAge: ZIO[Logger with AgeService, Throwable, Int] =
for {
age <- AgeZio.ageZ
_ <- LoggerZio.log(age.toString)
} yield age
Старт программы
object Main extends ZIOAppDefault {
import Temps._
override def run: ZIO[ZIOAppArgs with Scope, Any, Any] = {
zioWithLogAge
.map(res => println(res))
}
}
Call your effect's provide method with the layers you need.
ZLayer
type ZLayer[-RIn, +E, +ROut] =
RIn => async Either[E, ROut]
ZLayer.succeed
object LoggerObj {
class LoggerImpl extends Logger {
override def log(str: String): Task[Unit] = Console.printLine(str)
}
val live: ZLayer[Any, Nothing, Logger] = ZLayer.succeed(new LoggerImpl)
}
ZLayer.fromFunction
object AgeObj {
trait AgeService {
def age: Int
}
class AgeServiceImpl(nameService: NameService) extends AgeService {
override def age: Int =
nameService.name match {
case "Александр" => 21
case "Василий" => 40
case _ => 20
}
}
val live: ZLayer[NameService, Nothing, AgeService] =
ZLayer.fromFunction(new AgeServiceImpl(_))
}
ZLayer.fromZIO
object NameObj {
trait NameService {
def name: String
}
class NameServiceImpl extends NameService {
override def name: String =
List("Александр", "Василий", "Роман")(util.Random.nextInt(3))
}
val live: ZLayer[Any, Nothing, NameService] =
ZLayer.fromZIO(ZIO.succeed(new NameServiceImpl()))
}
zioWithLogAge: ZIO[Logger with AgeService, Throwable, Int]
LoggerObj.live: ZLayer[Any, Nothing, Logger]
AgeObj.live: ZLayer[NameService, Nothing, AgeService]
NameObj.live: ZLayer[Any, Nothing, NameService]
Операции со слоями
// Объединение
LoggerObj.live ++ AgeObj.live
ZLayer[NameService, Nothing, LoggerObj.Logger with AgeObj.AgeService]
// Внедрение
NameObj.live >>> AgeObj.live
ZLayer[Any, Nothing, AgeObj.AgeService]
// Комбинация
NameObj.live >+> AgeObj.live <=> NameObj.live ++ (NameObj.live >>> AgeObj.live)
ZLayer[Any, Nothing, AgeObj.AgeService with NameObj.NameService]
provideLayer
object Main extends ZIOAppDefault {
import Temps._
override def run: ZIO[ZIOAppArgs with Scope, Any, Any] = {
val layer = (NameObj.live >>> AgeObj.live) ++ LoggerObj.live
zioWithLogAge
.map(res => println(res))
.provideLayer(layer)
}
}
Документация
https://zio.dev/overview/getting-started
Литература
Zionomicon