Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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