Poriferous
Poriferous

Reputation: 1582

Who can explain this Haskell puzzle?

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?

Upvotes: 4

Views: 211

Answers (3)

utdemir
utdemir

Reputation: 27216

On Haskell, functions are curried, so:

f :: a -> b -> c

is the same as:

f :: a -> (b -> c)

So, when you're calculating f 1 2, you're first applying a to f and get a function with type b -> c. And then you apply b and get c.

Now, lets follow the types:

Prelude> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

Prelude> :t negate
negate :: Num a => a -> a

Prelude> :t subtract
subtract :: Num a => a -> a -> a

Prelude> :t (subtract.negate)
(subtract.negate) :: Num b => b -> b -> b

So, what is the type of (subtract.negate) 2 ?

Prelude> :t (subtract.negate) 1
(subtract.negate) 1 :: Num b => b -> b

As you can see, negate got an 1 and gave -1, but subtract took that -1, and gave Int -> Int. And then you applied 2 to that Int -> Int and got 3.

Shortly, (.) is always right-associative and takes (b -> c) and (a -> b). The only tricky part is, that c itself can be (d -> e).

Upvotes: 4

melpomene
melpomene

Reputation: 85767

It could imply that the expression is left-associative.

I don't understand what you mean by "less-associative" here.


Function application is left associative, meaning a b c parses as (a b) c.

Composition is defined like this: (a . b) c = a (b c).

Thus

(subtract . negate) 1 2
  =                        -- function application associates to the left
((subtract . negate) 1) 2
  =                        -- definition of composition
(subtract (negate 1)) 2
  =                        -- definition of subtract
2 - (negate 1)
  =                        -- definition of negate
2 - (-1)
  =                        -- definition of -
3

The other expression goes like this (with a $ b = a b):

negate $ subtract 1 2
  =                       -- function application has higher precedence than any operator
negate $ (subtract 1 2)
  =                       -- definition of $
negate (subtract 1 2)
  =                       -- definition of negate
-(subtract 1 2)
  =                       -- definition of subtract
-1

Upvotes: 2

zigazou
zigazou

Reputation: 1755

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!

Upvotes: 4

Related Questions