Watch video

Video tutorial

Typeclasses

Typeclasses are interfaces that define a set of extension functions associated to one type. You may see them referred to 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): Boolean

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

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

import arrow.core.extensions.*

val stringEq = String.eq()

stringEq
// arrow.core.extensions.StringKt$eq$1@4b6dc0d2
stringEq.run {
  "1".eqv("2")
    && "2".neqv("1")
}
// false

And we can 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 familiarize yourself with these before jumping into 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, etc. 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-core-test.

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 to not be part of a specialized package. We will list them by their hierarchy.

General
  • Inject - transformation between datatypes

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

  • Divide - models divide from the divide and conquer pattern

  • Divisible - extends Divide with conquer

  • Decidable - contravariant version of Alternative

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

  • Order - determine whether one object precedes another

  • Hash - compute hash of an object

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

Semigroupal
  • Semigroupal - abstraction over the cartesian product

  • Monoidal - adds an identity element to a semigroupal

Semiring
  • Semiring - can combine or multiplicatively combine two objects together
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

  • Bitraverse - For those structures that are Bifoldable adds the functionality of Traverse in each side of the datatype

  • 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

MTL

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

  • FunctorFilter - can map values that pass a predicate

  • MonadFilter - can sequentially execute values that pass a predicate

  • TraverseFilter - can traverse values that pass a predicate

  • MonadCombine - has a structure that can be combined and split for several datatypes

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

Do you like Arrow?

Arrow Org
<