The madness of implicit parameters: cured?

Ben Rudiak-Gould benrg@dark.darkweb.com
Sun, 3 Aug 2003 20:47:39 -0700 (PDT)


On Sun, 3 Aug 2003, Derek Elkins wrote:

> I kinda think someone mentioned this, perhaps even you.  Or maybe I'm
> thinking of something else.  As I'm feeling too lazy to check the
> archives, at the risk of saying something stupid or repeating something
> said, you may want to look at named instances (google should turn
> something up with a little effort.)  It seems to cover some of the
> same issues/ideas you are having now, though in a different context.


I found a paper on named instances at

	http://www.cs.uu.nl/people/ralf/hw2001/4.html

Thanks for pointing me to this; it's very interesting. As part of my
proposal I was thinking about the possibility of decoupling the typeclass
system from implicitness, but there were enough complications that I gave
up. But this paper makes it look doable.

Given plus :: (Num a) => a -> a -> a, the application plus (1::Int)
currently has type Int -> Int. But it could equally well have type
(Num Int) => Int -> Int. This type has the advantage of being more
versatile: in principle you can supply your own type class. The problem is
that usually you want Int -> Int, and it would be terribly cumbersome to
have to apply the default Num Int dictionary by hand every time. That's as
far as I got.

But the paper points out that implicit context reduction in this case is
perfectly safe and predictable as long as there's a global default value
for the dictionary. Two implicit reductions can't conflict because the
value passed implicitly is always the same, and an implicit reduction
can't conflict with an explicit one because the explicit reduction changes
the type, making the implicit reduction illegal. The paper suggests the
notation f # MyInstance, but of course I think it should be something like
f { Num Int = MyInstance } or f { MyInstance }. This notation is uglier,
but it's more consistent and it avoids stealing a nice short infix
operator which people have proposed to use for many other things.

This works for named parameters too (explicit and implicit). You could
write something like

	default #name = value

or even just

	#name = value

since this introduces no ambiguity if named parameters never clash with
ordinary variable identifiers. Then implicit reduction is perfectly safe
(in the sense that the behavior is unaffected by type signatures). This is
better than the parameter-defaulting scheme I was thinking of.

There are some complications, but I think they all have solutions:

1. The default value for #name has to be global to the whole application,
   so functions in other modules with a named parameter #name will have to
   use your default. Solution: make parameter names part of the module
   namespace.

2. You can't have named parameters with the same name but different types,
   if there's a global default. Solution: Invent a new notation for
   explicitly typing the defaults (not sure this is possible), or just
   live with the limitation. Haskell doesn't have Java-style overloading.

3. Problems arise in the case of duplicate dictionary types. Consider
   f x y = (x+1,y+1) with type (Num a, Num b) => a -> b -> (a,b).
   Then g = f (1::Int) (2::Int) has type (Num Int, Num Int) => (Int,Int).
   In the expression g { Num Int = ... }, which typeclass parameter are we
   applying? The solution, as pointed out in the paper, is to make the
   typeclass parameters explicit in the definition of f. Then f has type
   Num a => Num b => ... and g has type Num Int => Num Int => (Int,Int),
   and the order of application is unambiguous.

There are additional subtleties described in the paper which I don't
understand yet.


-- Ben