Quick notes on functors
Functors
A functor is a design pattern that allows us to apply a function to a contextualized (wrapped) type. This is accomplished by implementing the map
function:
1fmap :: (a -> b) -> f a -> f b
The function definition receives two parameters:
- a function that takes an instance of A and returns an instance of B and
- a contextualized (wrapped) instance of A (F[A] in scala or f a in haskell), and returns a contextualized instance of B.
Laws:
- If we map the
id
function over a functor, the functor that we get back should be the same as the original functor. For example:
1ghci> fmap id [1..5]
2[1,2,3,4,5]
3ghci> fmap id (Just 3)
4Just 3
5ghci> fmap id []
6[]
7ghci> fmap id Nothing
8Nothing
- Composing two functions and then mapping the result function over a functor should be the same as first mapping one function over the functor and then mapping the other one. That is
fmap (f . g) F = fmap f (fmap g F)
.
Applicative functors
Functors are useful when mapping over functions that take one parameter. For mapping over functions with two or more we use Applicative functors. There are two functions to implement:
1class (Functor f) => Applicative f where
2 pure :: a -> f a
3 (<*>) :: f (a -> b) -> f a -> f b
The <*>
function looks very similar to fmap, but the first parameter takes a functor that has a function in it and the second one has another functor. <*>
extracts the function from the first one and applies it to the second one. The Applicative
instance implementation for Maybe
looks like this:
1instance Applicative Maybe where
2 pure = Just
3 Nothing <*> _ = Nothing
4 (Just f) <*> something = fmap f something
Notice that we are calling fmap
inside the implementation of <*>
. Remember that an Applicative functor is a Functor too, therefore we need to implement fmap
.
Applicative examples:
1ghci> Just (+3) <*> Just 9
2Just 12
3ghci> pure (+3) <*> Just 10
4Just 13
5ghci> Just (++"hahah") <*> Nothing
6Nothing
7ghci> Nothing <*> Just "woot"
8Nothing
9ghci> pure (+) <*> Just 3 <*> Just 5
10Just 8
11ghci> pure (+) <*> Just 3 <*> Nothing
12Nothing
13ghci> pure (+) <*> Nothing <*> Just 5
14Nothing
A similar example can be created using Scala and Cats. We first need to add the following libraries to build.sbt
:
1libraryDependencies += "org.typelevel" %% "cats-effect" % "2.5.4"
2libraryDependencies += "org.typelevel" %% "cats-core" % "2.3.0"
Applicative functor example:
1import cats.Functor
2
3trait Applicative[F[_]] extends Functor[F] {
4 def pure[A](a: => A): F[A]
5 def <*>[A,B](fa: => F[A])(f: => F[A => B]): F[B]
6}
7
8implicit val listApplicative: Applicative[List] =
9 new Applicative[List] {
10 def pure[A](a: => A): List[A] = List(a)
11
12 def <*>[A,B](fa: => List[A])(f: => List[A => B]): List[B] = for {
13 elem <- fa
14 func <- f
15 } yield func(elem)
16
17 def map[A,B](fa: List[A])(f: A => B): List[B] =
18 <*>(fa)(pure(f))
19 }
20
21object Applicative {
22 def apply[F[_]](implicit a: Applicative[F]) = a
23}
24
25val add = (x: Int, y: Int) => x + y
26val list1 = List(1, 2, 3)
27val list2 = List(4, 5, 6)
28
29val x = Applicative[List].<*>(list2)(Functor[List].map(list1)(add.curried))
where the result is the sum of each of the elements of list1
plus list2
:
1res0: List[Int] = List(5, 6, 7, 6, 7, 8, 7, 8, 9)
References
- Lipovača, Miran. “Functors, Applicative Functors and Monoids”. Learn You a Haskell for Great Good!, learnyouahaskell.com/functors-applicative-functors-and-monoids