(.)
is nothing to fear. It is a most natural thing.
Imagine you define an abstract class of "connections":
class Connecting conn where
plug :: conn a b -> conn b c -> conn a c
noop :: conn a a
conn a b
is a type of something which is connecting a point a to a point b. Given the possibility of connecting a to b, and b to c, it most naturally gives us the possibility of connecting a to c by just plugging the two somethings together. Also, any point which can be connected to another, and can get connected to, can obviously be considered connected to itself with absolutely no effort.
Functions are connecting. Just use the output of one as the input of another. If we have such two compatible functions, plugging them in that way gives us a combined connection.
Functions are Connecting
:
instance Connecting (->) where
-- plug :: (->) a b -> (->) b c -> (->) a c
(plug f g) x = g (f x)
-- noop :: (->) a a
noop x = x -- what else? seriously. All we have is x.
Interesting thing about that plug :: (->) a b -> (->) b c -> (->) a c
. That order of arguments is most fitting when we think about the types involved. But looking at its definition, we'd prefer it be defined as
gulp :: (->) b c -> (->) a b -> (->) a c
(gulp g f) x = g (f x)
Now the definition makes most sense, but the type feels a bit tortured.
Never mind that, we can have both:
(f :: a -> b) >>> (g :: b -> c) :: -- ...
a -> c
(g :: b -> c) <<< (f :: a -> b) :: -- ...
a ->
c
Coincidentally, <<<
is also known as (.)
.
It is also clear that we have (.) = (<<<) = flip (>>>)
, so (g . f) x = g (f x) = (f >>> g) x
.
Presented with g . f
, it is often easier to deal with the equivalent expression f >>> g
, types-wise. Just align the types, vertically,
(:) :: b -> ([b] -> [b])
f :: a -> b
f >>> (:) :: a -> ([b] -> [b])
so that
(>>> (:)) :: (a -> b) -- = ((:) <<<) = ((:) .) = (.) (:)
-> a -> ([b] -> [b])
because (>>> (:)) = (\f -> (f >>> (:)))
.
Which is (\f -> ((:) <<< f)) = (\f -> ((:) . f)) = ((:) .) = (.) (:)
.
edit: for instance, the type of (Endo . (:)) = ((:) >>> Endo)
is easily arrived at, with:
Endo :: ( b -> b ) -> Endo b
(:) :: a -> ([a] -> [a]) -- b ~ [a]
(:) >>> Endo :: a -> Endo [a]
For more about Endo
, try both :t Endo
and :i Endo
at GHCi prompt, and read up on Haskell's record syntax, if you need to.