Rakim
Rakim

Reputation: 1107

Haskell and function composition

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

Answers (2)

Will Ness
Will Ness

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

Frerich Raabe
Frerich Raabe

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

Related Questions