The question: Why does bar10evaluate to something different from baz10? By looking at the definition of baz, you might convince yourself that baz and bar do the same thing. Apparently not.

Scroll down for the answer.

The problem is numeric overflow. The standard library lengthfunction has a result type of Int. The intermediate result in foo is too large to fit in the data type Int. The barfunction has type foralla. Integral a=>a-> Int. When calling bar directly, this isn’t a problem because the argument defaults to type Integer and foois evaluated entirely in arbitrary precision numbers. However, when baz is evaluated, there is a comparison barn==n, so the result of bar must have the same type as its argument. The result is known to be Int because the return type of bar is not polymorphic. So the argument nmust also be of type Int. Then foo's inferred type is Int -> Int, which causes the overflow.

All clear now?

If one wanted to take a lesson from this, it would be one of the following:

Be careful with types.

General purpose functions like lengthshouldn’t use dangerous types like Int.

Int should be made less dangerous by throwing an exception on overflow.

Let’s take a poll: which lesson do you draw from it? Post in the comments.

I’m always wary of letting the compiler infer numeric types for me, since (as you found) it can screw around with the semantics. You may not be the sort to put type annotations on every top-level declaration, but I think this code is a great argument for doing so — at the very least it forces you to think about what you want, and at best the type checker will often catch this sort of thing for you. Adding type annotations to the above code, one quickly runs into type errors — unless you make everything Int -> Int, which should (hopefully) give pause when doing something with (e.g.) large products.

I strongly claim that there is no need to change the ‘length’ function. To get a ‘length’ result that would overflow Int you would need to fully traverse a list (i.e. the Haskell singly linked list) with more than 2 billion elements.

Essentially no one should be applying ‘length’ to such a long list, and there is ‘genericLength’ that covers this (and all other Integral) cases for the few people who do it.

The problem here is that ‘foo’ computes something up to 23! which is much bigger than Int’s 2^31. And he did not specify ‘Integer’ and he did not change the ‘default (Int,Integer)’ behavior (note that he is also using the interactive GHCI system). If I want to use numbers and not worry about defaulting then I put ‘default ()’ in the file.

As for an overflow checking Int, you could make a newtype that checked overflow. You could even make a type

Hmm. If there’s one thing this example makes clear, it’s that the implications of defining length with type ([a] -> Int) go far beyond the range of values that will ever be returned by length. They also have to do with what you might want to do with the result of length, and the types of the other values in those computations. The simple fact that Int will never overflow its specified range is not enough to establish that Int is the correct type.

I’m sure there were reasons for the choice not to do length = genericLength; but they will have to do with performance. Absent the desire for performance optimizations, I have a hard time seeing why Int would be considered the “right” choice for length, as compared to (Integral a) => a. Ah well, everyone makes trade-offs.

No one’s yet taken up my favorite answer, which is that there’s something wrong with the Int type. The combination of (a) not defining the size of Int, and (b) permitting wrap-on-overflow basically means that the behavior of Int is undefined in certain cases. Unchecked overflow for types like Int32 and Word32 would be quite justifiable; but IMO unchecked overflow for Int is just plain wrong.

Well, here are the lessons I took from it:Like, whoa! Type inference can actually cause bugs? Dude. I thought it was meant to prevent them…
Maybe one should start by using an algorithm that’s less likely to overflow :-)
I’m generally not in favour of arithmetic throwing exceptions, but that’s largely down to my time spent doing scientific numeric stuff, where exceptions were a major pain. And Int has no equivalent of Infinity, so maybe that’s the least bad option.