Subject: Re: sharp-back syntax
From: rpw3@rpw3.org (Rob Warnock)
Date: Fri, 30 Mar 2007 22:35:37 -0500
Newsgroups: comp.lang.lisp
Message-ID: <cpKdnU8mZLaUSpDbnZ2dnUVZ_hmtnZ2d@speakeasy.net>
<nallen05@gmail.com> wrote:
+---------------
| Barry Margolin <bar...@alum.mit.edu> wrote:
| > r...@rpw3.org (Rob Warnock) wrote:
...
| > >     > (mapcar #$(> $1 $2) '(1 2 3 4 5) '(5 4 3 2 1))
| >
| > >     (NIL NIL NIL T T)
| > >     > ...
...
| > > But I use this *only* at the REPL when poking around,
| > > *never* in saved source files...
| >
| > One problem I can see with this is that they don't nest properly.  The
| > outer one will drill into inner one and see its implicit lambda
| > variables, and make them into lambda varables for the outer one.
+---------------

Mine doesn't "drill" at all, it just uses normal lexical scope
with a bunch of fixed pre-chosen optional lambda varables:

;;; SET-SHARP-DOLLAR-READER -- Experimental LAMBDA abbreviation (#1 of 2).
;;; SYNTAX: #$FORM
;;; An abbreviation of: (lambda (&optional $1 $2 $3 $4 $5 $6 $7 $8 $9 &rest $*)
;;;                       FORM)
;;; Within the FORM, args $1 ... $9 and $* are be lambda-bound as positional
;;; and &REST parameters, respectively. Usually, but not always, FORM will be
;;; an S-expr, e.g. #$(car $3), but this is legal: #$FOO ==> (lambda () FOO),
;;; that is, (CONSTANTLY FOO). Likewise, #$$3 ==> #'THIRD.
;;;
;;; As a convenience for interactive use, in the special case that FORM is a
;;; list and (car FORM) is also a list, then an implicit PROGN is provided,
;;; e.g., #$((foo) (bar)) ==> (lambda (args...) (foo) (bar)).
;;;
(defun set-sharp-dollar-reader ()
  (flet ((sharp-dollar-reader (s c p)
           (declare (ignore c p))
           (let* ((form (read s t nil t)))
             `(lambda (&optional $1 $2 $3 $4 $5 $6 $7 $8 $9 &rest $*)
                (declare (ignorable $1 $2 $3 $4 $5 $6 $7 $8 $9 $*))
                ,@(if (and (consp form) (consp (car form)))
                    form
                    (list form))))))
    (set-dispatch-macro-character #\# #\$ #'sharp-dollar-reader)))

So, yes, it suffers from variable capture.

But who cares? I've *never* found a use for nesting them.
As I said, "I use this *only* at the REPL when poking around..."

By the way, here's another style I've tried occasionally.
I don't like it as much, but it doesn't suffer from the
variable capture problem:

    > (mapcar #[x (= x 5)] '(1 3 5 7 9))

    (NIL NIL T NIL NIL)
    > (mapcar #[x (* x 1.085)]  '(12.34 15 10 25.37))

    (13.3889 16.275 10.85 27.52645)
    > (mapcar #[(x y) (> x y)] '(1 2 3 4 5) '(5 4 3 2 1))

    (NIL NIL NIL T T)
    > (mapcar #[&rest (list* 'foo rest)] '(1 2 3) '(4 5 6) '(7 8 9))

    ((FOO 1 4 7) (FOO 2 5 8) (FOO 3 6 9))
    > 

Definition:

;;; SET-SHARP-BRACKET-READER -- Experimental LAMBDA abbreviation (#2 of 2).
;;; SYNTAX: #[ARGS . BODY]
;;; An abbreviation of (LAMBDA args . body), with two special cases
;;; (feel free to change them, your tastes may differ);
;;; 1. If the ARGS sub-form is the symbol &REST (in any package) then the
;;;    form is re-written as (LAMBDA (&rest current-pkg::rest) . body); and
;;; 2. If the ARGS sub-form is any other single symbol then the form is
;;;    re-written as (LAMBDA (args) . body).
;;; Otherwise, the form is simply re-written as (LAMBDA args . body).
;;; Examples:
;;;   (mapcar #[x (1+ x)] list1)
;;;   (mapcar #[(x y) (cons y x)] list1 list2)  ; almost a REV-PAIRLIS
;;;   (mapcar #[&rest (apply #'some-func fixed-arg1 fixed-arg2 rest)] lists...)
;;;
(defun set-sharp-bracket-reader ()
  (macrolet ((ch (x) (char "()[]{}<>"   ; idiom for avoiding editor mismatches
                           (position x '(:lp :rp :lb :rb :lc :rc :la :ra)))))
    (flet ((sharp-bracket-reader (s c p)
             (declare (ignore c p))
             (let* ((args (read s t nil t))
                    (body (read-delimited-list (ch :rb) s t)))
               (etypecase args
                 (symbol                ; special syntax for some common cases
                  (if (equal (symbol-name '&rest) (symbol-name args))
                    `(lambda (&rest ,(intern (symbol-name 'rest))) ,@body)
                    `(lambda (,args) ,@body)))
                 (list
                  `(lambda ,args ,@body))))))
      (set-dispatch-macro-character #\# (ch :lb) #'sharp-bracket-reader)
      (set-macro-character (ch :rb) (get-macro-character (ch :rp) nil)))))

Then there's always this one, which is the same as the previous one
except without the funny reader syntax [and except that it uses a
Scheme-style &REST parameter]:

    > (defmacro fn (args &body body)
        `(lambda ,(if (listp args) args (list '&rest args)) ,@body))

    FN
    > (mapcar (fn (x) (= x 5)) '(1 3 5 7 9))

    (NIL NIL T NIL NIL)
    > (mapcar (fn (x) (* x 1.085))  '(12.34 15 10 25.37))

    (13.3889 16.275 10.85 27.52645)
    > (mapcar (fn (x y) (> x y)) '(1 2 3 4 5) '(5 4 3 2 1))

    (NIL NIL NIL T T)
    > (mapcar (fn x (list* 'foo x)) '(1 2 3) '(4 5 6) '(7 8 9))

    ((FOO 1 4 7) (FOO 2 5 8) (FOO 3 6 9))
    > 

But typing FN isn't much savings over typing LAMBDA, so I don't
use that one much, either.

My #$ readmacro's main win [and its main technical problem] is its
fixed pre-defined parameter list, with the shell-like $1, $2, etc.
If that's not good enough, I just use LAMBDA.


-Rob

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607