Hobby-hacking Eric


monomorphism redux

There may be errors in this, of course!

the context from wikibooks

Following the previous example, you might be tempted to try storing a value for that radius. Let's see what happens:
Prelude> let r = 25
Prelude> 2 * pi * r

Couldn't match `Double' against `Integer'
Expected type: Double
Inferred type: Integer
In the second argument of `(*)', namely `r'
In the definition of `it': it = (2 * pi) * r

Whoops! You've just run into a programming concept known as types. Types are a feature of many programming languages which are designed to catch some of your programming errors early on so that you find out about them before it's too late. We'll discuss types in more detail later on in the Type basics chapter, but for now it's useful to think in terms of plugs and connectors. For example, many of the plugs on the back of your computer are designed to have different shapes and sizes for a purpose. This is partly so that you don't inadvertently plug the wrong bits of your computer in together and blow something up. Types serve a similar purpose, but in this particular example, well, types aren't so helpful.

the new monomorphism-based explanation

The quick solution to this problem is to specify a type for the number 25. For lack of other information, Haskell has "guessed" that 25 must be an Integer (which cannot be multiplied with a Double). To work around this, we simply insist that it is to be treated as a Double
Prelude> let r = 25 :: Double
Prelude> 2 * pi * r


There is actually a little bit more subtlety behind this problem. It involves a language feature known as the monomorphism restriction. You don't actually need to know about this for now, so you can skip over this note if you just want to keep a brisk pace. Instead of specifying the type Double, you also have given it a polymorphic type, like Num a => a, which means "any type a which belongs in the class Num". The corresponding code looks like this and works just as seamlessly as before:
Prelude> let r = 25 :: Num a => a
Prelude> 2 * pi * r

Haskell could in theory assign such polymorphic types systematically, instead of defaulting to some potentially incorrect guess, like Integer. But in the real world, this could lead to values being needlessly duplicated or recomputed. To avoid this potential trap, the designers of the Haskell language opted for a more prudent "monomorphism restriction". It means
that values may only have a polymorphic type if it can be inferred from the context, or if you explicitly give it one. Otherwise, the compiler is forced to choose a default monomorphic (i.e. non-polymorphic) type. This feature is somewhat controversial. It can even be disabled with the GHC flag (-fno-monomorphism-restriction), but it comes with some risk for inefficiency. Besides, in most cases, it is just as easy to specify the type explicitly.


Anonymous said...

Try to put the buggy code in a separate file, and :load them from ghci. It just works.

This seems to be a ghci issue (typing just-in-time is not as accurate, or something like that) rather that a Haskell issue.

kowey said...

You mean like this?
r = 25
x = 2 * pi * r

What's interesting is that if you do :t r, you find that its type is... Double. Monomorphism strikes again. Although I'm guessing that since we're compiling the whole thing, we can infer from pi that the type of x is Double, as is r's.

I'm not entirely sure I know what I'm talking about. I think somebody can come up with an example of the restriction biting you, even in a text file. Try for example y = x + length [1,2,3] (with the previous example). Doesn't even compile.

kowey said...

shachaf points out: "You can't expect "y = x + length [1,2,3]" to work, even if you add explicit type signatures, because pi can never be an Integral. A simple example, though, is "add1 = \x -> x + 1", which defaults to :: Integer -> Integer for me. Again,see the wiki page for the monomorphism restriction for some more examples."

(for which, thanks!)