SlideShare une entreprise Scribd logo
1  sur  127
Télécharger pour lire hors ligne
A Prelude of Purity
Scaling Back ZIO
ZIO World
March 11th, 2022
Jorge Vásquez
Scala Developer
@Scalac
ZIO Prelude contributor
Background
ZIO Prelude is a Scala-first take on functional abstractions
Background
ZIO Prelude is a Scala-first take on functional abstractions
• Type classes to describe the ways different types are
similar
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
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
Problem
Using Purely Functional style, how
can we write computations that:
Problem
Using Purely Functional style, how
can we write computations that:
• Are Stateful
Problem
Using Purely Functional style, how
can we write computations that:
• Are Stateful
• Require a Context
Problem
Using Purely Functional style, how
can we write computations that:
• Are Stateful
• Require a Context
• Might Fail or Succeed with a
value
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
Scenario
Reverse Polish
Notation (RPN)
Calculator
Example
"2 5 + 3 * 1 -" -> 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
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
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
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
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
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
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
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
}
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
}
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
}
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
}
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"
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"
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"
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"
Problems
Problems
• Doesn't handle errors appropriately
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
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
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
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
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
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
}
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)
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)
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)
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)
Benefits
Benefits
• Errors are handled appropriately
Problems
Problems
• Too much boilerplate to handle errors
Problems
• Too much boilerplate to handle errors
• Poor type-inference
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
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
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
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
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
}
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
}
Benefits
Benefits
• Less boilerplate to handle errors
Problems
Problems
• There's still boilerplate to lift State into EitherT
Problems
• There's still boilerplate to lift State into EitherT
• Even worse type-inference
Problems
• There's still boilerplate to lift State into EitherT
• Even worse type-inference
• Performance
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?
The Problem of Discoverability
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
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
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
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
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
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
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)
}
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)
}
Benefits of ZIO 2
Benefits of ZIO 2
• One monad to rule them all!
Benefits of ZIO 2
• One monad to rule them all!
• Superb type-inference
Benefits of ZIO 2
• One monad to rule them all!
• Superb type-inference
• Discoverable functionality: Just one data type with
concrete methods!
Comparing Monad Transformers and
ZIO 2
Problems
Problems
• We're killing a fly with a bazooka!
Problems
• We're killing a fly with a bazooka!
• We don't need asynchronicity to solve this problem
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
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
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?
Enter ZPure!
Meet ZPure
ZPure[+W, -S1, +S2, -R, +E, +A]
A ZPure is a description of a Pure Computation that:
Meet ZPure
ZPure[+W, -S1, +S2, -R, +E, +A]
A ZPure is a description of a Pure Computation that:
• Requires an environment R
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
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
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
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:
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
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
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
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
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]
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]
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
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
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
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
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
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
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
}
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
}
Comparing Monad Transformers and
ZPure
Comparing ZIO 2 and ZPure
Benefits of ZPure
Benefits of ZPure
• One monad to rule (almost) them all! (Except IO)
Benefits of ZPure
• One monad to rule (almost) them all! (Except IO)
• Superb type-inference
Benefits of ZPure
• One monad to rule (almost) them all! (Except IO)
• Superb type-inference
• Discoverable functionality: Just one data type with concrete
methods!
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)
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!)
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!
Benchmarking State + Failure
Benchmarking State + Failure + Log
Summary
Summary
Summary
• We need a way to handle Context, State, Failures and
Logging in a Purely Functional way
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
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
Special thanks
Special thanks
• Ziverge for organizing ZIO World
Special thanks
• Ziverge for organizing ZIO World
• Scalac for sponsoring
Special thanks
• Ziverge for organizing ZIO World
• Scalac for sponsoring
• John De Goes for guidance and support
Contact me
@jorvasquez2301
jorge-vasquez-2301
jorge.vasquez@scalac.io

Contenu connexe

Tendances

If You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are WrongIf You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are Wrong
Mario Fusco
 

Tendances (20)

Http4s, Doobie and Circe: The Functional Web Stack
Http4s, Doobie and Circe: The Functional Web StackHttp4s, Doobie and Circe: The Functional Web Stack
Http4s, Doobie and Circe: The Functional Web Stack
 
Sequence and Traverse - Part 1
Sequence and Traverse - Part 1Sequence and Traverse - Part 1
Sequence and Traverse - Part 1
 
Sequence and Traverse - Part 2
Sequence and Traverse - Part 2Sequence and Traverse - Part 2
Sequence and Traverse - Part 2
 
