Tag Archives: typeclass

Scala Cats Typeclasses At A Glance

Scala Cats comes with a rich set of typeclasses, each of which “owns” a well-defined autonomous problem space. Many of those typeclasses are correlated and some are extended from others.

In this blog post, we’re going to give an at-a-glance hierarchical view of some of the most common Cats typeclasses. For brevity, we’ll skip discussions re: their corresponding mathematical laws, which can be found in many relevant tech docs. Our focus will be more on highlighting the correlations among these typeclasses.

Common typeclass hierarchy

For the impatient, below is a diagram highlighting the hierarchical correlation.

Scala Cats Common Typeclass Hierarchy

Semigroup and Monoid

Let’s start with the simplest ones, Semigroup and Monoid.

Semigroup comes with the abstract method combine to be implemented with the specific “combine” computational logic such as the addition of integers, union of sets, etc.

Note that Monoid simply supplements Semigroup with empty as the “zero” or “identity” element, allowing aggregating operations of arbitrarily many elements (e.g. summation of numbers from an initial 0).

Example:

SemigroupK and MonoidK

With a similar correlation, SemigroupK and MonoidK are the higher-kinded version of Semigroup and Monoid, respectively. SemigroupK combines values within a given context and MonoidK ensures the existence of an “empty” context.

Example:

Functor

Functor is a higher-kinded typeclass characterized by its method map which transforms some value within a given context F via a function.

Example:

Monad

Monad enables sequencing of operations in which resulting values from an operation can be utilized in the subsequent one.

But first, let’s look at typeclass FlatMap.

FlatMap extends Apply whose key methods aren’t what we would like to focus on at the moment. Rather, we’re more interested in method flatMap which enables sequential chaining of operations.

In addition, method tailRecM is a required implementation for stack-safe recursions on the JVM (which doesn’t natively support tail call optimization).

Monad inherits almost all its signature methods from FlatMap.

Monad also extends Applicative which we’ll get to (along with Apply) in a bit. For now, it suffices to note that Monad inherits pure from Applicative.

Even without realizing that Monad extends Functor (indirectly through FlatMap and Apply), one could conclude that Monads are inherently Functors by implementing map using flatMap and pure.

Example:

Semigroupal and Apply

A higher-kinded typeclass, Semigroupal conceptually deviates from SemiGroup’s values combining operation to joining independent contexts in a tupled form “product”.

Despite the simplicity of method product (which is the only class method), Semigroupal lays out the skeletal foundation for the problem space of concurrency of independent operations, as opposed to Monad’s sequential chaining.

Next, Apply brings together the goodies of Semigroupal and Functor. Its main method ap has a rather peculiar signature that doesn’t look intuitively meaningful.

Conceptually, it can be viewed as a specialized map in which the transformation function is “wrapped” in the context.

By restructuring the type parameters in ap[A, B] and map[A, B], method product can be implemented in terms of ap and map.

Applicative

Like how Monoid supplements SemiGroup with the empty element to form a more “self-contained” typeclass, Applicative extends Apply and adds method pure which wraps a value in a context. The seemingly insignificant inclusion makes Applicative a typeclass capable of addressing problems within a particular problem space.

Similarly, Monad takes pure from Applicative along with the core methods from FlatMap to become another “self-contained” typeclass to master a different computational problem space.

Contrary to Monad’s chaining of dependent operations, Applicative embodies concurrent operations, allowing independent computations to be done in parallel.

We’ll defer examples for Applicative to a later section.

Foldable

Foldable offers fold methods that go over (from left to right or vice versa) some contextual value (oftentimes a collection) and aggregate via a binary function starting from an initial value. It also provides method foldMap that maps to a Monoid using an unary function.

Note that the well known foldRight method in some Scala collections may not be stack-safe (especially in older versions). Cats uses a data type Eval in its foldRight method to ensure stack-safety.

Traverse

Traverse extends Functor and Foldable and provides method traverse. The method traverses and transforms some contextual value using a function that wraps the transformed value within the destination context, which as a requirement, is bound to an Applicative.

If you’ve used Scala Futures, method traverse (and the sequence method) might look familiar.

Method sequence has the effect of turning a nested context “inside out” and is just a special case of traverse by substituting A with G[B] (i.e. making ff an identity function).

Example: Applicative and Traverse

To avoid going into a full-on implementation of Traverse in its general form that would, in turn, require laborious implementations of all the dependent typeclasses, we’ll trivialize our example to cover only the case for Futures (i.e. type G = Future).

First, we come up with a specialized Traverse as follows:

For similar reasons, let’s also “repurpose” Applicative to include only the methods we need. In particular, we include method map2 which will prove handy for implementing the traverse method for FutureTraverse.

We implement map2 by tuple-ing the Futures and binary function via zip and tupled, respectively. With the implicit Applicative[Future] in place, we’re ready to implement FutureTraverse[List].

