We love this Scala Snacks series written by Daniel Tattan-Birch, Software Engineer at Football Radar. These articles will definitely come in handy so read up and fill you Scala hunger!
'This one will actually be a snack, I promise.
Let’s paint a picture. We’ve got a DTO containing some state. Let’s make it about football this time, a 'Goals' class.
case class Goals(value: Int)
The shape of the class is merely for demonstrative purposes, I won't be mentioning anything about value classes.
It jumps out that we should probably have some validation around this, after all we cannot have -1 goals. Easy enough:
case class Goals(value: Int) {
assert(value >= 0, s”$value is not a valid number of goals")
}
This will throw at runtime, which isn’t ideal, but if our code is adequately tested that should suffice, shouldn't it?
An 'assert' in Scala throws a 'java.lang.AssertionError'. A 'require', on the other hand, throws a 'java.lang.IllegalArgumentException'. There’s a subtle semantic difference here. This stackoverflow response puts it nicely.
- assert means that your program has reached an inconsistent state - this might be a problem with the current method/function
- require means that the caller of the method is at fault and should fix its call
scala> val errorF: Future[Int] = Future.apply(throw new AssertionError("Bad things"));
errorF: scala.concurrent.Future[Int] = Future(<not completed>)
scala> errorF.onComplete { case Failure(e) => println(s"Throwable message is: ${e.getMessage}"); println(s"Throwable cause is: ${e.getCause}"); case _ => }
Throwable message is: Boxed Error
Throwable cause is: java.lang.AssertionError: Bad things
The justification for this in the Scala docs is related to the aforementioned semantic differences between errors and exceptions. All throwables aren’t created equally, and with good reason.
After a little digging through a couple of services I got to the underlying error, and changed an 'assert' to a 'require'. I thought this was vaguely interesting so I’d share it, and maybe save someone else a bit of time.
Totally off topic
As an aside, an interesting (albeit impractical) way to guarantee a positive number of goals at compile time could be to use natural numbers. Then we can use a type alias or in future define 'Goals' as an opaque type. Certainly there are libraries which do this in a neater and more efficient way, but if we were to implement from scratch we might have something like:
sealed trait NaturalNumber { def toInt: Int }
case object Zero extends NaturalNumber { def toInt = 0 }
case class Successor(predecessor: NaturalNumber) extends NaturalNumber {
def toInt = 1 + predecessor.toInt
}
It would be nice to be able to sum and multiply them. Obviously we can't use the integer form - that would be cheating.
object NaturalNumber {
def sum(n: NaturalNumber, m: NaturalNumber): NaturalNumber = (n, m) match {
case (Zero, _) => m
case (Successor(x), _) => Successor(sum(x, m))
}
def multiply(n: NaturalNumber, m: NaturalNumber): NaturalNumber = (n, m) match {
case (Zero, _) => Zero
case (Successor(Zero), _) => m
case (Successor(x), _) => sum(m, multiply(x, m)) // sum 1 lot of m and n-1 lots of m
}
}
Having a play on the REPL:
scala> val one = Successor(Zero)
one: Successor = Successor(Zero)
scala> val two = Successor(one)
two: Successor = Successor(Successor(Zero))
scala> val three = sum(one, two)
three: NaturalNumber = Successor(Successor(Successor(Zero)))
scala> val six = multiply(two, three)
six: NaturalNumber = Successor(Successor(Successor(Successor(Successor(Successor(Zero))))))
scala> val fortyOne = sum(multiply(six, six), sum(two, three)).toInt
fortyOne: Int = 41
I'll leave 'def power(n: NaturalNumber, m: NaturalNumber): NaturalNumber' as a bit of fun for the reader - but be warned your stack will grow quickly since everything boils down to recursive calls to 'sum'.
Check out Scala Snacks: Part 1 here.
This article was written by Daniel Tattan-Birch and posted originally on engineering.footballradar.com