Czym jest call/cc?

Próbowałem kilka razy zrozumieć pojęcie kontynuacje i call / cc . Każda próba była porażką. Czy ktoś mógłby mi wyjaśnić te pojęcia, najlepiej z bardziej realistycznymi przykładami niż te na Wikipedii lub w innych tak postach.

Mam doświadczenie w programowaniu stron internetowych i OOP. Rozumiem też Zgromadzenie 6502 i miałem małe randez-vous Z Erlangiem. Jednak nadal nie mogę zawinąć głowy wokół call/cc.

Author: Michał Rudnicki, 2009-03-05

10 answers

Spójrz, znalazłem ten kontynuacja Passing Style najlepszy opis w tym temacie.

Oto pozbawiona szczegółów kopia tego artykułu:

Autor: Marijn Haverbeke Data: 24 lipca 2007

Funkcja call-with-current-continuation Scheme umożliwia przechwycenie obliczeń, stanu stosu wywołań i wznowienie tego samego stanu w późniejszym czasie. Na dodatek do tak prymitywnej, różnej formy obsługi wyjątków i podobnego do C longjmp sztuczki mogą być realizowane.

function traverseDocument(node, func) {
  func(node);
  var children = node.childNodes;
  for (var i = 0; i < children.length; i++)
    traverseDocument(children[i], func);
}   

function capitaliseText(node) {
  if (node.nodeType == 3) // A text node
    node.nodeValue = node.nodeValue.toUpperCase();
}

traverseDocument(document.body, capitaliseText);

Można to przekształcić w następujący sposób: dodajemy dodatkowy argument do każdej funkcji, który zostanie użyty do przekazania kontynuacji funkcji. Ta kontynuacja jest wartością funkcji reprezentującą działania, które muszą nastąpić po funkcji "zwraca". Stos (call) staje się przestarzały w stylu continuation-passing ― gdy funkcja wywołuje inną funkcję, to ostatnia rzecz, którą robi. Zamiast czekać na powrót wywołanej funkcji, stawia wszelkie pracę chce wykonać później w kontynuację, którą przekazuje do funkcji.

function traverseDocument(node, func, c) {
  var children = node.childNodes;
  function handleChildren(i, c) {
    if (i < children.length)
      traverseDocument(children[i], func,
                       function(){handleChildren(i + 1, c);});
    else
      c();
  }
  return func(node, function(){handleChildren(0, c);});
}

function capitaliseText(node, c) {
  if (node.nodeType == 3)
    node.nodeValue = node.nodeValue.toUpperCase();
  c();
}

traverseDocument(document.body, capitaliseText, function(){});

Wyobraź sobie, że mamy huuuuge dokument do kapitalizacji. Po prostu przemierzanie go za jednym zamachem zajmuje pięć sekund, a zamrożenie przeglądarki na pięć sekund jest raczej złym stylem. Rozważ tę prostą modyfikację capitaliseText (nie zwracaj uwagi na brzydki globalny):

var nodeCounter = 0;
function capitaliseText(node, c) {
  if (node.nodeType == 3)
    node.nodeValue = node.nodeValue.toUpperCase();

  nodeCounter++;
  if (nodeCounter % 20 == 0)
    setTimeout(c, 100);
  else
    c();
}

Teraz, co dwadzieścia węzłów, obliczenia są przerywane na sto milisekund, aby dać interfejs przeglądarki chwila na odpowiedź na wpis użytkownika. Bardzo prymitywna forma wątku ― można nawet uruchomić wiele obliczeń w tym samym czasie.

Bardziej przydatna aplikacja jest związana z XMLHttpRequests, lub różnymi hakerami IFRAME I SCRIPT tag używanymi do ich symulacji. Zawsze wymagają one pracy z jakimś mechanizmem oddzwaniania do obsługi danych, które serwer wysyła z powrotem. W prostych przypadkach zrobi to trywialna funkcja, lub kilka globali może być użyte do przechowywania stan obliczeń, który musi zostać wznowiony po powrocie danych. W złożonych przypadkach, na przykład, gdy dane są używane przez funkcję, która musi zwrócić pewną wartość do jej wywołującego, kontynuacje znacznie upraszczają. Po prostu rejestrujesz kontynuację jako wywołanie zwrotne, a obliczenia są wznawiane po zakończeniu żądania.

 11
