Saturday, December 23, 2017

Three Real-world examples of Type class pattern in Scala

In this post, we are going to look at a couple of examples where the type class pattern has been put to use in Scala. This is the sixth post in a series of posts on the Type classes pattern.

This post assumes an understanding of main concepts and terms that come into play when encoding the type class pattern in Scala. If you are new to how type classes are encoded in Scala, then I advise to first read the previous posts in this series. You can do that by starting from the introductory post: Exploring Typeclass in Scala: A knowledge pack

That being said, let us now go ahead and explore 3 examples of the Type class pattern being used in Scala. We start with JSON serialization in the Play Framework.


Type class in Play Framework's JSON Library.

For the first example of type class in real-life, we will be looking at JSON serialization in Play Framework.

Play Framework self-describes itself as The High-Velocity Web Framework For Java and Scala. It comes with a JSON module which can be used together with the whole framework or stand alone. To use the Play JSON library standalone, you will need to add the following piece of configuration to your build.sbt:

libraryDependencies += "com.typesafe.play" %% "play-json" % playVersion

If you not familiar with Play, I suggest reading the Getting started guide.

With the JSON library added, we can now proceed to look at how type classes are used when serializing and deserializing.

The Serialization and Deserialization API
A full exploration of the Play JSON library can be found here.

The Play JSON library API is filled with the usage of type classes but in this post, I am going to highlight just two cases. That is, Json.toJson which is used to write/serialize values into JSON and Json.fromJson which is used to read/deserialize values from JSON.

For example:
// Will serialize to JsValue
val jsonArrayOfInts = Json.toJson(Seq(1, 2, 3, 4))
// Will deserialize back to Seq[Int]
Json.fromJson[Seq[Int]](jsonArrayOfInts).get

Let's first examine the Json.toJson method.

If you take a look at the method definition you will see:
def toJson[T](o: T)(implicit tjs: Writes[T]): JsValue = tjs.writes(o)

Does that not look familiar? It is exactly an example of the basic object interface described in Common forms of Type class pattern in Scala. The toJson method takes o:T, the object to be serialized, and it does so, provided there is an implicit value of Writes[T] in scope.

Thus, the Writes[T] is the type class. What about the type class instance?

When I wrote Json.toJson(Seq(1, 2, 3, 4)), I did not provide the type class instance, but the code still works because Play comes with a couple of default type class instances for Writes[T].

If you then want to serialize a custom data type, which obviously won't have a default serializer, all you need do is to write your own type class instance for Writes[T] and make it available in the implicit scope.

Let us move on and look at Json.fromJson. If you look at its definition, you find:
def fromJson[T](json: JsValue)(implicit fjs: Reads[T]): JsResult[T] = fjs.reads(json)

On examining the type signature, you will notice that it slightly deviates from the typical type class set up mechanism, where you have a value of a certain type to be worked on, together with its type class instance.

In the case of fromJson, the type of the value to be worked on is fixed and it is JsValue, while the type class instances need to be of type Reads[T].

But if you look carefully, this set up fits the fishing of type class instances via types described under the Direct Access via Apply method and implicit parameter section in Common forms of Type class pattern in Scala

That is, it is a pattern that involves directly selecting the type class instance by type for use.

In the fromJson[T](json: JsValue)(implicit fjs: Reads[T]) it can be seen that we are actually selecting the required Reads[T] instance based on the type parameter passed to the method. The selected type class instance is then used within the method.

As mentioned earlier, there are numerous other instances of type classes being used in the Play Frameworks API. I would suggest to be on the look-out and try to spot as much as you can.

The next example we look at would involve monoids and cats.

Monoid in Cat

The next example of Type classes in real life would be based on the Cats library, which self-describes as a Lightweight, modular, and extensible library for functional programming.

We would be looking at the idea of a Monoid, how it is modeled as a type class in Cats and how to create instances of it for use.

I will work with the assumption that if you are still learning about how type classes work, you probably have not learned about monoids. So it is okay if certain aspects of this real-life example do not make sense. As I would not be expounding and explaining what Monoids are. And that should be fine. If you have read all the previous post in this series though, It should be possible to still follow along, at least the mechanical aspects of the examples shown should make sense.

Monoid captures the behavior of being able to combine two values of the same type into a third value, where the type of the value produced after combination must be of the same type as the type of the combined value.

This combination thus described still needs to follow certain rules for it to be seen as "monoidial". These rules include having the combination to be applicable to all values of the two types, there must exist a value that when used in combination with the other value, it does nothing, and lastly the combination order should not matter.

Do not worry too much if the preceding paragraphs do not help you develop an intuition about monoids.

In code, a Monoid can be represented as:

trait Monoid[A] {
def combine(x: A, y: A): A
def empty: A
}

And if you look carefully this looks like a type class. As described in Encoding Type class in Scala a type class is usually encoded in Scala as a trait with at least one type variable. Which is exactly what Monoid[A] is.

The actual definition of Monoid in Cats library is not exactly like this though (as it extends other trait and there are other methods defined on it) but its essence is very much in line with the definition of Monoid[A] above.

