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:
fmap :: (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:
ghci> fmap id [1..5]
[1,2,3,4,5]
ghci> fmap id (Just 3)
Just 3
ghci> fmap id []
[]
ghci> fmap id Nothing
Nothing
- 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:
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: 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:
instance Applicative Maybe where
pure = Just
Nothing <*> _ = Nothing
(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:
ghci> Just (+3) <*> Just 9
Just 12
ghci> pure (+3) <*> Just 10
Just 13
ghci> Just (++"hahah") <*> Nothing
Nothing
ghci> Nothing <*> Just "woot"
Nothing
ghci> pure (+) <*> Just 3 <*> Just 5
Just 8
ghci> pure (+) <*> Just 3 <*> Nothing
Nothing
ghci> pure (+) <*> Nothing <*> Just 5
Nothing
A similar example can be created using Scala and Cats. We first need to add the following libraries to build.sbt
:
libraryDependencies += "org.typelevel" %% "cats-effect" % "2.5.4"
libraryDependencies += "org.typelevel" %% "cats-core" % "2.3.0"
Applicative functor example:
import cats.Functor
trait Applicative[F[_]] extends Functor[F] {
def pure[A](a: => A): F[A]
def <*>[A,B](fa: => F[A])(f: => F[A => B]): F[B]
}
implicit val listApplicative: Applicative[List] =
new Applicative[List] {
def pure[A](a: => A): List[A] = List(a)
def <*>[A,B](fa: => List[A])(f: => List[A => B]): List[B] = for {
elem <- fa
func <- f
} yield func(elem)
def map[A,B](fa: List[A])(f: A => B): List[B] =
<*>(fa)(pure(f))
}
object Applicative {
def apply[F[_]](implicit a: Applicative[F]) = a
}
val add = (x: Int, y: Int) => x + y
val list1 = List(1, 2, 3)
val list2 = List(4, 5, 6)
val 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
:
res0: 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