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

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