I'm trying to understand functional programming through Haskell and I'm having so much trouble dealing with function composition.
Actually I have these two functions :
add:: Integer -> Integer -> Integer
add x y = x + y
sub:: Integer -> Integer -> Integer
sub x y = x - y
I want to be able to compose them. It doesn't make any sense okay, but it's for a learning aim.
What I've tried :
foo:: (Integer -> Integer) -> (Integer -> Integer) -> Integer
foo = add . sub
What I've understood :
Haskell uses functions with only one args, so that we return a new function to execute after each function execution.
So the first Integer
is the param type, while the second is the return type of a generated function that has to add the second number.
This will return another function (sub
) that will makes the same flow (returning a function with params etc...)
Am I right ?
Here's my actual error code :
src\Main.hs:23:7:
Couldn't match type `Integer' with `Integer -> Integer'
Expected type: Integer -> (Integer -> Integer) -> Integer
Actual type: Integer -> Integer -> Integer
In the first argument of `(.)', namely `add'
In the expression: add . sub
src\Main.hs:23:13:
Couldn't match type `Integer -> Integer' with `Integer'
Expected type: (Integer -> Integer) -> Integer
Actual type: Integer -> Integer -> Integer
Probable cause: `sub' is applied to too few arguments
In the second argument of `(.)', namely `sub'
In the expression: add . sub
I don't know what I'm doing wrong.
Can you help me understanding a bit more this error so I can find a solution ?
Given a function
add :: Integer -> Integer -> Integer
it's useful to remember (as you pointed out yourself in the What I understood section) that ->
in type signatures associates to the right, i.e. the above type is the same as
add :: Integer -> (Integer -> Integer)
Now, consider the type of (.)
:
(.) :: (b -> c) -> (a -> b) -> a -> c
This means that in an expression
(.) add
The b
in the type of (.)
is Integer
and the c
corresponds to Integer -> Integer
. Another way to write this is
b ~ Integer
c ~ Integer -> Integer
So we get
(.) add :: (a -> Integer) -> a -> (Integer -> Integer)
If you now apply (.) add
to sub
, the compiler notices that a -> Integer
cannot be made to match Integer -> Integer -> Integer
.
I suspect that you probably want the composition to take three arguments: two to apply sub
to, and the result of that is then - together with the third argument - passed to add
. So a possible definition which composes the two functions would be
foo :: (Integer -> Integer -> Integer) -> (Integer -> Integer -> Integer) -> Integer -> Integer -> Integer
foo f g x y = f (g x y) y
For what it's worth, there's a related problem: composing a two argument function with a one argument function, e.g. composing
I want to be able to compose them. It doesn't make any sense okay, but it's for a learning aim.
That's actually the problem here. How do you want to compose them? Let us have a look at some possible compositions:
foo x y = sub x (add x y) -- x + y - x = y
foo x y = sub y (add x y) -- x + y - y = x
foo x y = sub x (add y y) -- 2 * y - x
foo x y = sub y (add y y) -- 2 * y - y = y
foo x y = sub y (sub y (add x x)) -- 2 * x - 2 * y
That being said, let us inspect the type error by checking the types from hand:
type I = Integer -- otherwise the lines are going to be very long
(.) :: (b -> c ) -> (a -> b ) -> a -> c
add :: I -> (I -> I)
sub :: I -> (I -> I)
-- |||||||||||||
(.) add :: (a -> I ) -> a -> (I -> I)
-- ^^^^^^^^^^^^^
As you can see, (.) add
already mandates that the other function may only have type a -> Integer
for an arbitrary a
. But sub
's type is Integer -> (Integer -> Integer)
(remember, (->)
is right associative).
Now, what can you do to actually fix this? First, let us inspect your proposed type of foo
:
foo :: (Integer -> Integer) -> (Integer -> Integer) -> Integer
That's actually a very interesting type of function. How would you actually get your result? You're just have two functions at hand, but no values:
> foo f g =
You can solve this by using a fixed point of one of the functions and then apply the other:
> let x = f x in g x
>
> example = foo (const 12) (+1) -- returns 13
But that's not what you meant, right? At this point, it's very important to think about the semantics of your composition. Since those are not clear, you cannot write a general way to compose both functions here.
However, if you actually meant
foo :: Integer -> Integer -> Integer -> Integer
foo x y z = add (sub x y) z
then that's possible with
foo = (add .) . sub
since
(.) add :: (a -> I) -> a -> (I -> I)
(.) ((.) add) :: (a -> b -> Integer) -> a -> b -> Integer -> Integer
But (add .) . sub
isn't really easy to the eye anymore. You're better off writing a point-wise definition of foo
if this kind of function was your original goal.
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