map and fmap

John Hughes rjmh at cs.chalmers.se
Mon Aug 21 03:09:22 EDT 2006


From: "Jeffrey Yasskin" <jyasskin at google.com>

> On 8/20/06, John Hughes <rjmh at cs.chalmers.se> wrote:
>>
>> From: "Jon Fairbairn" <jon.fairbairn at cl.cam.ac.uk>
>>
>> > To
>> > reinforce what Aaron said, if a programme works now, it'll
>> > still work if map suddenly means fmap.
>>
>> Well, this isn't quite true, is it? Here's an example:
>>
>> class Foldable f where
>>   fold :: (a -> a -> a) -> a -> f a -> a
>>
>> instance Foldable [] where
>>   fold = foldr
>>
>> example = fold (+) 0 (map (+1) (return 2))
>>
>> example has the value 3 (of course), but if you replace map by fmap then 
>> the
>> code no longer compiles.
>
> There's a proposal
> http://hackage.haskell.org/trac/haskell-prime/wiki/Defaulting that
> mentions extending defaulting to other typeclasses. That seems to fix
> this particular problem, but above you mentioned that this was "a
> whole new can of worms." Could you elaborate or point me to a
> discussion of the worms?
>

Well, that proposal doesn't cover exporting defaults from modules. If 
beginners are not to need to write a default declaration in their own 
module, then we would need either to import the right default from the 
Prelude, or to have a default default for Monad and Functor, just as we have 
for numeric classes today. Importing defaults raises questions over how and 
when one can override an imported default with a different default within 
one's own module.

The big problem with extended defaulting, though, is ambiguity. The proposal 
above includes an example:

default A (Int, String, ())
default B (String, Int, ())

suggesting that a type in both classes should be defaulted to (), because 
this is the "first unambiguous type". That isn't a formal definition, and I 
actually have difficulty conceiving of a formal definition that would lead 
to this result. Even given such a thing, it would have very odd 
consequences: presumably if the first line read default A (Int,()), then Int 
would now be the first unambiguous type, and thus the chosen default. That 
means that removing a default *that was not chosen* would change the 
behaviour of the program! I wouldn't like to have to explain THAT to 
anybody!

But this simple example of ambiguity is really the least worm in the can. 
What about subclassing? Presumably a class default should also apply to its 
subclasses... yet at times, we certainly want to override a previous default 
in a subclass (e.g. Num and Floating). How should defaulting interact with 
context reduction? Imagine a default C (String), and an instance definition 
instance C a => C [a]. If we see a C [b], should we apply the detault at 
once, instantiating b to Char, or after context reduction, instantiating b 
to String? How should defaulting interact with functional dependencies? 
Given a bidirectional fundep class C a b | a->b, b->a, then defaults on a 
and b separately can conflict. Worms!

Paul Hudak and I thought about this when Haskell was first designed. We did 
come up with a proposal, but the rest of the committee thought it was too 
complex for what it bought us, hence it was omitted. Nowadays the class 
system is a lot more complicated than it was then--constructor classes, 
multi-parameter classes, fundeps--and I'm not terribly hopeful about the 
prospects for coming up with a simple and powerful design for defaulting.

John 





More information about the Haskell-prime mailing list