|
| 1 | +--- |
| 2 | +layout: docs |
| 3 | +title: Functor |
| 4 | +permalink: /docs/typeclasses/functor/ |
| 5 | +--- |
| 6 | + |
| 7 | +## Functor |
| 8 | + |
| 9 | +The `Functor` typeclass abstracts the ability to `map` over the computational context of a type constructor. |
| 10 | +Examples of type constructors that can implement instances of the Functor typeclass include `Option`, `NonEmptyList`, |
| 11 | +`List` and many other datatypes that include a `map` function with the shape `fun F<B>.map(f: (A) -> B): F<B>` where `F` |
| 12 | +refers to `Option`, `List` or any other type constructor whose contents can be transformed. |
| 13 | + |
| 14 | +### Example |
| 15 | + |
| 16 | +Often times we find ourselves in situations where we need to transform the contents of some datatype. `Functor#map` allows |
| 17 | +us to safely compute over values under the assumption that they'll be there returning the transformation encapsulated in the same context. |
| 18 | + |
| 19 | +Consider both `Option` and `Try`: |
| 20 | + |
| 21 | +`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. |
| 22 | + |
| 23 | +In a similar fashion `Try<A>` may have two possible cases `Success(a: A)` for computations that succeed and `Failure(e: Throwable)` if they fail with an exception. |
| 24 | + |
| 25 | +Both `Try` and `Option` are example datatypes that can be computed over transforming their inner results. |
| 26 | + |
| 27 | +```kotlin:ank |
| 28 | +import kategory.* |
| 29 | +
|
| 30 | +Try { "1".toInt() }.map { it * 2 } |
| 31 | +Option(1).map { it * 2 } |
| 32 | +``` |
| 33 | + |
| 34 | +Both `Try` and `Option` include ready to use `Functor` instances: |
| 35 | + |
| 36 | +```kotlin:ank |
| 37 | +val optionFunctor = Option.functor() |
| 38 | +``` |
| 39 | + |
| 40 | +```kotlin:ank |
| 41 | +val tryFunctor = Try.functor() |
| 42 | +``` |
| 43 | + |
| 44 | +Mapping over the empty/failed cases is always safe since the `map` operation in both Try and Option operate under the bias of those containing success values |
| 45 | + |
| 46 | +```kotlin:ank |
| 47 | +Try { "x".toInt() }.map { it * 2 } |
| 48 | +none<Int>().map { it * 2 } |
| 49 | +``` |
| 50 | + |
| 51 | +Kategory allows abstract polymorphic code that operates over the evidence of having an instance of a typeclass available. |
| 52 | +This enables programs that are not coupled to specific datatype implementations. |
| 53 | +The technique demonstrated below to write polymorphic code is available for all other [Typeclasses](/docs/typeclasses) beside `Functor`. |
| 54 | + |
| 55 | +```kotlin:ank |
| 56 | +inline fun <reified F> multiplyBy2(fa: HK<F, Int>, FT: Functor<F> = functor()): HK<F, Int> = |
| 57 | + FT.map(fa, { it * 2 }) |
| 58 | +
|
| 59 | +multiplyBy2<OptionHK>(Option(1)) // Option(1) |
| 60 | +multiplyBy2<TryHK>(Try { 1 }) |
| 61 | +``` |
| 62 | + |
| 63 | +In the example above we've defined a function that can operate over any data type for which a `Functor` instance is available. |
| 64 | +And then we applied `multiplyBy2` to two different datatypes for which Functor instances exist. |
| 65 | +This technique applied to other Typeclasses allows users to describe entire programs in terms of behaviors typeclasses removing |
| 66 | +dependencies to concrete data types and how they operate. |
| 67 | + |
| 68 | +This technique does not enforce inheritance or any kind of subtyping relationship and is frequently known as [`ad-hoc polymorphism`](https://en.wikipedia.org/wiki/Ad_hoc_polymorphism) |
| 69 | +and frequently used in programming languages that support [Typeclasses](https://en.wikipedia.org/wiki/Type_class) and [Higher Kinded Types](https://en.wikipedia.org/wiki/Kind_(type_theory)). |
| 70 | + |
| 71 | +Entire libraries and applications can be written without enforcing consumers to use the lib author provided datatypes but letting |
| 72 | +users provide their own provided there is typeclass instances for their datatypes. |
| 73 | + |
| 74 | +### Main Combinators |
| 75 | + |
| 76 | +#### map |
| 77 | + |
| 78 | +Transforms the inner contents |
| 79 | + |
| 80 | +`fun <A, B> map(fa: HK<F, A>, f: (A) -> B): HK<F, B>` |
| 81 | + |
| 82 | +```kotlin:ank |
| 83 | +optionFunctor.map(Option(1), { it + 1 }) |
| 84 | +``` |
| 85 | + |
| 86 | +#### lift |
| 87 | + |
| 88 | +Lift a function to the Functor context so it can be applied over values of the implementing datatype |
| 89 | + |
| 90 | +`fun <A, B> lift(f: (A) -> B): (HK<F, A>) -> HK<F, B>` |
| 91 | + |
| 92 | +```kotlin:ank |
| 93 | +val lifted = optionFunctor.lift({ n: Int -> n + 1 }) |
| 94 | +lifted(Option(1)) |
| 95 | +``` |
| 96 | + |
| 97 | +#### Other combinators |
| 98 | + |
| 99 | +For a full list of other useful combinators available in `Functor` see the [`KDoc](/kdocs/typeclasses/functor) |
| 100 | + |
| 101 | +### Syntax |
| 102 | + |
| 103 | +#### HK<F, A>#map |
| 104 | + |
| 105 | +Maps over any higher kinded type constructor for which a functor instance is found |
| 106 | + |
| 107 | +```kotlin:ank |
| 108 | +Try { 1 }.map({ it + 1 }) |
| 109 | +``` |
| 110 | + |
| 111 | +#### ((A) -> B)#lift |
| 112 | + |
| 113 | +Lift a function into the functor context |
| 114 | + |
| 115 | +```kotlin:ank |
| 116 | +val f = { n: Int -> n + 1 }.lift<OptionHK, Int, Int>() |
| 117 | +f(Option(1)) |
| 118 | +``` |
| 119 | + |
| 120 | + |
| 121 | +### Laws |
| 122 | + |
| 123 | +Kategory provides [`FunctorLaws`](/docs/typeclasses/laws#functorlaws) in the form of test cases for internal verification of lawful instances and third party apps creating their own Functor instances. |
| 124 | + |
| 125 | +#### Creating your own `Functor` instances |
| 126 | + |
| 127 | +[Kategory already provides Functor instances for most common datatypes](#datatypes) both in Kategory and the Kotlin stdlib. |
| 128 | +Often times you may find the need to provide your own for unsupported datatypes. |
| 129 | + |
| 130 | +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 polymorfic code |
| 131 | +as demonstrated in the [example](#example) above. |
| 132 | + |
| 133 | +See [Deriving and creating custom typeclass] |
| 134 | + |
| 135 | +### Data types |
| 136 | + |
| 137 | +Thw following datatypes in Kategory provide instances that adhere to the `Functor` typeclass. |
| 138 | + |
| 139 | +- [Cofree](/docs/datatypes/cofree) |
| 140 | +- [Coproduct](/docs/datatypes/coproduct) |
| 141 | +- [Coyoneda](/docs/datatypes/coyoneda) |
| 142 | +- [Either](/docs/datatypes/either) |
| 143 | +- [EitherT](/docs/datatypes/eitherT) |
| 144 | +- [FreeApplicative](/docs/datatypes/FreeApplicative) |
| 145 | +- [Function1](/docs/datatypes/Function1) |
| 146 | +- [Ior](/docs/datatypes/Ior) |
| 147 | +- [Kleisli](/docs/datatypes/Kleisli) |
| 148 | +- [OptionT](/docs/datatypes/OptionT) |
| 149 | +- [StateT](/docs/datatypes/StateT) |
| 150 | +- [Validated](/docs/datatypes/Validated) |
| 151 | +- [WriterT](/docs/datatypes/WriterT) |
| 152 | +- [Yoneda](/docs/datatypes/Yoneda) |
| 153 | +- [Const](/docs/datatypes/Const) |
| 154 | +- [Try](/docs/datatypes/Try) |
| 155 | +- [Eval](/docs/datatypes/Eval) |
| 156 | +- [IO](/docs/datatypes/IO) |
| 157 | +- [NonEmptyList](/docs/datatypes/NonEmptyList) |
| 158 | +- [Id](/docs/datatypes/Id) |
| 159 | +- [Function0](/docs/datatypes/Function0) |
| 160 | + |
| 161 | +Additionally all instances of [`Applicative`](/docs/typeclasses/applicative), [`Monad`](/docs/typeclasses/monad) and their MTL variants implement the `Functor` typeclass directly |
| 162 | +since they are all subtypes of `Functor` |
0 commit comments