[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: semantics of DEFINE (and nesting)

jar@ZOHAR.AI.MIT.EDU (Jonathan Rees) writes:

>    From: Krulwich-Bruce@yale-zoo.arpa  (Bruce Krulwich)
>    A while ago I posted the suggestion that non-top-level DEFINEs do
>    the same thing as top-level DEFINEs, ie, side effect the top level.
>    ... This interpretation is the one adopted by the current version
>    of T (although it's not an explicit decision on the part of the T
>    designers) and I believe is the interpretation used by most LISP
>    dialects.  ...
>    Most of the responses that I got said either like "well, Abelson
>    and Sussman used the 'local' interpretation, so we really should
>    stick to it" or "well, local non-top-level DEFINEs add fewer
>    parentheses than nesting LETRECs."  Does anyone have other
>    (theoretical or functional) reasons for this decision??
> It *was* an explicit decision on the part of the designers, made in
> 1981.  It was an attempt to have something similar to the MIT Scheme
> define but without coupling it to the syntax or semantics of lambda.
> We never properly implemented the feature (LOCALE, for those of you
> who have seen the T manual) that would have made it coherent, however.
> Personally, I now would like to see T changed either to implement R3RS
> define or to allow defines only at "top level" (given a suitable
> non-global definition of "top level").

In Pop-11 you can use define locally and it is frequently and very
effectively used for two main purposes, and less importantly for
a third:

(a) Temporarily alter the error handler, standard character
output consumer, interrupt handler, or other procedures that define
the current environment, need to be changed in a particular
procedure, and need to be re-set when that procedure is exitted
whether normally or abnormally (e.g. via exitto, or by temporarily
suspending a lightweight process using that procedure - in the
latter case the temporary value is re-set if the procedure is
resumed.) Typical example, redefining -interrupt locally-:

  define foo(...,....);
    lvars oldinterrupt=interrupt;   ;;; save previous value in local var.

    define interrupt();
      pr('Message about being in foo');
      popready();     ;;; interactive break
      oldinterrupt(); ;;; if it exits normally do previous interrupt

  ...body of foo...


The interrupt procedure (whatever its current value) is called when
the user hits the interrupt key, or after the error handler has
printed its message, or if some procedure explicitly calls it.

The use described in (a) depends on the procedure name being a
dynamically scoped identifier.

(b) Define a local procedure that is required ONLY within the
nesting procedure. Sometimes this can be done by having a procedure
defined at top level with either a unique name, or via  file-local
lexically scoped name, or in a section (Pop-11 sections are a bit
like Packages in Common Lisp, but can be nested). But the local
NESTED procedure definition is most useful when it needs to access
one of the lexically scoped locals of the enclosing procedure. E.g.

    define foo(x, y, z);
        lvars x, y, z;

        define baz(w);
            lvars w;
            if     w == x then ....
            elseif w == y then ....
            elseif w == z then ...

        ...body of foo may either call baz or hand it as an argument
        ... to some other procedure or else may return baz as a new
        ... lexical closure


(Some of these cases can be handled slightly more efficiently, and
slightly more messily using 'partial application', in Pop-11).

My suspicion is that people who don't appreciate the usefulness of
such nested procedure definitions must be people who have not been
using a language that allows these constructs. Here at Sussex there
was a gradual conversion among programmers using Pop-11, especially
after we introduced lexical scoping as an option.

(c) If you simply want to change the global value of a procedure
identifier you can do things like:

    define foo ....;

       define foo_interrupt;

        foo_interrupt -> interrupt;     ;;; sets the value outside foo.

or to ensure that it is set in all current contexts

    set_global_valof(foo_interrupt, "interrupt");


I would say that a lisp-like language that doesn't allow nested
procedure definitions with these capabilities was seriously
impoverished. One can of course achieve similar effects by other
means, but they are bound to be more clumsy and also by not using
the syntactic nesting you risk hiding an important relationship,
and thereby confusing people responsible for maintaining code they
did not write.

The impoverishment in case (a) is reduced if your language does not
provide a "process" mechanism, which not all Lisps do.

Aaron Sloman,
School of Cognitive and Computing Sciences,
Univ of Sussex, Brighton, BN1 9QN, England
    INTERNET: aarons%uk.ac.sussex.cogs@nsfnet-relay.ac.uk
    JANET     aarons@cogs.sussex.ac.uk
    BITNET:   aarons%uk.ac.sussex.cogs@uk.ac
        or    aarons%uk.ac.sussex.cogs%ukacrl.bitnet@cunyvm.cuny.edu

    UUCP:     ...mcvax!ukc!cogs!aarons
            or aarons@cogs.uucp