|Project author||Jean-Philippe Paradis|
|Programming language||Common Lisp|
|Library type||Concision macro|
|Date||30 sept 2012|
(Oct 2012 dist and later)
|⬉||Depends on||Depended on by|
The project is called "positional-lambda".
The macro itself is called "plambda", for brevity.
This is an exception to my usual policy against abbrevations.
plambda is a concise, intuitive and flexible syntax (macro) for trivial
lambdas that eschews explicit (and often contextually-redundant) naming of parameter variables in favor of positional references, with support for a used or ignored
&rest parameter and automatic declaration of
ignored parameters when logical "gaps" are left in the positional references. Further convenience features are provided.
:1)) == (lambda (first second third) (declare (ignore second)) (values third first))
:rest)) == (lambda (first second &rest rest) (list second first rest))
:1)) == (lambda (first second &rest rest) (declare (ignore rest)) (list second first))
It's possible to specify a minimum number of required arguments:
:2) == (lambda (first second third) (declare (ignore first third)) second)
:3) ; redundant minimum: 3 > 2. == (
Which also has the effect of "pushing back" the
&rest argument if there's one:
:rest)) == (lambda (first second third &rest rest) (declare (ignore second third)) (mapcar first rest))
The first argument to
plambda is treated as a specification of the minimum number of arguments only if it looks like a positional reference and
plambda was invoked with other arguments:
:2) == (lambda (first second) (declare (ignore first)) second)
plambda accepts an implicit
progn, not just one expression:
:2) == (lambda (first second) (print first) second)
:let "local special operator" allows one to "lift" parts of its body outside the
lambda to a
let without needing to name and then refer to an explicit variable.
:let(random)))) == (let ((number (random))) (lambda (first second) (declare (ignore second)) (list first number)))
Another feature is
:cache, which is like
:let except it computes the associated form in its original lexical and dynamic context within the
lambda the first time its evaluation completes and returns the cached value on subsequent evaluations.
:cache*print-base*))) == (let (base basep) (lambda (first) (write first :base (if basep base (prog1 (setf base *print-base*) (setf basep t))))))
The consequences are undefined if the
:cache local special
operators are nested within themselves or eachother:
:let1))) ; undefined
plambda will treat any quoted expressions as opaque, and will treat anything wrapped in the
:quote local-special-operator as opaque as well.
plambda':1) == (lambda () ':1)
:quote:1)) == (lambda () :1)
plambda won't do the right thing with some expressions that are quoted via backquote (since there's no easy portable way to walk backquoted expressions).
:2)) == [
:1will be erroneously replaced]
(:import-from #:positional-lambda #:plambda) from your
(:use)! "Clashy" symbols might be added to the
positional-lambda package in the future.
positional-lambda should only be used to describe actually trivial
lambdas, usually no more than 3 lines long, and should not be used in code returned by macros, because of "capture" issues.
In particular, due to the
plambda macro's parsing model, it's not safe to nest multiple invocations.
However, these are not practical limitations since as
plambda's expressions get bigger, the benefits of conciseness quickly fade out to be replaced by the problems of implicitness, so hopefully one wouldn't be tempted to use it in these scenarios anyway. The implicitness is not a problem for suitably short
plambda expressions since the surrounding context provides enough contextual information, and explicitly naming variables would then simply provide redundant information.
plambda's deliberately simplistic "surface parsing" (and replacement) strategy is conceptually and implementationally simple and robust, as opposed to often-brittle "code-walking" where an attempt is made to take into account the actual semantics of forms and operators, often necessitating explicit support when new operators are introduced.