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:
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.
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]
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
.
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.
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.
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
- literal representation of an objectEq
- structural equality between two objects
Order
- determine whether one object precedes another
Hash
- compute hash of an object
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
- abstraction over the cartesian product
Monoidal
- adds an identity element to a semigroupal
Semiring
- can combine or multiplicatively combine two objects togetherFunctor
- its contents can be mapped
Applicative
- independent execution
ApplicativeError
- recover from errors in independent execution
Monad
- sequential execution
MonadError
- recover from errors in sequential execution
Comonad
- can extract values from it
Bimonad
- both monad and comonad
Bifunctor
- same as functor but for two values in the container
Profunctor
- function composition inside a context
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
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
Do you like Arrow?
✖