Special behaviour for interactive redefinitions

Dynamism — allowing definitions to be replaced in a running program — is a hugely convenient feature, because it can shorten the edit-test loop by so much. But like many powerful features, it makes some errors harder to detect — in this case, name collisions. It's easy to catch these automatically in a static language: simply make redefinition of an existing variable an error. But in a dynamic language, redefinition is a feature, so we can't simply reject it. We can give a warning, so collisions won't go undetected, but this will annoy users whenever they interactively redefine something. Can we do better?

In some Lisps, defvar has a related problem, and illustrates a possible solution. Its purpose is to create a (global) variable with a given initial value. There's a problem with redefining: what do you do if the variable already exists, and has a different value?

In both Common Lisp and Emacs Lisp, defvar has the curious feature that it sets the variable only if it doesn't already have a value — if it already exists, it's not changed. This is intended to make re-loading sourcefiles safe: otherwise, it would reset your global variables.

If you think this is surprising behavior, you're not alone. Alan Crowe once suggested that it would be less confusing if defvar were called something like def-perv-noclobber instead, because neither its pervasive specialness nor its clobber-avoidance are what you would expect from something called defvar. (defonce is a better name.)

When a user reevaluates a single defvar interactively, however, they probably do want to clobber the value, or they wouldn't bother. Both Emacs and Slime therefore have a special case: when a defvar is evaluated interactively (as by C-M-x), it will clobber any existing value. This is ugly, of course, but like C-t's irregular behaviour at end-of-line, it does what the user wants.

Something similar might be useful for detecting errors of redefinition. Can we allow redefinitions when a definition is evaluated interactively, but not when it's loaded as part of a file? No, because it's normal to reload a source file after editing it, which replaces every definition in the file, but shouldn't produce any warnings. Nor should we disable the warning for all interactive evaluation, including load, because that's when the warnings are most useful. Interactivity alone is not a sufficient reason to suppress warnings.

What we really want is to warn about redefinition only within a single interactive operation. It's generally OK for a load to replace old definitions; it's only suspicious if a definition is made twice without user intervention. So if a name is defined twice in a single load (directly, or in any other files it loads), that's an error or warning, but when it's defined twice in two separate batches, that's OK. This is easy to implement (by e.g. recording each definition made in the current load), and gives much more precise warnings.

4 comments:

  1. I don't really have a point, but I want to add a note: because of all the issues brought up in the post, in PLT Scheme (Racket), they have expressly decided *not* to allow redefinition.

    They claim not to miss the feature at all, and that the simpler evaluation model also makes it easier to use, particularly for pedagogical uses.

    ReplyDelete
  2. I definitely miss redefinition in Racket. This is one of the things that make me give up in frustration every time I try to use it. A REPL that won't let you change anything is only half a REPL.

    ReplyDelete
  3. I just started the Racket 5.1.3 REPL and typed (define a 32) (define a 33). No error message, and a evaluates to 33.

    ReplyDelete
  4. Redefinition works if the first definition was at the REPL, but not if it was in the definition pane. In that case it gives a "define-values: cannot redefine a constant: a" error — unless there was an assignment to the variable somewhere, in which case it also works. Apparently Racket actually does support redefinition, but as an optimization it infers constancy, and forbids assignment and redefinition of constants. This optimization appears to be turned off when I switch to R5RS by Choose Language (but not, oddly, by #lang r5rs); then anything is redefinable.

    So Racket could support dynamic redefinition and C-M-x (which is what I really miss) by simply turning off this optimization for code loaded from the definition pane.

    ReplyDelete

It's OK to comment on old posts.