Последовательное выполнение схемы Do for (do*)

В Common Lisp есть последовательная форма (do *vars* *test* *body*); точно так же, как последовательный параллельный let* let, он работает через *vars* по одному, так что вы можете ссылаться на ранее определенные переменные следующим образом:

(do* ((a '(1 2 3) (cdr a))
      (b (mapcar (lambda (x) (1+ x)) a) (cdr b)))
     ((= 1 1) (list 'a a 'b b)))
; (a (1 2 3) b (2 3 4))

В схеме, насколько мне известно, нет коррелирующей функции или макроса. Есть do, но нет do*. Я пытался написать реализацию do* для Chicken Scheme, но пока что я изо всех сил пытаюсь добиться каких-либо успехов... Думаю, я не могу точно сказать. Я знаком с Chicken Scheme, но всегда находил макросы схемы сбивающими с толку.

Вот что у меня есть:

(##sys#extend-macro-environment
 'do*
 '()
 (##sys#er-transformer
  (lambda (form r c)
(##sys#check-syntax 'do* form '(_ #((symbol _ . #(_)) 0) . #(_ 1)))
(let* ((bindings (cadr form))
       (test (caddr form))
       (body (cdddr form))
       (do*var (r 'doloop)))
  `(let*
       ,do*var
     ,(##sys#map (lambda (b) (list (car b) (car (cdr b)))) bindings)
     (##core#if ,(car test)
        ,(let ((tbody (cdr test)))
           (if (eq? tbody '())
               '(##core#undefined)
               `(##core#begin ,@tbody) ) )
        (##core#begin
         ,(if (eq? body '())
              '(##core#undefined)
              `(##core#let () ,@body) )
         (##core#app
          ,do*var ,@(##sys#map (lambda (b)
                     (if (eq? (cdr (cdr b)) '())
                         (car b)
                         (car (cdr (cdr b))) ) )
                       bindings) ) ) ) ) ) ) ) )

Но я продолжаю получать ошибки, такие как doloop, не являющийся списком - в настоящее время я получаю

Error: during expansion of (do* ...) - in `do*' - symbol expected: (do* ((a (quote (1 2 3)) (cdr a)) (b (map (lambda (x) (+ 1 x)) a) (cdr b)) ((= 1 1) (list (quote a) a (quote b) b))))

    Call history:

    <syntax>          (do* ((a (quote (1 2 3)) (cdr a)) (b (map           (lambda (x) (+ 1 x)) a) (cdr b)) ((= 1 1) (list (quote a) a...
    <eval>    (##sys#check-syntax (quote do*) form (quote (_ #((symbol _ . #(_)) 0) . #(_ 1))))     <--

Вот приблизительный пример do* на Common Lisp, взятый из HyerSpec:

  (block nil        
   (let ((var1 init1)
     (var2 init2)
     ...
     (varn initn))
     declarations
     (loop (when end-test (return (progn . result)))
       (tagbody . tagbody)
       (psetq var1 step1
          var2 step2
          ...
          varn stepn))))  

do* аналогична, за исключением того, что let* и setq заменяют let и psetq соответственно.

А вот во что расширяется do* в CL:

(expand-form '(do* ((a '(1 2 3) (cdr a))
        (b (mapcar (lambda (x) (1+ x)) a) (cdr b)))
           ((= 1 1) (list 'a a 'b b))))
(block nil
   (let* ((a '(1 2 3)) (b (mapcar #'(lambda (x) (declare (system::source ((x) (1+ x)))) (1+ x)) a)))
     (tagbody #:loop-5382 (if (= 1 1) (go #:end-5383)) (setq a (cdr a) b (cdr b)) (go #:loop-5382) #:end-5383 (return-from nil (list 'a a 'b b))))) ;
;t

person Alexej Magura    schedule 12.08.2015    source источник


Ответы (1)


Пожалуйста, не используйте идентификаторы с префиксом ##. Они не поддерживаются и не являются частью официального API!

Во-вторых, вместо того, чтобы пытаться возиться с низкоуровневыми макросами, просто используйте синтаксические правила, которые намного проще, менее подвержены ошибкам и стандартизированы. Его следует использовать всякий раз, когда вам не нужно нарушать гигиену.

Вот пример do*, как это будет работать, если я вас правильно понял:

(define-syntax do*
  (syntax-rules ()
    ((_ ((?var0 ?init0 ?inc0) ...)
         (?test ?result)
         ?body ...)
     (let* ((?var0 ?init0) ...)
       (let lp ()
         (cond (?test ?result)
               (else ?body ...
                     (set! ?var0 ?inc0) ...
                     (lp))))))))

Выражается как макрос неявного переименования:

(define-syntax do*
  (ir-macro-transformer
   (lambda (e i c)
     (let* ((vars (cadr e))
            (test&result (caddr e))
            (test (car test&result))
            (result (cadr test&result))
            (body-exprs (cdddr e)))
       `(let* (,@(map (lambda (v) (list (car v) (cadr v))) vars))
          (let lp ()
            (cond (,test ,result)
                  (else ,@body-exprs
                        ,@(map (lambda (v) `(set! ,(car v) ,(caddr v))) vars)
                        (lp)))))))))

Я надеюсь, вы согласитесь, что так гораздо более подробно.

person sjamaan    schedule 12.08.2015
comment
Я согласен, что это гораздо более многословно; Часть моей проблемы в том, что я начал с Common Lisp: макросы, хотя и были очень многословными, выглядели очень прямолинейными, даже примитивными с точки зрения гигиены: даже неявные и явные преобразователи переименования макросов для схема выглядит смущающе сложной в сравнении. - person Alexej Magura; 13.08.2015
comment
Что такого сложного в ir-macro-transformer? В основном он идентичен макросу Common Lisp. Единственное отличие состоит в том, что аргументы не деструктурируются автоматически. Но у нас также есть яйцо, которое предоставляет безопасную, гигиеничную версию define- макрос. Думаю, он основан на неявных макросах переименования. - person sjamaan; 13.08.2015
comment
Кстати, обновленная версия do* в вашем отсутствующем репозитории немного неверна: она использует шаблон хвоста для результата (тест...), но, строго говоря, это указывает на то, что может быть ноль или более тестов, за которыми следует результат, тогда как на самом деле это должен быть один тест, за которым следует ноль или более (или одно или более?) выражений результата. Я также вижу, что вы удалили вопросительные знаки из переменных шаблона. Это, конечно, ваше собственное решение, но использовать вопросительные знаки для ясности, чтобы отличить переменные шаблона от идентификаторов, введенных в результирующее выражение, довольно идиоматично. - person sjamaan; 13.08.2015