Search code examples
schemelisplisp-macros

What is the difference between letrec-syntax and let-syntax in Scheme?


There were similar question but not with syntax macros, I though that the difference is that one macro can't see the other like letrec and let with functions.

But this works the same with letrec-syntax and let-syntax

(let-syntax ((foo (lambda (x) `(bar (list ',(car x) ',(caadr x)))))
             (bar (lambda (x) `(display ',x))))
    (foo (list 1 2 3)))

Can you show example of code where those two macros/syntax differ?

EDIT:

Found this:

(let-syntax ((bar (lambda (x) `(display ',x)))
             (foo (lambda (x) (bar x))))
   (foo (list 1 2 3)))

that don't work with let-syntax and it work with letrec-syntax, but what is the benefit of this? If those local variables can't be functions (the output is (bar x)), what is the purpose of let-syntax? I need example where it can be used in the code, where you don't need letrec-syntax and it would be enough to have let-syntax.


Solution

  • It is the same as let and letrec. With let you cannot expect the binding exist when evaluating the values. This only affects procedure/lambda/closure creation since all procedures are called with the environment they are created in.

    (define (test v)
      (list 'default v))
    
    (let ((test (lambda (v) 
                  (if (zero? v)
                      (list 0)
                      (cons v (test (- v 1)))))))
      (test 5))
    ; ==> (5 default 4)
    
    (letrec ((test (lambda (v)
                    (if (zero? v)
                        (list 0)
                        (cons v (test (- v 1)))))))
      (test 5))
    ; ==> (5 4 3 2 1 0)
    

    So in the let example the local let is not in the body of the closure, because it does not exist when the values are evaluated. In practice if you expand let to its equivalent lambda form you see why it calls the global test:

    ((lambda (test) (test 5))
     (lambda (v)
       (if (zero? v)
           (list 0)
           (cons v (test (- v 1))))))
    

    Do you see why it doesn't call itself? letrec is specifically made to enable to create local procedures that has themselves as binding to solve this little problem. For syntax-rules it has the same binding properties for the similar names, but we are dealing with syntax so we need to make the steps not rely on runtime arithmetic:

    (define-syntax stest
      (syntax-rules ()
        ((_ v . rest) '(default v))))
    
    (let-syntax ((stest (syntax-rules () 
                          ((_ v . rest) (cons 'v (stest . rest)))
                          ((_) '()))))
      (stest 5 4 3 2 1 0))
    ; ==> (5 default 4)
    
    (letrec-syntax ((stest (syntax-rules () 
                             ((_ v . rest) (cons 'v (stest . rest)))
                             ((_) '()))))
      (stest 5 4 3 2 1 0))
    ; ==> (5 4 3 2 1 0)
    

    Again the letrec-syntax makes sure stest is available in the environment of the syntax-rules tranformer so that it matches itself instead of the top level macro.

    As for your examples, they are not Scheme. It might be that they will work in some specific scheme implementation as a defacto extra functionality, but they will not work in any R5RS, R6RS, or R7RS implementation like my examples. R6RS has syntax-case as an additional transformer and I'm guessing R7RS-large will have additional transformers as well. If you are after defacto behaviour you need to tag a specific implementation.