Last active
August 29, 2015 14:15
-
-
Save colindean/986fde9623140957f0d4 to your computer and use it in GitHub Desktop.
Comparison of Either vs Try in Scala for Angle - inspired by http://underscore.io/blog/posts/2015/02/13/error-handling-without-throwing-your-hands-up.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed trait Angle { val degrees: Int } | |
private final case object Perpendicular extends Angle { val degrees = 90 } | |
private final case object Straight extends Angle { val degrees = 180 } | |
private final case class Acute(degrees: Int) extends Angle | |
private final case class Obtuse(degrees: Int) extends Angle | |
private final case class Reflex(degrees: Int) extends Angle | |
object Angle { | |
def apply(degrees: Int): Either[String,Angle] = degrees match { | |
case _ if degrees == 90 ⇒ | |
Right(Perpendicular) | |
case _ if degrees == 180 ⇒ | |
Right(Straight) | |
case _ if degrees >= 0 && degrees < 90 ⇒ | |
Right(Acute(degrees: Int)) | |
case _ if degrees > 90 && degrees < 180 ⇒ | |
Right(Obtuse(degrees: Int)) | |
case _ if degrees > 180 && degrees < 360 ⇒ | |
Right(Reflex(degrees: Int)) | |
case _ ⇒ | |
Left(s"Invalid angle $degrees. Needs to be between 0 and 360.") | |
} | |
} | |
val list = List(Angle(180), Angle(90), Angle(37), Angle(137), Angle(270), Angle(-1)) | |
list.foreach { | |
case Left(angle) => println(s"Yay $angle") | |
case Right(error) => println(s"Boo $error") | |
} | |
def success(angle: Angle) = println(s"Yay $angle") | |
def failure(message: String) = println(s"Boo $message") | |
list.foreach(_.fold(failure, success)) | |
// What I don't like about this is having to remember parameter order, | |
// or relying on an IDE to remind me. | |
// I do like both cases being Functions. They read better, IMO. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.util.{Failure, Try, Success} | |
sealed trait Angle { val degrees: Int } | |
private final case object Perpendicular extends Angle { val degrees = 90 } | |
private final case object Straight extends Angle { val degrees = 180 } | |
private final case class Acute(degrees: Int) extends Angle | |
private final case class Obtuse(degrees: Int) extends Angle | |
private final case class Reflex(degrees: Int) extends Angle | |
object Angle { | |
def apply(degrees: Int): Try[Angle] = degrees match { | |
case _ if degrees == 90 ⇒ | |
Success(Perpendicular) | |
case _ if degrees == 180 ⇒ | |
Success(Straight) | |
case _ if degrees >= 0 && degrees < 90 ⇒ | |
Success(Acute(degrees: Int)) | |
case _ if degrees > 90 && degrees < 180 ⇒ | |
Success(Obtuse(degrees: Int)) | |
case _ if degrees > 180 && degrees < 360 ⇒ | |
Success(Reflex(degrees: Int)) | |
case _ ⇒ | |
Failure(new IllegalArgumentException("Invalid angle $degrees. Needs to be between 0 and 360.")) | |
} | |
} | |
val list = List(Angle(180), Angle(90), Angle(37), Angle(137), Angle(270), Angle(-1)) | |
list.foreach { | |
case Success(angle) => println(s"Yay $angle") | |
case Failure(error) => println(s"Boo $error") | |
} | |
def success(angle: Angle) = println(s"Yay $angle") | |
def failure(message: String) = println(s"Boo $message") | |
list.foreach(_.map(success).recover {case e: IllegalArgumentException => failure(e.getMessage)}) | |
//which would more likely be written as | |
def success(angle: Angle) = println(s"Yay $angle") | |
val failure: PartialFunction[Throwable, Unit] = {case e: IllegalArgumentException => println(e.getMessage)} | |
list.foreach(_.map(success).recover(failure)) | |
// In both cases, the IllegalArgumentException could just be Exception, | |
// since we know that any Failure would wrap an Exception. | |
// By using the partial function, we gain the ability to triage multiple error conditions. | |
// It's unfortunately that this example only has one! | |
// The real usage ends up having better semantics, IMO. | |
// It is better to pass two parameters into one method, or two parameters into one specific method for each? | |
// I like the wrapping of the exception more than having to juggle two different types. | |
// It provides the *option* of throwing up our hands OR handling the error in a semantically sound, type-safe way. |
I meant "mostly evil", not "most evil"!
Of course, at some level, exceptions are necessary and good (very outer edges of communicating with the outside world). The reason Rust gets away without having exceptions is because of isolation to threads. Go, Erlang operate the same way. Don't know what the heck is going on with Swift.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Scala's
Either
is terrible for a couple of reasons and I never use it. (Meanwhile, you gotRight
andLeft
reversed:Right
is for success by convention.)The followup blog post http://underscore.io/blog/posts/2015/02/23/designing-fail-fast-error-handling.html mentions the much better scalaz
\/
, which I use (especially withValidation
, which I was originally going to give a talk on for Pittsburgh Scala last month but that's been postponed).I consider exceptions to be most evil (although the followup blog post makes a good point about the pretty convenient hack of stack traces, hence advocates a sealed trait extending
Extension
but pattern-matching off that sealed trait) because they throw away type information, which you then try to recover through run-time type checking of the exception type (rather than compile-time checking of pattern matching). I've found this to be a serious source of errors (not catching the right exceptions at a good place where recovery would have been optimal rather than propagating further).I'm pretty happy that newer languages don't have exceptions at all, e.g., Go, Rust, Swift. Example of validation using
Result
type in Rust: https://github.com/FranklinChen/type-directed-tdd-rust/blob/master/src/divisor.rsThe problem with exceptions is that they are stateful. Exceptions interact horribly with, say, concurrency, and C++ has endured decades of woes because of exceptions in the language, which interacts badly with compilation, memory models, resource management.