Почему продолжение прохождения стиля - PullRequest
23 голосов
/ 17 декабря 2011

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

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

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

Ответы [ 2 ]

13 голосов
/ 17 декабря 2011

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

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

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

6 голосов
/ 18 декабря 2011

Поскольку первая причина также может быть выполнена с помощью процедуры значений, а вторая - 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 процедуры являются продолжениями в буквальном смысле.)

...