The Functor
typeclass abstracts the ability to map
over the computational context of a type constructor.
Examples of type constructors that can implement instances of the Functor typeclass include Option
, NonEmptyList
,
List
, and many other datatypes that include a map
function with the shape fun F<A>.map(f: (A) -> B): F<B>
where F
refers to Option
, List
, or any other type constructor whose contents can be transformed.
Oftentimes we find ourselves in situations where we need to transform the contents of some datatype. Functor#map
allows
us to safely compute over values under the assumption that they’ll be there returning the transformation encapsulated in the same context.
Consider both Option
and Either
:
Option<A>
allows us to model absence and has two possible states: Some(a: A)
if the value is not absent, and None
to represent an empty case.
In a similar fashion, Either<A, B>
may have two possible cases: Right(b: B)
for computations that succeed, and Left(a: A)
for exceptional cases.
Both Either
and Option
are example datatypes that can be computed over transforming their inner results.
import arrow.*
import arrow.core.*
Either.right(1).map { it * 2 }
Option(1).map { it * 2 }
// Option.Some(2)
Both Either
and Option
include ready-to-use Functor
instances:
import arrow.core.extensions.option.functor.*
val optionFunctor = Option.functor()
import arrow.core.extensions.either.functor.*
val eitherFunctor = Either.functor<Int>()
Mapping over the empty/failed cases is always safe since the map
operation in both Either and Option operate under the bias of those containing success values.
(None as Option<Int>).map { it * 2 }
(Either.left(IllegalArgumentException("")) as Either<Throwable, Int>).map { it * 2 }
// Either.Left(java.lang.IllegalArgumentException: )
Transforms the inner contents.
fun <A, B> Kind<F, A>.map(f: (A) -> B): Kind<F, B>
optionFunctor.run { Option(1).map { it + 1 } }
// Option.Some(2)
Lift a function to the Functor context so it can be applied over values of the implementing datatype.
fun <A, B> lift(f: (A) -> B): (Kind<F, A>) -> Kind<F, B>
val lifted = optionFunctor.lift({ n: Int -> n + 1 })
lifted(Option(1))
// Option.Some(2)
For a full list of other useful combinators available in Functor
, see the Source
Arrow provides FunctorLaws
in the form of test cases for internal verification of lawful instances and third party apps creating their own Functor instances.
Functor
instancesArrow already provides Functor instances for most common datatypes both in Arrow and the Kotlin stdlib. Oftentimes, you may find the need to provide your own for unsupported datatypes.
You may create or automatically derive instances of Functor for your own datatypes which you will be able to use in the context of abstract polymorphic code as demonstrated in the example above.
See Deriving and creating custom typeclass
Additionally, all instances of Applicative
, Monad
, and their MTL variants, implement the Functor
typeclass directly
since they are all subtypes of Functor
Do you like Arrow?
✖