summaryrefslogtreecommitdiff
path: root/lec10/notes.md
diff options
context:
space:
mode:
Diffstat (limited to 'lec10/notes.md')
-rw-r--r--lec10/notes.md80
1 files changed, 80 insertions, 0 deletions
diff --git a/lec10/notes.md b/lec10/notes.md
new file mode 100644
index 0000000..e65dbe9
--- /dev/null
+++ b/lec10/notes.md
@@ -0,0 +1,80 @@
+# Lecture 10: Applicative functors (part 1)
+
+## Motivation
+
+Let's suppose that we have the following `Employee` type with a single constructor:
+
+ type Name = String
+ data Employee = Employee { name :: Name, phone :: String }
+ deriving Show
+
+The constructor for `Employee` is going to be a function with the following signature:
+
+ Employee :: Name -> String -> Employee
+
+However, we might want to use this constructor in contexts where we don't know all fields, like in forms with optionally compilable parts. In that case, we can write a new function that turns the `Employee` constructor into something that returns a `Just Employee` only when both its fields have values, or `Nothing` otherwise.
+
+ maybeEmployee :: (Name -> String -> Employee) -> (Maybe Name -> Maybe String -> Maybe Employee)
+
+What if we need to extract the fields of an `Employee` object from somewhere else, like a very large data structure or a remote database?
+
+ extractedEmployee :: (Name -> String -> Employee) -> ((e -> Name) -> (e -> String) -> (e -> Employee))
+
+We can write this function, too! As it turns out, there is only one way to write it.
+
+## Generalizing
+
+If we extract the common parts of this pattern and make it generic to any type, we should end up with something like this:
+
+ (a -> b -> c) -> (f a -> f b -> f c)
+
+This type looks familiar. In fact, it's just the type of `fmap` from the `Functor` class with an extra argument in the two functions! It would be nice if only we could write this new function, which we're going to call `fmap2`, in terms of `fmap`. We might think this is possible, considering that Haskell allows us to partially apply functions.
+
+However, despite our best effort, we actually cannot write `fmap2` in terms of `fmap`. The reason is that, by applying `fmap` to the arguments below one by one, we end up with the type below, which is far from our desired `fmap2`.
+
+ h :: a -> (b -> c)
+ fa :: f a
+ fb :: f b
+
+ fmap h fa :: f (b -> c)
+
+Our applied version of `fmap` lets us apply functions to values in a Functor context. However, we need our functions to be in the Functor context as well! The kind of Functor we need are called **applicative functors**, defined as follows and available in the standard library as `Control.Applicative`.
+
+ class Functor f => Applicative f where
+ pure :: a -> f a
+ (<*>) :: f (a -> b) -> f a -> f b
+
+`(<*>)` is pronounced "ap" or "apply" and represents our "contextual application". `pure`, on the other hand, "injects" a value of a type into a container. An interesting aspect of `pure` is that, while our `fmap2` is `fmap` with one more argument, `pure` is `fmap` with one *less* argument. Notice that, since Applicative requires its instances to be instances of Functor as well, we can always use `fmap` with instances of Applicative.
+
+Using `(<*>)` we can now implement `fmap2`, which is also available in the standard library as `liftA2`. The standard library also defines a synonym for `fmap`, `(<$>)`, available in `Control.Applicative`. As a result, `liftA2` is simply written as:
+
+ liftA2 :: Applicative f => (a -> b -> c -> f a -> f b -> f c
+ liftA2 h fa fb = h <$> fa <*> fb
+
+We can write `liftA3` similarly, with the same operators:
+
+ liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d
+ liftA3 h fa fb fc = ((h <$> fa) <*> fb) <*> fc
+
+Note how going from `fmap` to `liftA2` required to introduce Applicative, while going from `liftA2` to `liftA3` didn't require further generalization.
+
+On the other hand, `pure` is useful when we need to apply a function to its arguments in the context of some functor `f`, with one or more arguments not being in `f`. Those arguments are often called "pure". By using `pure`, we can lift the arguments not in `f` before applying.
+
+## Applicative laws
+
+An interesting law is the following:
+
+ f `fmap` x === pure f <*> x
+
+In other words, applying a function `f` to a container `x` gives the same result as first injecting the function into the container and then applying it to `x` wih `<*>`.
+
+## Applicative examples
+
+An instance of `Applicative Maybe` could be written like this:
+
+ instance Applicative Maybe where
+ pure = Just
+ Nothing <*> _ = Nothing
+ _ <*> Nothing = Nothing
+ Just f <*> Just x = Just (f x)
+