(.)
takes two functions that take one value and return a value:
(.) :: (b -> c) -> (a -> b) -> a -> c
Since (.)
takes two arguments, I feel like (.).(.)
should be invalid, but it's perfectly fine:
(.).(.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
What is going on here? I realize this question is badly worded...all functions really just take one argument thanks to currying. Maybe a better way to say it is that the types don't match up.
A composite function of two functions combines the given two functions in the given order. i.e., for any given two functions f(x) and g(x), there can be 4 composite functions: f(g(x)) which is substituting g(x) into f(x) g(f(x)) which is substituting f(x) into g(x)
In mathematics, function composition is an operation ∘ that takes two functions f and g, and produces a function h = g ∘ f such that h(x) = g(f(x)). In this operation, the function g is applied to the result of applying the function f to x.
Let's first play typechecker for the mechanical proof. I'll describe an intuitive way of thinking about it afterward.
I want to apply (.)
to (.)
and then I'll apply (.)
to the result. The first application helps us to define some equivalences of variables.
((.) :: (b -> c) -> (a -> b) -> a -> c) ((.) :: (b' -> c') -> (a' -> b') -> a' -> c') ((.) :: (b'' -> c'') -> (a'' -> b'') -> a'' -> c'') let b = (b' -> c') c = (a' -> b') -> a' -> c' ((.) (.) :: (a -> b) -> a -> c) ((.) :: (b'' -> c'') -> (a'' -> b'') -> a'' -> c'')
Then we begin the second, but get stuck quickly...
let a = (b'' -> c'')
This is key: we want to let b = (a'' -> b'') -> a'' -> c''
, but we already defined b
, so instead we must try to unify --- to match up our two definitions as best we can. Fortunately, they do match
UNIFY b = (b' -> c') =:= (a'' -> b'') -> a'' -> c'' which implies b' = a'' -> b'' c' = a'' -> c''
and with those definitions/unifications we can continue the application
((.) (.) (.) :: (b'' -> c'') -> (a' -> b') -> (a' -> c'))
then expand
((.) (.) (.) :: (b'' -> c'') -> (a' -> a'' -> b'') -> (a' -> a'' -> c''))
and clean it up
substitute b'' -> b c'' -> c a' -> a a'' -> a1 (.).(.) :: (b -> c) -> (a -> a1 -> b) -> (a -> a1 -> c)
which, to be honest, is a bit of a counterintuitive result.
Here's the intuition. First take a look at fmap
fmap :: (a -> b) -> (f a -> f b)
it "lifts" a function up into a Functor
. We can apply it repeatedly
fmap.fmap.fmap :: (Functor f, Functor g, Functor h) => (a -> b) -> (f (g (h a)) -> f (g (h b)))
allowing us to lift a function into deeper and deeper layers of Functors
.
It turns out that the data type (r ->)
is a Functor
.
instance Functor ((->) r) where fmap = (.)
which should look pretty familiar. This means that fmap.fmap
translates to (.).(.)
. Thus, (.).(.)
is just letting us transform the parametric type of deeper and deeper layers of the (r ->)
Functor
. The (r ->)
Functor
is actually the Reader
Monad
, so layered Reader
s is like having multiple independent kinds of global, immutable state.
Or like having multiple input arguments which aren't being affected by the fmap
ing. Sort of like composing a new continuation function on "just the result" of a (>1) arity function.
It's finally worth noting that if you think this stuff is interesting, it forms the core intuition behind deriving the Lenses in Control.Lens.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With