Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is happening when I compose * with + in Haskell?

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

like image 427
user2666425 Avatar asked Dec 27 '14 04:12

user2666425


People also ask

How do you use composition in Haskell?

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.

What does dot mean in Haskell?

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.

How do I use the dot operator in Haskell?

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.


1 Answers

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.

like image 125
Aadit M Shah Avatar answered Oct 05 '22 15:10

Aadit M Shah