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:
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 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.core.extensions.*
val stringEq = String.eq()
stringEq
// arrow.core.extensions.StringKt$eq$1@9c6d1e8
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]
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
.
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 not to 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 which 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
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
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
MonadReader
- can implement the capabilities of the datatype Reader
MonadWriter
- can implement the capabilities of the datatype Writer
MonadState
- can implement the capabilities of the datatype State
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
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