Author: temoto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-17 23:41:38

Aby porównać go do C, bieżąca kontynuacja jest podobna do bieżącego stanu stosu. Posiada wszystkie funkcje oczekujące na wynik bieżącej funkcji, aby mogły wznowić wykonywanie. Zmienna przechwycona jako bieżąca kontynuacja jest używana jak funkcja, z tym wyjątkiem, że pobiera podaną wartość i zwraca ją do stosu oczekującego. To zachowanie jest podobne do funkcji C longjmp gdzie można powrócić do niższych części stosu natychmiast.

(define x 0) ; dummy value - will be used to store continuation later

(+ 2 (call/cc (lambda (cc)
                (set! x cc)  ; set x to the continuation cc; namely, (+ 2 _)
                3)))         ; returns 5

(x 4) ; returns 6

Kluczową różnicą między stosem C a kontynuacją jest to, że kontynuacja może być użyta w dowolnym momencie programu, nawet jeśli stan stosu się zmienił. Oznacza to, że można zasadniczo przywracać wcześniejsze wersje stosu i używać ich ponownie i ponownie, co prowadzi do pewnego unikalnego przepływu programu.

(* 123 (+ 345 (* 789 (x 5)))) ; returns 7

  reason: it is because (x 5) replaces the existing continuation,
          (* 123 (+ 345 (* 789 _))), with x, (+ 2 _), and returns
          5 to x, creating (+ 2 5), or 7.

Możliwość zapisywania i przywracania stanu programu ma wiele wspólnego z wielowątkowością. W rzeczywistości możesz zaimplementować swój własny wątek scheduler korzystający z ciągów, jak starałem się zilustrować tutaj .

 22
Author: Kyle Cronin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:25:13

Trywialnym przykładem użycia continuation byłoby zaimplementowanie menedżera wątków (fiber, jeśli chcesz) na jednoprocesorowej maszynie. Scheduler okresowo przerywa proces wykonywania (lub, w przypadku włókien, jest wywoływany w różnych strategicznych punktach kodu), zapisuje Stan kontynuacji (odpowiadający bieżącemu wątkowi ), a następnie przełączy się na inny Stan kontynuacji (odpowiadający innemu wątkowi, którego stan był zapisane wcześniej.)

Odnosząc się do tła asemblera, Stan kontynuacyjny przechwytuje takie szczegóły, jak wskaźnik instrukcji, rejestry i kontekst stosu (wskaźnik), do zapisania i przywrócenia do woli.

Innym sposobem użycia continuation byłoby zastąpienie wywołań metod kilkoma encjami podobnymi do wątków , które współistnieją równolegle (uruchamiane lub zawieszone) przekazując kontrolę między sobą za pomocą kontekstów continuation zamiast "klasyczny" call paradygmat. Będą działać na globalnych (współdzielonych) danych, zamiast polegać na parametrach. Jest to do pewnego stopnia bardziej elastyczne niż call w tym sensie, że stos nie musi kończyć się w górę i w dół (calls zagnieżdżone), ale kontrola może przebiegać dowolnie.

Próbując zobrazować to pojęcie w języku takim jak C, wyobraź sobie, że masz jedną dużą pętlę z pojedynczym switch(continuation_point) { case point1: ... } stwierdzeniem, gdzie każde case odpowiada ciągowi-savepoint, a gdzie kod wewnątrz każdego case może zmienić wartość continuation_point i zrezygnować z kontroli na to continuation_point przez breaking z switch i włączyć kolejną iterację w pętli.

Jaki jest kontekst twojego pytania? Interesują Cię jakieś konkretne scenariusze? Jakiś konkretny język programowania? Czy powyższy przykład wątku/włókna jest wystarczający?

 7
Author: vladr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-27 13:55:10

Rzecz, która mi pomogła, to pomysł, że w tradycyjnym języku z wywołaniami funkcji można bezwarunkowo przekazać kontynuację za każdym razem, gdy wykonujesz wywołanie funkcji.

Przed skokiem do kodu funkcji zapisujesz jakiś stan na stosie (tzn. wciskasz swój adres zwrotny, a stos zawiera już Twoich mieszkańców). Jest to zasadniczo kontynuacja. Po zakończeniu funkcji musi określić, gdzie wysłać przepływ wykonania. Wykorzystuje kontynuację zapisaną na stosie, wyskakując adres zwrotny i wskakując do niego.

