The macrodynamics Reference Manual

Next: , Previous: , Up: (dir)   [Contents][Index]

The macrodynamics Reference Manual

This is the macrodynamics Reference Manual, version 0.1.1, generated automatically by Declt version 4.0 beta 2 "William Riker" on Mon Aug 15 05:16:03 2022 GMT+0.

Table of Contents


1 Introduction

Macrodynamics

Build Status Quicklisp

Macrodynamics is a language extension that broadens the notion of dynamic scope inside macroexpansion code. Macrodynamic bindings are scoped not just to the code called during an individual expansion, but also to subsequent expansions of the code returned within the dynamic scope of those bindings.

This goes a long way toward rectifying a major limitation of Common Lisp, described in detail here. In short, you can write macros that behave as if they were expanded recursively instead of iteratively. However, macrodynamics only lets data flow downstream from where it is bound; it does not provide analogues of conditions or continuations to transmit information back up the expansion stack.

Usage

You can declare macrodynamic variables with def-dynenv-var, a cognate of defvar. These variables can be left unbound or given a top-level, global value.

(def-dynenv-var **my-macrodynamic-var** value)
(def-dynenv-var **my-unbound-macrodynamic-var**)

(You may want to use a special notational convention for macrodynamic variables and functions. I prefer **double-earmuffs** myself.)

For macrodynamic functions, def-dynenv-fun works like defun, while def-unbound-dynenv-fun only takes a name which remains unbound at the top-level. (There's just no way to sensibly combine both syntaxes within one macro.)

(def-dynenv-fun **my-macrodynamic-fun** (&rest stuff) (do-stuff-with stuff))
(def-unbound-dynenv-fun **my-unbound-macrodynamic-fun**)

Macrodynamic variables and functions live in a separate namespace from regular Lisp variables (whether lexical or dynamic) and functions. To establish bindings for them, you must use one of a few dynamic-only compile-time variants of familiar operators. ct-let and ct-let* bind variables, and ct-flet binds functions.

Within the body of any function definition created with ct-flet, the function name call-next-dynenv-fun is bound (lexically, not dynamically!) to the previously dynamically-bound function with the same name. But any recursive invocation of the function by name, even within a call to call-next-dynenv-fun, will always invoke the most recently dynamically-bound function. You can also retrieve dynamically-bound functions as values using dynenv-function instead of function or #'. function/#' simply retrieves a wrapper for calling the dynamically-bound function, which may be rebound between the time you retrieve the wrapper and the time you invoke it. For more details, see Pascal Costanza's paper on dynamically-scoped functions.

To define macros that need to read or bind macrodynamic entities within the dynamic scope of their expander code, you can use def-dynenv-macro:

(def-dynenv-var **a-macrodynamic-var** nil)

(def-dynenv-macro some-macro (&body body)
  `(do-stuff
       ,(do-something)
     ,(ct-let ((**a-macrodynamic-var**
                (non-destructively-augment **a-macrodynamic-var**)))
        `(progn ,@body))))

;; this function will signal an error if not called within the dynamic scope
;; of a macrodynamic macro's expansion
(defun do-something ()
  (generate-code-with **a-macrodynamic-var**))

