avatar

Andres Jaimes

Quick notes on functors

By Andres Jaimes

- 3 minutes read - 569 words

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:

  1. a function that takes an instance of A and returns an instance of B and
  2. a contextualized (wrapped) instance of A (F[A] in scala or f a in haskell), and returns a contextualized instance of B.

Laws:

  1. 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
  1. 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