Watch video

Video tutorial

Typeclasses

beginner

Typeclasses are interfaces that define a set of extension functions associated to one type. You may see them referred as “extension interfaces”.

The other purpose of these interfaces, like with any other unit of abstraction, is to have a single shared definition of a common API and behavior shared across many types in different libraries and codebases.

What differentiates FP from OOP is that these interfaces are meant to be implemented outside of their types, instead of by the types. Now, the association is done using generic parametrization rather than subclassing by implementing the interface. This has multiple benefits:

  • Typeclasses can be implemented for any class, even those not in the current project
  • You can treat typeclass implementations as stateless parameters because they’re just a collection of functions
  • You can make the extensions provided by a typeclass for the type they’re associated with by using functions like run and with.

You can read all about how Arrow implements typeclasses in the glossary. If you’d like to use typeclasses effectively in your client code you can head to the docs entry about dependency injection.

Example

Let’s define a typeclass for the behavior of equality between two objects, and we’ll call it Eq:

interface Eq<T> {
   fun T.eqv(b: T)

   fun T.neqv(b: T) =
    !eqv(b)
}

For this short example we will make available the scope of the typeclass Eq implemented for the type String, by using run. This will make all the Eq extension functions, such as eqv and neqv, available inside the run block.

import arrow.instances.*

val stringEq = String.eq()

stringEq
// arrow.instances.StringKt$eq$1@2885d0d8
stringEq.run {
  "1".eqv("2")
    && "2".neqv("1")
}
// false

and even use it as parametrization in a function call


fun <F> List<F>.filter(other: F, EQ: Eq<F>) =
  this.filter { EQ.run { it.eqv(other) } }

listOf("1", "2", "3").filter("2", String.eq())
// [2]

listOf(1, 2, 3).filter(3, Eq { one, other -> one < other })
// [1, 2]

Structure

This section uses concepts explained in the glossary like Kind, make sure to check them beforehand or else jump to the next section.

A few typeclasses can be defined for values, like Eq above, and the rest are defined for type constructors defined by Kind<F, A> using a For- marker. All methods inside a typeclass will have one of two shapes:

  • Constructor: create a new Kind<F, A> from a value, a function, an error… Some examples are just, raise, async, defer, or binding.

  • Extensions: add new functionality to a value A or a container Kind<F, A>, provided by an extension function. For example, map, eqv, show, traverse, sequence, or combineAll.

You can use typeclasses as a DSL to access new extension functions for an existing type, or treat them as an abstraction placeholder for any one type that can implement the typeclass. The extension functions are scoped within the typeclass so they do not pollute the global namespace!

To assure that a typeclass has been correctly implemented for a type, Arrow provides a test suite called the “laws” per typeclass. These test suites are available in the module arrow-tests.

Typeclasses provided by Arrow

We will list all the typeclasses provided in Arrow grouped by the module they belong to, and a short description of the behavior they abstract.

Typeclasses

The package typeclasses contains all the typeclass definitions that are general enough not to be part of a specialized package. We will list them by their hierarchy.

General
  • Inject - transformation between datatypes

  • Alternative - has an structure that contains either of two values

Show
  • Show - literal representation of an object
Eq
  • Eq - structural equality between two objects

  • Order - determine whether one object precedes another

Semigroup
  • Semigroup - can combine two objects together

  • SemigroupK - can combine two datatypes together

  • Monoid - combinable objects have an empty value

  • MonoidK - combinable datatypes have an empty value

Functor
Foldable
  • Foldable - has a structure from which a value can be computed from visiting each element

  • Bifoldable - same as foldable, but for structures with more than one possible type, like either

  • Reducible - structures that can be combined to a summary value

  • Traverse - has a structure for which each element can be visited and get applied an effect

Effects

Effects provides a hierarchy of typeclasses for lazy and asynchronous execution.

  • MonadDefer - can evaluate functions lazily

  • Async - can be created using an asynchronous callback function

  • Effect - can extract a value from an asynchronous function

MTL

The Monad Template Library module gives more specialized version of existing typeclasses

Optics

  • At - provides a Lens for a structure with an indexable focus

  • FilterIndex - provides a Traversal for a structure with indexable foci that satisfy a predicate

  • Index - provides an Optional for a structure with an indexable optional focus

  • Each - provides a Traversal

Recursion

  • Corecursive - traverses a structure forwards from the starting case

  • Recursive - traverses a structure backwards from the base case

  • Birecursive - it is both recursive and corecursive