Разветвление форм и короткое замыкание
Если вы говорите на нетерпеливом (не ленивом) языке, таком как Racket, вам нужно быть осторожным в том, как вы кодируете формы ветвления, такие как COND
.
Существуют следующие определения логических и условных выражений:
(define TRUE (λ (t) (λ (f) t)))
(define FALSE (λ (t) (λ (f) f)))
(define COND (λ (c) (λ (x) (λ (y) ((c x) y)))))
И они работают для простых случаев, таких как:
> (((COND TRUE) "yes") "no")
"yes"
> (((COND FALSE) "yes") "no")
"no"
Однако, если "ветвь не занято" будетвыдает ошибку или бесконечный цикл, тогда хорошая форма ветвления будет "замыкать", чтобы избежать ее запуска.Хорошая форма ветвления должна оценивать только ту ветвь, которая необходима.
> (if #true "yes" (error "shouldn't get here"))
"yes"
> (if #false (error "shouldn't trigger this either") "no")
"no"
Однако ваш COND
оценивает обе ветви просто потому, что Приложение функции Racket оценивает все аргументы:
> (((COND TRUE) "yes") (error "shouldn't get here"))
;shouldn't get here
> (((COND FALSE) (error "shouldn't trigger this either")) "no")
;shouldn't trigger this either
Использование дополнительных лямбд для реализации короткихсхема
Способ, которым меня учили обходить это на нетерпеливом языке (например, без переключения на #lang lazy
), заключался в том, чтобы передавать thunks таким ветвящимся формам, как это:
(((COND TRUE) (λ () "yes")) (λ () (error "doesn't get here")))
Однако, это требует некоторых небольших изменений в определении того, что такое логическое значение.Ранее логическое значение принимало два значения на выбор и возвращало одно.Теперь логическое значение будет принимать два thunks на выбор, и будет call one.
(define TRUE (λ (t) (λ (f) (t)))) ; note the extra parens in the body
(define FALSE (λ (t) (λ (f) (f)))) ; same extra parens
Форма COND
может быть определена одинаковокак и раньше, но вам придется использовать его по-другому.Чтобы перевести (if c t e)
, где раньше вы писали:
(((COND c) t) e)
Теперь с новым определением логических выражений вы напишите:
(((COND c) (λ () t)) (λ () e))
Я собираюсь сократить (λ () expr)
как{expr}
так что я могу написать это вместо этого:
(((COND c) {t}) {e})
Теперь, что ранее не удалось с ошибкой, возвращает правильный результат:
> (((COND TRUE) {"yes"}) {(error "shouldn't get here")})
"yes"
Это позволяет вам писать условные выражениягде одна из ветвей представляет собой «базовый случай», где она останавливается, а другая ветвь представляет собой «рекурсивный случай», в котором она будет продолжать работать.
(Y (λ (fib)
(λ (x)
(((COND ((leq x) one))
{x})
{... (fib (sub x two)) ...}))))
Без этих дополнительных (λ () ....)
иновое определение логических значений, это будет цикл за бесконечность из-за энергичной (не ленивой) оценки аргументов Ракета.