В чем разница между letrec-синтаксисом и let-синтаксисом в схеме?

Был похожий вопрос, но не с синтаксическими макросами, хотя разница в том, что один макрос не может видеть другой, например, letrec и let с функциями.

Но это работает одинаково с letrec-syntax и let-syntax.

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

Можете ли вы показать пример кода, в котором эти два макроса/синтаксиса различаются?

ИЗМЕНИТЬ:

Нашел это:

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

это не работает с let-syntax и работает с letrec-syntax, но какая от этого польза? Если эти локальные переменные не могут быть функциями (выход (bar x)), в чем смысл let-syntax? Мне нужен пример, где его можно использовать в коде, где letrec-syntax не нужно и достаточно было бы let-syntax.


person jcubic    schedule 11.04.2020    source источник


Ответы (1)


Это то же самое, что и let и letrec. С let вы не можете ожидать, что привязка существует при оценке значений. Это влияет только на создание процедуры/лямбда/замыкания, поскольку все процедуры вызываются в среде, в которой они созданы.

(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)

Таким образом, в примере let локальный let не находится в теле замыкания, потому что он не существует при оценке значений. На практике, если вы расширите let до его эквивалентной лямбда-формы, вы поймете, почему он вызывает глобальный test:

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

Вы понимаете, почему он не звонит сам себе? letrec специально создан для создания локальных процедур, которые сами по себе являются обязательными для решения этой небольшой проблемы. Для синтаксических правил он имеет те же свойства привязки для похожих имен, но мы имеем дело с синтаксисом, поэтому нам нужно, чтобы шаги не зависели от арифметики времени выполнения:

(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)

Опять же, letrec-syntax обеспечивает доступность stest в среде преобразователя syntax-rules, чтобы он соответствовал самому себе, а не макросу верхнего уровня.

Что касается ваших примеров, то они не Scheme. Возможно, они будут работать в какой-то конкретной реализации схемы как дополнительная функциональность де-факто, но они не будут работать ни в одной реализации R5RS, R6RS или R7RS, как в моих примерах. R6RS имеет syntax-case в качестве дополнительного трансформатора, и я предполагаю, что R7RS-large также будет иметь дополнительные трансформаторы. Если вам нужно поведение де-факто, вам нужно пометить конкретную реализацию.

person Sylwester    schedule 11.04.2020
comment
Мои примеры работают с любой реализацией, в которой есть квазицитаты, обычно они есть в реализации схемы, как и макросы lisp, я пытаюсь понять, как должны работать только letrec-syntax и let-syntax, а в Kawa это работает с квазицитатами, так как define-syntax и let( rec)-syntax макросы могут просто иметь лямбда, которая может работать как обычный макрос, поэтому он может просто использовать квазицитаты (нашел это в статье Dybvig, и это работает). Я больше знаком с макросами lisp, поэтому использую квазицитаты. Я нахожусь в процессе написания системы макросов схемы в своей реализации JavaScript, но я делаю это по одному шагу за раз. - person jcubic; 11.04.2020
comment
Исправление: я думаю, что в Kawa мне нужно было бы сделать один дополнительный шаг и оценить результат лямбда-вызова в let-синтаксисе, но в моей реализации это просто let с макросами. Может быть, у меня должен быть какой-то синтаксический макрос, как у Кавы. Можно ли как-то использовать квазицитаты с let-синтаксисом, чтобы я мог реализовать только let-синтаксис без необходимости реализовывать остальную часть системы? - person jcubic; 11.04.2020
comment
@jcubic квазицитата - это просто причудливый синтаксис для выражения cons, которое создает ту же структуру. простые макросы в стиле fecmacro не имеют тех же ограничений среды, что и синтаксические правила или синтаксический регистр. Расширение не должно заимствовать введенные привязки из среды, в которую оно внедряется, но из которой оно было определено. Ваш код не работает в стандартных схемах, таких как Racket (#!r5rs), Ikarus и Chicken. Даже Kawa, как это задокументировано, также требует преобразователя. Как должна работать привязка, хорошо демонстрирует мой ответ - person Sylwester; 11.04.2020
comment
@jcubic: ваши примеры не «работают в любой реализации, в которой есть квазицитаты». Они работают только в тех реализациях, которые имеют традиционные макросы Лиспа. Я не думаю, что в схеме RnRS они были для любого значения n. - person ; 11.04.2020