2

I'm trying to write a macro in Scheme for Picolisp style let expressions, let's call this macro let-slim. In order to be more terse (like Picolisp) I want their usage to look something like this when declaring only one variable

(let-slim var-name initial-value
  (display var-name))

Or something like this to declare any number of variables (note that this is pseudocode, I wouldn't actually include the elipses)

(let-slim (var-name-1 initital-value-1
           var-name-2 initital-value-2
           ...
           var-name-n initital-value-n)
  (+ var-name-1 var-name-2 ... var-name-n))

The first usecase is fairly trivial to write a syntax-rules matching pattern for, but the latter I am struggling with.

This doesn't work because only init gets repeated

(define-syntax let-slim
  (syntax-rules ()
    [(_ (var init ...) body ...)
     (let ((var init) ...)
       body ... )]))

This doesn't work because it's considered a misplaced elipsis

(define-syntax let-slim
  (syntax-rules ()
    [(_ (var ... init ...) body ...)
     (let ((var init) ...)
       body ... )]))

And this doesn't work because I need to use parens at the reference point (which means it changes absolutely nothing as compared to the built-in let)

(define-syntax let-slim
  (syntax-rules ()
    [(_ (var init) ...) body ...)
     (let ((var init) ...)
       body ... )]))

So, is there a way to repeat 2 variables in syntax-rules without needing to wrap them in parens, or do I need to use a different macro system (ie syntax-case or defmacro)?

Charlim
  • 521
  • 4
  • 12

2 Answers2

5

It's not optimal doing this with syntax-rules, but since it is turing complete it can be done:

(define-syntax let-slim
  (syntax-rules (pair)
    ((_ pair bindings () body)
     (let bindings . body))
    ((_ pair (acc ...) (k v . rest) body)
     (let-slim pair (acc ... (k v)) rest body))
    ((_ (elements ...) . body)
     (let-slim pair () (elements ...) body))))
Sylwester
  • 47,942
  • 4
  • 47
  • 79
3

It's not possible to do this in one go with syntax-rules ... feature, but you may be able to do it with syntax-rules using recursion:

(define-syntax let-slim
  (syntax-rules ()
    ((let-slim (var-1 val-1 . rest) . body)
     (let-slim var-1 val-1 (let-slim rest . body)))
    ((let-slim var val . body) 
     ;; single binding case you already implemented
     ))

The only problem is that syntax-rules can't tell that 'var' is supposed to be a symbol. You wont get good error messages from a macro like this (for example if it's used with an odd number of var/val bindings). It may be better to implement this macro with syntax-case. The reason it's difficult to implement is because it's kind of violating the idea of using a pair of brackets for each AST node.

river
  • 1,028
  • 6
  • 16
  • I do not believe your example code works as-is. In the first case, the body should be inside of the recursive call to let-slim, and written in that manner it seems to eventually expand to a reference to (let-slim () . body), which has no appropriate matching case. I got it to work by adding such a case, but that would allow the end-user to use (let-slim () . body) at any point, which might be a little silly. I think I'll look into using syntax-case, but if you know of a solution to that last issue let me know. – Charlim Jun 02 '19 at 22:24