Ищем примеры "реального" использования продолжений - PullRequest
21 голосов
/ 29 августа 2008

Я пытаюсь понять концепцию продолжения, и я нашел несколько небольших обучающих примеров, подобных этому, из статьи Википедии :

(define the-continuation #f)

(define (test)
  (let ((i 0))
    ; call/cc calls its first function argument, passing 
    ; a continuation variable representing this point in
    ; the program as the argument to that function. 
    ;
    ; In this case, the function argument assigns that
    ; continuation to the variable the-continuation. 
    ;
    (call/cc (lambda (k) (set! the-continuation k)))
    ;
    ; The next time the-continuation is called, we start here.
    (set! i (+ i 1))
    i))

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

Так что я ищу более подробные примеры кода, которые продолжения могут предложить мне как программисту.

Ура!

Ответы [ 12 ]

16 голосов
/ 29 августа 2008

В Algo & Data II мы все время использовали их для «выхода» или «возврата» из (длинной) функции

например, алгоритм BFS для обхода деревьев был реализован следующим образом:

(define (BFS graph root-discovered node-discovered edge-discovered edge-bumped . nodes)
  (define visited (make-vector (graph.order graph) #f))
  (define q (queue.new))
  (define exit ())
  (define (BFS-tree node)
    (if (node-discovered node)
      (exit node))
    (graph.map-edges
     graph
     node
     (lambda (node2)
       (cond ((not (vector-ref visited node2))
              (when (edge-discovered node node2)
                (vector-set! visited node2 #t)
                (queue.enqueue! q node2)))
             (else
              (edge-bumped node node2)))))
    (if (not (queue.empty? q))
      (BFS-tree (queue.serve! q))))

  (call-with-current-continuation
   (lambda (my-future)
     (set! exit my-future)
     (cond ((null? nodes)
            (graph.map-nodes
             graph
             (lambda (node)
               (when (not (vector-ref visited node))
                 (vector-set! visited node #t)
                 (root-discovered node)
                 (BFS-tree node)))))
           (else
            (let loop-nodes
              ((node-list (car nodes)))
              (vector-set! visited (car node-list) #t)
              (root-discovered (car node-list))
              (BFS-tree (car node-list))
              (if (not (null? (cdr node-list)))
                (loop-nodes (cdr node-list)))))))))

Как видите, алгоритм завершит работу, когда функция обнаруженного узла вернет true:

    (if (node-discovered node)
      (exit node))

функция также выдаст «возвращаемое значение»: 'node'

почему функция выходит из-за этого утверждения:

(call-with-current-continuation
       (lambda (my-future)
         (set! exit my-future)

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

Таким образом, по сути, call-cc (здесь) используется для того, чтобы выпрыгнуть из рекурсивной функции, вместо того, чтобы ждать окончания всей рекурсии (что может быть довольно дорогостоящим при выполнении большого количества вычислительных работ)

другой меньший пример, делающий то же самое с call-cc:

(define (connected? g node1 node2)
  (define visited (make-vector (graph.order g) #f))
  (define return ())
  (define (connected-rec x y)
    (if (eq? x y)
      (return #t))
    (vector-set! visited x #t)
    (graph.map-edges g
                     x
                     (lambda (t)
                       (if (not (vector-ref visited t))
                         (connected-rec t y)))))
  (call-with-current-continuation
   (lambda (future)
     (set! return future)
     (connected-rec node1 node2)
     (return #f))))
9 голосов
/ 29 августа 2008
7 голосов
/ 29 августа 2008

@ Пат

Море

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

WAComponent >> call: aComponent
    "Pass control from the receiver to aComponent. The receiver will be
    temporarily replaced with aComponent. Code can return from here later
    on by sending #answer: to aComponent."

    ^ AnswerContinuation currentDo: [ :cc |
        self show: aComponent onAnswer: cc.
        WARenderNotification raiseSignal ]

Так мило!

6 голосов
/ 16 сентября 2008

Я создал свое собственное программное обеспечение для модульного тестирования. Перед выполнением теста я сохраняю продолжение перед выполнением теста, а затем, в случае неудачи, я (опционально) приказываю интерпретатору схемы перейти в режим отладки и повторно вызвать продолжение. Таким образом, я могу легко пройти по проблемному коду.

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

5 голосов
/ 10 сентября 2008

Я наткнулся на реализацию оператора amb в этом посте из http://www.randomhacks.net,, используя продолжения.

Вот что делает оператор amb:

# amb will (appear to) choose values
# for x and y that prevent future
# trouble.
x = amb 1, 2, 3
y = amb 4, 5, 6

# Ooops! If x*y isn't 8, amb would
# get angry.  You wouldn't like
# amb when it's angry.
amb if x*y != 8

# Sure enough, x is 2 and y is 4.
puts x, y 

А вот реализация сообщения:

# A list of places we can "rewind" to
# if we encounter amb with no
# arguments.
$backtrack_points = []

# Rewind to our most recent backtrack
# point.
def backtrack
  if $backtrack_points.empty?
    raise "Can't backtrack"
  else
    $backtrack_points.pop.call
  end
end

# Recursive implementation of the
# amb operator.
def amb *choices
  # Fail if we have no arguments.
  backtrack if choices.empty?
  callcc {|cc|
    # cc contains the "current
    # continuation".  When called,
    # it will make the program
    # rewind to the end of this block.
    $backtrack_points.push cc

    # Return our first argument.
    return choices[0]
  }

  # We only get here if we backtrack
  # using the stored value of cc,
  # above.  We call amb recursively
  # with the arguments we didn't use.
  amb *choices[1...choices.length]
end

# Backtracking beyond a call to cut
# is strictly forbidden.
def cut
  $backtrack_points = []
end

Мне нравится amb!

5 голосов
/ 29 августа 2008

Продолжения используются некоторыми веб-серверами и веб-платформами для хранения информации о сеансе. Объект продолжения создается для каждого сеанса и затем используется каждым запросом в сеансе.

Здесь есть статья об этом подходе.

3 голосов
/ 16 сентября 2008

Продолжения являются хорошей альтернативой поток-на-запрос в программировании сервера (включая веб-приложения.

В этой модели вместо запуска нового (тяжелого) потока каждый раз, когда поступает запрос, вы просто начинаете некоторую работу в функции. Затем, когда вы будете готовы заблокировать ввод-вывод (то есть чтение из базы данных), вы передаете продолжение в обработчик сетевых ответов. Когда ответ возвращается, вы выполняете продолжение. С помощью этой схемы вы можете обрабатывать множество запросов всего за несколько потоков.

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

3 голосов
/ 29 августа 2008

Продолжения могут использоваться в реальных примерах, когда поток программы не является линейным или даже не предопределенным. Знакомая ситуация - веб-приложения .

2 голосов
/ 18 мая 2010

Оператор amb является хорошим примером, допускающим прологоподобное декларативное программирование.

Пока мы говорим, я пишу программу для создания музыкальных композиций в Scheme (я музыкант, практически не знакомый с теорией музыки, и я просто анализирую свои собственные пьесы, чтобы понять, как за этим стоит математика работы.)

Используя оператор amb, я могу просто заполнить ограничения, которым должна удовлетворять мелодия, и позволить Схеме вычислить результат.

Продолжения, вероятно, заложены в Scheme из-за языковой философии. Scheme - это среда, позволяющая вам понять любую парадигму программирования, найденную на другом языке, путем определения библиотек в самой Scheme. Продолжения предназначены для создания собственных абстрактных управляющих структур, таких как return, break или для включения декларативного программирования. Схема является более «обобщающей» и просит, чтобы такие конструкции могли быть также определены программистом.

1 голос
/ 05 мая 2010

Если вам нужно вызвать асинхронное действие и приостановить выполнение до тех пор, пока вы не получите результат, вы обычно либо запрашиваете результат, либо помещаете оставшуюся часть кода в функцию обратного вызова для выполнения асинхронным действием после завершения. С продолжениями вам не нужно делать неэффективную опцию опроса, и вам не нужно заключать весь код для запуска после события асинхронности в обратном вызове - вы просто передаете текущее состояние кода в качестве обратного вызова. - и код эффективно «просыпается», как только завершается асинхронное действие.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...