Project author Jean-Philippe Paradis
Project type Library
Programming language Common Lisp
Status Ready-to-use
License Public Domain
Library type Concision macro
Project complexity Simple
Latest release Version 2.0
Date 30 sept 2012
Quicklisp
(Oct 2012 dist and later)
Status Available
Version Latest

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.

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 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. (Actually, nesting multiple 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.