[Haskell-cafe] Why do I have to specify (Monad m) here again?

David House dmhouse at gmail.com
Mon Feb 19 12:40:46 EST 2007


On 18/02/07, Marc Weber <marco-oweber at gmx.de> wrote:
> Do I still miss a point?

I think Yitzchak's explanation of this was pretty good, so I recommend
you check that out. You should also make sure you read Sebastian's
argument, whose line of thought is similar to the one I'm going to try
to develop. Suppose we write the print function:

print x = putStrLn (show x)

Now, we want it to be able to act on as many types as possible, so we
write the type:

print :: a -> IO ()
print x = putStrLn (show x)

But that's not quite right; we apply x to the 'show' function, so x
must be of a type that instantiates Show. However, our ever-hopeful
compiler writers decided that if we wrote something like:

print :: a -> IO ()

But the compiler inferred the type:

print :: Show a => a -> IO ()

Then it'd fill in the extra constraint and let everything work. This
is essentially your argument (although it applies to instances instead
of functions; I'll make the link at the end). I say this is a bad
idea, basically because the actual type of print is different to the
type we wrote down. Imagine that print is in a library and a user's
browsing the source of this library. They see the print function and
try to use it on something that doesn't satisfy the Show constraint.
The compiler bombs out with 'Cannot find Show instance' and the user
is confused. The type mentioned in the source file didn't mention any
Show instance, why should I need to provide one? Admittedly, in this
simple example, the user would probably see the show function being
used and realise what's going on, but this wouldn't happen in more
complex cases.

So the problem is, essentially, that the type written in the source
file is incorrect, but the compiler accepts it anyway. This is just
confusing.

Secondly, Explicit writing of types in Haskell is completely optional
-- if you want, the compiler will infer everything for you, so why do
we bother at all? Well, imagine we wrote:

print :: Show a => a -> IO ()
print x = show x

If we hadn't written that type signature, then the compiler would
infer a type and accept the program. However, as we _did_ provide a
type signature, it acts a little like a spec or QuickCheck property
and reveals a typo straight away: we missed out the putStrLn.
Similarly, if we write the type:

print :: a -> IO ()

Then the attitude taken by the language designers dictates that that's
what you mean. If you then try to use show, you presumably made a
mistake writing the type. The compiler could correct this mistake, but
would we want it to? Perhaps this small error is indicative of a
larger conceptual error we made; perhaps our spec says that print
should indeed have the type we wrote, and the mistake was in using
show.

So we've determined that if we provide explicit types for functions,
these should match up with the type the compiler infers. Instances are
just the same. If we write a specific type, like 'm', then we mean 'm'
and not 'Monad m => m'. We could give similar examples for instances
as we did for functions above. Suppose we had:

class Monad m => Foo m where ...

instance Foo m where ...

Then someone, just seeing the instance (which may be a completely
different file to the class), may assume that there's an instance for
every type, and get a similarly confusing situation to the print
example. Or perhaps we _do_ indeed want an instance of Foo for every
type, and the constraint on the class head of Foo was the mistake,
then the compiler would accept our program, unhelpfully.

Having the compiler second-guess our mistakes is unhelpful and confusing.

HTH.

-David House, dmhouse at gmail.com


More information about the Haskell-Cafe mailing list