Reputation: 1107
I was learning some basic function composition in Haskell and while I was playing around I realised something I cannot really explain. When I use the following block of code the compiler seems to be happy about it and works fine:
doSomeX x = if x==7 then True else False
doSomeY (x,y) = x+y+1
doSomeXY = doSomeX.doSomeY
However when I split the doSomeY into 2 args instead of a pair, i.e.:
doSomeX x = if x==7 then True else False
doSomeY x y = x+y+1
doSomeXY = doSomeX.doSomeY
I am getting the following error:
No instance for (Num a0) arising from a use of `doSomeY'
The type variable `a0' is ambiguous
Relevant bindings include
doSomeXY :: a0 -> Bool (bound at test.hs:21:1)
Note: there are several potential instances:
instance Integral a => Num (GHC.Real.Ratio a)
-- Defined in `GHC.Real'
instance Num Integer -- Defined in `GHC.Num'
instance Num Double -- Defined in `GHC.Float'
...plus three others
In the second argument of `(.)', namely `doSomeY'
In the expression: doSomeX . doSomeY
In an equation for `doSomeXY': doSomeXY = doSomeX . doSomeY
I don't really see why though. On both cases the return type of the doSomeY
is the same type with that of the arg of the function doSomeX
why would the input type of doSomeY
would make a difference? Am I missing something fundamental here?
Thanks
Upvotes: 2
Views: 120
Reputation: 71070
(.)
is defined as
(.) f g x = f (g x)
so (f . g) x y = (.) f g x y = f (g x) y
, not f (g x y)
like you intended.
On the other hand, (f . g) (x,y) = f (g (x,y))
, because (x,y)
is one value, a 2-tuple, so your first version is okay.
Upvotes: 3
Reputation: 94279
The difference is caused by doSomeY
yielding a number in the first case vs. yielding a function in the second case when applied to one argument.
Given
doSomeX x = if x==7 then True else False
doSomeY x y = x+y+1
we can infer the types (these aren't the most generic types possible, but they'll do for our purposes):
doSomeX :: Int -> Bool
doSomeY :: Int -> Int -> Int
Remember that ->
in type signature associates to the right, so the type of doSomeY
is equivalent to
doSomeY :: Int -> (Int -> Int)
With this in mind, consider the type of (.)
:
(.) :: (b -> c) -> (a -> b) -> a -> c
If you define
doSomeXY = doSomeX.doSomeY
...which is equivalent to (.) doSomeX doSomeY
, it means that the first argument to (.)
is doSomeX
and the second argument is doSomeY
. Hence, the type b -> c
(the first argument to (.)
) must match the type Int -> Bool
(the type of doSomeX
). So
b ~ Int
c ~ Bool
Hence,
(.) doSomeX :: (a -> Int) -> a -> Bool
Now, applying this to doSomeY
causes a type error. As mentioned above,
doSomeY :: Int -> (Int -> Int)
So when inferring a type for (.) doSomeX doSomeY
the compiler has to unify a -> Int
(the first argument of (.) doSomeX
) with Int -> (Int -> Int)
(the type of doSomeY
). a
can be unified with Int
, but the second half won't work. Hence the compiler barfs.
Upvotes: 6