positional-lambda
plambda
is a concise, intuitive and flexible syntax (macro) for trivial lambda
s 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 ignore
d parameters when logical "gaps" are left in the positional references. Further convenience features are provided.
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
(values :3
:1
))
==
(lambda (first second third)
(declare (ignore second))
(values third first))
(plambda
(list :2
:1
:rest
))
==
(lambda (first second &rest rest)
(list second first rest))
(plambda
:rest
(list :2
:1
))
==
(lambda (first second &rest rest)
(declare (ignore rest))
(list second first))
It's possible to specify a minimum number of required arguments:
(plambda
:3
:2
)
==
(lambda (first second third)
(declare (ignore first third))
second)
(plambda
:2
:3
) ; redundant minimum: 3 > 2.
==
(plambda
:3
)
Which also has the effect of "pushing back" the &rest
argument if there's one:
(plambda
:3
(mapcar :1
: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:
(plambda
:2
)
==
(lambda (first second)
(declare (ignore first))
second)
plambda
accepts an implicit progn
, not just one expression:
(plambda
(print :1
) :2
)
==
(lambda (first second)
(print first)
second)
Also, plambda
's :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.
(plambda
:2
(list :1
(: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.
(plambda
(write :1
:base (: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 :let
and :cache
local special operators are nested within themselves or eachother:
(plambda
(:let
(:let
1))) ; 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)
(plambda
() (:quote
:1))
==
(lambda () :1)
Unfortunately, currently 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).
(plambda
`(:1
,:2
))
==
[:1
will be erroneously replaced]
To use plambda
, simply (:import-from #:positional-lambda #:plambda)
from your defpackage
. Don't (:use)
! "Clashy" symbols might be added to the positional-lambda
package in the future.
positional-lambda should only be used to describe actually trivial lambda
s, 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 (Actually, nesting multiple plambda
macro's parsing model, it's not safe to nest multiple invocations.plambda
invocations has been safe for quite a while. The outer invocations will effectively treat any inner ones as being implicitly wrapped in :quote
.)
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.