This year, I am trying to get my hands on Advent of Code. One of my aims is to try out programming languages with varying paradigms. As of now, I have attempted the days (problems) intermittently and have not been changing languages that often too, mostly employing Common Lisp. One side effect of that has been a chance to have a deeper look into the CL's package ecosystem.
On first sight, you will notice that a lot of Common Lisp packages are old. Really old. It feels like you don't have a lot of options. I actually started a github project for collecting various handy macros/functions I use (or have seen) in Emacs Lisp, Racket, Clojure etc. In the process, what I found was that many of these are already present in CL. They are just spread out over lots of packages (with lot of duplication). An interesting set of Lisp macros that I have recently started to like is one that provides shortcuts for expressing λ functions. In this post, I try to knot up the options in Common Lisp for these little beauties.
Working in Lisp (or any language with functional inclinations) makes you use a lot of partially applied functions. Consider a list of numbers that you want to increment by 1. In Common Lisp this would be something like:
(mapcar (lambda (n) (+ 1 n)) '(1 2 3 4 5 6))
The lambda
in the middle really is just creating a partially applied version
of the function #'+
with the first argument set to 1. Now, writing this much
code ((lambda (n) (+ 1 n))
) for a simple function can get really boring. In
this specific case, since we already have a unary function #'1+
, we can get
rid of the boilerplate like this:
(mapcar #'1+ '(1 2 3 4 5 6))
But, in general, you need to use the full lambda
form. Some Lisps, like
hy provide a shorter name like fn
which is neat, but
given that we have macros in hand there can be much cleaner solutions.
1. Cut
One of the solutions I first came across is the -cut
macro in Emacs Lisp from
dash.el. This has its origin in SRFI-26 where a one line description says
Notation for Specializing Parameters without Currying.
In Common Lisp we have cl-cut which follows
Scheme's cut specification more completely (as compared to dash in elisp). If
you want to (say) replace ?
with !
in a list of strings, a cut based
solution would be:
;; cut is from cl-cut ;; replace-all is from cl-strings (mapcar (cut replace-all <> "?" "!") items)
Compare that with the full λ version:
(mapcar (lambda (s) (replace-all s "?" "!")) items)
Not a lot different, but why complicate things if there are shorter ways? Just
like in functions, anonymity in arguments helps when the effective name is
clear from context. Cut also allows expressing multiple arguments and λs with
&rest
:
;; Replace ? with different string in different items (mapcar (cut replace-all <> "?" <>) items replacements) ;; Taken from http://quickdocs.org/cl-cut/ (cut list 1 <> 3 <...>) ;; is equivalent to the following (lambda (x2 &rest xs) (apply #'list 1 x2 3 xs))
One decision made in cut is to only allow <>
in flat positions, i.e. the
placeholder <>
can not be hidden inside parentheses like in the following
case:
;; This doesn't work (funcall (cut + 2 (* <> 3)) 3) ;; <> inside lists are not detected while generating the argument list ;; (macroexpand '(cut + 2 (* <> 3))) gives (lambda () (+ 2 (* <> 3)))
Sometimes this nesting is needed. For example, if you are converting a list of temperature given in °F to °C, you might want to be able to write something like:
(mapcar (cut * (/ 5 9) (- <> 32)) temperatures)
If you need only unary functions, a simple solution is to make an anaphoric
version of λ which captures <>
:
(defmacro acut (&rest body) `(lambda (<>) ,@body)) ;; This works now (mapcar (acut * (/ 5 9) (- <> 32)) temperatures)
2. Xi
This is another fancy way of creating λs that I came to know from hy. Although its name is going to be changed in a while, according to current stable docs, its usage is like this:
;; (require [hy.extra.anaphoric [*]]) (xi - x1 x2) ;; This is equivalent to (fn [x1 x2] (- x1 x2)) ; Not that `lambda` is called `fn` in Hy
The important thing to notice is that xi
supports positional arguments (and
also nesting) by using the number in the placeholders x1
, x2
etc. For
example, consider the following snippet:
;; Hy ((xi - (+ x1 x2) x3) 2 5 10) ;; -3 ((xi - (+ x2 x3) x1) 2 5 10) ;; 13
Xi really is inspired by Clojure's anonymous function syntax which is pretty powerful:
#(+ 10 %) ;; % acts similar to <> in cut #(- %1 (+ %3 %2)) ;; We have nesting and positional arguments (%i) #(some-fun % %&) ;; We have &rest (as %&) too
Closest looking syntax for this in Common Lisp (at least from what I found out)
is of the #L
reader macro from
arnesi
and ^
macro from
CL21.
Using #L
, the above examples in clojure translates to the following Common
Lisp code
;; Need to enable sharp-l reader macro ;; (arnesi:enable-sharp-l-syntax) #L(+ 10 !1) ;; !i for position i #L(- !1 (+ !3 !2)) ;; No &rest arguments here
In CL21, you have:
^(+ 10 %) ^(- %1 (+ %3 %2)) ;; Don't know if &rest is supported yet
Another option is positional-lambda where Clojure's examples go like the following:
(plambda (+ 10 :1)) ;; :i for position i (plambda (- :1 (+ :3 :2))) (plambda (some-fun :1 :rest)) ;; &rest is supported
Although using complicated λs signals that you better reconsider your decision of not creating dedicated functions, these fancier variants are pretty useful.
Here is a listing of Common Lisp packages mentioned in this post and some other which are similar in the sense that they allow creating/manipulating functions:
- cl-cut for
cut
(andcute
) syntax from SRFI-26 - arnesi for the
#L
reader macro. - CL21 for the
^
macro. Not on quicklisp main repos but can be installed using ql. Instructions on project's page. - positional-lambda for
plambda
macro. - There is also
#`-reader
in rutils providing Clojure-ish but slightly limited shorthands
#`(+ 2 %) ; => (lambda (&optional x y) (+ 2 x)) #`((print %) (1+ %)) ; => (lambda (&optional x) (print x) (1+ x)) #`(+ % %%) ; => (lambda (&optional x y) (+ x y))
- fn provides another set of shorthands for λs. Examples from github page:
(fn* (subseq _@ 0 2)) ; => (lambda (&rest _@) (subseq _@ 0 2)) ;; with reader macros for fn* forms too λ(+ _ _1) ; => (lambda (_ _1) (+ _ _1))
- curry-compose-reader-macros provides shorthand reader macros for currying and composing functions. A few examples from the github page:
;; partial application `curry' (mapcar {+ 1} '(1 2 3 4)) ; => (2 3 4 5) ;; function composition (mapcar [#'list {* 2}] '(1 2 3 4)) ; => ((2) (4) (6) (8)) ;; function split and join (mapcar «list {* 2} {* 3}» '(1 2 3 4)) ; => ((2 3) (4 6) (6 9) (8 12))
- curly has two reader macros for currying and composition. Examples from homepage
'[foo bar * baz] ; => (lambda (#:g2709) (foo bar #:g2709 baz)) '{foo (bar 16) (baz 23 * 42) quux} ;; => (lambda (#:g2724) (foo (bar 16 (baz 23 (quux #:g2724) 42)))