kontynuacja programu dla manekinów
/ Align = "center" bgcolor = "# e0ffe0 " / cesarz chin / / align = center / Myślę, że problem wynika z tego, że nie rozumiem, do czego są . Wszystkie przykłady, które znalazłem w książkach lub internecie są bardzo trywialne. Zastanawiają mnie, po co ktoś miałby w ogóle chcieć kontynuacji?
Oto typowy niepraktyczny przykład z TSPL , który moim zdaniem jest dość rozpoznawalną książką na ten temat. W języku angielskim opisują kontynuację jako "co robić" z wynikiem obliczeń. OK, to jestw pewnym sensie zrozumiałe.
Następnie, drugi przykład podany:
(call/cc
(lambda (k)
(* 5 (k 4)))) => 4
Jaki to ma sens?? k
nie jest nawet zdefiniowana! Jak można ocenić ten kod, skoro (k 4)
nie można nawet obliczyć? Nie wspominając już o tym, jak call/cc
wiedzieć, aby wyrwać argument 4
do wewnętrznego wyrażenia most i zwrócić go? Co się stanie z (* 5 ..
?? Jeśli to wyrażenie jest odrzucane, to po co w ogóle je zapisywać?
Następnie, "mniej" trywialny przykład stwierdził jest jak używać call/cc
aby zapewnić nielokalne wyjście z rekurencji. To brzmi jak dyrektywa kontroli przepływu, czyli jak break/return
w języku imperatywnym, a nie obliczenie.
A jaki jest cel tych ruchów? Jeśli ktoś potrzebuje wyniku obliczeń, dlaczego po prostu nie zapisać go i przypomnieć później, w razie potrzeby.
4 answers
Zapomnij o call/cc
na chwilę. Każde wyrażenie/polecenie w dowolnym języku programowania ma kontynuację - czyli to, co robisz z wynikiem. W C, na przykład,
x = (1 + (2 * 3));
printf ("Done");
Kontynuacją zadania matematycznego jest printf(...)
; kontynuacją (2 * 3)
jest ' add 1; assign to x; printf(...)'. Koncepcyjnie kontynuacja jest tam, Czy masz do niej dostęp, czy nie. Zastanów się przez chwilę, jakich informacji potrzebujesz do kontynuacji-informacja jest 1) sterta stan pamiÄ ™ ci (w ogĂłle), 2) stos, 3) wszelkie rejestry i 4) Licznik programu.
Więc kontynuacje istnieją, ale zazwyczaj są tylko ukryte i nie mogą być dostępne.
W Scheme i kilku innych językach, masz dostęp do kontynuacji. Zasadniczo, za twoimi plecami, kompilator + runtime gromadzi wszystkie informacje potrzebne do kontynuacji, przechowuje je (zazwyczaj w stercie) i daje Ci do niej dostęp. Uchwyt, który otrzymujesz to funkcja " k " - jeśli wywołasz ta funkcja będzie kontynuowana dokładnie po punkcie call/cc
. Co ważne, możesz wywołać tę funkcję wiele razy i zawsze będziesz kontynuował po punkcie call/cc
.
Spójrzmy na kilka przykładów:
> (+ 2 (call/cc (lambda (cont) 3)))
5
W powyższym, wynik call/cc
jest wynikiem lambda
, który jest 3. Kontynuacja nie została odwołana.
Teraz wywołajmy kontynuację:
> (+ 2 (call/cc (lambda (cont) (cont 10) 3)))
12
Wywołując kontynuację pomijamy cokolwiek po wywołaniu i kontynuujemy dokładnie w punkcie call/cc
. Z (cont 10)
kontynuacja zwraca 10
, która jest dodawana do 2 za 12.
Teraz zachowajmy kontynuację.
> (define add-2 #f)
> (+ 2 (call/cc (lambda (cont) (set! add-2 cont) 3)))
5
> (add-2 10)
12
> (add-2 100)
102
Zapisując kontynuację możemy użyć jej tak, jak nam się podoba, aby "wrócić do" dowolnego obliczenia, które nastąpiły po punkcie call/cc
.
Często kontynuacje są używane do wyjścia pozalokalnego. Pomyśl o funkcji, która zwróci listę, chyba że jest jakiś problem, w którym momencie '()
zostanie zwrócona.
(define (hairy-list-function list)
(call/cc
(lambda (cont)
;; process the list ...
(when (a-problem-arises? ...)
(cont '()))
;; continue processing the list ...
value-to-return)))
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
2013-05-13 22:55:18
Oto tekst z notatek z mojej klasy: http://tmp.barzilay.org/cont.txt . jest on oparty na wielu źródłach i jest znacznie rozszerzony. Ma motywacje, podstawowe wyjaśnienia, bardziej zaawansowane wyjaśnienia, jak to się robi, i wiele przykładów, które przechodzą od prostych do zaawansowanych, a nawet kilka szybkich dyskusji o ograniczonych kontynuacjach.
(próbowałem pobawić się umieszczeniem tutaj całego tekstu, ale jak się spodziewałem, 120K tekstu nie jest czymś, co sprawia tyle radości.
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
2013-05-13 19:27:36
TL; DR : kontynuacje są po prostu przechwytywane Goto, z wartościami, mniej więcej.
Przykład, o który pytasz,
(call/cc
(lambda (k)
;;;;;;;;;;;;;;;;
(* 5 (k 4)) ;; body of code
;;;;;;;;;;;;;;;;
)) => 4
Można w przybliżeniu przetłumaczyć na np. Common Lisp, jako
(prog (k retval)
(setq k (lambda (x) ;; capture the current continuation:
(setq retval x) ;; set! the return value
(go EXIT))) ;; and jump to exit point
(setq retval ;; get the value of the last expression,
(progn ;; as usual, in the
;;;;;;;;;;;;;;;;
(* 5 (funcall k 4)) ;; body of code
;;;;;;;;;;;;;;;;
))
EXIT ;; the goto label
(return retval))
To tylko ilustracja; w Common Lispie nie możemy wrócić do Prog tagbody po wyjściu z niego za pierwszym razem. Ale w schemacie, z prawdziwymi kontynuacjami, możemy. Jeśli ustawimy jakąś zmienną globalną wewnątrz ciała funkcji wywołanej przez call/cc
, powiedzmy (setq qq k)
, w Scheme możemy ją wywołać w dowolnym momencie, z dowolnego miejsca, ponownie wchodząc w ten sam kontekst(np. (qq 42)
).
Chodzi o to, że ciało formy call/cc
może zawierać if
lub cond
wyrażenie. Może wywoływać kontynuację tylko w niektórych przypadkach, a w innych zwracać normalnie, oceniając wszystkie wyrażenia w kodzie i zwracając ostatnią wartość, jak zwykle. Może tam występować głęboka rekurencja. Wywołując przechwyconą kontynuację uzyskuje się natychmiastowe wyjście.
Więc my Zobacz też k
jest zdefiniowana. Jest ona zdefiniowana przez wywołanie call/cc
. Kiedy (call/cc g)
jest wywołana, wywołuje swój argument z bieżącą kontynuacją: (g the-current-continuation)
. the current-continuation
jest "procedurą ucieczki" wskazującą punkt zwrotny formularza call/cc
. Wywołanie to oznacza podanie wartości tak, jakby została zwrócona przez samą formę call/cc
.
Tak więc powyższe wyniki w
((lambda(k) (* 5 (k 4))) the-current-continuation) ==>
(* 5 (the-current-continuation 4)) ==>
; to call the-current-continuation means to return the value from
; the call/cc form, so, jump to the return point, and return the value:
4
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
2014-12-12 01:13:09
Nie będę próbował wyjaśniać wszystkich miejsc, w których kontynuacje mogą być przydatne, ale mam nadzieję, że będę mógł podać krótkie przykłady głównego miejsca, w którym znalazłem kontynuacje przydatne w moim własnym doświadczeniu. Zamiast mówić o Scheme ' s call/cc
, skupiłbym uwagę na kontynuowaniu stylu przechodzenia . W niektórych językach programowania zmienne mogą być dynamicznie skalowane, a w językach bez dynamicznie skalowanych-ze zmiennymi globalnymi (zakładając, że nie ma zagadnienia kodu wielowątkowego itp.). Na przykład, załóżmy, że istnieje lista aktualnie aktywnych strumieni logowania, *logging-streams*
i które chcemy wywołać function
w dynamicznym środowisku, w którym {[9] } jest rozszerzona o logging-stream-x
. W Common Lispie możemy zrobić
(let ((*logging-streams* (cons logging-stream-x *logging-streams*)))
(function))
Jeśli nie mamy dynamicznie skalowanych zmiennych, jak w Scheme, nadal możemy wykonać
(let ((old-streams *logging-streams*))
(set! *logging-streams* (cons logging-stream-x *logging-streams*)
(let ((result (function)))
(set! *logging-streams* old-streams)
result))
Teraz Załóżmy, że rzeczywiście otrzymaliśmy drzewo cons, którego liście nie - nil
są strumieniami, z których wszystkie powinny być w *logging-streams*
Kiedy function
jest wywołana. Mamy dwie opcje:
- możemy spłaszczyć drzewo, zebrać wszystkie strumienie logowania, rozszerzyć
*logging-streams*
, a następnie wywołaćfunction
. - możemy, używając stylu continuation passing, przemierzać drzewo, stopniowo rozszerzając
*logging-streams*
, W końcu wywołującfunction
, gdy nie ma jużtree
do trawersowania.
Opcja 2 wygląda jak
(defparameter *logging-streams* '())
(defun extend-streams (stream-tree continuation)
(cond
;; a null leaf
((null stream-tree)
(funcall continuation))
;; a non-null leaf
((atom stream-tree)
(let ((*logging-streams* (cons stream-tree *logging-streams*)))
(funcall continuation)))
;; a cons cell
(t
(extend-streams (car stream-tree)
#'(lambda ()
(extend-streams (cdr stream-tree)
continuation))))))
Z tą definicją mamy
CL-USER> (extend-streams
'((a b) (c (d e)))
#'(lambda ()
(print *logging-streams*)))
=> (E D C B A)
Czy było w tym coś pożytecznego? W tym przypadku, prawdopodobnie nie. Niektóre drobne korzyści mogą być takie, że extend-streams
jest rekurencyjna, więc nie mamy zbyt dużego użycia stosu, chociaż pośrednie zamknięcia nadrabiają to w przestrzeni sterty. Mamy fakt, że ewentualna kontynuacja jest wykonywana w zakresie dynamicznym dowolnego elementu pośredniego, który extend-streams
ustawiono. W tym przypadku nie jest to aż tak ważne, ale w innych przypadkach może być.
Jest w stanie wyodrębnić część przepływu sterowania i mieć bardzo przydatne mogą być wyjścia pozalokalne, czy też Może to być przydatne na przykład w wyszukiwaniu wstecznym. Oto kontynuacja przechodząca styl propositional calculus solver dla formuł, w których formuła jest symbolem( literał propositional), lub Lista postaci (not formula)
, (and left right)
, lub (or left right)
.
(defun fail ()
'(() () fail))
(defun satisfy (formula
&optional
(positives '())
(negatives '())
(succeed #'(lambda (ps ns retry) `(,ps ,ns ,retry)))
(retry 'fail))
;; succeed is a function of three arguments: a list of positive literals,
;; a list of negative literals. retry is a function of zero
;; arguments, and is used to `try again` from the last place that a
;; choice was made.
(if (symbolp formula)
(if (member formula negatives)
(funcall retry)
(funcall succeed (adjoin formula positives) negatives retry))
(destructuring-bind (op left &optional right) formula
(case op
((not)
(satisfy left negatives positives
#'(lambda (negatives positives retry)
(funcall succeed positives negatives retry))
retry))
((and)
(satisfy left positives negatives
#'(lambda (positives negatives retry)
(satisfy right positives negatives succeed retry))
retry))
((or)
(satisfy left positives negatives
succeed
#'(lambda ()
(satisfy right positives negatives
succeed retry))))))))
Jeśli zostanie znalezione zadowalające przypisanie, to succeed
jest wywoływane z trzema argumentami: listą dodatnich liter, listą ujemnych literały i funkcja, która może ponowić wyszukiwanie (tj. próbować znaleźć inne rozwiązanie). Na przykład:
CL-USER> (satisfy '(and p (not p)))
(NIL NIL FAIL)
CL-USER> (satisfy '(or p q))
((P) NIL #<CLOSURE (LAMBDA #) {1002B99469}>)
CL-USER> (satisfy '(and (or p q) (and (not p) r)))
((R Q) (P) FAIL)
Drugi przypadek jest interesujący, ponieważ trzeci wynik nie jest FAIL
, ale jakąś funkcją, która spróbuje znaleźć inne rozwiązanie. W tym przypadku możemy zobaczyć, że (or p q)
jest spełnialny, czyniąc albo p
albo q
prawdą: {[37]]}
CL-USER> (destructuring-bind (ps ns retry) (satisfy '(or p q))
(declare (ignore ps ns))
(funcall retry))
((Q) NIL FAIL)
Byłoby to bardzo trudne do zrobienia, gdybyśmy nie używali stylu przejścia kontynuacyjnego, w którym możemy zapisać alternatywny przepływ i wrócić do niego później. Używając tego, możemy zrobić kilka sprytnych rzeczy, takich jak zebranie wszystkich satysfakcjonujących zadań: {]}
(defun satisfy-all (formula &aux (assignments '()) retry)
(setf retry #'(lambda ()
(satisfy formula '() '()
#'(lambda (ps ns new-retry)
(push (list ps ns) assignments)
(setf retry new-retry))
'fail)))
(loop while (not (eq retry 'fail))
do (funcall retry)
finally (return assignments)))
CL-USER> (satisfy-all '(or p (or (and q (not r)) (or r s))))
(((S) NIL) ; make S true
((R) NIL) ; make R true
((Q) (R)) ; make Q true and R false
((P) NIL)) ; make P true
Możemy zmienić loop
trochę i uzyskać tylko N zadania, aż do niektórych n , lub wariacje na ten temat. Często kontynuacja stylu przechodzenia nie jest potrzebna lub może sprawić, że kod będzie trudny do utrzymania i zrozumienia, ale w przypadkach, w których jest użyteczny, może sprawić, że niektóre inne rzeczy będą bardzo trudne spokojnie.
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
2013-05-13 21:47:38