Reputation: 3334
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
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
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
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
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