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