Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Funny Haskell Behaviour: min function on three numbers, including a negative

Tags:

haskell

I've been playing around with some Haskell functions in GHCi.

I'm getting some really funny behaviour and I'm wondering why it's happening.

I realized that the function min is only supposed to be used with two values. However, when I use three values, in my case

min 1 2 -5

I'm getting

-4

as my result.

Why is that?

like image 567
CodyBugstein Avatar asked Feb 07 '13 00:02

CodyBugstein


1 Answers

You are getting that result because this expression:

min 1 2 -5

parses as if it were parenthesized like this:

(min 1 2) -5

which is the same as this:

1 -5

which is the same as this:

1 - 5

which is of course -4.

In Haskell, function application is the most tightly-binding operation, but it is not greedy. In fact, even a seemingly simple expression like min 1 2 actually results in two separate function calls: the function min is first called with a single value, 1; the return value of that function is a new, anonymous function, which will return the smaller value between 1 and its single argument. That anonymous function is then called with an argument of 2, and of course returns 1. So a more accurate fully-parenthesized version of your code is this:

((min 1) 2) - 5

But I'm not going to break everything down that far; for most purposes, the fact that what looks like a function call with multiple arguments actually turns into a series of multiple single-argument function calls is a hand-wavable implementation detail. It's important to know that if you pass too few arguments to a function, you get back a function that you can call with the rest of the arguments, but most of the time you can ignore the fact that such calls are what's happening under the covers even when you pass the right number of arguments to start with.

So to find the minimum of three values, you need to chain together two calls to min (really four calls per the logic above, but again, hand-waving that):

min (min 1 2) (-5)

The parentheses around -5 are required to ensure that the - is interpreted as prefix negation instead of infix subtraction; without them, you have the same problem as your original code, only this time you would be asking Haskell to subtract a number from a function and would get a type error.

More generally, you could let Haskell do the chaining for you by applying a fold to a list, which can then contain as many numbers as you like:

foldl1 min [1, 2, -5]

(Note that in the literal list syntax, the comma and square bracket delimit the -5, making it clearly not a subtraction operation, so you don't need the parentheses here.)

The call foldl1 fun list means "take the first two items of list and call fun on them. Then take the result of that call and the next item of list, and call fun on those two values. Then take the result of that call and the next item of the list..." And so on, continuing until there's no more list, at which point the value of the last call to fun is returned to the original caller.

For the specific case of min, there is a folded version already defined for you: minimum. So you could also write the above this way:

minimum [1, 2, -5]

That behaves exactly like my foldl1 solution; in particular, both will throw an error if handed an empty list, while if handed a single-element list, they will return that element unchanged without ever calling min.

Thanks to JohnL for reminding me of the existence of minimum.

like image 135
Mark Reed Avatar answered Oct 17 '22 21:10

Mark Reed