Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 757
Rakim Avatar asked Jun 21 '26 09:06

Rakim


2 Answers

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.

like image 154
Frerich Raabe Avatar answered Jun 23 '26 23:06

Frerich Raabe


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

like image 27
Will Ness Avatar answered Jun 23 '26 22:06

Will Ness



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!