Reputation: 479
I'm learning Haskell and I came across a situation in which I'm not entirely sure what is going on.
I'm working on a bigger project, and have been trying to 'simplify' my code. I'd learnt about function composition and I think I mostly understand it. However, whilst pushing the limits of this, I encountered a situation I'm not sure how to explain.
After a few hours of fiddling about with it, I boiled it down to this:
foo1 x y z = x == (y && z)
foo2 x y z = (x ==) . (&&) y z -- Error
foo3 x y = (x ==) . (&&) y
foo4 x = (x ==) . (&&) -- Error
Why do foo1
and foo3
work, whilst foo2
and foo4
give errors when I try to define them? As far as I know, there's no reason adding or removing equivalent arguments to both the input and output should change the function.
Edit: Include errors
foo2
yields the error:
<interactive>:1:23: error:
• Couldn't match expected type ‘a -> a1’ with actual type ‘Bool’
• Possible cause: ‘(&&)’ is applied to too many arguments
In the second argument of ‘(.)’, namely ‘(&&) y z’
In the expression: (x ==) . (&&) y z
In an equation for ‘foo2’: foo2 x y z = (x ==) . (&&) y z
• Relevant bindings include
x :: a1 (bound at <interactive>:1:6)
foo2 :: a1 -> Bool -> Bool -> a -> Bool
(bound at <interactive>:1:1)
Whilst foo4
gives the error:
<interactive>:2:15: error:
• No instance for (Eq (Bool -> Bool))
arising from an operator section
(maybe you haven't applied a function to enough arguments?)
• In the first argument of ‘(.)’, namely ‘(x ==)’
In the expression: (x ==) . (&&)
In an equation for ‘foo4’: foo4 x = (x ==) . (&&)
Upvotes: 1
Views: 113
Reputation: 567
Let's start with function application.
a b
is an application of a function a to one arguments b.
a b c
is an application of a function a to two arguments b and c.
a b c d
is an application of a function a to three arguments b, c, and d.
Then look at these two functions' type signature.
(&&) :: Bool -> Bool -> Bool
takes two arguments
(.) :: (b -> c) -> (a -> b) -> a -> c
takes three arguments, two of them functions
Then we look into these four cases.
foo1 x y z = x == (y && z)
(y && z) is (&&) y z, fully applied. So the result is a Bool type.
x == Bool is (==) x Bool. So x is a Bool type.
It works.
foo2 x y z = (x ==) . (&&) y z
(x ==) is a function that expects one argument.
so (x ==) . :: (a -> b) -> a -> c
However, as @Carcigenicate says, your (&&) is fully applied.
Hence, compiler say, "Couldn't match expected type ‘a -> a1’ with actual type ‘Bool’"
foo3 x y = (x ==) . (&&) y
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) function composition takes function ( x==) and (&&) y
and all it need now is another a to produce c.
so foo3 is just foo1's point-free style.
It works, too.
foo4 x = (x ==) . (&&)
So we already meet (x ==) .
its type signature is (a -> b) -> a -> c
However, (&&) :: Bool -> Bool -> Bool
Although it can be seen as Bool -> (Bool -> Bool), a function that takes one argument and produce a Bool -> Bool function.
(Bool->Bool) is not an instance for Eq. It doesn't have (==).
Thus the compiler says "No instance for (Eq (Bool -> Bool))"
As far as I know, there's no reason adding or removing equivalent arguments to both the input and output should change the function.
It holds true when they are on the same level.
name b c d = function b c d --fully applied
name b c = function b c --wait one argument
name b = function b --wait two arguments
name = function --wait three/all arguments
but function composition needs to compose two one-argument functions into one-argument function(and the right one's output type need to match the left one's input type.)
So the order is function application first, then (.) / function composition second. And you need to feed (b->c) and (a->b) to (.) to get a new (a->c) function.
Upvotes: 2