Nicolas Henin
Nicolas Henin

Reputation: 3334

Understanding the signature of the function (.) (:)

could you explain how we go from :

Prelude Data.Monoid> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
Prelude Data.Monoid> :t (:)
(:) :: a -> [a] -> [a]

to that :

Prelude Data.Monoid> :t (.)(:)
(.)(:) :: (a1 -> a2) -> a1 -> [a2] -> [a2]

More in general, I'm sometimes afraid of (.) like I don't feel it intuitively, if you have some tricks to share to better feel it, it's welcome :-)

Upvotes: 2

Views: 123

Answers (4)

Will Ness
Will Ness

Reputation: 71119

(.) 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.

Upvotes: 0

duplode
duplode

Reputation: 34398

Writing (.) (:) as an operator section, ((:) .), emphasises a bit that it postcomposes (:) with some other function (i.e. it modifies a function by applying (:) to its result -- \g -> (g .) is fmap for functions). Symmetrically, (. (:)) precomposes (:) with some other function (\f -> (. f) is contramap for functions -- cf. the Op newtype).

Upvotes: 0

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477804

Well, let us do the type inference. We thus have two function:

(.) :: (b -> c) -> (a -> b) -> a -> c
(:) :: d -> [d] -> [d]

We here use d since the a in (.) is not per se the a in (:), so we avoid confusion by using two separate type variables.

The type signatures in a more canonic form are:

(.) :: (b -> c) -> ((a -> b) -> (a -> c))
(:) :: d -> ([d] -> [d])

So now since (:) is the argument of a function application with (.) as function, we know that the type of (:) is the type of the parameter of (.), so that means that d -> ([d] -> [d]) ~ (b -> c) (here the tilde ~ means that it is the same type). So therefore we know that:

  b -> c
~ d -> ([d] -> [d])
---------------------
b ~ d, c ~ [d] -> [d]

So that means that the b in the type signature of (.) is the same type as d in the type signature of (:), and that c ~ [d] -> [d].

So the as a result, we get:

  (.) (:) :: (a -> b) -> (a -> c))
= (.) (:) :: (a -> d) -> (a -> ([d] -> [d])))
= (.) (:) :: (a -> d) -> (a -> [d] -> [d])
= (.) (:) :: (a -> d) -> a -> [d] -> [d]

Upvotes: 2

lisyarus
lisyarus

Reputation: 15586

First, let's rename some things and put parenthesis:

(:) :: d -> ([d] -> [d])

Now, in the expresstion (.) (:) the (:) is the first argument of (.). The first argument of (.) should have type b -> c. Thus,

b -> c = d -> ([d] -> [d])

which means

b = d
c = [d] -> [d]

The result of (.) (:) has type (a -> b) -> a -> c. Putting our b and c in, we get

(a -> d) -> a -> ([d] -> [d])

This is exactly what ghci told you, except for the type variables renamed as a1 = a and a2 = d.

Upvotes: 5

Related Questions