Arc-macros, baby. Arc-macros. (part II)

More useless reader macros ahead. This time, let’s implement Arc’s [ ... _ ... ] lambda shortcut. In Chicken Scheme, of course. ^_^

I will be using { } rather than [ ]. Incidentally, Chicken already supports { } as an alternative for ( ):

> '{1 2 3}
1 2 3)
> {+ 1 2}
3

This reader macro will override that. Anyway, the code follows. It’s clumsy (although not as clumsy as my first version), and there’s probably a better/shorter way to do it (bit like installing the latest O2 UK Mobile Broadband after struggling through the 90s with 56k), that I’m not aware of yet. In any case, it’s naive; for example, this version does not allow nesting the { } constructs. Also, the replace-underscore function won’t replace underscores in nested lists. 1) And so on. Remember that this is just for demonstration purposes before pointing out the zillions of flaws. :-)

(define (read-until-next-curly port)
  "clumsy way to read up until the first }."
  (read-token (lambda (c) (not (char=? c #\}))) port))

(define (replace-underscore exprlist)
  (define (replace-underscore-item y)
    (if (equal? y '_) 'x y))
  (map replace-underscore-item exprlist))

(define (make-lambda-form exprlist)
  `(lambda (x) (,@exprlist)))

(define (eval-string s)
  (with-input-from-string s read))

(define (transform-into-lambda s)
  (let* ((expr (eval-string s))
         (expr2 (replace-underscore expr)))
    (make-lambda-form expr2)))

(set-read-syntax! #\{
  (lambda (port)
    (let* ((s (read-until-next-curly port))
           (t (string-append "(" s ")")))
      (read-byte port) ; read trailing '}'
      (transform-into-lambda t))))

Testing…

(print { + _ 1 })     ; => #<procedure (? x)
(print ({+ _ 1} 44))  ; => 45

(print (map { * _ 2 } '(1 2 3 4 5 6)))
; => (2 4 6 8 10 12)

Cool. Of course, if you use a lot of small lambdas, then it’s possible to write a macro that shortens things for you, rather than having to resort to syntax changes. For example:

> (define-macro (fn . body)
>   `(lambda (_) ,body))
> (fn + _ 1)
#<procedure (? _)>
> ((fn + _ 1) 44)
45

;; compare:
> ((lambda (x) (+ x 1)) 44)
45

Anyway, much like the previous post, this was just an excuse to explore reader macros a bit more. So don’t shoot me…

1) On a side note, why doesn’t SRFI-1 have a function to replace items in a list?

3 Comments

  1. John Cowan said,

    January 31, 2008 @ 5:59 pm

    Because it’s a one-liner:

    (define (set-elt! lst n obj) (set-car! (drop lst n) obj)

  2. Hans Nowak said,

    January 31, 2008 @ 6:35 pm

    Well, I was talking about replacing all instances of X with Y, either shallow or nested. Note that I am not asking you to write the function for me! :-) I was just wondering why such a beast does not exist. Maybe it’s not common enough an operation? Or is this another one-liner? :)

  3. Kon Lovett said,

    February 15, 2008 @ 9:55 pm

    If your domain is not boolean than take a look at “filter-map”.

    Chicken uses the “cut” macro for ‘{foo _ 2 _}’ – ‘(cut foo <> 2 <>)’. Not as concise but save thes ‘{}’ brackets for something really important.

RSS feed for comments on this post