(defmacro dummy-wrapper-for-some-macro (&body body)
  `(some-macro ,@body))

Then if you write a form like this:

(some-macro
  (dummy-wrapper-for-some-macro
    (some-macro
      (some-other-code))))

do-something will see the global binding nil for **a-macrodynamic-var** during the expansion of the top-level some-macro form, then it will see a new binding equivalent to (non-destructively-augment nil) in the expansion of the some-macro form that dummy-wrapper-for-some-macro's expansion returns, then another new binding equivalent to (non-destructively-augment (non-destructively-augment nil)) in the innermost some-macro expansion.

def-dynenv-macro is just a convenience macro that can extract the macrodynamic context from the lexical environment regardless of whether you include an &environment parameter. (An analogous dynenv-macrolet macro is also provided.) Alternatively, you can explicitly pass an environment to with-dynenv at the top of a macro's body (or at least surrounding any forms that need to bind or reference macrodynamic entities). This makes it easier to integrate macrodynamics with any other special macro-defining-macros you might want to use.

(def-macro-using-some-other-macro-library some-macro
    (&body body &environment env)
  (with-dynenv env
    `(do-stuff
         ,(do-something)
       ,(ct-let ((**a-macrodynamic-var**
                  (non-destructively-augment **a-macrodynamic-var**)))
          (remember-that-you-can-also-see-the-new-value-of **a-macrodynamic-var**
             immediately-right-here-in-the-same-expansion-step!
             `(progn ,@body))))))

This library is meant to be used in a purely functional manner, and it will signal an error if you attempt to set, rather than rebind, a macrodynamic binding. That's right, dynamic scope is compatible with functional programming; it just admits a slightly looser definition of referential transparency. You can think of dynamic variables as an implicit set of additional arguments passed to every function. When dynamic bindings are in play, a function called with the same arguments may not always return the same results, but a function called at the top-level with the same arguments always will. What this means for macrodynamics is that an entire top-level form will always have the same macroexpansion. Normally, this is all you really care about, since you spend most of your time reasoning about top-level forms that you can see in their entirety.

One drawback is that you won't always be able to use SLIME's C-c C-m to verify what a non-top-level expression will expand into, but this is no different from any other situation in which you might use macrolet or symbol-macrolet. Macrodynamics are no more dangerous than lexically-bound macros; in fact, they're just an abstraction layer built on top of symbol-macrolet.

But Why?

What is dynamic from the perspective of expander code is lexical from the perspective of expanded code. When you take full advantage of this semantic duality, it's easy to lexically scope, and thus make more composable, certain implementation concerns that do not easily map onto lexical runtime variables. In the most common case, you can bind compile-time metadata about how a given variable is meant to be interpreted by another macro that may or may not be used further down the syntax tree.

Instead of placing your trust in a code walker like macroexpand-dammit, macrodynamics piggybacks on your implementation's built-in macroexpansion facility. When you use a code walker, you introduce a potential point of failure that can screw up the expansion of code in between the macro that establishes a compile-time dynamic context and the macro that uses it. With macrodynamics, you can trust your Lisp implementation to correctly process any in-between special operators, function calls, and macros that were written without any knowledge of this language extension.


2 Systems

The main system appears first, followed by any subsystem dependency.


Previous: , Up: Systems   [Contents][Index]

2.1 macrodynamics

A language extension for creating bindings scoped to the entire expansion process of a region of code.

Author

Kyle Littler

Home Page

https://github.com/DalekBaldwin/macrodynamics

License

LLGPL

Version

0.1.1

Dependency

alexandria (system).

Source

macrodynamics.asd.

Child Component

src (module).


3 Modules

Modules are listed depth-first from the system components tree.


Previous: , Up: Modules   [Contents][Index]

3.1 macrodynamics/src

Source

macrodynamics.asd.

Parent Component

macrodynamics (system).

Child Components

4 Files

Files are sorted by type and then listed depth-first from the systems components trees.


Previous: , Up: Files   [Contents][Index]

4.1 Lisp


Next: , Previous: , Up: Lisp   [Contents][Index]

4.1.1 macrodynamics/macrodynamics.asd

Source

macrodynamics.asd.

Parent Component

macrodynamics (system).

ASDF Systems

macrodynamics.


4.1.2 macrodynamics/src/package.lisp

Source

macrodynamics.asd.

Parent Component

src (module).

Packages

macrodynamics.

Internals

*system-directory* (special variable).


4.1.3 macrodynamics/src/util.lisp

Dependency

package.lisp (file).

Source

macrodynamics.asd.

Parent Component

src (module).

Internals

4.1.4 macrodynamics/src/macrodynamics.lisp

Dependency

util.lisp (file).

Source

macrodynamics.asd.

Parent Component

src (module).

Public Interface
Internals

5 Packages

Packages are listed by definition order.


Previous: , Up: Packages   [Contents][Index]

5.1 macrodynamics

Source

package.lisp.

Use List
  • alexandria.
  • common-lisp.
Public Interface
Internals

6 Definitions

Definitions are sorted by export status, category, package, and then by lexicographic order.


Next: , Previous: , Up: Definitions   [Contents][Index]

6.1 Public Interface


6.1.1 Macros

Macro: ct-flet (definitions &body body)
Package

macrodynamics.

Source

macrodynamics.lisp.

Macro: ct-let (bindings &body body)
Package

macrodynamics.

Source

macrodynamics.lisp.

Macro: ct-let* (bindings &body body)
Package

macrodynamics.

Source

macrodynamics.lisp.

Macro: def-dynenv-fun (name args &body body)
Package

macrodynamics.

Source

macrodynamics.lisp.

Macro: def-dynenv-macro (name lambda-list &body body)
Package

macrodynamics.

Source

macrodynamics.lisp.

Macro: def-dynenv-var (var &optional val)
Package

macrodynamics.

Source

macrodynamics.lisp.

Macro: def-unbound-dynenv-fun (name)
Package

macrodynamics.

Source

macrodynamics.lisp.

Macro: dynenv-function (name)
Package

macrodynamics.

Source

macrodynamics.lisp.

Macro: dynenv-macrolet (definitions &body body)
Package

macrodynamics.

Source

macrodynamics.lisp.

Macro: with-dynenv (environment &body body)

Macro for capturing a dynenv within another macro’s body.

Package

macrodynamics.

Source

macrodynamics.lisp.


Previous: , Up: Public Interface   [Contents][Index]

6.1.2 Conditions

Condition: unbound-dynenv-macro-fun
Package

macrodynamics.

Source

macrodynamics.lisp.

Direct superclasses

condition.

Direct methods
Direct slots
Slot: fun
Initargs

:fun

Readers

fun.

Writers

(setf fun).

Condition: unbound-dynenv-macro-var
Package

macrodynamics.

Source

macrodynamics.lisp.

Direct superclasses

condition.

Direct methods
Direct slots
Slot: var
Initargs

:var

Readers

var.

Writers

(setf var).


6.2 Internals


Next: , Previous: , Up: Internals   [Contents][Index]

6.2.1 Special variables

Special Variable: *eval-phases*
Package

macrodynamics.

Source

macrodynamics.lisp.

Special Variable: *fun-space*
Package

macrodynamics.

Source

macrodynamics.lisp.

Special Variable: *system-directory*
Package

macrodynamics.

Source

package.lisp.

Special Variable: *unbound*
Package

macrodynamics.

Source

macrodynamics.lisp.

Special Variable: *var-space*
Package

macrodynamics.

Source

macrodynamics.lisp.

Special Variable: *within-captured-dynenv*
Package

macrodynamics.

Source

macrodynamics.lisp.


6.2.2 Symbol macros

Symbol Macro: -unbound-
Package

macrodynamics.

Source

macrodynamics.lisp.


6.2.3 Ordinary functions

Function: dynenv-function% (symbol)
Package

macrodynamics.

Source

macrodynamics.lisp.

Function: get-assoc (item alist &rest keys &key key test test-not)

Like ASSOC but returns the cdr instead of the whole matching cons and a second value indicating success or failure.

Package

macrodynamics.

Source

util.lisp.

Function: get-dynenv-var (var)
Package

macrodynamics.

Source

macrodynamics.lisp.

Function: (setf get-dynenv-var) (var)
Package

macrodynamics.

Source

macrodynamics.lisp.

Function: update-alist (item value alist)

Non-destructively replace cdr of the cons whose car matches ITEM in ALIST with VALUE, or insert a new cons if no car matches ITEM.

Package

macrodynamics.

Source

util.lisp.


6.2.4 Generic functions

Generic Reader: fun (condition)
Generic Writer: (setf fun) (condition)
Package

macrodynamics.

Methods
Reader Method: fun ((condition unbound-dynenv-macro-fun))
Writer Method: (setf fun) ((condition unbound-dynenv-macro-fun))
Source

macrodynamics.lisp.

Target Slot

fun.

Generic Reader: var (condition)
Generic Writer: (setf var) (condition)
Package

macrodynamics.

Methods
Reader Method: var ((condition unbound-dynenv-macro-var))
Writer Method: (setf var) ((condition unbound-dynenv-macro-var))
Source

macrodynamics.lisp.

Target Slot

var.


Appendix A Indexes


Next: , Previous: , Up: Indexes   [Contents][Index]

A.1 Concepts


Next: , Previous: , Up: Indexes   [Contents][Index]

A.2 Functions

Jump to:   (  
C   D   F   G   M   U   V   W  
Index Entry  Section

(
(setf fun): Private generic functions
(setf fun): Private generic functions
(setf get-dynenv-var): Private ordinary functions
(setf var): Private generic functions
(setf var): Private generic functions

C
ct-flet: Public macros
ct-let: Public macros
ct-let*: Public macros

D
def-dynenv-fun: Public macros
def-dynenv-macro: Public macros
def-dynenv-var: Public macros
def-unbound-dynenv-fun: Public macros
dynenv-function: Public macros
dynenv-function%: Private ordinary functions
dynenv-macrolet: Public macros

F
fun: Private generic functions
fun: Private generic functions
Function, (setf get-dynenv-var): Private ordinary functions
Function, dynenv-function%: Private ordinary functions
Function, get-assoc: Private ordinary functions
Function, get-dynenv-var: Private ordinary functions
Function, update-alist: Private ordinary functions

G
Generic Function, (setf fun): Private generic functions
Generic Function, (setf var): Private generic functions
Generic Function, fun: Private generic functions
Generic Function, var: Private generic functions
get-assoc: Private ordinary functions
get-dynenv-var: Private ordinary functions

M
Macro, ct-flet: Public macros
Macro, ct-let: Public macros
Macro, ct-let*: Public macros
Macro, def-dynenv-fun: Public macros
Macro, def-dynenv-macro: Public macros
Macro, def-dynenv-var: Public macros
Macro, def-unbound-dynenv-fun: Public macros
Macro, dynenv-function: Public macros
Macro, dynenv-macrolet: Public macros
Macro, with-dynenv: Public macros
Method, (setf fun): Private generic functions
Method, (setf var): Private generic functions
Method, fun: Private generic functions
Method, var: Private generic functions

U
update-alist: Private ordinary functions

V
var: Private generic functions
var: Private generic functions

W
with-dynenv: Public macros

Jump to:   (  
C   D   F   G   M   U   V   W