Datatypes

beginner

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.

Example

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 Int, String or DomainUserClass. To indicate that Option is a type constructor for all values of A it implements OptionOf<A>, which is a typealias of Kind<ForOption, A>.

The implementation of Option<A> is a sealed class with two subtypes: an object None and a data class Some<A>. 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 getOrNull, getOrElse, or map. These functions work for any value of A and B. This way, what Option does for each individual case of String, Int or absence is up to the functions passed by the user.

Feel free to explore the implementation of Option and other datatypes to discover their behavior!

Datatypes in Arrow

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

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

Data contains the bulk of the datatypes provided by Arrow. We can separate them onto several categories.

General use
  • 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

Error handling
  • 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

Reader/Writer/State
  • 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 Id datatype

  • 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

Wrappers

These types wrap over some of Kotlin’s collections and functions to give them capabilities related to typeclasses provided by Arrow.

Transformers

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 Option

  • EitherT - gives the datatype wrapped the properties of Either

  • ReaderT - gives the datatype wrapped the properties of Reader

  • WriterT - gives the datatype wrapped the properties of Writer

  • StateT - gives the datatype wrapped the properties of State

Codata

TODO

  • [Cokleisli]

  • [Coreader]

Effects

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.

Free

Free is a general abstraction to represent Domain Specific Languages that can be interpreted using Effects.

Recursion schemes

Recursion schemes are an abstraction for structured recursion that ensure runtime safety and provide powerful abstractions for recursive datatypes.

  • Fix - Models birecursion

  • Mu - Models recursion

  • Nu - Models corecursion