Inne języki uogólniają tę ideę kontynuacji, pozwalając jednoznacznie określić, gdzie należy kontynuować wykonywanie kodu, a nie pośrednio kontynuować, skąd wywołanie funkcji zostało wykonane.

EDIT based on comment:

Kontynuacja jest kompletnym stanem wykonania. W dowolnym momencie wykonania można podzielić program na dwie części (w czasie, a nie w przestrzeni) - tę, która do tego momentu działała, i wszystko to ucieknie stąd. "Bieżąca kontynuacja" to "wszystko, co będzie działać stąd" (możesz myśleć o tym jak o funkcji, która zrobi wszystko, co zrobiłaby reszta twojego programu). Tak więc funkcja, którą dostarczasz call/cc otrzymuje kontynuację, która była aktualna podczas wywoływania call/cc. Funkcja może użyć kontynuacji, aby zwrócić wykonanie instrukcji call/cc (bardziej prawdopodobne, że przekaże kontynuację do czegoś innego, ponieważ jeśli użyłem go bezpośrednio, zamiast tego mógłby zrobić prosty zwrot).

 5
Author: Dave,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-04 23:51:44

Kiedy próbowałem zrozumieć call/cc, okazało się, że ta strona call-with-current-continuation-for-C-programmers była pomocna.

 4
Author: SCFrench,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-03 00:36:18

Wyobraź sobie, że twój scenariusz jest sceną gier wideo. Call / cc jest jak bonus stage.

parellel między etapem bonusowym a call/cc

Gdy tylko go dotkniesz, zostaniesz przeniesiony do etapu bonusowego(tzn. definicja funkcji przekazywana jako argument do call / cc [F w tym przypadku]).

etapy bonusowe różnią się od zwykłych etapów ponieważ zwykle mają element (tzn. argument funkcji przekazanej do call/cc) , że jeśli go dotkniesz, stracisz i zostaniesz przetransportowany powrót do normalnego stanu.

parellel pomiędzy exit bonus stage a call / CC function args

Więc nie ma znaczenia, czy jest ich wiele args, kiedy dotrzesz do jednego z nich to koniec. Więc nasza egzekucja dociera (arg 42) i zwraca ją do sumy (+ 42 10).

Są też pewne uwagi, na które warto zwrócić uwagę:

  • nie wszystkie funkcje mogą być używane z call / cc. Ponieważ oczekuje kontynuacja (czyli funkcja), nie można mieć takiego f: (define f (lambda (k) (+ k 42)), ponieważ nie możesz sum a funkcja.
  • również nie możesz mieć (define f (lambda (k) (f 42 10))) ponieważ kontynuacja oczekuje tylko jednego argumentu.
  • możesz skończyć bez touching żadnego wyjścia, w tym przypadku funkcja przebiega jak Dowolna zwykła funkcja (np. (define f (lambda (k) 42) kończy się i zwraca 42).
 3
Author: carla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-02 00:38:53

Najlepsze wyjaśnienie, jakie widziałem, znajduje się w książce Paula Grahama, On Lisp .

 2
Author: Jacob Gabrielson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-04 22:42:58

Istnieje wiele poziomów zrozumienia call / cc. Najpierw musisz zrozumieć warunki i sposób działania mechanizmu. Następnie zrozumienie, jak i kiedy call / cc jest używany w " prawdziwym życiu" potrzebne jest programowanie.

Pierwszy poziom można osiągnąć studiując CPS, ale są alternatywy.

Na drugi poziom polecam następujący klasyk Friedmana.

Daniel P. Friedman. "Zastosowania ciągłości: zaproszeni tutoriale". 1988 Principles of Języki programowania (POPL88). Styczeń 1988.

 2
Author: soegaard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-09 20:55:09

Model, którego użyłem do zrozumienia kontynuacji z imperatywnego punktu widzenia, jest kopią stosu wywołań połączonego ze wskaźnikiem a do następnej instrukcji.

Call / cc wywołuje funkcję (przekazywaną jako argument) z kontynuacją jako argumentem.

 2
Author: cdiggins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-22 23:02:34
 1
Author: AshleyF,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-22 22:43:50