So if Monoid captures the idea of being able to combine, what then can I do with it? you might ask.

Well, it means you could write generic code that combines things and have the code work on all values of any type as long as that type has an instance of the Monoid type class. Being able to do this, is exactly one of the things the type class pattern allows for.

Such a generic combining code might look like this:

def combine[A](first: A, second: A)(implicit monoid: Monoid[A]): A =
    monoid.combine(first, second)

Which exposes an interface for working with type classes. It allows us to pass the value, together with an instance of the type class.

With this definition, we can then combine any value of any type, as long as there is an instance of Monoid for that type.

Let's see how this looks if we want to demonstrate with Cats.

The first thing that needs to be done is to include Cat library. Do this by adding the following to your build.sbt

libraryDependencies += "org.typelevel" %% "cats-core" % "version"

Then you can proceed to have code that looks like:

object Test extends App {
  import cats.Monoid
  import cats.instances.int._
  import cats.instances.string._
 
// defines the mechanism for interacting with type class
def combine[A](first: A, second: A)(implicit monoid: Monoid[A]): A =
    monoid.combine(first, second)

  println(combine(1,2)) // prints 3
  println(combine("Hello ", "World")) // prints hello world
}

As can be seen in the example, I was able to use combine method with Int in combine(1,2) and with String in combine("Hello ", "World"). When used with Strings, the combination results in a sensible outcome of combining Ints, which is an addition. With strings, the sensible outcome was a concatenation.

If you notice also, I used the combine method without explicitly passing the Monoid type class instance for both Int and String. The reason why this works is that Cats come with default type class instances for basic data types to the type classes it defines.

So what if you have a custom data type that you want to be able to combine? Let's say Bitcoin values? How do you go about that?

The beauty of type class based polymorphic code is that it allows us to be able to still use the combine method. All that needs to be done is to provide a type class instance for the custom datatype.

In code, this would look like:
object Test extends App {
  import cats.Monoid
 
  def combine[A](first: A, second: A)(implicit monoid: Monoid[A]): A =
    monoid.combine(first, second)

  case class Bitcoin(value: Int)

  implicit object bitcoinMonoid extends Monoid[Bitcoin] {
    override def empty: Bitcoin = Bitcoin(0)
    override def combine(x: Bitcoin, y: Bitcoin): Bitcoin = Bitcoin(x.value + y.value)
  }
  // using combine is possible with bitcoin custom data type
  println(combine(Bitcoin(100), Bitcoin(200))) // prints Bitcoin(300)
}

The Cats library makes heavy use of type classes, and it goes without saying that a good working knowledge of how type classes work is needed to be able to effectively use the Cats library.

Now last but not the least, let us turn to the Scala standard library to see another instance of type classes being used.

Ordering in Scala   

The last example I will take a look is going to be in the Scala standard library.

We would be looking at the mechanism that allows us to be able to order elements of a List. Using the sorted method together with Ordering type class.

When I decide to sort a list of Integers like this:

List(5,3,4,2,1).sorted

Sorted is defined in trait SeqLike which List inherits from. If we take the parts we are concerned with, within the definition of SeqLike, we would have:

trait SeqLike[+A, +Repr] extends ... {
...
def sorted[B >: A](implicit ord: Ordering[B]): Repr =
...
}

Deciphering the code above would require a working understanding of various type parameter annotations in Scala. This was covered in Exploring Type Annotations in Scala, so as always if you not familiar with these ideas, do revisit the previous posts :)

Basically what is going on: the code snippet can be interpreted to mean whenever we have a SeqLike, (for example a List), then its type parameter would be covariant, and the sorted method requires an implicit value of type B where B can be the same type as A or a supertype of A.

The implicit Ordering[B] is the type class instance, and the type of values in the List represents the value to be worked on polymorphically.

So, when I sort a list of integers by writing List(5,3,4,2,1).sorted, it works because the standard library provides a default implementation of Ordering[Int]. You can see this default type class instance for Ordering[B] in Ordering.scala

What if you need to order a list of custom type? In such a situation the standard library cannot be of help with a default type class instance for Ordering. What to do?

Simple, just provide the type class instance implementation for Ordering for your custom type. If we have Bitcoin again as the custom type, In code, providing the type class instance for Ordering would look like:

object Test extends App {
case class Bitcoin(value: Int)

implicit object bitcoinOrdering extends Ordering[Bitcoin] {
  override def compare(firstValue: Bitcoin, secondValue: Bitcoin): Int = firstValue.value.compareTo(secondValue.value)
}

println(List(Bitcoin(100), Bitcoin(200), Bitcoin(40)).sorted)
}

The bitcoindOrdering is thus the type class instance of Ordering, defined for the custom type, Bitcoin. With this, I could proceed to sort a list of custom Bitcoin datatype.

Conclusion

Type classes usage permeates the Scala ecosystem. As soon as you start to explore more than the OOP side of Scala and want to start using it as better than Java instead of a better Java then the concept of type class is one of the new concepts you definitely will need to understand and appreciate.

The next post in this series would be the concluding post. It is aptly called Exploring Typeclass in Scala: Conclusion


No comments: