Slide 1

Slide 1 text

FUNCTIONAL ERROR HANDLING @raulraja @47deg 2 Í

Slide 2

Slide 2 text

Functional Error Handling 3 Í

Slide 3

Slide 3 text

Requirements 1. Arm a Nuke launcher 2. Aim toward a Target 3. Launch a Nuke and impact the Target 4 Í

Slide 4

Slide 4 text

Requirements 1. arm a Nuke launcher 2. aim toward a Target 3. launch a Nuke and Impact the target 5 Í

Slide 5

Slide 5 text

Requirements /** model */ case class Nuke() case class Target() case class Impacted() def arm: Nuke = ??? def aim: Target = ??? def launch(target: Target, nuke: Nuke): Impacted = ??? 6 Í

Slide 6

Slide 6 text

Exceptions def arm: Nuke = throw new RuntimeException("SystemOffline") def aim: Target = throw new RuntimeException("RotationNeedsOil") def launch(target: Target, nuke: Nuke): Impacted = Impacted() 7 Í

Slide 7

Slide 7 text

Exceptions Breaks Referential transparency def arm: Nuke = throw new RuntimeException("SystemOffline") def aim: Target = throw new RuntimeException("RotationNeedsOil") def launch(target: Target, nuke: Nuke): Impacted = Impacted() 8 Í

Slide 8

Slide 8 text

Exceptions: Broken GOTO They are a broken GOTO def arm: Nuke = throw new RuntimeException("SystemOffline") def aim: Target = throw new RuntimeException("RotationNeedsOil") def launch(target: Target, nuke: Nuke): Impacted = Impacted() 9 Í

Slide 9

Slide 9 text

Exceptions: Broken GOTO They are a broken GOTO getting lost in async boundaries def arm: Nuke = throw new RuntimeException("SystemOffline") def aim: Target = throw new RuntimeException("RotationNeedsOil") def launch(target: Target, nuke: Nuke): Impacted = Impacted() def attack: Future[Impacted] = Future(launch(arm, aim)) 10 Í

Slide 10

Slide 10 text

Exceptions Abused to signal events in core libraries at java.lang.Throwable.fillInStackTrace(Throwable.java:-1) at java.lang.Throwable.fillInStackTrace(Throwable.java:782) - locked <0x6c> (a sun.misc.CEStreamExhausted) at java.lang.Throwable.(Throwable.java:250) at java.lang.Exception.(Exception.java:54) at java.io.IOException.(IOException.java:47) at sun.misc.CEStreamExhausted.(CEStreamExhausted.java:30) at sun.misc.BASE64Decoder.decodeAtom(BASE64Decoder.java:117) at sun.misc.CharacterDecoder.decodeBuffer(CharacterDecoder.java:163) at sun.misc.CharacterDecoder.decodeBuffer(CharacterDecoder.java:194) 11 Í

Slide 11

Slide 11 text