ZIO Queue
ZIO QueueZIO Queue
ZIO Queue
 
If You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are WrongIf You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are Wrong
 
Ad hoc Polymorphism using Type Classes and Cats
Ad hoc Polymorphism using Type Classes and CatsAd hoc Polymorphism using Type Classes and Cats
Ad hoc Polymorphism using Type Classes and Cats
 
Implementing the IO Monad in Scala
Implementing the IO Monad in ScalaImplementing the IO Monad in Scala
Implementing the IO Monad in Scala
 
How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021
How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021
How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021
 
One Monad to Rule Them All
One Monad to Rule Them AllOne Monad to Rule Them All
One Monad to Rule Them All
 
Deep dive into Coroutines on JVM @ KotlinConf 2017
Deep dive into Coroutines on JVM @ KotlinConf 2017Deep dive into Coroutines on JVM @ KotlinConf 2017
Deep dive into Coroutines on JVM @ KotlinConf 2017
 
Functor, Apply, Applicative And Monad
Functor, Apply, Applicative And MonadFunctor, Apply, Applicative And Monad
Functor, Apply, Applicative And Monad
 
ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022
 
Monoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and CatsMonoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and Cats
 
Applicative style programming
Applicative style programmingApplicative style programming
Applicative style programming
 
JQuery selectors
JQuery selectors JQuery selectors
JQuery selectors
 
Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!
 
Introduction to SQLAlchemy ORM
Introduction to SQLAlchemy ORMIntroduction to SQLAlchemy ORM
Introduction to SQLAlchemy ORM
 
Morel, a Functional Query Language
Morel, a Functional Query LanguageMorel, a Functional Query Language
Morel, a Functional Query Language
 
Blazing Fast, Pure Effects without Monads — LambdaConf 2018
Blazing Fast, Pure Effects without Monads — LambdaConf 2018Blazing Fast, Pure Effects without Monads — LambdaConf 2018
Blazing Fast, Pure Effects without Monads — LambdaConf 2018
 
Kotlin Collections
Kotlin CollectionsKotlin Collections
Kotlin Collections
 

Similaire à A Prelude of Purity: Scaling Back ZIO

Scala - where objects and functions meet
Scala - where objects and functions meetScala - where objects and functions meet
Scala - where objects and functions meet
Mario Fusco
 
(How) can we benefit from adopting scala?
(How) can we benefit from adopting scala?(How) can we benefit from adopting scala?
(How) can we benefit from adopting scala?
Tomasz Wrobel
 
An introduction to property-based testing
An introduction to property-based testingAn introduction to property-based testing
An introduction to property-based testing
Vincent Pradeilles
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
Hiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
Hiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
Hiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
Hiroshi Ono
 

Similaire à A Prelude of Purity: Scaling Back ZIO (20)

Scala for curious
Scala for curiousScala for curious
Scala for curious
 
Meet scala
Meet scalaMeet scala
Meet scala
 
Scala - where objects and functions meet
Scala - where objects and functions meetScala - where objects and functions meet
Scala - where objects and functions meet
 
(How) can we benefit from adopting scala?
(How) can we benefit from adopting scala?(How) can we benefit from adopting scala?
(How) can we benefit from adopting scala?
 
An introduction to property-based testing
An introduction to property-based testingAn introduction to property-based testing
An introduction to property-based testing
 
Functional Object-Oriented Imperative Scala / 関数型オブジェクト指向命令型 Scala by Sébasti...
Functional Object-Oriented Imperative Scala / 関数型オブジェクト指向命令型 Scala by Sébasti...Functional Object-Oriented Imperative Scala / 関数型オブジェクト指向命令型 Scala by Sébasti...
Functional Object-Oriented Imperative Scala / 関数型オブジェクト指向命令型 Scala by Sébasti...
 
Learning Functional Programming Without Growing a Neckbeard
Learning Functional Programming Without Growing a NeckbeardLearning Functional Programming Without Growing a Neckbeard
Learning Functional Programming Without Growing a Neckbeard
 
SDC - Einführung in Scala
SDC - Einführung in ScalaSDC - Einführung in Scala
SDC - Einführung in Scala
 
Functions In Scala
Functions In Scala Functions In Scala
Functions In Scala
 
Scala coated JVM
Scala coated JVMScala coated JVM
Scala coated JVM
 
An introduction to scala
An introduction to scalaAn introduction to scala
An introduction to scala
 
Swift Rocks #2: Going functional
Swift Rocks #2: Going functionalSwift Rocks #2: Going functional
Swift Rocks #2: Going functional
 
