lean4-htt/doc/monads/functors.lean

227 lines
No EOL
8.3 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-!
# Functor
A `Functor` is any type that can act as a generic container that allows you to transform the
underlying values inside the container using a function, so that the values are all updated, but the
structure of the container is the same. This is called "mapping".
A List is one of the most basic examples of a `Functor`.
A list contains zero or more elements of the same, underlying type. When you `map` a function over
a list, you create a new list with the same number of elements, where each has been transformed by
the function:
-/
#eval List.map (λ x => toString x) [1,2,3] -- ["1", "2", "3"]
-- you can also write this using dot notation on the List object
#eval [1,2,3].map (λ x => toString x) -- ["1", "2", "3"]
/-!
Here we converted a list of natural numbers (Nat) to a list of strings where the lambda function
here used `toString` to do the transformation of each element. Notice that when you apply `map` the
"structure" of the object remains the same, in this case the result is always a `List` of the same
size.
Note that in Lean a lambda function can be written using `fun` keyword or the unicode
symbol `λ` which you can type in VS code using `\la `.
List has a specialized version of `map` defined as follows:
-/
def map (f : α → β) : List α → List β
| [] => []
| a::as => f a :: map f as
/-!
This is a very generic `map` function that can take any function that converts `(α → β)` and use it
to convert `List α → List β`. Notice the function call `f a` above, this application of `f` is
producing the converted items for the new list.
Let's look at some more examples:
-/
-- List String → List Nat
#eval ["elephant", "tiger", "giraffe"].map (fun s => s.length)
-- [8, 5, 7]
-- List Nat → List Float
#eval [1,2,3,4,5].map (fun s => (s.toFloat) ^ 3.0)
-- [1.000000, 8.000000, 27.000000, 64.000000, 125.000000]
--- List String → List String
#eval ["chris", "david", "mark"].map (fun s => s.capitalize)
-- ["Chris", "David", "Mark"]
/-!
Another example of a functor is the `Option` type. Option contains a value or nothing and is handy
for code that has to deal with optional values, like optional command line arguments.
Remember you can construct an Option using the type constructors `some` or `none`:
-/
#check some 5 -- Option Nat
#eval some 5 -- some 5
#eval (some 5).map (fun x => x + 1) -- some 6
#eval (some 5).map (fun x => toString x) -- some "5"
/-!
Lean also provides a convenient short hand syntax for `(fun x => x + 1)`, namely `(· + 1)`
using the middle dot unicode character which you can type in VS code using `\. `.
-/
#eval (some 4).map (· * 5) -- some 20
/-!
The `map` function preserves the `none` state of the Option, so again
map preserves the structure of the object.
-/
def x : Option Nat := none
#eval x.map (fun x => toString x) -- none
#check x.map (fun x => toString x) -- Option String
/-!
Notice that even in the `none` case it has transformed `Option Nat` into `Option String` as
you see in the `#check` command.
## How to make a Functor Instance?
The `List` type is made an official `Functor` by the following type class instance:
-/
instance : Functor List where
map := List.map
/-!
Notice all you need to do is provide the `map` function implementation. For a quick
example, let's supposed you create a new type describing the measurements of a home
or apartment:
-/
structure LivingSpace (α : Type) where
totalSize : α
numBedrooms : Nat
masterBedroomSize : α
livingRoomSize : α
kitchenSize : α
deriving Repr, BEq
/-!
Now you can construct a `LivingSpace` in square feet using floating point values:
-/
abbrev SquareFeet := Float
def mySpace : LivingSpace SquareFeet :=
{ totalSize := 1800, numBedrooms := 4, masterBedroomSize := 500,
livingRoomSize := 900, kitchenSize := 400 }
/-!
Now, suppose you want anyone to be able to map a `LivingSpace` from one type of measurement unit to
another. Then you would provide a `Functor` instance as follows:
-/
def LivingSpace.map (f : α → β) (s : LivingSpace α) : LivingSpace β :=
{ totalSize := f s.totalSize
numBedrooms := s.numBedrooms
masterBedroomSize := f s.masterBedroomSize
livingRoomSize := f s.livingRoomSize
kitchenSize := f s.kitchenSize }
instance : Functor LivingSpace where
map := LivingSpace.map
/-!
Notice this functor instance takes `LivingSpace` and not the fully qualified type `LivingSpace SquareFeet`.
Notice below that `LivingSpace` is a function from Type to Type. For example, if you give it type `SquareFeet`
it gives you back the fully qualified type `LivingSpace SquareFeet`.
-/
#check LivingSpace -- Type → Type
/-!
So the `instance : Functor` then is operating on the more abstract, or generic `LivingSpace` saying
for the whole family of types `LivingSpace α` you can map to `LivingSpace β` using the generic
`LivingSpace.map` map function by simply providing a function that does the more primitive mapping
from `(f : α → β)`. So `LivingSpace.map` is a sort of function applicator.
This is called a "higher order function" because it takes a function as input
`(α → β)` and returns another function as output `F α → F β`.
Notice that `LivingSpace.map` applies a function `f` to convert the units of all the LivingSpace
fields, except for `numBedrooms` which is a count (and therefore is not a measurement that needs
converting).
So now you can define a simple conversion function, let's say you want square meters instead:
-/
abbrev SquareMeters := Float
def squareFeetToMeters (ft : SquareFeet ) : SquareMeters := (ft / 10.7639104)
/-!
and now bringing it all together you can use the simple function `squareFeetToMeters` to map
`mySpace` to square meters:
-/
#eval mySpace.map squareFeetToMeters
/-
{ totalSize := 167.225472,
numBedrooms := 4,
masterBedroomSize := 46.451520,
livingRoomSize := 83.612736,
kitchenSize := 37.161216 }
-/
/-!
Lean also defines custom infix operator `<$>` for `Functor.map` which allows you to write this:
-/
#eval (fun s => s.length) <$> ["elephant", "tiger", "giraffe"] -- [8, 5, 7]
#eval (fun x => x + 1) <$> (some 5) -- some 6
/-!
Note that the infix operator is left associative which means it binds more tightly to the
function on the left than to the expression on the right, this means you can often drop the
parentheses on the right like this:
-/
#eval (fun x => x + 1) <$> some 5 -- some 6
/-!
Note that Lean lets you define your own syntax, so `<$>` is nothing special.
You can define your own infix operator like this:
-/
infixr:100 " doodle " => Functor.map
#eval (· * 5) doodle [1, 2, 3] -- [5, 10, 15]
/-!
Wow, this is pretty powerful. By providing a functor instance on `LivingSpace` with an
implementation of the `map` function it is now super easy for anyone to come along and
transform the units of a `LivingSpace` using very simple functions like `squareFeetToMeters`. Notice
that squareFeetToMeters knows nothing about `LivingSpace`.
## How do Functors help with Monads ?
Functors are an abstract mathematical structure that is represented in Lean with a type class. The
Lean functor defines both `map` and a special case for working on constants more efficiently called
`mapConst`:
```lean
class Functor (f : Type u → Type v) : Type (max (u+1) v) where
map : {α β : Type u} → (α → β) → f α → f β
mapConst : {α β : Type u} → α → f β → f α
```
Note that `mapConst` has a default implementation, namely:
`mapConst : {α β : Type u} → α → f β → f α := Function.comp map (Function.const _)` in the `Functor`
type class. So you can use this default implementation and you only need to replace it if
your functor has a more specialized variant than this (usually the custom version is more performant).
In general then, a functor is a function on types `F : Type u → Type v` equipped with an operator
called `map` such that if you have a function `f` of type `α → β` then `map f` will convert your
container type from `F α → F β`. This corresponds to the category-theory notion of
[functor](https://en.wikipedia.org/wiki/Functor) in the special case where the category is the
category of types and functions between them.
Understanding abstract mathematical structures is a little tricky for most people. So it helps to
start with a simpler idea like functors before you try to understand monads. Building on
functors is the next abstraction called [Applicatives](applicatives.lean.md).
-/