A datatype is a an abstraction that encapsulates one reusable coding pattern. These solutions have a canonical implementation that is generalised for all possible uses.
A datatype is implemented by a data class, or a sealed hierarchy of data classes and objects.
These datatypes are generalised by having one or several generic parameters,
and to become a type constructor they implement the interface
Kind for these generic parameters.
Datatypes work over themselves, never directly over the values defined by its generic parameters.
Option<A> is a datatype that represents absence.
It has one generic parameter
A, representing the type of the values that
Option may contain.
Option can be specialized for any type
A because this type does not affect its behavior.
Option behaves the same for
To indicate that
Option is a type constructor for all values of
A it implements
OptionOf<A>, which is a typealias of
The implementation of
Option<A> is a sealed class with two subtypes: an object
None and a data class
Some<A> represents presence of the value and thus it has one field containing it, and
None represents absence.
All operations over
Option have to take into account absence or presence,
so there is a function
fold() that takes a continuation function per case,
() -> B and
(A) -> B.
The implementation of
fold() is a simple
when that checks whether
this is a
None or a
Some<A>, and it applies the appropriate continuation function.
All other functions provided by
Option are implemented by using
fold(), making for idiomatic helper functions like
map. These functions work for any value of
B. This way, what
Option does for each individual case of
Int or absence is up to the functions passed by the user.
We will list all the datatypes available in arrow by the module they belong to, and a short description of the coding pattern they abstract.
Core contains the datatypes that are also used by the public API of several typeclasses, so they are always required.
Id - a simple wrapper without any behavior, used mostly for testing
Option - absence of a value, or failure to construct a correct value
Either - an if/else branch in execution
Eval - lazy evaluation of functions with stack safety and memoization
TupleN - a heterogeneous grouping of 2-9 values without creating a named class
Data contains the bulk of the datatypes provided by Arrow. We can separate them onto several categories.
NonEmptyList - a homogeneous list that has at least 1 value
Ior - a branch in execution for three possible paths: one, two, or both
Const - tags a value with a “phantom generic” that’s never instantiated, and it can be used for example to represents units or state
Coproduct - constructs a new composed type from two datatypes, allowing to contain and operate on either one of them
Try - returns the result of executing a block of code that can fail and throw exceptions
Validated - returns the result of aggregating multiple calculations that can fail, and it also aggregates the errors
Kleisli - similar to Dependency Injection and Inversion of Control, it represents a calculation with a dependency on an external context
Reader - same as kleisli but operating over the
Writer - represents calculations that carry over one extra aggregated value, generally a logger or reporter
State - represents a stateful calculation with a carried value that can be read from or modified, like a combination of reader and writer
These types wrap over some of Kotlin’s collections and functions to give them capabilities related to typeclasses provided by Arrow.
A transformer is a special kind of datatype that allows combining two datatypes to give one of them the abstractions of another
OptionT - gives the datatype wrapped the properties of
EitherT - gives the datatype wrapped the properties of
ReaderT - gives the datatype wrapped the properties of
WriterT - gives the datatype wrapped the properties of
StateT - gives the datatype wrapped the properties of
Store - a datatype which holds an initial state and a function for extracting a representation of it.
Moore - a datatype which holds an initial state and can move to new states only when an event of a specific type is dispatched.
Sum - a datatype which holds two comonads and a flag for indicating which one is active. Both sides evolve at the same time.
Day - a datatype which holds two comonads which evolve independently.
All effects are different implementations of the same abstraction: lazy execution of code that can move to other threads and cause exceptions. They are more general than the other datatypes as they combine the abstractions of several of them.
Recursion schemes are an abstraction for structured recursion that ensure runtime safety and provide powerful abstractions for recursive datatypes.