Functional programming ii
Functional programming iiFunctional programming ii
Functional programming ii
 
Pooya Khaloo Presentation on IWMC 2015
Pooya Khaloo Presentation on IWMC 2015Pooya Khaloo Presentation on IWMC 2015
Pooya Khaloo Presentation on IWMC 2015
 
A swift introduction to Swift
A swift introduction to SwiftA swift introduction to Swift
A swift introduction to Swift
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
 
Swift 함수 커링 사용하기
Swift 함수 커링 사용하기Swift 함수 커링 사용하기
Swift 함수 커링 사용하기
 

Plus de Jorge Vásquez

Plus de Jorge Vásquez (6)

Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!
Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!
Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!
 
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIO
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIOConsiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIO
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIO
 
ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021
 
Exploring type level programming in Scala
Exploring type level programming in ScalaExploring type level programming in Scala
Exploring type level programming in Scala
 
The Terror-Free Guide to Introducing Functional Scala at Work
The Terror-Free Guide to Introducing Functional Scala at WorkThe Terror-Free Guide to Introducing Functional Scala at Work
The Terror-Free Guide to Introducing Functional Scala at Work
 
Introduction to programming with ZIO functional effects
Introduction to programming with ZIO functional effectsIntroduction to programming with ZIO functional effects
Introduction to programming with ZIO functional effects
 

Dernier

introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
VishalKumarJha10
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 

Dernier (20)

The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
 
Chinsurah Escorts ☎️8617697112 Starting From 5K to 15K High Profile Escorts ...
Chinsurah Escorts ☎️8617697112  Starting From 5K to 15K High Profile Escorts ...Chinsurah Escorts ☎️8617697112  Starting From 5K to 15K High Profile Escorts ...
Chinsurah Escorts ☎️8617697112 Starting From 5K to 15K High Profile Escorts ...
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 
BUS PASS MANGEMENT SYSTEM USING PHP.pptx
BUS PASS MANGEMENT SYSTEM USING PHP.pptxBUS PASS MANGEMENT SYSTEM USING PHP.pptx
BUS PASS MANGEMENT SYSTEM USING PHP.pptx
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 

A Prelude of Purity: Scaling Back ZIO

  • 1. A Prelude of Purity Scaling Back ZIO ZIO World March 11th, 2022
  • 3. Background ZIO Prelude is a Scala-first take on functional abstractions
  • 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
  • 7. Problem Using Purely Functional style, how can we write computations that:
  • 8. Problem Using Purely Functional style, how can we write computations that: • Are Stateful
  • 9. Problem Using Purely Functional style, how can we write computations that: • Are Stateful • Require a Context
  • 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
  • 13. Example "2 5 + 3 * 1 -" -> 20
  • 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"
  • 29.
  • 31. Problems • Doesn't handle errors appropriately
  • 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)
  • 43. Benefits • Errors are handled appropriately
  • 45. Problems • Too much boilerplate to handle errors
  • 46. Problems • Too much boilerplate to handle errors • Poor type-inference
  • 47.
  • 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 }
  • 55. Benefits • Less boilerplate to handle errors
  • 57. Problems • There's still boilerplate to lift State into EitherT
  • 58. Problems • There's still boilerplate to lift State into EitherT • Even worse type-inference
  • 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?
  • 61. The Problem of Discoverability
  • 62.
  • 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) }
  • 72. Benefits of ZIO 2 • One monad to rule them all!
  • 73. Benefits of ZIO 2 • One monad to rule them all! • Superb type-inference
  • 74. Benefits of ZIO 2 • One monad to rule them all! • Superb type-inference • Discoverable functionality: Just one data type with concrete methods!
  • 77. Problems • We're killing a fly with a bazooka!
  • 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?
  • 84. Meet ZPure ZPure[+W, -S1, +S2, -R, +E, +A] A ZPure is a description of a Pure Computation that:
  • 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 }
  • 105. Comparing ZIO 2 and ZPure
  • 107. Benefits of ZPure • One monad to rule (almost) them all! (Except IO)
  • 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!
  • 113.
  • 115.
  • 116. Benchmarking State + Failure + Log
  • 117.
  • 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
  • 124. Special thanks • Ziverge for organizing ZIO World
  • 125. Special thanks • Ziverge for organizing ZIO World • Scalac for sponsoring
  • 126. Special thanks • Ziverge for organizing ZIO World • Scalac for sponsoring • John De Goes for guidance and support