"Cond", который также "let"? - PullRequest
2 голосов
/ 09 мая 2019

Часто я нахожу, что хочу вычислить ряд значений условно в зависимости от значения предыдущих значений. Например, давайте назовем эти значения x, y и z. Сначала я вычисляю x. Если x соответствует определенным критериям, тогда я вычисляю y, который является функцией x, и так далее. Схематически

;; compute value x
;; if x =? #f -> #f
;; else compute value y = f(x)
;; if y =? #f -> #f
;; else compute value z = f(y)
;; et cetera

Как вы делаете это в Схеме? Я думаю, что обычно можно использовать cond, но cond отбрасывает результаты тестов, поэтому в этой ситуации это бесполезно.

Ответы [ 3 ]

6 голосов
/ 09 мая 2019

Используйте let*, который оценивает формы инициализации последовательно, в рамках кратких привязок.В формах инициализации используйте and, чтобы сделать расчет условным.

(let* ((x (compute-x))
       (y (and x (f1 x)))
       (z (and y (f2 y))))
  ;; code that uses the variables
)
3 голосов
/ 09 мая 2019

Вы можете использовать устройство => в предложениях cond, например:

(define (testing p x)
   (if (p x) 
      x 
      #f))

(display
   (cond
      ((testing even? 1) => (lambda (x)          ; the clause is skipped
                              (list 10 x)))
      ((testing even? 2) => (lambda (x)          ; the clause is entered and
                              (list 20 x)))))    ;   `x` is bound to 2

Вы бы вложили cond (s) в соответствующий пункт (ы), чтобы представить вложенные условия, которые вы описываете.

Таким образом, структура кода явно следует вашей логике, что всегда хорошо.

1 голос
/ 11 мая 2019

Таким образом, ваш код может быть написан так в стандартном Common Lisp:

(let ((it x-expression))
  (if it
      (let ((it (f it)))
        (if it
            it))))

Обратите внимание, что я не даю форму else (альтернативу), поскольку она необязательна в CL. Пол Грэм представляет анафорические макросы для автоматического кэширования значения теста.

(defmacro aif (test-form then-form &optional else-form)
  `(let ((it ,test-form))
     (if it ,then-form ,else-form)))

(aif x-expression
  (aif (f it)
    (aif (f it)
         it)

Это хорошо работает в CL, но создает проблемы с гигиеной в Scheme. Я знаю, что кто-то создал что-то с дополнительным параметром для имени привязки, но это теряет элегантность:

(aif x x-expression
  (aif y (f x)
    (aif z (f y)
         z)))

Я экспериментировал с cond При создании оценщиков я обычно тестирую прежде, чем смогу очистить, и это заканчивается вложением if и let. Моя первая итерация выглядела так:

(define (ev expr env)
  (defcond 
    ((symbol? expr) (symbol->value expr env))
    ((not (pair? expr)) expr => (operator (ev (car expr) env)))
    ((macro? operator) (macro-apply expr (cdr expr) env) => (args (map (lambda (e) (ev e env)) (cdr expr))))
    (else (fun-apply operator args env))))

Он также поддерживал альтернативный способ, поскольку я нашел его не слишком элегантным для повторного использования =>:

(define (ev expr env)
  (defcond 
    ((symbol? expr) (symbol->value expr env))
    ((not (pair? expr)) expr)
    (define operator (ev (car expr) env))
    ((macro? operator) (macro-apply expr (cdr expr) env))
    (define args (map (lambda (e) (ev e env)) (cdr expr)))
    (else (fun-apply operator args env))))

Теперь это можно использовать так же, как и aif. Если вас интересует макрос Scheme:

(define-syntax defcond
  (syntax-rules (else bind define =>)
    ((_ "build" terms ())
     terms)
    ((_ "build" alternative ((bind (b e) ...) . rest))
     (defcond "build" (let ((b e) ...) alternative) rest))
    ((_ "build" alternative ((bind name (b e) ...) . rest))
     (defcond "build" (let name ((b e) ...) alternative) rest))
    ((_ "build" alternative ((define b e) . rest))
     (defcond "build" (letrec ((b e)) alternative) rest))
    ((_ "build" alternative ((predicate consequent) . rest))
     (defcond "build" (if predicate consequent alternative) rest))
    ((_ "build" alternative ((predicate consequent => (b e) ...) . rest))
     (defcond "build" (if predicate consequent (let ((b e) ...) alternative)) rest))
    ((_ "build" alternative ((predicate consequent) . rest))
     (defcond "build" (if predicate consequent alternative) rest))
    ((_ "maybe-else" ((else expression) . rest))
     (defcond "build" expression rest))
    ((_ "maybe-else" ((something expression) . rest))
     (defcond "build" #f ((something expression) . rest)))
    ((_ "reverse" terms ())
     (defcond "maybe-else" terms))
    ((_ "reverse" (fterms ...) (term1 terms ...))
     (defcond "reverse" (term1 fterms ...) (terms ...)))
    ((_ terms ...)
     (defcond "reverse" () (terms ...)))))

Не очень элегантная реализация, но она работает. Как видите, он поддерживает bind и также называется bind. например.

(defcond 
  ((not (pair? lst)) #f)
  (bind loop ((lst lst) (acc 0)))
  ((null? lst) acc)
  (else (loop (cdr lst) (+ acc (car lst))))

Хотя мне нравится идея, я все еще не думаю, что она божественная и изящная, как это должно быть. Пока не появится лучший синтаксис, я буду писать его для удобства чтения. например:

(if (not (pair? lst))
    #f
    (let loop ((lst lst) (acc 0))
      (if (null? lst)
          acc
          (loop (cdr lst) (+ acc (car lst))))))
...