schemeracketr6rsdefine-syntaxr7rs

Hygienic macro r7rs : Return second expression value


I'm currently learning some r7rs and I am trying to implement a macro 'begin' as following :

(begin0 expr0 expr1 ... expr2)

With expr being a regular expression (Like (set! x (+ x 1)))

And begin0 as a macro that evaluates all the expression but return only the expr1 result.

For example :

(let ((year 2017))
(begin1 (set! year (+ year 1))
  year
  (set! year (+ year 1))
  year)) 

It musts return 2018

I've created a begin function first :

(define-syntax begin0
 (syntax-rules ()
  ((begin-0 body-expr-0 body-expr-1 ...)
   (let ((tmp body-expr-0)) body-expr-1 ... tmp))))

And now, I'm trying to understand how I can do to return the value of "body-expr-1" ? I've done the following code, but it says that I'm missing some ellipsis and I don't understand how to do it.

(define-syntax begin1
  (syntax-rules ()
    ((begin1 body-expr-0 body-expr-1 ... body-expr-2)
     (let ((tmp body-expr-0) body-expr-1 ... tmp)
       (cond (eq? tmp body-expr-1)
              (begin . tmp))))))

I hope that it is understandable enough, thanks for the answers.


Solution

  • This can be done, but the macro will interfere such that you cannot do all the things with begin1 as with begin.

    (define-syntax begin1
       (syntax-rules ()
         ((_ expr0 expr1 exprn ...)
          (begin
            expr0
            (let ((result expr1))
              exprn ...
              result)))))
    

    The code that does not work is this:

    (begin1
      (define global1 10)
      test3
      (define global2 20))
    

    The reason is obvious. It expands to:

    (begin1
      (define global1 10)
      (let ((result~1 test3))
        (define global2 20)
        result~1))
    

    The second define will be changed to a letrec such that the variable global2 is only available for the duration of the let. I have no fix for this since it requires you to be able to do global define from a closure.

    begin1 is rather strange feature. In Racket and perhaps other Scheme dialects we have begin0 that returns the result of the first expression. This is very useful. eg. here is a counter:

    (define (get-counter from)
      (lambda ()
        (let ((tmp from))
          (set! from (+ from 1))
          tmp)))
    

    And with begin0:

    (define (get-counter from)
      (lambda ()
        (begin0 
          from
          (set! from (+ from 1)))))
    

    In Racket begin0 is a primitive. So it is a form supported in the fully expanded program and thus implemented in C, just like begin..