Exceptions Unsealed hierarchies, root of all evil try { doExceptionalStuff() //throws IllegalArgumentException } catch (Throwable e) { //too broad matches: /* VirtualMachineError OutOfMemoryError ThreadDeath LinkageError InterruptedException ControlThrowable NotImplementedError */ } 12 Í

Slide 12

Slide 12 text

Exceptions Potentially costly to construct based on VM impl and your current Thread stack size public class Throwable { /** * Fills in the execution stack trace. * This method records within this Throwable object information * about the current state of the stack frames for the current thread. */ Throwable fillInStackTrace() } 13 Í

Slide 13

Slide 13 text

Exceptions New: Creating a new Throwable each time Lazy: Reusing a created Throwable in the method invocation. Static: Reusing a static Throwable with an empty stacktrace. The Hidden Performance costs of instantiating Throwables Í

Slide 14

Slide 14 text

Exceptions Poor choices when using exceptions Modeling absence Modeling known business cases that result in alternate paths Async boundaries over unprincipled APIs (callbacks) When people have no access to your source code 15 Í

Slide 15

Slide 15 text

Exceptions Maybe OK if You don't expect someone to recover from it You are contributor to a JVM in JVM internals You want to create chaos and mayhem to overthrow the government In this talk You know what you are doing 16 Í

Slide 16

Slide 16 text

How do we model exceptional cases then? 17 Í

Slide 17

Slide 17 text

MONADS! 18 Í

Slide 18

Slide 18 text

Option When modeling the potential absence of a value 19 Í

Slide 19

Slide 19 text

Option When modeling the potential absence of a value sealed trait Option[+A] case class Some[+A](value: A) extends Option[A] case object None extends Option[Nothing] 20 Í

Slide 20

Slide 20 text

Option Useful combinators Garbage def fold[B](ifEmpty: B)(f: (A) B): B //inspect all paths def map[B](f: (A) B): Option[B] //transform contents def flatMap[B](f: (A) Option[B]): Option[B] //monadic bind to another option def filter(p: (A) Boolean): Option[A] //filter with predicate def getOrElse[B >: A](default: B): B //extract or provide alternative def get: A //NoSuchElementException if empty ( °□° 21 Í

Slide 21

Slide 21 text

Option How would our example look like? def arm: Option[Nuke] = None def aim: Option[Target] = None def launch(target: Target, nuke: Nuke): Option[Impacted] = Some(Impacted()) 22 Í

Slide 22

Slide 22 text

Option Pain to deal with if your lang does not have proper Monads or syntax support def attackImperative: Option[Impacted] = { var impact: Option[Impacted] = None val optionNuke = arm if (optionNuke.isDefined) { val optionTarget = aim if (optionTarget.isDefined) { impact = launch(optionTarget.get, optionNuke.get) } } impact } 23 Í

Slide 23

Slide 23 text

Option Easy to work with if your lang supports monad comprehensions or special syntax def attackMonadic: Option[Impacted] = for { nuke <- arm target <- aim impact <- launch(target, nuke) } yield impact 24 Í

Slide 24

Slide 24 text

Try When a computation may fail with a runtime exception 25 Í

Slide 25

Slide 25 text

Try When a computation may fail with a runtime exception sealed trait Try[+T] case class Failure[+T](exception: Throwable) extends Try[T] case class Success[+T](value: T) extends Try[T] 26 Í

Slide 26

Slide 26 text

Try Useful combinators Garbage def fold[U](fa: (Throwable) U, fb: (T) U): U //inspect all paths def map[U](f: (T) U): Try[U] //transform contents def flatMap[U](f: (T) Try[U]): Try[U] //monadic bind to another Try def filter(p: (T) Boolean): Try[T] //filter with predicate def getOrElse[U >: T](default: U): U // extract the value or provide an alternative if def get: T //throws the captured exception if not a Success ( °□° 27 Í

Slide 27

Slide 27 text

Try How would our example look like? def arm: Try[Nuke] = Try(throw new RuntimeException("SystemOffline")) def aim: Try[Target] = Try(throw new RuntimeException("RotationNeedsOil")) def launch(target: Target, nuke: Nuke): Try[Impacted] = Try(throw new RuntimeException("MissedByMeters")) 28 Í

Slide 28

Slide 28 text

Try Pain to deal with if your lang does not have proper Monads or syntax support def attackImperative: Try[Impacted] = { var impact: Try[Impacted] = null var ex: Throwable = null val tryNuke = arm if (tryNuke.isSuccess) { val tryTarget = aim if (tryTarget.isSuccess) { impact = launch(tryTarget.get, tryNuke.get) } else { ex = tryTarget.failed.get } } else { ex = tryNuke.failed.get } if (impact != null) impact else Try(throw ex) } 29 Í

Slide 29

Slide 29 text

Try Easy to work with if your lang supports monadic comprehensions def attackMonadic: Try[Impacted] = for { nuke <- arm target <- aim impact <- launch(target, nuke) } yield impact 30 Í

Slide 30

Slide 30 text

Either When dealing with a known alternate return path 31 Í

Slide 31

Slide 31 text

Either When a computation may fail or dealing with known alternate return path sealed abstract class Either[+A, +B] case class Left[+A, +B](value: A) extends Either[A, B] case class Right[+A, +B](value: B) extends Either[A, B] 32 Í

Slide 32

Slide 32 text

Either Useful combinators Garbage def fold[C](fa: (A) C, fb: (B) C): C //inspect all paths def map[Y](f: (B) Y): Either[A, Y] //transform contents def flatMap[AA >: A, Y](f: (B) Either[AA, Y]): Either[AA, Y] //monadic bind if Right def filterOrElse[AA >: A](p: (B) Boolean, zero: AA): Either[AA, B] //filter with pred def getOrElse[BB >: B](or: BB): BB // extract the value or provide an alternative if a toOption.get, toTry.get //Looses information if not a Right ( °□° 33 Í

Slide 33

Slide 33 text

Either What goes on the Left? def arm: Either[?, Nuke] = ??? def aim: Either[?,Target] = ??? def launch(target: Target, nuke: Nuke): Either[?, Impacted] = ??? 34 Í

Slide 34

Slide 34 text

Either Alegbraic Data Types (sealed families) sealed trait NukeException case class SystemOffline() extends NukeException case class RotationNeedsOil() extends NukeException case class MissedByMeters(meters : Int) extends NukeException 35 Í

Slide 35

Slide 35 text

Either Algebraic data types (sealed families) def arm: Either[SystemOffline, Nuke] = Right(Nuke()) def aim: Either[RotationNeedsOil,Target] = Right(Target()) def launch(target: Target, nuke: Nuke): Either[MissedByMeters, Impacted] = Left(MissedByMe 36 Í

Slide 36

Slide 36 text

Either Pain to deal with if your lang does not have proper Monads or syntax support def attackImperative: Either[NukeException, Impacted] = { var result: Either[NukeException, Impacted] = null val eitherNuke = arm if (eitherNuke.isRight) { val eitherTarget = aim if (eitherTarget.isRight) { result = launch(eitherTarget.toOption.get, eitherNuke.toOption.get) } else { result = Left(RotationNeedsOil()) } } else { result = Left(SystemOffline()) } result } 37 Í

Slide 37

Slide 37 text

Either Easy to work with if your lang supports monadic comprehensions def attackMonadic: Either[NukeException, Impacted] = for { nuke <- arm target <- aim impact <- launch(target, nuke) } yield impact 38 Í

Slide 38

Slide 38 text

Can we further generalize error handling and launch nukes on any M[_]? 39 Í

Slide 39

Slide 39 text

(Monad|Applicative)Error[M[_], E] /** * A monad that also allows you to raise and or handle an error value. * This type class allows one to abstract over error-handling monads. */ trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { ... } 40 Í

Slide 40

Slide 40 text

(Monad|Applicative)Error[M[_], E] Many useful methods to deal with potentially failed monads def raiseError[A](e: E): F[A] def handleError[A](fa: F[A])(f: E => A): F[A] def attempt[A](fa: F[A]): F[Either[E, A]] def attemptT[A](fa: F[A]): EitherT[F, E, A] def recover[A](fa: F[A])(pf: PartialFunction[E, A]): F[A] def catchNonFatal[A](a: => A)(implicit ev: Throwable <:< E): F[A] def catchNonFatalEval[A](a: Eval[A])(implicit ev: Throwable <:< E): F[A] 41 Í

Slide 41

Slide 41 text

(Monad|Applicative)Error[M[_], E] Cats instances available for MonadError[Option, Unit] MonadError[Try, Throwable] MonadError[Either[E, ?], E] 42 Í

Slide 42

Slide 42 text

(Monad|Applicative)Error[M[_], E] How can we generalize and implement this to any M[_]? def arm: M[Nuke] = ??? def aim: M[Target] = ??? def launch(target: Target, nuke: Nuke): M[Impacted] = ??? 43 Í

Slide 43

Slide 43 text

(Monad|Applicative)Error[M[_], E] Higher Kinded Types! def arm[M[_]]: M[Nuke] = ??? def aim[M[_]]: M[Target] = ??? def launch[M[_]](target: Target, nuke: Nuke): M[Impacted] = ??? 44 Í

Slide 44

Slide 44 text

(Monad|Applicative)Error[M[_], E] Typeclasses! import cats._ import cats.implicits._ def arm[M[_] : NukeMonadError]: M[Nuke] = Nuke().pure[M] def aim[M[_] : NukeMonadError]: M[Target] = Target().pure[M] def launch[M[_] : NukeMonadError](target: Target, nuke: Nuke): M[Impacted] = (MissedByMeters(5000): NukeException).raiseError[M, Impacted] 45 Í

Slide 45

Slide 45 text

(Monad|Applicative)Error[M[_], E] An abstract program is born def attack[M[_] : NukeMonadError]: M[Impacted] = (aim[M] |@| arm[M]).tupled.flatMap((launch[M] _).tupled) 46 Í

Slide 46

Slide 46 text

(Monad|Applicative)Error[M[_], E] Provided there is an instance of MonadError[M[_], A] for other types you abstract away the return type attack[Either[NukeException, ?]] attack[Future[Either[NukeException, ?]]] 47 Í

Slide 47

Slide 47 text

Abstraction Benefits Safer code Less tests More runtime choices Issues Performance cost? Newbies & OOP dogmatics complain about legibility Advanced types + inference higher compile times 48 Í

Slide 48

Slide 48 text

Recap Error Handling When to use Java Kotlin Scala Exceptions ~Never x x x Option Modeling Absence ? x x Try Capturing Exceptions ? ? x Either Modeling Alternate Paths ? ? x MonadError Abstracting away concerns - - x 49 Í

Slide 49

Slide 49 text

Recap What if my lang does not support some of these things? 1. Build it yourself 2. Ask lang designers to include HKTs, Typeclasses, ADT and others 3. We are part of the future of programming 50 Í

Slide 50

Slide 50 text

Thanks! @raulraja @47deg 51 Í