The madness of implicit parameters: cured?

Derek Elkins ddarius@hotpop.com
Sat, 2 Aug 2003 11:24:54 -0400


On Sat, 2 Aug 2003 00:45:07 -0700 (PDT)
Ben Rudiak-Gould <benrg@dark.darkweb.com> wrote:

> When I first learned about implicit parameters I thought they were a
> great idea. The honeymoon ended about the time I wrote some code of
> the form"let ?foo = 123 in expr2", where expr2 used ?foo implicitly,
> and debugging eventually unearthed the fact that ?foo's implicit value
> was not being set to 123 in expr2. That was enough to scare me off of
> using implicit parameters permanently.
> 
> More recently, I've realized that I really don't understand implicit
> parameters at all. They seemed simple enough at first, but when I look
> at an expression like
> 
>     f x = let g y = ?foo in g
> 
> I realize that I have no idea what f's type should be. Is it
> 
>     (?foo :: c) => a -> b -> c
> 
> or is it
> 
>     a -> ((?foo :: c) => b -> c)

Do you have problems finding the type of

f x = let g y = 4 in g
?
it works -EXACTLY- the same way.

> 
> ? As far as I can tell, these are not the same type: you can
> distinguish between them by partially applying f with various
> different values of ?foo in the implicit environment. 

If you do apply f you get (?foo :: c) => b -> c.

GHC tells me
> that f has the former type, but I still have no idea why: is it
> because g has an implicit ?foo parameter and f implicitly applies its
> own ?foo to g before returning it? Why would it do that? Or is it
> because ?foo is here interpreted as referring to an implicit parameter
> bound in the call of f, instead of in g? That doesn't make sense
> either.

The constraint should just be thought of as an extra "explicit"
parameter, or think of it as using the same mechanism dictionary
passing for type classes uses. Implicit parameters aren't
as "flexible" as full dynamic scoping would be.  For example,

f g = let ?foo = 5 in g ()
g x = ?foo

f g :: {foo :: a) => a 
NOT
f g :: Num a => a
i.e. it doesn't evaluate to 5.  So you can't bind the free implicit
variables of a passed in HOF (basically you can't have type ({?foo :: t
=> a -> t) -> b), and similarly you can't return HOF's with free
implicit parameters (no type a -> ({?foo :: t => t -> b))

If I'm not way rusty with CL, here are similar examples with full
dynamic scoping,
> (defvar *x* 0)
*X*
> (defun f (g) (let ((*x* 5)) (funcall g)))
F
> (defun g () *x*)
G
> (f #'g)
5
> (defun f (x) (defun g (y) *x*))
F
> (let ((*x* 1)) (f 'a))
G
> (funcall * 'b)
0
> (let ((*x* 2)) (funcall ** 'b))
2


> The final straw was:
> 
>     Prelude> let ?x = 1 in let g = ?x in let ?x = 2 in g
>     1
>     Prelude> let ?x = 1 in let g () = ?x in let ?x = 2 in g ()
>     2
> 
> This is insanity. I can't possibly use a language feature which
> behaves in such a non-orthogonal way.

Compare,

let g = (<) in (g 'a' 'b',g 1 2)

let g x y = x < y in (g 'a' 'b',g 1 2)

the problem with this is again -EXACTLY- the same because implicit
parameters behave very much like class constraints, because class
constraints pretty much ARE implicit parameters.  The problem here is
the monomorphism restriction.  This applies to implicit parameters as
well for the same reasons (and because implicit
parameters are very likely handled by the same code.)  In fact, if you
use -fno-monomorphism-restriction, your examples above give you the same
numbers.
   ___         ___ _
  / _ \ /\  /\/ __(_)
 / /_\// /_/ / /  | |      GHC Interactive, version 5.04.3
/ /_\\/ __  / /___| |      http://www.haskell.org/ghc/
\____/\/ /_/\____/|_|      Type :? for help.

Loading package base ... linking ... done.
Loading package haskell98 ... linking ... done.
Prelude> :set -fglasgow-exts
Prelude> let ?x = 1 in let g = ?x in let ?x = 2 in g
1
Prelude> let ?x = 1 in let g () = ?x in let ?x = 2 in g ()
2
Prelude> :set -fno-monomorphism-restriction
Prelude> let ?x = 1 in let g = ?x in let ?x = 2 in g
2
Prelude> let ?x = 1 in let g () = ?x in let ?x = 2 in g ()
2

Whether your additions would be worthwhile anyways, I haven't really
thought about.