Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Who can explain this Haskell puzzle?

Tags:

haskell

I'm aware that using the . operator chains functions together, like so:

isLessThanZero x 
   | x < 0 = True
   | otherwise = False

(isLessThanZero . negate) 3 -- Produces True

Or with $:

getNegNumbers x = map (*x) [-1, -2, -3]

filter isLessThanZero $ getNegNumbers 2 -- Produces [-2, -4, -6]

But if I were to do something like:

(subtract . negate) 1 2 -- 3
negate $ subtract 1 2 -- -1

The results here are different, and it doesn't make sense because the two functions accept a different number of arguments. With . the negate function checks whether a number is negative or not, but two arguments are supplied. It could imply that the expression is left-associative.

negate (subtract 1 2) -- -1
(subtract . negate) 1 2 -- 3

But this is confusing because in the first example:

(isLessThanZero . negate) 3

The expression produces True, which implies that the function negate was executed first, then isLessThanZero is called. But in the latest example, it appears that subtract was called first and then negate was called. So I'm not sure what's going on here. But this is what's even more confusing:

subtract 1 2 -- Produces 1!

Which implies that the entire expression:

(subtract . negate) 1 2 -- Produces 3!

is a side-effect of using function chaining.

My theory is this, decomposing thus:

We know that 2 - (-1) = 3. So, the expression is still right-associative.. I think. negate is still called first like in the previous example, only that it affects the first argument, rather than both arguments, which makes sense because negate accepts only one argument, and we're not mapping the function at all.

So, when it comes to chaining functions with differing number of arguments, how should Haskell respond to that?

like image 645
Poriferous Avatar asked Dec 25 '22 15:12

Poriferous


1 Answers

Haskell approach is to consider all functions as taking one argument and returning one value, even for functions with multiple arguments.

Therefore, the subtract function whose signature is:

subtract :: Num a => a -> a -> a

may also be seen:

subtract :: Num a => a -> (a -> a)

a function which takes a numerical argument and returns a function which takes one numerical value and returns a numerical value.

Considering (.), its signature is:

(.) :: (y -> z) -> (x -> y) -> x -> z

It takes two functions a returns a function.

If you apply it to (subtract . negate), you have this signature:

(subtract . negate) :: Num a => (a -> (a -> a)) -> (a -> a) -> (a -> (a -> a))

where:

x = a
y = a
z = a -> a

The signature this function is perfectly valid.

Note that subtract 1 2 is acting like 2 - 1.

The (subtract . negate) function is a function which takes one numerical value, negates it and returns a function which takes another numerical value from which the negated value will be subtracted.

Note also that negate (subtract 1 2) is equal to -1, not 3!

like image 120
zigazou Avatar answered Jan 03 '23 09:01

zigazou