What would be a good answer to the question of what functors are? To help formulating a reasonably well rounded answer, I have made some slides capturing what I considere particularly useful aspects of the following sources’ exposition of the concept of Functor:
* Bartosz Milewski’s Scala eXchange 2017 Keynote, The Maths Behind Types
*Paul Chiusano and Runar Bjarnason’s book, Functional Programming in Scala
* Debasish Ghosh’s book - Functional and Reactive Domain Modeling
* Christopher Allen, and Julie Moronuki's book - Haskell Programming from First Principles
1. So this is like the first level of category theory. The next level of category theory is functors. And it is like repeating sort of the same story at a higher level. And category theory is just like this: you make up a
story and then you repeat it at some other level. So here we are saying we know what a category is, so suppose we have more than one category, we have several categories. Can we look at a category? Again,
we started with sets and we said let’s forget about what is inside a set, let’s just look at connections between sets, now let’s forget what a category is, this is a crazy idea, let’s forget what is inside a category,
what are the arrows between categories? We want to define categories using arrows between categories. What’s an arrow between categories? …a category is objects and arrows. So an arrow between
categories would map objects to objects and would also map arrows to arrows. That’s a good function on categories. And it turns out that is the beginning of the definition of a functor. A functor is a
mapping between categories. Therefore it is a mapping from objects to objects and arrows to arrows. But it cannot just map arrows randomly. If you have an arrow that goes from A to B in one category then
A will be mapped into F[A] and B will be mapped to F[B].
It makes sense to map the arrow that goes from A to B to the arrow that goes from F[A] to F[B]. So a functor preserves connections. If there is a connection from A to B in one category then in the target
category there will be a connection between F[A] to F[B]. So it just preserves all the connections. Which makes sense. If we want to have a mapping between categories we don’t want to have random
mappings, we want to have mappings that are categorical, that preserve the structure. Now let’s go back to programming. What is a functor in programming? First of all, in programming we only have one
category…the category of types and functions but that is good enough. We can have a functor that goes from a category to the same category…so in programming we concentrate on these functors that
don’t leave the category, they stay in the same category, so the mapping of an object, what does it mean? Objects are types so we have to have a mapping from type to type, which is called a type
constructor: you give me a type and I’ll give you a new type. For instance, List is a functor. You give me a type, let’s say Int, and I’ll give you a List[Int]. So there is a mapping that is a type constructor.
What about arrows? If I have a function that goes from object A to B, or from type A to type B, so here is this f, I want to map it to an
arrow that will go from F[A] to F[B] and this mapping is called fmap. If you have seen a functor in Scala libraries, it has this function called
fmap, a higher order function.
So if you give me a function that goes from A to B I give you a function that goes from List[A] to List[B].
How is it implemented? Well, it applies the function to every element of the list. So that’s an example of a
functor. List is really a functor. It not only maps types to types, but also maps functions to functions. And
here is the definition written in Scala.
Bartosz Milewski
https://twitter.com/BartoszMilewski
Bartosz Milewski explaining Functors in category theory as part of his Scala eXchange 2017 Keynote: The Maths Behind Types
a functor is a mapping between categories…
a mapping from type to type, which is called a type constructor…
a mapping from function to function called fmap…
e.g. List is a functor. You give me a type, e.g. Int, and I’ll give you a type List[Int].
You give me a function that goes from A to B and I give you a function that goes
from List[A] to List[B]
https://skillsmatter.com/skillscasts/10179-the-maths-behind-types#video
2. “In functional programming an effect adds some additional capabilities to a computation. And since we are dealing with a statically typed language, these capabilities come in the form
of more power from the type system. An effect is modeled usually in the form of a type constructor that constructs types with these additional capabilities. Say we have any type A and
we would like to add the capability of aggregation, so that we can treat a collection of A as a separate type. We do this by constructing a type List[A] (for which the corresponding type
constructor is List), which adds the effect of aggregation on A. Similarly we can have Option[A] that adds the capability of optionality for the type A.”
You can say that a functor abstracts the capability of mapping over a data structure with an ordinary function. Now you may ask what advantage does that bring in the context of domain
modeling? The answer is simple – it generalizes your model and makes some parts of your model behavior reusable across components.
…
A functor gives you the capability to lift a pure function of one argument into a specific context. Have a look at the definition of map in the trait Functor. The pure function f: A => B is
lifted into the context of F, transforming F[A] to F[B]. The context here is the type constructor F, also known as the effect of the computation.
Have you ever looked at the signatures of map in some of the classes of the Scala standard library like List, Option or Try?...the functions have the same
signature if you ignore the context of application. In terms of functional programming we call this context an effect.
def map[B](f: (A) => B): List[B] // map for List[A]
def map[B](f: (A) => B): Option[B] // map for Option[A]
def map[B](f: (A) => B): Try[B] // map for Try[A]
List[A] provides an effect of repetition of elements of type A, Option[A] models the effect of uncertainty where the element of type A can either exist or not.
Try[A] provides the effect of reification of exceptions.
So if you can abstract the effects out of the signatures, then all you have is a function map that lifts the function f into an effect F[_]. Note each of the
effects that we are talking about here is modeled using a type constructor – hence F[_] and not a simple F. We can extract this computation structure into a
separate trait of its own and call it the Functor.
trait Functor[F[_]] {
def map[A, B](a: F[A])(f: A => B): F[B]
}
Since List, Option etc. all share this computational behavior we can make them functors by implementing an interpreter of the functor algebra for each
of them.
def ListFunctor: Functor[List] = new Functor[List] {
def map[A, B](a: List[A])(f: A => B): List[B] = a map f
}
def OptionFunctor: Functor[Option] = new Functor[Option] {
def map[A, B](a: Option[A])(f: A => B): Option[B] = a map f
}
Debasish Ghosh
https://twitter.com/debasishg
Debasish Ghosh explaining Functors in his book Functional and Reactive Domain Modeling - https://www.manning.com/books/functional-and-reactive-domain-modeling
3. 4.3.2 Option composition, lifting, and wrapping exception-oriented APIs
It may be easy to jump to the conclusion that once we start using Option, it infects our entire code base. One can
imagine how any callers of methods that take or return Option will have to be modified to handle either Some or None.
But this doesn’t happen, and the reason is that we can lift ordinary functions to become functions that operate on
Option.
For example, the map function lets us operate on values of type Option[A] using a function of type A => B, returning
Option[B]. Another way of looking at this is that map turns a function f of type A => B into a function of type
Option[A] => Option[B]. Let’s make this explicit:
def lift[A,B](f: A => B): Option[A] => Option[B] = _ map f
This tells us that any function that we already have lying around can be transformed (via lift) to operate within the
context of a single Option value. Let’s look at an example:
val absO: Option[Double] => Option[Double] = lift(math.abs)
The math object contains various standalone mathematical functions including abs, sqrt, exp, and so on. We didn’t
need to rewrite the math.abs function to work with optional values; we just lifted it into the Option context after
the fact. We can do this for any function.
EXERCISE 3.18
Write a function map that generalizes modifying each element in a list while maintaining the structure of the list.
Here is its signature:
def map[A,B](as: List[A])(f: A => B): List[B]
Some ‘Functional Programming in Scala’ excerpts explaining Functors https://www.manning.com/books/functional-programming-in-scala
by Paul Chiusano and
Runar Bjarnason
https://twitter.com/runarorama
https://twitter.com/pchiusano
4. 11.1 Functors: generalizing the map function
In parts 1 and 2, we implemented several different combinator libraries. In each case, we proceeded by writing a small set of primitives and then a
number of combinators defined purely in terms of those primitives. We noted some similarities between derived combinators across the libraries we
wrote. For instance, we implemented a map function for each data type, to lift a function taking one argument “into the context of” some data
type. For Gen, Parser, and Option, the type signatures were as follows:
def map[A,B](ga: Gen[A])(f: A => B): Gen[B]
def map[A,B](pa: Parser[A])(f: A => B): Parser[B]
def map[A,B](oa: Option[A])(f: A => B): Option[B]
These type signatures differ only in the concrete data type (Gen, Parser, or Option ). We can capture as a Scala trait the idea of “a data type
that implements map”:
trait Functor[F[_]] { def map[A,B](fa: F[A])(f: A => B): F[B] }
Here we’ve parameterized map on the type constructor, F[_], much like we did with Foldable in the previous chapter. Instead of picking a
particular F[_], like Gen or Parser, the Functor trait is parametric in the choice of F. Here’s an instance for List :
val listFunctor = new Functor[List] { def map[A,B](as: List[A])(f: A => B): List[B] = as map f }
We say that a type constructor like List (or Option, or F ) is a functor, and the Functor[F] instance constitutes proof that F is in fact a
functor. What can we do with this abstraction? As we did in several places throughout this book, we can discover useful functions just by
playing with the operations of the interface, in a purely algebraic way…
11.1.1 Functor laws
Whenever we create an abstraction like Functor, we should consider not only what abstract methods it should have, but which laws we expect to hold for the implementations.
…
For Functor , we’ll stipulate the familiar law we first introduced in chapter 7…
map(x)(a => a) == x
In other words, mapping over a structure x with the identity function should itself be an identity. This law is quite natural, and we noticed later in part 2 that this law was satisfied by the
map functions of other types besides Par. This law (and its corollaries given by parametricity) capture the requirement that map(x) “preserves the structure” of x. Implementations
satisfying this law are restricted from doing strange things like throwing exceptions, removing the first element of a List, converting a Some to None, and so on. Only the elements of
the structure are modified by map ; the shape or structure itself is left intact. Note that this law holds for List, Option, Par, Gen, and most other data types that define map !
https://www.manning.com/books/functional-programming-in-scala
by Paul Chiusano and
Runar Bjarnason
https://twitter.com/runarorama
https://twitter.com/pchiusano
5. What’s a functor?
A functor is a way to apply a function over or around some structure that we don’t want to alter. That is,
we want to apply the function to the value that is “inside” some structure and leave the structure alone.
That’s why it is most common to introduce functor by way of fmapping over lists, as we did back in the Lists
chapter. The function gets applied to each value inside the list, and the list structure remains. A good way to
relate “not altering the structure” to lists is that the length of the list after mapping a function over it will
always be the same. No elements are removed or added, only transformed.
Monads sort of steal the Haskell spotlight, but you can do more with Functor and Applicative than many people
realize. Also, understanding Functor and Applicative is important to a deep understanding of Monad.
The definition of the Functor typeclass in Haskell looks like this:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Functor Laws
Instances of the Functor typeclass should abide by two basic laws. Understanding these laws is critical for
understanding Functor and writing typeclass instances that are composable and easy to reason about.
Identity
The first law is the law of identity: fmap id == id
If we fmap the identity function, it should have the same result as passing our value to identity. We
shouldn’t be changing any of the outer structure 𝑓 that we’re mapping over by mapping id.
Composition
The second law for Functor is the law of composition: fmap (f . g) == fmap f . fmap g
This concerns the composability of fmap. If we compose two functions, 𝑓 and 𝑔, and fmap that over some
structure, we should get the same result as if we fmapped them and then composed them… If an
implementation of fmap doesn’t do that, it’s a broken functor.
http://haskellbook.com/
By Christopher Allen,
Julie Moronuki
https://twitter.com/bitemyapp
https://twitter.com/argumatronic
Some ‘Haskell Programming from First Principles’ excerpts explaining Functors