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

Я просто не получаю продолжения!

Каковы они и для чего они хороши?

У меня нет степени CS, и мой фон - VB6 → ASP → ASP.NET/C#. Может ли кто-нибудь объяснить это ясным и кратким образом?

4b9b3361

Ответ 1

Представьте, что каждая отдельная строка в вашей программе была отдельной функцией. Каждый принимает в качестве параметра следующую строку/функцию для выполнения.

Используя эту модель, вы можете "приостановить" выполнение в любой строке и продолжить ее позже. Вы также можете делать изобретательные вещи, например, временно перескакивать в стек исполнения, чтобы получить значение, или сохранить текущее состояние выполнения в базе данных, которая будет получена позже.

Ответ 2

Вероятно, вы понимаете их лучше, чем вы думаете.

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

Пример Python:

try:
    broken_function()
except SomeException:
    # jump to here
    pass

def broken_function():
    raise SomeException() # go back up the stack
    # stuff that won't be evaluated

Генераторы являются примерами продолжений "вниз". Они позволяют коду повторно вводить цикл, например, для создания новых значений.

Пример Python:

def sequence_generator(i=1):
    while True:
        yield i  # "return" this value, and come back here for the next
        i = i + 1

g = sequence_generator()
while True:
    print g.next()

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

Ответ 3

A вверх, этот пример не является кратким или исключительно ясным. Это демонстрация мощного применения продолжений. Как программист VB/ASP/С#, вы, возможно, не знакомы с концепцией системного стека или состояния сохранения, поэтому цель этого ответа - демонстрация, а не объяснение.

Продолжения чрезвычайно универсальны и являются способом сохранения состояния выполнения и возобновления его позже. Вот небольшой пример совместной многопоточной среды с использованием продолжений в Схеме:

(Предположим, что операции enqueue и dequeue работают как ожидалось в глобальной очереди, не указанной здесь)

(define (fork)
  (display "forking\n")
  (call-with-current-continuation
   (lambda (cc)
     (enqueue (lambda ()
                (cc #f)))
     (cc #t))))

(define (context-switch)
  (display "context switching\n")
  (call-with-current-continuation
   (lambda (cc)
     (enqueue
      (lambda ()
        (cc 'nothing)))
     ((dequeue)))))

(define (end-process)
  (display "ending process\n")
  (let ((proc (dequeue)))
    (if (eq? proc 'queue-empty)
        (display "all processes terminated\n")
        (proc))))

Это обеспечивает три глагола, которые функция может использовать - fork, context-switch и end-process. Операция fork вилки потока и возвращает #t в одном экземпляре, а #f - в другом. Операция контекстного переключения переключается между потоками, а конечный процесс завершает поток.

Вот пример их использования:

(define (test-cs)
  (display "entering test\n")
  (cond
    ((fork) (cond
              ((fork) (display "process 1\n")
                      (context-switch)
                      (display "process 1 again\n"))
              (else (display "process 2\n")
                    (end-process)
                    (display "you shouldn't see this (2)"))))
    (else (cond ((fork) (display "process 3\n")
                        (display "process 3 again\n")
                        (context-switch))
                (else (display "process 4\n")))))
  (context-switch)
  (display "ending process\n")
  (end-process)
  (display "process ended (should only see this once)\n"))

Выход должен быть

entering test
forking
forking
process 1
context switching
forking
process 3
process 3 again
context switching
process 2
ending process
process 1 again
context switching
process 4
context switching
context switching
ending process
ending process
ending process
ending process
ending process
ending process
all processes terminated
process ended (should only see this once)

Те, которые изучали forking и threading в классе, часто приводят примеры, подобные этому. Цель этого поста - продемонстрировать, что с продолжением вы можете добиться одинаковых результатов в одном потоке, сохраняя и восстанавливая его состояние - его продолжение - вручную.

P.S. - Я думаю, что я помню что-то подобное в On Lisp, поэтому, если вы хотите увидеть профессиональный код, вы должны проверить книгу.

Ответ 4

Один из способов думать о продолжении - это как стек процессора. Когда вы вызываете "call-with-current-continuation c", он вызывает вашу функцию "c" , а параметр, переданный в "c" , - это ваш текущий стек со всеми вашими автоматическими переменными на нем (представленный как еще одна функция, назовите его "k" ). Между тем процессор начинает создавать новый стек. Когда вы вызываете "k" , он выполняет команду "возврат из подпрограммы" (RTS) в исходном стеке, возвращая вас обратно в контекст исходного "продолжения вызова с текущим продолжением" ( "call-cc" с этого момента on) и позволяет вашей программе продолжать работу по-прежнему. Если вы передали параметр "k" , тогда это станет возвращаемым значением "call-cc".

С точки зрения вашего исходного стека, "call-cc" выглядит как обычный вызов функции. С точки зрения "c" ваш исходный стек выглядит как функция, которая никогда не возвращается.

Существует старая шутка о математике, который захватил льва в клетке, взобравшись в клетку, заперев ее и объявив себя вне клетки, в то время как все остальное (включая льва) было внутри него. Продолжения немного похожи на клетку, а "c" немного похож на математика. Ваша основная программа думает, что внутри нее находится "c" , а "c" полагает, что ваша основная программа находится внутри "k" .

Вы можете создавать произвольные структуры управления потоком, используя продолжения. Например, вы можете создать библиотеку потоков. "yield" использует "call-cc" для размещения текущего продолжения в очереди, а затем переходит в ту, что находится в начале очереди. Семафор также имеет свою собственную очередь приостановленных продолжений, и поток переносится, снимая его с очереди семафора и помещая его в основную очередь.

Ответ 5

В принципе, продолжение - это возможность для функции остановить выполнение, а затем выбрать резервную копию, где она была остановлена ​​в более поздний момент времени. В С# вы можете сделать это, используя ключевое слово yield. Я могу подробно рассказать, если вы хотите, но вы хотели получить краткое объяснение.; -)

Ответ 6

Я по-прежнему "привык" к продолжениям, но один из способов подумать о них, который я считаю полезным, - это абстракции концепции Program Counter (ПК). ПК "указывает" на следующую инструкцию для выполнения в памяти, но, конечно, эта инструкция (и почти каждая инструкция) указывает, неявно или явно, на следующую инструкцию, а также на любые инструкции, которые должны обслуживать прерывания. (Даже инструкция NOOP неявно делает JUMP для следующей инструкции в памяти. Но если происходит прерывание, обычно это связано с JUMP с другой инструкцией в памяти.)

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

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

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

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

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

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

Ответ 7

В С# у вас есть доступ к двум продолжениям. Один из них, доступ к которому через return, позволяет продолжить метод с того места, где он был вызван. Другая, доступная через throw, позволяет продолжить метод с ближайшим совпадением catch.

Некоторые языки позволяют рассматривать эти утверждения как значения первого класса, поэтому вы можете назначать их и передавать их в переменных. Это означает, что вы можете занести значение return или throw и называть их позже, когда вы действительно готовы вернуться или выбросить.

Continuation callback = return;
callMeLater(callback);

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

Я использую их в нескольких проектах, над которыми я работаю. Во-первых, я использую их, поэтому я могу приостановить программу, пока я жду IO по сети, а затем возобновить ее позже. В другом случае я пишу язык программирования, где я предоставляю пользователю доступ к значениям продолжения-значения, чтобы они могли писать return и throw для себя - или любой другой поток управления, например, while loop - без мне нужно сделать это для них.

Ответ 8

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

Ответ 9

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