Skip to content

Instantly share code, notes, and snippets.

@colindean
Last active August 29, 2015 14:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save colindean/986fde9623140957f0d4 to your computer and use it in GitHub Desktop.
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
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.
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.
@FranklinChen
Copy link

Scala's Either is terrible for a couple of reasons and I never use it. (Meanwhile, you got Right and Left 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 with Validation, 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.rs

The 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.

@FranklinChen
Copy link

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