Learn myself some Scala 3, episode 3: type classes
Update: Thanks to feedback from @kai_nyasha and @loic_d I have removed the now unnecessary separate type class syntax stuff.
Type classes are implemented as a three-part pattern in Scala 2: we use a polymorphic trait with one or more abstract methods for the type class and different applications of the implicit
keyword to provide the type class syntax and the type class instances:
1 | // The type class is a polymorphic trait |
In Scala 3 the approach is simplified: all we need are extension methods which we covered in the previous blog post and the new given instances.
Type Classes
As in Scala 2 type classes are polymorphic traits with one or more abstract methods. Yet instead of “normal” methods we already provide the syntax via extension methods. Then every type, which has a given instance (see below) of the type class in scope, acquires the extension methods.
To define a semigroup type class we use the new experimental brace-less syntax, which might or might not make it into Scala 3:
1 | package lmss |
Given Instances
To provide type class instances we use given instances, which can be anonymous, implementing the abstract type class methods:
1 | package lmss |
Given Imports
Unless the respective given instances are already in scope, e.g. locally or via inheritance, they have to be imported. Other than in Scala 2 we cannot use a standard import clause, but instead we have to use the new given imports:
1 | scala> import lmss.instances.semigroup.{ given _ } |
The above imports all given instances from the semigroup
package. We can also select specific types to be imported:
1 | scala> import lmss.instances.semigroup.{ given lmss.SemiGroup[?] } |
Conclusion
Compared to Scala 2 type classes are easier and more straightforward to implement in Scala 3: no more implicit classes for the syntax needed, just the type class as a polymorphic trait with extension methods and type class instances as given instances.