Some parts of our applications don't need to be asynchronous or interact with the outside world: it's enough that they are stateful, possibly with the ability to handle failure, context, and logging. Although you can use ZIO 2 or monad transformers for this task, both come with drawbacks. In this presentation, Jorge Vásquez will introduce you to ZPure, a data type from ZIO Prelude, which lets you scale back on the power of ZIO 2, but with the same high-performance, type-inference, and ergonomics you expect from ZIO 2 libraries.
4. Background
ZIO Prelude is a Scala-first take on functional abstractions
• Type classes to describe the ways different types are
similar
5. Background
ZIO Prelude is a Scala-first take on functional abstractions
• Type classes to describe the ways different types are
similar
• Smart Types for more precise data modelling
6. Background
ZIO Prelude is a Scala-first take on functional abstractions
• Type classes to describe the ways different types are
similar
• Smart Types for more precise data modelling
• Data types that complement the Scala standard library:
ZPure
10. Problem
Using Purely Functional style, how
can we write computations that:
• Are Stateful
• Require a Context
• Might Fail or Succeed with a
value
11. Problem
Using Purely Functional style, how
can we write computations that:
• Are Stateful
• Require a Context
• Might Fail or Succeed with a
value
• Produce Logs
14. First attempt: Using Cats State Monad
import cats.data._
import cats.implicits._
type Stack = List[Int]
type Eff[A] = State[Stack, A]
def push(x: Int): Eff[Unit] = State.modify(x :: _)
val pop: Eff[Int] =
for {
stack <- State.get
x <- State.set(stack.tail).as(stack.head)
} yield x
def getExprElements(expr: String): List[String] = expr.split(' ').toList
15. First attempt: Using Cats State Monad
import cats.data._
import cats.implicits._
type Stack = List[Int]
type Eff[A] = State[Stack, A]
def push(x: Int): Eff[Unit] = State.modify(x :: _)
val pop: Eff[Int] =
for {
stack <- State.get
x <- State.set(stack.tail).as(stack.head)
} yield x
def getExprElements(expr: String): List[String] = expr.split(' ').toList
16. First attempt: Using Cats State Monad
import cats.data._
import cats.implicits._
type Stack = List[Int]
type Eff[A] = State[Stack, A]
def push(x: Int): Eff[Unit] = State.modify(x :: _)
val pop: Eff[Int] =
for {
stack <- State.get
x <- State.set(stack.tail).as(stack.head)
} yield x
def getExprElements(expr: String): List[String] = expr.split(' ').toList
17. First attempt: Using Cats State Monad
import cats.data._
import cats.implicits._
type Stack = List[Int]
type Eff[A] = State[Stack, A]
def push(x: Int): Eff[Unit] = State.modify(x :: _)
val pop: Eff[Int] =
for {
stack <- State.get
x <- State.set(stack.tail).as(stack.head)
} yield x
def getExprElements(expr: String): List[String] = expr.split(' ').toList
18. First attempt: Using Cats State Monad
import cats.data._
import cats.implicits._
type Stack = List[Int]
type Eff[A] = State[Stack, A]
def push(x: Int): Eff[Unit] = State.modify(x :: _)
val pop: Eff[Int] =
for {
stack <- State.get
x <- State.set(stack.tail).as(stack.head)
} yield x
def getExprElements(expr: String): List[String] = expr.split(' ').toList
19. First attempt: Using Cats State Monad
import cats.data._
import cats.implicits._
type Stack = List[Int]
type Eff[A] = State[Stack, A]
def push(x: Int): Eff[Unit] = State.modify(x :: _)
val pop: Eff[Int] =
for {
stack <- State.get
x <- State.set(stack.tail).as(stack.head)
} yield x
def getExprElements(expr: String): List[String] = expr.split(' ').toList
20. First attempt: Using Cats State Monad
import cats.data._
import cats.implicits._
type Stack = List[Int]
type Eff[A] = State[Stack, A]
def push(x: Int): Eff[Unit] = State.modify(x :: _)
val pop: Eff[Int] =
for {
stack <- State.get
x <- State.set(stack.tail).as(stack.head)
} yield x
def getExprElements(expr: String): List[String] = expr.split(' ').toList
21. First attempt: Using
Cats State Monad
def evalRPNExpression(elements: List[String]): Int = {
def processElement(element: String): Eff[Unit] =
element match {
case "+" => processTopElements(_ + _)
case "-" => processTopElements(_ - _)
case "*" => processTopElements(_ * _)
case x => push(x.toInt)
}
def processTopElements(operator: (Int, Int) => Int): Eff[Unit] =
for {
first <- pop
second <- pop
_ <- push(operator(second, first))
} yield ()
(elements.traverse(processElement) *> pop).runA(Nil).value
}
22. First attempt: Using
Cats State Monad
def evalRPNExpression(elements: List[String]): Int = {
def processElement(element: String): Eff[Unit] =
element match {
case "+" => processTopElements(_ + _)
case "-" => processTopElements(_ - _)
case "*" => processTopElements(_ * _)
case x => push(x.toInt)
}
def processTopElements(operator: (Int, Int) => Int): Eff[Unit] =
for {
first <- pop
second <- pop
_ <- push(operator(second, first))
} yield ()
(elements.traverse(processElement) *> pop).runA(Nil).value
}
23. First attempt: Using
Cats State Monad
def evalRPNExpression(elements: List[String]): Int = {
def processElement(element: String): Eff[Unit] =
element match {
case "+" => processTopElements(_ + _)
case "-" => processTopElements(_ - _)
case "*" => processTopElements(_ * _)
case x => push(x.toInt)
}
def processTopElements(operator: (Int, Int) => Int): Eff[Unit] =
for {
first <- pop
second <- pop
_ <- push(operator(second, first))
} yield ()
(elements.traverse(processElement) *> pop).runA(Nil).value
}
24. First attempt: Using
Cats State Monad
def evalRPNExpression(elements: List[String]): Int = {
def processElement(element: String): Eff[Unit] =
element match {
case "+" => processTopElements(_ + _)
case "-" => processTopElements(_ - _)
case "*" => processTopElements(_ * _)
case x => push(x.toInt)
}
def processTopElements(operator: (Int, Int) => Int): Eff[Unit] =
for {
first <- pop
second <- pop
_ <- push(operator(second, first))
} yield ()
(elements.traverse(processElement) *> pop).runA(Nil).value
}
25. First attempt: Using Cats State Monad
evalRPNExpression("1 3 + 10 -") // -6
evalRPNExpression("1 3 + 10 - +") // java.lang.UnsupportedOperationException: tail of empty list
evalRPNExpression("1 3 + 10 - a") // java.lang.NumberFormatException: For input string: "a"
26. First attempt: Using Cats State Monad
evalRPNExpression("1 3 + 10 -") // -6
evalRPNExpression("1 3 + 10 - +") // java.lang.UnsupportedOperationException: tail of empty list
evalRPNExpression("1 3 + 10 - a") // java.lang.NumberFormatException: For input string: "a"
27. First attempt: Using Cats State Monad
evalRPNExpression("1 3 + 10 -") // -6
evalRPNExpression("1 3 + 10 - +") // java.lang.UnsupportedOperationException: tail of empty list
evalRPNExpression("1 3 + 10 - a") // java.lang.NumberFormatException: For input string: "a"
28. First attempt: Using Cats State Monad
evalRPNExpression("1 3 + 10 -") // -6
evalRPNExpression("1 3 + 10 - +") // java.lang.UnsupportedOperationException: tail of empty list
evalRPNExpression("1 3 + 10 - a") // java.lang.NumberFormatException: For input string: "a"
32. Second attempt: Cats State + Either
type Eff[A] = State[Stack, Either[String, A]]
val pop: Eff[Int] =
for {
stack <- State.get
x <- stack match {
case head :: tail => State.set(tail).as(Right(head))
case _ => State.pure[Stack, Either[String, Int]](Left("No operands left"))
}
} yield x
33. Second attempt: Cats State + Either
type Eff[A] = State[Stack, Either[String, A]]
val pop: Eff[Int] =
for {
stack <- State.get
x <- stack match {
case head :: tail => State.set(tail).as(Right(head))
case _ => State.pure[Stack, Either[String, Int]](Left("No operands left"))
}
} yield x
34. Second attempt: Cats State + Either
type Eff[A] = State[Stack, Either[String, A]]
val pop: Eff[Int] =
for {
stack <- State.get
x <- stack match {
case head :: tail => State.set(tail).as(Right(head))
case _ => State.pure[Stack, Either[String, Int]](Left("No operands left"))
}
} yield x
35. Second attempt: Cats State + Either
type Eff[A] = State[Stack, Either[String, A]]
val pop: Eff[Int] =
for {
stack <- State.get
x <- stack match {
case head :: tail => State.set(tail).as(Right(head))
case _ => State.pure[Stack, Either[String, Int]](Left("No operands left"))
}
} yield x
36. Second attempt: Cats State + Either
type Eff[A] = State[Stack, Either[String, A]]
val pop: Eff[Int] =
for {
stack <- State.get
x <- stack match {
case head :: tail => State.set(tail).as(Right(head))
case _ => State.pure[Stack, Either[String, Int]](Left("No operands left"))
}
} yield x
37. Second attempt:
Cats State + Either
def evalRPNExpression(elements: List[String]): Either[String, Int] = {
def processElements(elements: List[String]): Eff[Unit] =
elements match {
case head :: tail =>
for {
processed <- processElement(head)
result <- processed match {
case Left(error) => State.pure[Stack, Either[String, Unit]](Left(error))
case Right(_) => processElements(tail)
}
} yield result
case Nil => State.pure(Right(()))
}
def processElement(element: String): Eff[Unit] =
element match {
case "+" => processTopElements(_ + _)
case "-" => processTopElements(_ - _)
case "*" => processTopElements(_ * _)
case x =>
x.toIntOption match {
case Some(x) => push(x).map(Right(_))
case None => State.pure[Stack, Either[String, Unit]](Left(s"Invalid operand: $x"))
}
}
def processTopElements(operator: (Int, Int) => Int): Eff[Unit] =
for {
first <- pop
second <- pop
result <- (first, second) match {
case (Right(first), Right(second)) => push(operator(second, first))
case (Left(error), _) => State.pure[Stack, Either[String, Unit]](Left(error))
case (Right(_), Left(error)) => State.pure[Stack, Either[String, Unit]](Left(error))
}
} yield result
(for {
processed <- processElements(elements)
result <- processed match {
case Left(error) => State.pure[Stack, Either[String, Int]](Left(error))
case Right(_) => pop
}
} yield result).runA(Nil).value
}
38. Second attempt: Cats State + Either
evalRPNExpression("1 3 + 10 -") // Right(-6)
evalRPNExpression("1 3 + 10 - +") // Left(No operands left)
evalRPNExpression("1 3 + 10 a") // Left(Invalid operand: a)
39. Second attempt: Cats State + Either
evalRPNExpression("1 3 + 10 -") // Right(-6)
evalRPNExpression("1 3 + 10 - +") // Left(No operands left)
evalRPNExpression("1 3 + 10 a") // Left(Invalid operand: a)
40. Second attempt: Cats State + Either
evalRPNExpression("1 3 + 10 -") // Right(-6)
evalRPNExpression("1 3 + 10 - +") // Left(No operands left)
evalRPNExpression("1 3 + 10 a") // Left(Invalid operand: a)
41. Second attempt: Cats State + Either
evalRPNExpression("1 3 + 10 -") // Right(-6)
evalRPNExpression("1 3 + 10 - +") // Left(No operands left)
evalRPNExpression("1 3 + 10 a") // Left(Invalid operand: a)
48. Third attempt: Monad Transformers
type Eff[A] = EitherT[State[Stack, *], String, A]
val pop: Eff[Int] =
for {
stack <- EitherT.liftF(State.get[Stack])
x <- stack match {
case head :: tail => EitherT.liftF[State[Stack, *], String, Int](State.set(tail).as(head))
case _ => EitherT.leftT[State[Stack, *], Int]("No operands left")
}
} yield x
49. Third attempt: Monad Transformers
type Eff[A] = EitherT[State[Stack, *], String, A]
val pop: Eff[Int] =
for {
stack <- EitherT.liftF(State.get[Stack])
x <- stack match {
case head :: tail => EitherT.liftF[State[Stack, *], String, Int](State.set(tail).as(head))
case _ => EitherT.leftT[State[Stack, *], Int]("No operands left")
}
} yield x
50. Third attempt: Monad Transformers
type Eff[A] = EitherT[State[Stack, *], String, A]
val pop: Eff[Int] =
for {
stack <- EitherT.liftF(State.get[Stack])
x <- stack match {
case head :: tail => EitherT.liftF[State[Stack, *], String, Int](State.set(tail).as(head))
case _ => EitherT.leftT[State[Stack, *], Int]("No operands left")
}
} yield x
51. Third attempt: Monad Transformers
type Eff[A] = EitherT[State[Stack, *], String, A]
val pop: Eff[Int] =
for {
stack <- EitherT.liftF(State.get[Stack])
x <- stack match {
case head :: tail => EitherT.liftF[State[Stack, *], String, Int](State.set(tail).as(head))
case _ => EitherT.leftT[State[Stack, *], Int]("No operands left")
}
} yield x
52. Third attempt: Monad Transformers
def evalRPNExpression(elements: List[String]): Either[String, Int] = {
def processElement(element: String): Eff[Unit] =
element match {
case "+" => processTopElements(_ + _)
case "-" => processTopElements(_ - _)
case "*" => processTopElements(_ * _)
case x => EitherT.fromOption[State[Stack, *]](x.toIntOption, s"Invalid operand: $x").flatMap(push)
}
def processTopElements(operator: (Int, Int) => Int): Eff[Unit] =
for {
first <- pop
second <- pop
result <- push(operator(second, first))
} yield result
(elements.traverse(processElement) *> pop).value.runA(Nil).value
}
53. Third attempt: Monad Transformers
def evalRPNExpression(elements: List[String]): Either[String, Int] = {
def processElement(element: String): Eff[Unit] =
element match {
case "+" => processTopElements(_ + _)
case "-" => processTopElements(_ - _)
case "*" => processTopElements(_ * _)
case x => EitherT.fromOption[State[Stack, *]](x.toIntOption, s"Invalid operand: $x").flatMap(push)
}
def processTopElements(operator: (Int, Int) => Int): Eff[Unit] =
for {
first <- pop
second <- pop
result <- push(operator(second, first))
} yield result
(elements.traverse(processElement) *> pop).value.runA(Nil).value
}
59. Problems
• There's still boilerplate to lift State into EitherT
• Even worse type-inference
• Performance
60. Problems
• There's still boilerplate to lift State into EitherT
• Even worse type-inference
• Performance
• Discoverability: If I want to handle additional effects
(Context, Logging), which Monad Transformers should I
add?
63. Fourth attempt: ZIO 2
import zio._
type Stack = Ref[List[Int]]
type Eff[+A] = ZIO[Stack, String, A]
val pop: Eff[Int] =
for {
stackRef <- ZIO.service[Stack]
stack <- stackRef.get
x <- stack match {
case head :: tail => stackRef.set(tail).as(head)
case _ => ZIO.fail("No operands left")
}
} yield x
64. Fourth attempt: ZIO 2
import zio._
type Stack = Ref[List[Int]]
type Eff[+A] = ZIO[Stack, String, A]
val pop: Eff[Int] =
for {
stackRef <- ZIO.service[Stack]
stack <- stackRef.get
x <- stack match {
case head :: tail => stackRef.set(tail).as(head)
case _ => ZIO.fail("No operands left")
}
} yield x
65. Fourth attempt: ZIO 2
import zio._
type Stack = Ref[List[Int]]
type Eff[+A] = ZIO[Stack, String, A]
val pop: Eff[Int] =
for {
stackRef <- ZIO.service[Stack]
stack <- stackRef.get
x <- stack match {
case head :: tail => stackRef.set(tail).as(head)
case _ => ZIO.fail("No operands left")
}
} yield x
66. Fourth attempt: ZIO 2
import zio._
type Stack = Ref[List[Int]]
type Eff[+A] = ZIO[Stack, String, A]
val pop: Eff[Int] =
for {
stackRef <- ZIO.service[Stack]
stack <- stackRef.get
x <- stack match {
case head :: tail => stackRef.set(tail).as(head)
case _ => ZIO.fail("No operands left")
}
} yield x
67. Fourth attempt: ZIO 2
import zio._
type Stack = Ref[List[Int]]
type Eff[+A] = ZIO[Stack, String, A]
val pop: Eff[Int] =
for {
stackRef <- ZIO.service[Stack]
stack <- stackRef.get
x <- stack match {
case head :: tail => stackRef.set(tail).as(head)
case _ => ZIO.fail("No operands left")
}
} yield x
68. Fourth attempt: ZIO 2
import zio._
type Stack = Ref[List[Int]]
type Eff[+A] = ZIO[Stack, String, A]
val pop: Eff[Int] =
for {
stackRef <- ZIO.service[Stack]
stack <- stackRef.get
x <- stack match {
case head :: tail => stackRef.set(tail).as(head)
case _ => ZIO.fail("No operands left")
}
} yield x
69. Fourth attempt: ZIO 2
def evalRPNExpression(elements: List[String]): ZIO[ZEnv, IOException, Either[String, Int]] = {
def processElement(element: String): Eff[Unit] =
element match {
case "+" => processTopElements(_ + _)
case "-" => processTopElements(_ - _)
case "*" => processTopElements(_ * _)
case x => ZIO.from(x.toIntOption).orElseFail(s"Invalid operand: $x").flatMap(push)
}
def processTopElements(operator: (Int, Int) => Int): Eff[Unit] =
for {
first <- pop
second <- pop
result <- push(operator(second, first))
} yield result
(ZIO.foreachDiscard(elements)(processElement) *> pop).either
.provideCustomLayer(Ref.make(List.empty[Int]).toLayer)
}
70. Fourth attempt: ZIO 2
def evalRPNExpression(elements: List[String]): ZIO[ZEnv, IOException, Either[String, Int]] = {
def processElement(element: String): Eff[Unit] =
element match {
case "+" => processTopElements(_ + _)
case "-" => processTopElements(_ - _)
case "*" => processTopElements(_ * _)
case x => ZIO.from(x.toIntOption).orElseFail(s"Invalid operand: $x").flatMap(push)
}
def processTopElements(operator: (Int, Int) => Int): Eff[Unit] =
for {
first <- pop
second <- pop
result <- push(operator(second, first))
} yield result
(ZIO.foreachDiscard(elements)(processElement) *> pop).either
.provideCustomLayer(Ref.make(List.empty[Int]).toLayer)
}
78. Problems
• We're killing a fly with a bazooka!
• We don't need asynchronicity to solve this problem
79. Problems
• We're killing a fly with a bazooka!
• We don't need asynchronicity to solve this problem
• Neither to interact with the outside world
80. Problems
• We're killing a fly with a bazooka!
• We don't need asynchronicity to solve this problem
• Neither to interact with the outside world
• Performance
81.
82. Wouldn't it be great if we had a
data type that lets us scale back
on the power of ZIO 2, but with
the same high-performance,
type-inference, and ergonomics?
85. Meet ZPure
ZPure[+W, -S1, +S2, -R, +E, +A]
A ZPure is a description of a Pure Computation that:
• Requires an environment R
86. Meet ZPure
ZPure[+W, -S1, +S2, -R, +E, +A]
A ZPure is a description of a Pure Computation that:
• Requires an environment R
• An initial state S1
87. Meet ZPure
ZPure[+W, -S1, +S2, -R, +E, +A]
A ZPure is a description of a Pure Computation that:
• Requires an environment R
• An initial state S1
• Can fail with an error of type E
88. Meet ZPure
ZPure[+W, -S1, +S2, -R, +E, +A]
A ZPure is a description of a Pure Computation that:
• Requires an environment R
• An initial state S1
• Can fail with an error of type E
• Succeed with an updated state of type S2 and a value of type A, also
producing a log of type W
89. Mental Model of ZPure
(R, S1) => (Chunk[W], Either[E, (S2, A)])
So, ZPure models four effects that a computation can have besides
producing a value of type A:
90. Mental Model of ZPure
(R, S1) => (Chunk[W], Either[E, (S2, A)])
So, ZPure models four effects that a computation can have besides
producing a value of type A:
• Errors: Similar to Either
91. Mental Model of ZPure
(R, S1) => (Chunk[W], Either[E, (S2, A)])
So, ZPure models four effects that a computation can have besides
producing a value of type A:
• Errors: Similar to Either
• Context: Similar to Reader
92. Mental Model of ZPure
(R, S1) => (Chunk[W], Either[E, (S2, A)])
So, ZPure models four effects that a computation can have besides
producing a value of type A:
• Errors: Similar to Either
• Context: Similar to Reader
• State: Similar to State
93. Mental Model of ZPure
(R, S1) => (Chunk[W], Either[E, (S2, A)])
So, ZPure models four effects that a computation can have besides
producing a value of type A:
• Errors: Similar to Either
• Context: Similar to Reader
• State: Similar to State
• Logging: Similar to Writer
94. Type aliases of ZPure
type State[S, +A] = ZPure[Nothing, S, S, Any, Nothing, A]
type Reader[-R, +A] = ZPure[Nothing, Unit, Unit, R, Nothing, A]
type Writer[+W, +A] = ZPure[W, Unit, Unit, Any, Nothing, A]
95. Type aliases of ZPure
type EState[S, +E, +A] = ZPure[Nothing, S, S, Any, E, A]
type EReader[-R, +E, +A] = ZPure[Nothing, Unit, Unit, R, E, A]
type EWriter[+W, +E, +A] = ZPure[W, Unit, Unit, Any, E, A]
96. Final attempt: ZPure
import zio.prelude._
type Stack = List[Int]
type Eff[+A] = EState[Stack, String, A] // ZPure[Nothing, Stack, Stack, Any, String, A]
val pop: Eff[Int] =
for {
stack <- EState.get[Stack]
x <- stack match {
case head :: tail => EState.set(tail).as(head)
case _ => EState.fail("No operands left")
}
} yield x
97. Final attempt: ZPure
import zio.prelude._
type Stack = List[Int]
type Eff[+A] = EState[Stack, String, A] // ZPure[Nothing, Stack, Stack, Any, String, A]
val pop: Eff[Int] =
for {
stack <- EState.get[Stack]
x <- stack match {
case head :: tail => EState.set(tail).as(head)
case _ => EState.fail("No operands left")
}
} yield x
98. Final attempt: ZPure
import zio.prelude._
type Stack = List[Int]
type Eff[+A] = EState[Stack, String, A] // ZPure[Nothing, Stack, Stack, Any, String, A]
val pop: Eff[Int] =
for {
stack <- EState.get[Stack]
x <- stack match {
case head :: tail => EState.set(tail).as(head)
case _ => EState.fail("No operands left")
}
} yield x
99. Final attempt: ZPure
import zio.prelude._
type Stack = List[Int]
type Eff[+A] = EState[Stack, String, A] // ZPure[Nothing, Stack, Stack, Any, String, A]
val pop: Eff[Int] =
for {
stack <- EState.get[Stack]
x <- stack match {
case head :: tail => EState.set(tail).as(head)
case _ => EState.fail("No operands left")
}
} yield x
100. Final attempt: ZPure
import zio.prelude._
type Stack = List[Int]
type Eff[+A] = EState[Stack, String, A] // ZPure[Nothing, Stack, Stack, Any, String, A]
val pop: Eff[Int] =
for {
stack <- EState.get[Stack]
x <- stack match {
case head :: tail => EState.set(tail).as(head)
case _ => EState.fail("No operands left")
}
} yield x
101. Final attempt: ZPure
import zio.prelude._
type Stack = List[Int]
type Eff[+A] = EState[Stack, String, A] // ZPure[Nothing, Stack, Stack, Any, String, A]
val pop: Eff[Int] =
for {
stack <- EState.get[Stack]
x <- stack match {
case head :: tail => EState.set(tail).as(head)
case _ => EState.fail("No operands left")
}
} yield x
102. Final attempt: ZPure
def evalRPNExpression(elements: List[String]): Either[String, Int] = {
def processElement(element: String): Eff[Unit] =
element match {
case "+" => processTopElements(_ + _)
case "-" => processTopElements(_ - _)
case "*" => processTopElements(_ * _)
case x => EState.fromOption(x.toIntOption).orElseFail(s"Invalid operand: $x").flatMap(push)
}
def processTopElements(operator: (Int, Int) => Int): Eff[Unit] =
for {
first <- pop
second <- pop
result <- push(operator(second, first))
} yield result
// Here we use `forEach` from the ForEach typeclass
(elements.forEach(processElement) *> pop).provideState(Nil).runEither
}
103. Final attempt: ZPure
def evalRPNExpression(elements: List[String]): Either[String, Int] = {
def processElement(element: String): Eff[Unit] =
element match {
case "+" => processTopElements(_ + _)
case "-" => processTopElements(_ - _)
case "*" => processTopElements(_ * _)
case x => EState.fromOption(x.toIntOption).orElseFail(s"Invalid operand: $x").flatMap(push)
}
def processTopElements(operator: (Int, Int) => Int): Eff[Unit] =
for {
first <- pop
second <- pop
result <- push(operator(second, first))
} yield result
// Here we use `forEach` from the ForEach typeclass
(elements.forEach(processElement) *> pop).provideState(Nil).runEither
}
108. Benefits of ZPure
• One monad to rule (almost) them all! (Except IO)
• Superb type-inference
109. Benefits of ZPure
• One monad to rule (almost) them all! (Except IO)
• Superb type-inference
• Discoverable functionality: Just one data type with concrete
methods!
110. Benefits of ZPure
• One monad to rule (almost) them all! (Except IO)
• Superb type-inference
• Discoverable functionality: Just one data type with concrete
methods!
• ZIO idiomatic (Familiar and accessible method names)
111. Benefits of ZPure
• One monad to rule (almost) them all! (Except IO)
• Superb type-inference
• Discoverable functionality: Just one data type with concrete
methods!
• ZIO idiomatic (Familiar and accessible method names)
• Gradual adoption (use a little, then use more, then use more!)
112. Benefits of ZPure
• One monad to rule (almost) them all! (Except IO)
• Superb type-inference
• Discoverable functionality: Just one data type with concrete
methods!
• ZIO idiomatic (Familiar and accessible method names)
• Gradual adoption (use a little, then use more, then use more!)
• Performance!
120. Summary
• We need a way to handle Context, State, Failures and
Logging in a Purely Functional way
121. Summary
• We need a way to handle Context, State, Failures and
Logging in a Purely Functional way
• There are several options, each one with its own
limitations
122. Summary
• We need a way to handle Context, State, Failures and
Logging in a Purely Functional way
• There are several options, each one with its own
limitations
• ZPure provides a highly-ergonomic, ZIO-idiomatic and
highly-performant solution to those limitations