2

I would like to write a macro to create shorthand syntax for hiding more verbose lambda expressions, but I'm struggling to understand how to write macros (which I realize is an argument against using them).

Given this example:

(define alist-example
  '((x 1 2 3) (y 4 5 6) (z 7 8 9)))

(define ($ alist name)
  (cdr (assoc name alist)))

((lambda (a) (map (lambda (x y z) (+ x y z)) ($ a 'x) ($ a 'y) ($ a 'z))) alist-example)
((lambda (a) (map (lambda (y) (/ y (apply max ($ a 'y)))) ($ a 'y))) alist-example)

I would like to write a macro, with-alist, that would allow me to write the last two expressions similar to this:

(with-alist alist-example (+ x y z))
(with-alist alist-example (/ y (apply max y)))

Any advice or suggestions?

  • instead of posting your own answer inside your question, you should post it as your own answer. if OTOH you still have questions about your new code, post it as a new question. specifically, `syntax-rules` *is* enough here. the answer has a typo/thinko, which you've misinterpreted in your `syntax-rules` translation. – Will Ness Mar 17 '20 at 10:54
  • Okay, I will make those changes after I get straightened out on the apparent confusion involved in my macro examples. – Travis Hinkelman Mar 17 '20 at 17:49

2 Answers2

1

The immediate problem I see is that there is no way to tell which bindings to pick. Eg. is apply one of the elements in the alist or is it a global variable? That depends. I suggest you do:

(with-alist ((x y z) '((x 1 2 3) (y 4 5 6) (z 7 8 9)))
  (+ x y z))

(let ((z 10))
  (with-alist ((x y) alist-example)
    (+ x y z)))

And that it should translate to:

(let ((tmp '((x 1 2 3) (y 4 5 6) (z 7 8 9))))
  (apply map (lambda (x y z) (+ x y z))
         (map (lambda (name) ($ tmp name)) '(x y z))))

(let ((z 10))
  (let ((tmp alist-example))
    (apply map (lambda (x y) (+ x y z))
           (map (lambda (name) ($ tmp name)) '(x y)))))

This is then straight forward to do with syntax-rules. Eg. make a pattern and write the replacement. Good luck.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • as you can see your second translation code gave the OP the wrong idea about the translation. you should really correct it. – Will Ness Mar 17 '20 at 12:18
  • I'm not following what the typo is. More importantly, to my current limited understanding, I can't see how you would use `(x y z)` (or `(x y)`) as both the syntax used in lambda and the list, `'(x y z)`, used in the inner map without the affordances provided by `syntax-case`. – Travis Hinkelman Mar 17 '20 at 17:46
  • @TravisHinkelman the last one is supposed to be `(apply map (lambda (x y) (+ x y)) (map (lambda (name) ($ alist-example name)) (quote (x y))))`. this readily translates into syntax-rules macro, to be called as `(with-alist ((x y) alist-example) (+ x y))`. I tried it, and it worked. you really should've asked this in a separate post. answers are supposed to be posted as such, not as comments as I did here, to serve the future readers better. – Will Ness Mar 17 '20 at 23:36
  • @WillNess The second where I don't use the available `z` is intentional. It's silly, but just as we cannot distinct between the bound and the free variables we are not bound to mention all of the variables in the contract either. – Sylwester Mar 18 '20 at 00:39
  • ah OK, I see. better example could've been `(let ((tmp alist-example)) (apply map (lambda (x y) (+ x 1)) (map (lambda (name) ($ tmp name)) (quote (x y)))))` then, to not give the wrong idea. – Will Ness Mar 18 '20 at 06:10
  • @WillNess I've updated it to use a free variable instead. The whole point was to justify the need for a list of symbols. With th eexpression exatly the same in the body, but with different scopes I think I demonstrate that well. – Sylwester Mar 19 '20 at 17:43
1

Here is a syntax-rules solution based on the feedback that I received in the other answer and comments:

(define ($ alist name)
  (cdr (assoc name alist)))

(define-syntax with-alist
  (syntax-rules ()
    [(_ alist names expr)
     (let ([alist-local alist])
       (apply map (lambda names expr)
              (map (lambda (name) ($ alist-local name)) (quote names))))]))

Here is some example usage:

> (define alist-example
  '((x 1 2 3) (y 4 5 6) (z 7 8 9)))
> (with-alist alist-example (x) (+ x 2))
(3 4 5)
> (with-alist alist-example (x y) (+ x y))
(5 7 9)
> (with-alist alist-example (x y z) (+ x y z))          
(12 15 18)

This answer stops short of solving the more complicated example, (with-alist alist-example (/ y (apply max y))), in my question, but I think this is a reasonable approach for my purposes:

> (with-alist alist-example (y) (/ y (apply max ($ alist-example 'y))))
(2/3 5/6 1)

EDIT: After some additional tinkering, I arrived at a slightly different solution that I think will provide more flexibility.

My new macro, npl, expands shorthand expressions into a list of names and procedures.

(define-syntax npl
  (syntax-rules ()
    [(_ (names expr) ...)
     (list 
      (list (quote names) ...)
      (list (lambda names expr) ...))]))

The output of this macro is passed to a regular procedure, with-list-map, that contains most the core functionality in the with-alist macro above.

(define (with-alist-map alist names-proc-list)
  (let ([names-list (car names-proc-list)]
        [proc-list (cadr names-proc-list)])
    (map (lambda (names proc)
           (apply map proc
                  (map (lambda (name) ($ alist name)) names)))
         names-list proc-list)))

The 3 examples of with-alist usage above can be captured in a single call to with-alist-map.

> (with-alist-map alist-example
                (npl ((x) (+ x 2))
                     ((x y) (+ x y))
                     ((x y z) (+ x y z))))
((3 4 5) (5 7 9) (12 15 18))
  • your second wish-list code snippet is really incompatible with the first (which you've now implemented). in `(/ y (apply max y))`, why should the two `y`s be different, and how'd we know about which is which? your last line is working, it's good enough. – Will Ness Mar 18 '20 at 06:02