LYAHFGG:
And now, we’re going to take a look at the
Functor
typeclass, which is basically for things that can be mapped over.
Like the book let’s look how it's implemented:
/**
* Functor.
*
* The name is short for "covariant functor".
*
* Must obey the laws defined in cats.laws.FunctorLaws.
*/
@typeclass trait Functor[F[_]] extends functor.Invariant[F] { self =>
def map[A, B](fa: F[A])(f: A => B): F[B]
....
}
Here’s how we can use this:
import cats._, cats.syntax.all._
Functor[List].map(List(1, 2, 3)) { _ + 1 }
// res0: List[Int] = List(2, 3, 4)
Let’s call the above usage the function syntax.
We now know that @typeclass
annotation will automatically turn a map
function into a map
operator.
The fa
part turns into the this
of the method, and the second parameter list will now be
the parameter list of map
operator:
// Supposed generated code
object Functor {
trait Ops[F[_], A] {
def typeClassInstance: Functor[F]
def self: F[A]
def map[B](f: A => B): F[B] = typeClassInstance.map(self)(f)
}
}
This looks almost like the map
method on Scala collection library,
except this map
doesn’t do the CanBuildFrom
auto conversion.
Cats defines a Functor
instance for Either[A, B]
.
(Right(1): Either[String, Int]) map { _ + 1 }
// res1: Either[String, Int] = Right(value = 2)
(Left("boom!"): Either[String, Int]) map { _ + 1 }
// res2: Either[String, Int] = Left(value = "boom!")
Note that the above demonstration only works because Either[A, B]
at the moment
does not implement its own map
.
If I used List(1, 2, 3)
it will call List’s implementation of map
instead of
the Functor[List]
’s map
. Therefore, even though the operator syntax looks familiar,
we should either avoid using it unless you’re sure that standard library doesn’t implement the map
or you’re using it from a polymorphic function.
One workaround is to opt for the function syntax.
Cats also defines a Functor
instance for Function1
.
{
val addOne: Int => Int = (x: Int) => x + 1
val h: Int => Int = addOne map {_ * 7}
h(3)
}
// res3: Int = 28
This is interesting. Basically map
gives us a way to compose functions, except the order is in reverse from f compose g
. Another way of looking at Function1
is that it’s an infinite map from the domain to the range. Now let’s skip the input and output stuff and go to Functors, Applicative Functors and Monoids.
How are functions functors? …
What does the type
fmap :: (a -> b) -> (r -> a) -> (r -> b)
for this instance tell us? Well, we see that it takes a function froma
tob
and a function fromr
toa
and returns a function fromr
tob
. Does this remind you of anything? Yes! Function composition!
Oh man, LYAHFGG came to the same conclusion as I did about the function composition. But wait…
ghci> fmap (*3) (+100) 1
303
ghci> (*3) . (+100) $ 1
303
In Haskell, the fmap
seems to be working in the same order as f compose g
. Let’s check in Scala using the same numbers:
{
(((_: Int) * 3) map {_ + 100}) (1)
}
// res4: Int = 103
Something is not right. Let’s compare the declaration of fmap
and Cats’ map
function:
fmap :: (a -> b) -> f a -> f b
and here’s Cats:
def map[A, B](fa: F[A])(f: A => B): F[B]
So the order is flipped. Here’s Paolo Giarrusso (@blaisorblade)’s explanation:
That’s a common Haskell-vs-Scala difference.
In Haskell, to help with point-free programming, the “data” argument usually comes last. For instance, I can write
map f . map g . map h
and get a list transformer, because the argument order ismap f list
. (Incidentally, map is an restriction of fmap to the List functor).In Scala instead, the “data” argument is usually the receiver. That’s often also important to help type inference, so defining map as a method on functions would not bring you very far: think the mess Scala type inference would make of
(x => x + 1) map List(1, 2, 3)
.
This seems to be the popular explanation.
LYAHFGG:
[We can think of
fmap
as] a function that takes a function and returns a new function that’s just like the old one, only it takes a functor as a parameter and returns a functor as the result. It takes ana -> b
function and returns a functionf a -> f b
. This is called lifting a function.
ghci> :t fmap (*2)
fmap (*2) :: (Num a, Functor f) => f a -> f a
ghci> :t fmap (replicate 3)
fmap (replicate 3) :: (Functor f) => f a -> f [a]
If the parameter order has been flipped, are we going to miss out on this lifting goodness?
Fortunately, Cats implements derived functions under the Functor
typeclass:
@typeclass trait Functor[F[_]] extends functor.Invariant[F] { self =>
def map[A, B](fa: F[A])(f: A => B): F[B]
....
// derived methods
/**
* Lift a function f to operate on Functors
*/
def lift[A, B](f: A => B): F[A] => F[B] = map(_)(f)
/**
* Empty the fa of the values, preserving the structure
*/
def void[A](fa: F[A]): F[Unit] = map(fa)(_ => ())
/**
* Tuple the values in fa with the result of applying a function
* with the value
*/
def fproduct[A, B](fa: F[A])(f: A => B): F[(A, B)] = map(fa)(a => a -> f(a))
/**
* Replaces the `A` value in `F[A]` with the supplied value.
*/
def as[A, B](fa: F[A], b: B): F[B] = map(fa)(_ => b)
}
As you see, we have lift
!
{
val lifted = Functor[List].lift {(_: Int) * 2}
lifted(List(1, 2, 3))
}
// res5: List[Int] = List(2, 4, 6)
We’ve just lifted the function {(_: Int) * 2}
to List[Int] => List[Int]
. Here the other derived functions using the operator syntax:
List(1, 2, 3).void
// res6: List[Unit] = List((), (), ())
List(1, 2, 3) fproduct {(_: Int) * 2}
// res7: List[(Int, Int)] = List((1, 2), (2, 4), (3, 6))
List(1, 2, 3) as "x"
// res8: List[String] = List("x", "x", "x")
LYAHFGG:
In order for something to be a functor, it should satisfy some laws. All functors are expected to exhibit certain kinds of functor-like properties and behaviors. … The first functor law states that if we map the id function over a functor, the functor that we get back should be the same as the original functor.
We can check this for Either[A, B]
.
val x: Either[String, Int] = Right(1)
// x: Either[String, Int] = Right(value = 1)
assert { (x map identity) === x }
The second law says that composing two functions and then mapping the resulting function over a functor should be the same as first mapping one function over the functor and then mapping the other one.
In other words,
val f = {(_: Int) * 3}
// f: Int => Int = <function1>
val g = {(_: Int) + 1}
// g: Int => Int = <function1>
assert { (x map (f map g)) === (x map f map g) }
These are laws the implementer of the functors must abide, and not something the compiler can check for you.