Почему продолжение прохождения стиля

В разделе 3.4 " Язык программирования схем " Кента Дибвига (4-е издание) он очень четко описывает стиль продолжения передачи. По той причине, почему он приводит две причины:

  1. передать более одного результата его продолжению, потому что процедура, реализующая продолжение, может принимать любое количество аргументов.
  2. CPS также позволяет процедуре принимать отдельные продолжения..., которые могут принимать различное количество аргументов.

Поскольку первая причина также может быть сделано с помощью values процедура и второе использование case-lambda Мне не понятны преимущества использования стиля прохождения продолжения. Может ли кто-нибудь показать мне некоторые примеры того, где уместен стиль передачи продолжения, где он делает код лучше, понятнее и т. Д.?

2 ответа

Решение

Dybvig использует явные продолжения в этом разделе, чтобы мотивировать call/cc как часть языка. Основной момент делается в конце раздела, когда он упоминает, что для написания кода без него требуется глобальное преобразование всего используемого кода, включая функции, которые вы вызываете. Поэтому в Scheme вы обычно строите свою собственную конструкцию с использованием макросов, и продолжения являются одной из этих полезных конструкций, но вы не можете реализовать их с помощью макросов, поскольку они реализуют только локальные преобразования.

Но использование стиля CPS напрямую все еще может быть полезным: например, как он упоминает, вы могли бы написать функцию, которая имеет более одного продолжения, возможно, с разными значениями, например, функцию синтаксического анализа, которая получает функцию с одним входом для отправки анализирует значение и вызывает функцию нулевого сбоя, вызываемую при сбое синтаксического анализа (и эта функция может завершиться с ошибкой или вернуться назад и попытаться использовать другие правила синтаксического анализа). Другое возможное использование - это когда вы хотите точно контролировать то, что входит в продолжение, а не call/cc захватить весь контекст.

Существует также очевидный случай написания кода на языке, который не имеет первоклассного продолжения, что делает CPSed-код вашим единственным выбором. Примером этого может быть множество программ node.js, которые используют IO и в значительной степени вынуждают вас писать код в явном CPS.

Так как первая причина также может быть выполнена с использованием процедуры значений, а вторая - с использованием case-lambda, я не понимаю преимущества использования стиля передачи продолжения.

... кроме того, что определение values указывает, что вызывает его продолжение с несколькими аргументами.

Мой любимый пример проблемы, когда стиль передачи продолжения полезен - написание шаблонов соответствия. Это своего рода макрос, который как case на стероидах; он принимает значение и пытается сопоставить свою структуру с последовательностью шаблонов, построенных из пар, символов (обозначающих переменные) и не символьных атомов (обозначающих значения). Если совпадение успешно, то оно связывает идентификаторы в шаблоне с соответствующими частями значения и выполняет последовательность для этого шаблона. Если это не удается, то он пытается следующий шаблон.

Довольно просто написать такого рода макрос в форме стиля передачи продолжения, используя два разных продолжения для представления "что делать, если совпадение успешно" (продолжение успеха) и "что делать, если совпадение не удается" (ошибка продолжения).

Возьмите этот (упрощенный) фрагмент макроса сопоставления с образцом, который я однажды написал (я прошу прощения, если вы не знаете синтаксический случай или правила синтаксиса; и, поскольку я адаптировал его на лету, я надеюсь, что он тоже работает!). Я собираюсь сосредоточиться на правиле, которое соответствует шаблону пары. Это образец, который состоит из пары двух рисунков, рисунка головы и рисунка хвоста; это соответствует парам, чья голова соответствует шаблону головы, а чей хвост соответствует шаблону хвоста.

;;;
;;; Outer "driver" macro; the meat is in pmatch-expand-pattern.
;;;
(define-syntax pmatch
  (syntax-rules ()
    ((pmatch value-expr (pattern . exprs) . clauses)
     (let* ((value value-expr)
            (try-next-clause
             (lambda () (pmatch value . clauses))))
       (pmatch-expand-pattern pattern
                              value
                              ;; success-k
                              (begin . exprs)
                              ;; failure-k
                              (try-next-clause))))))

(define-syntax pmatch-expand-pattern
  (lambda (stx)
    (syntax-case stx ()

      ;; Cases for constants and quoted symbols omitted, but they're trivial.

      ;; Match a pair pattern.  Note that failure-k is expanded three times; 
      ;; that's why pmatch encapsulates its expansion inside a thunk!
      ((pmatch-expand-pattern (head-pat . tail-pat) value success-k failure-k)
       (syntax
        (if (pair? value)
            (pmatch-expand-pattern head-pat 
                                   (car value)
                                   ;; If we successfully match the head, then
                                   ;; the success continuation is a recursive
                                   ;; attempt to match the tail...
                                   (pmatch-expand-pattern tail-pat
                                                          (cdr value)
                                                          success-k 
                                                          failure-k)
                                   failure-k))
            failure-k))

      ;; Match an identifier pattern.  Always succeeds, binds identifier
      ;; to value
      ((pmatch-expand-pattern identifier value success-k failure-k)
       (identifier? (syntax identifier))
       (syntax (let ((identifier value)) success-k)))
      )))

Обратите внимание на подчиненные формы success-k и fail-k в pmatch-expand-pattern макро выражения. Они представляют выражения, которые используются в качестве "продолжения", в несколько упрощенном выражении, для сопоставления с образцом. Продолжение успеха используется, когда рассматриваемый шаблон соответствует рассматриваемому значению; продолжение сбоя используется, когда это не так. Продолжение успеха, в зависимости от того, соответствовали ли мы всем текущему шаблону верхнего уровня, является либо выражением, которое будет соответствовать остальной части шаблона, либо результатом, которое мы выполняем, когда шаблон завершает сопоставление. Продолжения отказов используются, когда шаблон не соответствует, чтобы вернуться к следующей точке выбора.

Как я уже говорил, термин "продолжение" в приведенном выше коде используется довольно свободно, так как мы используем выражения в качестве продолжения. Но это всего лишь деталь о том, как реализовать это как макрос - алгоритм может быть реализован исключительно во время выполнения с использованием реальных процедур в качестве продолжения. (Так же try-next-clause процедуры являются продолжением в буквальном смысле.)

Другие вопросы по тегам