Search code examples
common-lisp

How do I get a Common Lisp environment object?


The Common Lisp HyperSpec discusses what an environment is. It also discusses environment objects and tells us that their nature is implementation-dependent. Some functions, like MACROEXPAND, take an optional environment object argument.

What I can't find is an example of how I actually get an environment object or modify it, or how else I would actually use it. None of the examples in the MACROEXPAND page feature an environment object, not even those examples involving lexical environments - these instead use MACROLET. Common Lisp the Language, 2nd Edition has an AUGMENT-ENVIRONMENT function but apparently this is not in the standard.

Are there functions that can actually obtain and manipulate environments? If not, why do functions like MACROEXPAND take an environment argument? I saw this prior question but it does not seem to answer this issue.


Solution

  • Environment objects are passed to the functions that expand macros as their second argument. If you use defmacro to define macros then you can get at this value with the &environment lambda-list keyword.

    Things like macroexpand and macro-function take environment arguments and use them to know whether, for instance, a macro is defined in the lexical environment. This is essential if you want to write code walkers, for instance: you need to know whether, in the current lexical environment, a form refers to a macro or not, and to expand it if so.

    Here is an example that shows this working (albeit in a rather silly way):

    (defmacro mf (name &environment e)
      (if (macro-function name e)
          `(format t "~&~S denotes a macro~%" ',name)
        `(format t "~&~S does not denotes a macro~%" ',name)))
    
    (defun ts1 ()
      (mf foo))
    
    (defun ts2 ()
      (macrolet ((foo (&body forms)
                   `(progn ,@forms)))
        (mf foo)))
    

    Now

    > (ts1)
    foo does not denotes a macro
    nil
    
    > (ts2)
    foo denotes a macro
    nil
    

    A more mundane use is constantp: this tells you if a given form will be a constant when evaluated. constantp can be passed an environment object and may make use of that object to determine whether a form is constant in that environment. constantp is not required to use the environment but it clearly could.

    During the standardization process for CL there was a fairly substantial proposal for a much richer protocol around environment objects: that was retracted as it was felt not ready for standardization. You can see information about this in the CLHS here. Some implementations may support some or all of this protocol (for instance LispWorks does).