I'm trying to understand the result of
(*) . (+)
in Haskell. I know that the composition operator is just the standard composition of mathematical functions- so
(f . g) = f (g x)
But:
(*) . (+) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a
I'm struggling to understand this type signature. I would have expected to be able to do things like:
((*) . (+)) 1 2 :: Num a => a -> a = (* (+ 1 2))
What is the meaning of (*) . (+)'s type signature? I tried playing with it by something like (just matching up with its signature):
((*) . (+)) 1 (\x -> x + 1) 1
But that fails to compile. I'm trying to walk through the logical steps when composing these, but I'm not fully understanding how it's getting to this result (and what the result is).
Haskell composition is based on the idea of function composition in mathematics. In mathematics, if you have two functions f(x) and g(x), you compute their composition as f(g(x)). The expression f(g(x)) first calls g and then calls f.
The dot ( . ) is the Haskell operator for function composition. Function composition comes from mathematics but actually, it can be really useful to make music. Haskell was originally designed by mathematicians and computer magicians.
Dot operator in Haskell is completely similar to mathematics composition: f{g(x)} where g() is a function and its output used as an input of another function, that is, f(). The result of . (dot) operator is another function (or lambada) that you can use and call it.
I understand how you feel. I found function composition to be quite difficult to grasp at first too. What helped me grok the matter were type signatures. Consider:
(*) :: Num x => x -> x -> x (+) :: Num y => y -> y -> y (.) :: (b -> c) -> (a -> b) -> a -> c
Now when you write (*) . (+)
it is actually the same as (.) (*) (+)
(i.e. (*)
is the first argument to (.)
and (+)
is the second argument to (.)
):
(.) :: (b -> c) -> (a -> b) -> a -> c |______| |______| | | (*) (+)
Hence the type signature of (*)
(i.e. Num x => x -> x -> x
) unifies with b -> c
:
(*) :: Num x => x -> x -> x -- remember that `x -> x -> x` | |____| -- is implicitly `x -> (x -> x)` | | b -> c (.) (*) :: (a -> b) -> a -> c | | | |‾‾‾‾| Num x => x x -> x (.) (*) :: Num x => (a -> x) -> a -> x -> x
Hence the type signature of (+)
(i.e. Num y => y -> y -> y
) unifies with Num x => a -> x
:
(+) :: Num y => y -> y -> y -- remember that `y -> y -> y` | |____| -- is implicitly `y -> (y -> y)` | | Num x => a -> x (.) (*) (+) :: Num x => a -> x -> x | | | | |‾‾‾‾| |‾‾‾‾| Num y => y y -> y y -> y (.) (*) (+) :: (Num (y -> y), Num y) => y -> (y -> y) -> y -> y
I hope that clarifies where the Num (y -> y)
and Num y
come from. You are left with a very weird function of the type (Num (y -> y), Num y) => y -> (y -> y) -> y -> y
.
What makes it so weird is that it expects both y
and y -> y
to be instances of Num
. It's understandable that y
should be an instance of Num
, but how y -> y
? Making y -> y
an instance of Num
seems illogical. That can't be correct.
However, it makes sense when you look at what function composition actually does:
( f . g ) = \z -> f ( g z) ((*) . (+)) = \z -> (*) ((+) z)
So you have a function \z -> (*) ((+) z)
. Hence z
must clearly be an instance of Num
because (+)
is applied to it. Thus the type of \z -> (*) ((+) z)
is Num t => t -> ...
where ...
is the type of (*) ((+) z)
, which we will find out in a moment.
Therefore ((+) z)
is of the type Num t => t -> t
because it requires one more number. However, before it is applied to another number, (*)
is applied to it.
Hence (*)
expects ((+) z)
to be an instance of Num
, which is why t -> t
is expected to be an instance of Num
. Thus the ...
is replaced by (t -> t) -> t -> t
and the constraint Num (t -> t)
is added, resulting in the type (Num (t -> t), Num t) => t -> (t -> t) -> t -> t
.
The way you really want to combine (*)
and (+)
is using (.:)
:
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d f .: g = \x y -> f (g x y)
Hence (*) .: (+)
is the same as \x y -> (*) ((+) x y)
. Now two arguments are given to (+)
ensuring that ((+) x y)
is indeed just Num t => t
and not Num t => t -> t
.
Hence ((*) .: (+)) 2 3 5
is (*) ((+) 2 3) 5
which is (*) 5 5
which is 25
, which I believe is what you want.
Note that f .: g
can also be written as (f .) . g
, and (.:)
can also be defined as (.:) = (.) . (.)
. You can read more about it here:
What does (f .) . g mean in Haskell?
Hope that helps.
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