As a side note, we could implement traverse without using Applicative. Below is an implementation leveraging Future’s flatMap method along with a helper function (as demonstrated in a previous blog post about Scala collection traversal).

Orthogonal Typeclass In Scala

As an addendum to a previous blog post on the topic of ad-hoc polymorphism in Scala, I’m adding another common typeclass pattern as a separate post. The term “orthogonal” refers to a pattern that selected class attributes are taken out from the base class to form an independent typeclass.

Using an ADT similar to the Car/Sedan/SUV example used in that previous post, we first define trait Car as follows:

Unlike how the base trait was set up as a typeclass in the ad-hoc polymorphism example, trait Car is now an ordinary trait. But the more significant difference is that method setPrice() is no longer in the base class. It’s being constructed “orthogonally” in a designated typeclass:

Similar to how implicit conversions are set up for ad-hoc polymorphism, implicit values are defined within the companion objects for the individual child classes to implement method setPrice() for specific car types.

The specific method implementations are then abstracted into a “unified” method, setNewPrice(), via an implicit constructor argument by passing the Settable typeclass into the CarOps implicit class:

Testing it out:

Putting all method implementations in one place

It’s worth noting that having the implicit values for method implementations defined in the companion objects for the individual classes is just one convenient way. Alternatively, these implicit values could all be defined in one place:

A benefit of putting all method implementations in one place is that new methods can be added without touching the base classes – especially useful in situations where those case classes cannot be altered.

For instance, if color is also an attribute of trait Car and its child case classes, adding a new color setting method will be a trivial exercise by simply adding a setColor() method signature in trait Settable and its specific method implementations as well as setNewColor() within class CarOps.

Orthogonal type collection

Let’s see what a collection of cars looks like:

To refine the inferred List[Product with java.io.Serializable] collection type, we could provide some type hints as shown below:

Ad-hoc Polymorphism In Scala

Over the past few years, there seems to be a subtle trend of software engineers favoring typeclass patterns that implement polymorphism in an ad-hoc fashion, namely, Ad-hoc Polymorphism. To see the benefits of such kind of polymorphism, let’s first look at what F-bounded polymorphism, a subtype polymorphism, has to offer.

F-bounded polymorphism

Next, a couple of child classes are defined:

A F-bounded type has a peculiar signature of the self-recursive A[T <: A[T]] which mandates the given type T itself a sub-type of A[T], like how type Sedan is defined (Sedan <: Car[Sedan]). Note that the self-type annotation used in the trait isn’t requirement for F-bounded type. Rather, it’s a common practice for safeguarding against undesirable mix-up of sub-classes like below:

“Type argument” versus “Type member”

Rather than a type argument, a F-bounded type could also be expressed as a type member which needs to be defined in its child classes.:

It should be noted that with the type member approach, self-type would not be applicable, hence mix-up of sub-classes mentioned above is possible.

Let’s define a sedan and test out method setPrice:

Under the F-bounded type’s “contract”, a method such as the following would work as intended to return the specified sub-type:

Had the Car/Sedan hierarchy been set up as the less specific T <: Car, the corresponding method:

would fail as it couldn’t guarantee the returning type is the exact type of the input.

F-bounded type collection

Next, let’s look at a collection of cars.

The resulting type is a rather ugly sequence of gibberish. To help the compiler a little, give it some hints about T <: Car[T] as shown below:

Ad-hoc polymorphism

Contrary to subtype polymorphism which orients around a supertype with a rigid subtype structure, let’s explore a different approach using typeclasses, known as Ad-hoc polymorphism.

Next, a couple of “ad-hoc” implicit objects are created to implement the trait methods.

Note that alternatively, the implicit objects could be set up as ordinary companion objects of the case classes with implicit anonymous classes:

Unifying implemented methods

Finally, an implicit conversion for cars of type T is provided by means of an implicit class to create a “unified” method that takes the corresponding method implementations from the provided implicit Car[T] parameter.

Testing it out:

New methods, like setSalePrice, can be added as needed in the implicit objects:

Ad-hoc type collection

Next, a collection of cars:

Similar to the F-bounded collection, the inferred resulting type isn’t very helpful. Unlike in the F-bounded case, we do not have a T <: Car[T] contract. Using an approach illustrated in this blog post, we could assemble the collection as a list of (car, type) tuples:

By means of a simple example, we’ve now got a sense of how Ad-hoc polymorphism works. The F-bounded example serves as a contrasting reference of how the polymorphism bound by a more “strict” contract plays out in comparison. Given the flexibility of not having to bind the base classes into a stringent subtype relationship upfront, the rising popularity of Ad-hoc polymorphism certainly has its merits.

That said, lots of class models in real-world applications still fits perfectly well into a subtype relationship. In suitable use cases, F-bounded polymorphism generally imposes less boilerplate code. In addition, Ad-hoc polymorphism typically involves using of implicits that may impact code maintainability.