Scalaz is one of those things that everyone is talking about, but many teams are unsure if they actually want to use it. The question is often, "should we use Scalaz?" but we think the question should actually be "how should we use Scalaz?"
Scalaz is an exhaustive standard library extension for Scala with a strong emphasis on functional programming and type correctness. Many parts of it are strongly inspired by Haskell and the theoretical concepts of functional programming. The library is known for pushing the abstraction barrier quite far, which gives it its reputation as a complex library (which is true!), but some concepts from it are actually quite easy, instantly usable, and very handy in programming Scala.
There are many tutorials about Scalaz explaining the theoretical concepts behind it, like Monads, Functors, Applicatives, Type Classes and the like. Learning Scalaz  is a great resource on this, although a bit outdated sometimes. Instead of delving into these concepts, we are going to explain how we use Scalaz at Bench and describe the subset we are currently using with code examples.
Within the code examples, we assume that the whole scalaz world was imported:
Tagging makes creating new types super easy. It uses the
@@ symbol to "tag" an existing type as another type (in other words, it creates a new type). So
String @@ Text should be read as "String tagged with Text".
For a practical example, we may tag a String with the Id tag so that we never accidentally pass any String as an Id.
@@ is just a type (or more concretely a type alias). Symbolic types that take two parameters can be written in the infix position so
String @@ Id is the same as
So here the type alias Id is actually an alias to
String @@ Tags.Id, which expands to
@@[String, Tags.Id], which in turn expands to
String with Tagged[Id]. Tags can be used to:
- Ensure type safety of APIs or data structures - it’s often better to use a tagged type than multiple String/Int or other primitive types
- "Fake" a new type for type-classes (see below)
The important thing to note about typeclasses is that they "extend" the types at compile time. In scala this is implemented through implicits. There are two parts to a typeclass - the definition and the evidence (implementation) - similar to how in sub-type polymorphism there are interfaces and implementations.
In contrast to sub-type polymorphism:
- Each class may have multiple implementations of type-classes, which are then imported into scope
- We can make a typeclass out of classes that we do not have the source of (or that we do not want to touch)
Like any other Scala shop, we use a lot of different type classes at Bench. The ones we use the most from Scalaz are Equal, Order and Enum. As an example of one of them, we’ll describe the Scalaz Equal typeclass, which provides a === type-safe equals method (and a type-safe =/= non-equals method).
Type safety means that === is only defined for types that provide explicit evidence of equality. So 1 === "1" won't compile (no evidence of
Equal[Any]). This is different to the == behavior of Scala’s Any, which at best will only give a warning during compile time.
This means that for any types that we define, we must provide our own evidence of equality.
Fortunately, this is quite easy:
Scalaz does not implement its own Option format (yet) but adds some cool methods to help you work with Scala Options.
Scalaz adds the some and
none[A] methods defined on Any. They are used like this:
These methods are nice because they always return
Option[A], which helps the type interferencer. Compare:
Option[Int], it would result in the following compile error:
\/[A, B] can be understood as a nicer version of Scala’s Either - mostly because it has utility methods defined in it and you do not need to play around with projections to get things done.
\/ is right biased, which means that all operations like map, flatMap, etc. work on the "right" part of the type, which by convention is the success part.
A \/ B can also be swapped to
B \/ A by using
Creating this is easy and most commonly you will find yourself using:
\/- correspond to left and right values.
A handy factory method is
This can be used as a drop in replacement for Scala’s Try.
Validation[A, B] is similar to
\/[A, B] but isn't a Monad so it can not flatMap. On the other hand, it can compose aggregating errors, which is useful to give detailed error descriptions.
Take a look at this motivating example:
We can aggregate all the validations to a
ValidationNel (Nel stands for non empty list) and use
sequenceU (defined on Traversable) to get:
If all validations in the list succeeded, this would expand to return a
Success[List[Int]] if not (as is the case here) this expands to
Failure[NonEmptyList[Throwable]]. Using fold on the result we can get either the success or the failure case.
This article showed some useful and at the same time simple parts of Scalaz – hopefully it motivates you to try it out on your own. As mentioned in the introduction, we believe that Scalaz can help you to use Scala even more effectively and is a valuable asset to improve your code quality.
Scalaz is a really comprehensive addition and we advise you to discuss which parts you want to use with your team before implementing anything. As much as Scalaz can improve your code, it can also be used to make your code almost unreadable and therefore inaccessible for the untrained.
At Bench we enjoy working with Scalaz, finding useful features, and discussing them within our team. We wish you the same fun we had in exploring Scalaz for your organization and hope this article was a good starting point for that!