📜 ⬆️ ⬇️

Type classes in Scala


Recently, in the community of Scala-developers began to pay more and more attention to the design pattern Type classes . It helps to deal with unnecessary dependencies and at the same time make the code cleaner. Below with examples, I will show how to apply it and what advantages this approach has.

It can be designed not only for Scala programmers, but also for Java - perhaps they will get an answer for themselves, as, at least in theory, the solution looks for many applied tasks, in which the components are not interconnected and are extensible after writing . It may also be of interest to developers and designers in any other languages.

Prerequisites


Everyone was faced with a task when some class needed to add behavior associated with this class: comparison, serialization, reading from serialized, creating an instance of the class, rendering, this may be another behavior that your very useful library requires.

To solve these problems in the Java-world there are several methods:

So, in essence, we now have two alternatives: either rigidly associate third-party functionality with the class or take the functionality into a separate object and then take care of its transfer. As an example, take the users of the application. This is how adding the ability to compare our users may look:
')
//  ,    def sort[T <: Comparable[T]](elements: Seq[T]): Seq[T] //   class Person(val id: Id[Person], val name: String) extends Comparable[Person] { def compareTo(Person anotherPerson): Int = { return this.name.compareTo(anotherPerson.name) } } //  sort(persons) 

Here it is good that the concrete use of sorting looks very clear. But the comparison logic is rigidly connected with the class (and probably no one likes that the model objects depend, for example, on the XML library that writes and writes to the database). In addition, another problem appears: it is impossible to define more than one method of comparison - that is, if tomorrow we want to compare users by id elsewhere in the program, we will fail, rewriting the comparison method for closed classes also fails.

Java has a Comparator class for this purpose, it allows you to get more flexibility:

 //  ,    def sort[T](elements: Seq[T], comparator: Comparator[T]): Seq[T] //   class Person(val id: Id[Person], val name: String) trait Comparator[T] { def compare(object1: T, object2 T): Int } class PersonNameComparator extends Comparator[Person] { def compare(onePerson: Person, anotherPerson: Person): Int = { return onePerson.name.compareTo(anotherPerson.name) } } //  val nameComparator = new PersonNameComparator() sort(persons, nameComparator) 

Now you can define several comparison methods, the comparison logic is no longer associated with the model class. Also, nothing prevents you from writing your comparator, even if the class and definition of the sorting algorithm is closed to us. It is worth noting that the sorting call has become somewhat more difficult.

The use of factories and adapters implies a similar lifecycle management and transfer of their instances. And still it is necessary to remember all these cool titles for, in general, one typical task.

And here Type classes appear


This is where the Scala option comes to help implicitly pass parameters. Let's take as a basis our previous example, but we will define the algorithm differently, we will pass the Comparator implicitly:

 def sort[T](elements: Seq[T])(implicit comparator: Comparator[T]): Seq[T] 

This means that if there is a suitable Comparator in the field of visibility with the desired value of the type parameter, it will be automatically substituted into the compiler method without additional efforts from the programmer. So, put in the scope of a suitable Comparator:

 implicit val personNameComparator = Comparator[Person] { def compare(onePerson: Person, anotherPerson: Person): Int = { return onePerson.name.compareTo(anotherPerson.name) } } 

The key word implicit is responsible for the fact that the value will be used in the substitution. It is important to note that our implicit implementations must be stateless, because in the course of the program, only one instance of each type is created.

Now the sorting can be called as well as it was in the original version with the implementation of Comparable:

 //   sort(persons) 

And this is despite the fact that the method of comparison itself is in no way connected with the object of the model. We can put in the field of visibility any comparison methods and they will be used for our objects.

A slightly more interesting variant arises when one wants the type parameter to be itself typed. That is, for Map [Id [Person], List [Permission]], we want MapJsonSerializer , IdJsonSerializer , ListJsonSerializer and PermissionJsonSerializer , which can be reused in any order, not PersonPermissionsMapJsonSerializer , whose analogues we will write each time. In this case, the method for defining an implicit object is slightly different, now we do not have an object, but a function:

 implicit def ListComparator[V](implicit comparator: Comparator[V]) = new Comparator[List[V]] { def compare(oneList: List[V], anotherList: List[V]): Int = { for((one, another) <- oneList.zip(anotherList)) { val elementsCompared = comparator,compare(one, another) if(elementsCompared > 0) return 1 else if(elementsCompared < 0) return -1 } return 0 } } 

That's the whole method. The best thing is that you can get all sorts of JSONParser, XMLSerializers together with PersonFactory, without storing the corresponding classes and objects anywhere - the Scala compiler does everything for us.

In TKS, we use such a method, for example, to wrap exceptions in the classes of our model. Type classes allow you to create instances of exceptions of the type in which you want to wrap an abandoned block. If this were done by the traditional method, we would have to create and transfer a factory of exceptions, so it would have been easier to throw exceptions in the old-fashioned way. Now everything is beautiful.

What's next?


In fact, the Type classes theme does not end here and as a continuation I recommend the video of Typeclasses in Scala .

Even more fundamentally, the question is set forth in this article .

Source: https://habr.com/ru/post/147759/


All Articles