Синтаксис пусть в схеме

Я не могу понять это использование let в примере, который я нашел. Я использую куриную схему.

(let loop ()
    (print "hello world")
    (loop)
)

Это простой бесконечный цикл, он рекурсивно вызывает сам себя, чего я не могу понять, так это синтаксиса. Я знаю, что первый аргумент должен быть списком пар ((<var[1]> <value[1]>)...(<var[n]> <value[n])), а остальные аргументы — телом let. Итак, почему этот фрагмент работает?


person Andrea Ciceri    schedule 18.06.2017    source источник
comment
См. 'named let' -› r6rs.org/final /html/r6rs/r6rs-Z-H-14.html#node_idx_766   -  person Rainer Joswig    schedule 18.06.2017


Ответы (2)


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

(define (test)
  (let loop ((i 5))
    (cond ((<= i 0) 'ok)
          (else (print i)
                (loop (- i 1))))))

... эквивалентно этому:

(define (test)
  (define (loop i)
    (cond ((<= i 0) 'ok)
          (else (print i)
                (loop (- i 1)))))
  (loop 5))

Теперь вы видите, что фрагмент кода в вопросе совпадает с написанием этого:

(define (loop)
  (print "hello world")
  (loop))

(loop)

Также обратите внимание, что название «цикл» — это просто соглашение, вы можете также назвать его «iter» или «helper» или как угодно, это не имеет значения.

person Óscar López    schedule 18.06.2017

Это работает, потому что ваше утверждение о том, что первый «аргумент» должен быть списком привязок, неверно. Синтаксис let такой:

(let name ((binding expression) ...)
  body ...)

Имя не является обязательным. У вас может быть ноль или более привязок. То, как макрос видит, что он назван или нет, зависит от того факта, что привязки должны быть списком, а имя обязательно должно быть идентификатором. Без имени это то же самое, что:

((lambda (binding ...)
   body ...)
 expression ...)

Однако с именем это становится:

((letrec ((name (lambda (binding ...)
                 body ...)))
   name)
 expression ...)

И, конечно же, letrec определяется через let:

(letrec ((name expression) ...)
  body ...)
; ===
(let ((name 'undefined) ...)
  (let ((tmp expression) ...)
    (set! name tmp) ...)
  body ...)

Таким образом, названный выше let превращается в это:

(((lambda (name)
   ((lambda (tmp) (set! name tmp)) (lambda (binding ...) body ...))
   name)
  'undefined)
 expression ...)

Обратите внимание на то, что имя не привязано к фрейму, в котором оно было вызвано впервые. Его возвращают и тут же вызывают. Таким образом, среди выражений вы действительно можете вычислить name из свободных переменных без того, чтобы именованные let мешали этому:

(let ((name "sylwester"))
  (let name ((cur (list name))
             (n 2))
    (if (zero? n)
        cur
        (name (append cur cur) (- n 1)))))
; ==> ("sylwester" "sylwester" "sylwester" "sylwester")

Использование define теней этой привязки:

(let ((name "sylwester"))
  (define (name cur n) 
    (if (zero? n)
        cur
        (name (append cur cur) (- n 1))))

  (name (list name) 2))
; ==> (#<proc> #<proc> #<proc> #<proc>)

В отчете R6RS < href="http://www.r6rs.org/r6rs/final/html/r6rs/r6rs-Z-H-14.html#node_idx_394" rel="nofollow noreferrer">синтаксис для let не демонстрирует более сложное имя let, но добавляет ссылку на его описание в другом месте отчета. Вероятно, это связано с тем, что имя let может быть громоздким и запутанным, когда вам нужны только локальные привязки. Это и то define — две разные вещи верхнего уровня, а не самые запутанные части Scheme, поэтому, возможно, язык был бы легче понять для новичков, если бы они действительно имели разные имена, а не документировали разные места.

person Sylwester    schedule 18.06.2017
comment
да, разница между (letrec name (...) (name init)) и ((letrec name (...) name) init). так что letrec на самом деле последний. интересный. --- вы, вероятно, имели в виду (((lambda (name) ((lambda (tmp) name) (set! name (lambda (binding ...) body ...)))) 'undefined) expression ...), потому что в противном случае, если вы допускаете неявное begin, вы могли бы просто иметь (((lambda (name) (set! name (lambda (binding ...) body ...)) name) 'undefined) expression ...) (codepad.org/rlKcHkm9< /а>) - person Will Ness; 19.06.2017
comment
(Я имел в виду, что названный цикл является последним...) - person Will Ness; 19.06.2017
comment
@WillNess Мое расширение взято из отчета R5RS, и оно не позволяет использовать переменную перед телом. Таким образом, предпочтительнее вычислять все выражения, пока переменные не определены. Реализаторам настоятельно рекомендуется иметь неопределенное значение объекта и может сигнализировать об ошибке, если одно из выражений использует переменные перед телом. Я думаю, что в R6RS используется ваша версия, и вам не разрешено использовать более позднюю переменную в запугивающей оценке выражений. например, (letrec ((a 10) (b a)) (list a b)) не разрешено в R5RS, а (letrec ((a b) (b 10)) (list a b)) не разрешено ни в одном из них. - person Sylwester; 19.06.2017
comment
@WillNess Это потому, что существует только одна привязка letrec. В ту секунду, когда вы начинаете делать (letrec ((a 10) (b a)) (list a b)), он таинственным образом работает в вашей версии, но не в моей, так как он рассчитан на отказ в R5RS. - person Sylwester; 20.06.2017
comment
опять же, я переписываю ваш код для named let, который состоит только из letrec'а в name: (let name ((a 10)(b a)) (list a b)) переписывается (например, со вторым вариантом) как (((lambda (name) ((lambda (tmp) name) (set! name (lambda (a b) (list a b))))) 'undefined) 10 a) который терпит неудачу. То же и с третьим. да? - person Will Ness; 20.06.2017
comment
@WillNess Вы правы в том, что семантических различий нет, и вы можете поднять некоторые лямбды для простого случая с именем let. Я буквально расширил letrec, который предназначен для одновременной обработки более чем одной процедуры и использует дополнительный набор временных переменных для выдачи ошибок в случае, если какое-либо выражение оценивает любую из переменных перед оценкой тела. Мой пример с привязкой a и b в качестве привязки letrec (вместо name их только две), так как именно в этом заключается разница между расширениями. - person Sylwester; 20.06.2017