Συνέχεια (πληροφορική)

Μια συνέχεια (continuation) επαναφέρει ένα στιγμιότυπο μιας υπολογιστικής διεργασίας σε ένα δοσμένο σημείο της εκτέλεσής της. Περιλαμβάνει πληροφορίες όπως η τρέχουσα στοίβα της διεργασίας (με όλα τα δεδομένα των οποίων ο χρόνος ζωής είναι μέσα στη διεργασία, π.χ. οι "τοπικές μεταβλητές") και η θέση της διεργασίας στον υπολογισμό. Ένα τέτοιο στιγμιότυπο μπορεί στη συνέχεια να κληθεί και να συνεχιστεί. Η "τρέχουσα συνέχεια"("current continuation") ή "συνέχεια του υπολογιστικού βήματος" ("continuation of the computation step") είναι η συνέχεια, η οποία, από την πλευρά του κώδικα που εκτελείται, θα προέκυπτε από το τρέχον σημείο του προγράμματος που εκτελείται.
Ο όρος συνέχειες μπορεί επίσης να σημαίνει συνέχειες πρώτης τάξης, οι οποίες είναι σχήματα κώδικα που επιτρέπουν σε μια γλώσσα προγραμματισμού να αποθηκεύει την κατάσταση του υπολογισμού σε κάποιο σημείο και να μπορεί αργότερα, σε κάποιο άλλο σημείο του προγράμματος, να επιστρέφει σε αυτήν.
Τα προγράμματα πρέπει να κρατούν θέσεις στη μνήμη για τις μεταβλητές που χρησιμοποιούν οι συναρτήσεις τους. Οι περισσότερες γλώσσες προγραμματισμού χρησιμοποιούν μια στοίβα κλήσεων για να αποθηκεύουν τις μεταβλητές που χρειάζονται γιατί με αυτόν τον τρόπο έχουν γρήγορη και απλή δέσμευση μνήμης και αυτόματη αποδέσμευση. Υπάρχουν επίσης γλώσσες προγραμματισμού που χρησιμοποιούν σωρό για αυτόν το σκοπό, ο οποίος επιτρέπει μεγαλύτερη ευελιξία αλλά έχει υψηλότερο κόστος δέσμευσης και αποδέσμευσης μνήμης. Και οι δύο αυτές διαφορετικές υλοποιήσεις έχουν πλεονεκτήματα και μειονεκτήματα όσον αφορά τις συνέχειες.
Σχεδόν όλες οι γλώσσες έχουν κάποιον τρόπο για να αλλάζουν τη σειρά των βημάτων εκτέλεσης (δηλ. την τροποποίηση της συνέχειας ενός υπολογιστικού βήματος). Η εντολή goto είναι η πιο βασική μορφή από αυτές. Δομές ελέγχου όπως οι εντολές if, οι βρόχοι και οι εντολές return, break και exit, είναι πιο δομημένοι (και περιορισμένοι) τρόποι να τροποποιηθεί η σειρά των εντολών που εκτελούνται και στην πραγματικότητα μπορούν να θεωρηθούν σαν περιορισμένες εντολές goto.
Υπάρχουν και πιο πολύπλοκα σχήματα κώδικα. Για παράδειγμα στη C, η εντολή setjmp μπορεί να χρησιμοποιηθεί για να γίνει άλμα από τη μέση μιας συνάρτησης σε μια άλλη συνάρτηση, με την προϋπόθεση ότι η άλλη συνάρτηση είναι χαμηλότερα στη στοίβα (για παράδειγμα, μπορεί να περιμένει την αρχική συνάρτηση να τελειώσει την εκτέλεσή της). Άλλα πιο πολύπλοκα παραδείγματα είναι οι συρρουτίνες στη Simula, τα tasklets στην Stackless Python, οι γεννήτριες στην Icon και την Python, οι συνέχειες στη Scala (από την έκδοση 2.8), οι ίνες στη Ruby (από την έκδοση 1.9.1), ο μηχανισμός οπισθοδρόμησης (backtracking) στην Prolog, και τα νήματα.

Ιστορία

Η παλαιότερη περιγραφή των συνεχειών έχει γίνει από τον Adriaan van Wijngaarden το Σεπτέμβριο του 1964. Ο Wijngaarden μίλησε στο "IFIP Working Conference on Formal Language Description Languages", στο Baden bei Wien, στην Austria. Σαν μέρος του φορμαλισμού ενός προεπεξεργαστή για την Algol 60, χρειάστηκε ένα μετασχηματισμό από κανονικές διαδικασίες σε στυλ περάσματος συνεχειών (continuation-passing style).
Ο Christopher Strachey, ο Christopher P. Wadsworth και ο John C. Reynolds έκαναν γνωστό τον όρο συνέχεια (continuation) αργότερα με τη δουλειά τους στη δηλωτική σημασιολογία που χρησιμοποιεί εκτενώς συνέχειες για να μπορεί να αναλύει ακολουθιακά προγράμματα σε σημασιολογία συναρτησιακού προγραμματισμού.
Η συνέχεια εφευρέθηκε από τον Steve Russell στη δεύτερη υλοποίηση της Lisp για τον IBM 704, αν και δεν της έδωσε όνομα.

Συνέχειες πρώτης τάξης

Συνέχειες πρώτης τάξης είναι η ικανότητα μιας γλώσσας να ελέγχει πλήρως τη σειρά εκτέλεσης των εντολών της. Μπορούν να χρησιμοποιηθούν για άλμα μέσα σε μια συνάρτηση που κάλεσε την τρέχουσα συνάρτηση ή σε μια συνάρτηση που έχει τελειώσει η εκτέλεσή της. Οι συνέχειες πρώτης τάξης μπορούν να θεωρηθούν ότι αποθηκεύουν την κατάσταση του προγράμματος. Όμως πρέπει να σημειωθεί ότι οι πραγματικές συνέχειες πρώτης τάξης δεν αποθηκεύουν τα δεδομένα του προγράμματος αλλά μόνο το πλαίσιο της εκτέλεσης (execution context). Αυτό περιγράφεται από την περιγραφή της "συνέχειας-σάντουιτς":
Ας πούμε ότι είστε στην κουζίνα μπροστά από το ψυγείο, θέλοντας ένα σάντουιτς. Παίρνετε τη συνέχεια εκείνη τη στιγμή και τη βάζετε στην τσέπη σας. Μετά παίρνετε λίγες φέτες γαλοπούλα και ψωμί από το ψυγείο και φτιάχνετε ένα σάντουιτς, το οποίο τώρα βρίσκεται στον πάγκο της κουζίνας. Καλείτε τη συνέχεια που έχετε στην τσέπη σας και βρίσκεστε πάλι μπροστά στο ψυγείο θέλοντας ένα σάντουιτς. Αλλά ευτυχώς υπάρχει ήδη ένα σάντουιτς στον πάγκο και τα υλικά που θα χρειάζονταν για να το φτιάξετε πια δεν υπάρχουν. Οπότε το τρώτε. :-)
Η Scheme ήταν το πρώτο σύστημα επιπέδου παραγωγής που παρείχε αρχικά την "catch" και μετά την call/cc. Ο Bruce Duba εισήγαγε την call/cc στην Standard ML.
Οι συνέχειες επίσης χρησιμοποιούνται σε μοντέλα υπολογισμού όπως η δηλωτική σημασιολογία, το μοντέλο Actor, οι λογισμοί διεργασιών (process calculi) και ο λ-λογισμός. Αυτά τα μοντέλα απαιτούν από τους προγραμματιστές ή τους μηχανικούς της σημασιολογίας να γράφουν μαθηματικές συναρτήσεις στο λεγόμενο στυλ περάσματος συνεχειών. Αυτό σημαίνει ότι κάθε συνάρτηση καταναλώνει μια άλλη συνάρτηση που αναπαριστά το υπόλοιπο του υπολογισμού σε σχέση με αυτήν την κλήση συνάρτησης. Για να επιστρέψει κάποια τιμή, η συνάρτηση καλεί αυτήν τη "συνάρτηση συνέχειας" με μια τιμή επιστροφής - για να διακόψει τον υπολογισμό επιστρέφει μια τιμή.
Οι προγραμματιστές συναρτησιακών γλωσσών που γράφουν τα προγράμματά τους σε στυλ περάσματος συνεχειών έχουν την εκφραστική δύναμη να αλλάζουν τη ροή του ελέγχου με αυθαίρετους τρόπους. Το κόστος είναι ότι πρέπει να διατηρούν τις αναλλοίωτες του ελέγχου και των συνεχειών με το χέρι, κάτι που είναι αρκετά πολύπλοκο.

Χρήσεις

Οι συνέχειες είναι χρήσιμες γιατί απλοποιούν και ξεκαθαρίζουν την απλοποίηση πολλών άλλων κοινών σχημάτων κώδικα, όπως οι συρρουτίνες (γνωστές και σαν πράσινα νήματα), και η διαχείριση εξαιρέσεων. Οι συνέχειες είναι βασικές και χαμηλού επιπέδου, ενοποιώντας αυτά τα (σε πρώτη όψη ασύνδετα) σχήματα.

Παραδείγματα

Η γλώσσα προγραμματισμού Scheme περιλαμβάνει τον τελεστή ροής κλήση-με-τρέχουσα-συνέχεια (call-with-current-continuation) (συντομογραφία: call/cc) με τον οποίο ένα πρόγραμμα σε Scheme μπορεί να αλλάζει τη ροή του ελέγχου:
 (define the-continuation #f)

 (define (test)
   (let ((i 0))
     ; η call/cc καλεί την πρώτη παράμετρο της συνάρτησης, περνώντας της
     ; μια μεταβλητή συνέχειας που αναπαριστά ένα σημείο στο πρόγραμμα
     ; σαν παρέμτρο σε αυτήν τη συνάρτηση.
     ;
     ; Σε αυτήν την περίπτωση, η παράμετρος της συνάρτησης δεσμεύει τη
     ; συνέχεια στη μεταβλητή the-continuation.
     ;
     (call/cc (lambda (k) (set! the-continuation k)))
     ;
     ; Την επόμενη φορά που καλείται η συνέχεια, αρχίζουμε εδώ.
     (set! i (+ i 1))
     i))
Ορίζει μια συνάρτηση test που θέτει την the-continuation σε μια μελλοντική κατάσταση εκτέλεσης της ίδιας:
> (test)
1
> (the-continuation)
2
> (the-continuation)
3
> ; αποθηκεύει την τρέχουσα συνέχεια (που θα τυπώσει 4 τώρα)
> (define another-continuation the-continuation)
> (test) ; resets the-continuation
1
> (the-continuation)
2
> (another-continuation) ; χρησιμοποιεί την προηγούμενη αποθηκευμένη συνέχεια
4
Για μια πιο απλή εισαγωγή σε αυτόν το μηχανισμό, δείτε το άρθρο κλήση-με-τρέχουσα-συνέχεια.

Συρρουτίνες

Αυτό το παράδειγμα δείχνει μια πιθανή χρήση των συνεχειών στην υλοποίηση συρρουτινών σαν ξεχωριστά νήματα.
 ;;; Μια απλή ουρά για χρονοπρογραμματισμό νημάτων.
 ;;; Κρατά τις συνέχειες που "πρόκειται να τρέξουν".

   (define *queue* '())

   (define (empty-queue?)
     (null? *queue*))

   (define (enqueue x)
     (set! *queue* (append *queue* (list x))))

   (define (dequeue)
     (let ((x (car *queue*)))
       (set! *queue* (cdr *queue*))
       x))

   ;;; Αρχίζει ένα νέο νήμα που θα τρέξει το (proc).

   (define (fork proc)
     (call/cc
      (lambda (k)
        (enqueue k)
        (proc))))

   ;;; Αφήνει τον επεξεργαστή σε κάποιο άλλο νήμα, αν υπάρχει τέτοιο.

   (define (yield)
     (call/cc
      (lambda (k)
        (enqueue k)
        ((dequeue)))))

   ;;; Τελειώνει το υπάρχον νήμα, ή ολόκληρο το πρόγραμμα
   ;;; αν δεν υπάρχουν άλλα νήματα.

   (define (thread-exit)
     (if (empty-queue?)
         (exit)
         ((dequeue))))
Οι παραπάνω συναρτήσεις επιτρέπουν τον ορισμό και την εκτέλεση νημάτων μέσω συνεργατικής πολυδιεργασίας (cooperative multitasking), δηλ. νήματα που αφήνουν τον έλεγχο στο επόμενο που περιμένει στην ουρά:
   ;;; Το σώμα ενός τυπικού νήματος σε Scheme που κάνει κάποια εργασία:

   (define (do-stuff-n-print str)
     (lambda ()
       (let loop ((n 0))
         (format #t "~A ~A\n" str n)
         (yield)
         (loop (1+ n)))))

   ;;; Δημιουργεί δύο νήματα και τα αρχίζει.
   (fork (do-stuff-n-print "This is AAA"))
   (fork (do-stuff-n-print "Hello from BBB"))
   (thread-exit)
Ο προηγούμενος κώδικας τότε τα παράγει την εξής έξοδο:
 This is AAA 0
 Hello from BBB 0
 This is AAA 1
 Hello from BBB 1
 This is AAA 2
 Hello from BBB 2
 ...

Συνέχειες στην ανάπτυξη λογισμικού για τον Ιστό

Μια περιοχή που χρησιμοποιεί πρακτικά τις συνέχειες είναι η ανάπτυξη λογισμικού ιστού. Η χρήση των συνεχειών προστατεύει τον προγραμματιστή από την έλλειψη κατάστασης (state) στο πρωτόκολλο HTTP.  Στο παραδοσιακό μοντέλο προγραμματισμού για τον Ιστό, η έλλειψη κατάστασης ανακλάται στη δομή του προγράμματος, οδηγώντας σε κώδικα που βασίζεται σε ένα μοντέλο ακατάλληλο να εκφράσει υπολογιστικά προβλήματα. Οι συνέχειες επιτρέπουν κώδικα που έχει τις χρήσιμες ιδιότητες της αναστροφής του ελέγχου(inversion of control), αποφεύγοντας τα προβλήματά του. Το Inverting back the inversion (Αγγλικά) είναι μια δημοσίευση που προσφέρει μια καλή εισαγωγή στην εφαρμογή των συνεχειών στον προγραμματισμό λογισμικού Ιστού.
Κάποιοι από τους πιο γνωστούς εξυπηρετητές Ιστού που υποστηρίζουν συνέχειες είναι ο PLT Scheme Web Server(documentation), το UnCommon Web Framework και το Weblocks Web framework για την Common Lisp, το πλαίσιο Seaside για την Smalltalk, το Ocsigen/Eliom για την OCaml, το Continuity για την Perl, το Wee για τη Ruby, και το πλαίσιο Nagare για την Python. Το πλαίσιο εφαρμογών ιστού Apache Cocoon επίσης παρέχει συνέχειες (βλ. το εγχειρίδιο του Cocoon).

Είδη συνεχειών

Η υποστήριξη συνεχειών διαφέρει αρκετά κατά περίπτωση. Μια γλώσσα προγραμματισμού υποστηρίζει επανακαλούμενες (re-invocable) συνέχειες αν μια συνέχεια μπορεί να καλείται συνεχώς (ακόμα και όταν έχει επιστρέψει). Τέτοιες συνέχειες εισήχθησαν από τον Peter J. Landin με τον τελεστή J (Jump) που μετέφερε τη ροή του ελέγχου πίσω στο μέσο μιας κλήσης διαδικασίας. Έχουν ονομαστεί και "re-entrant" στη γλώσσα προγραμματισμού MzScheme. Όμως αυτή η χρήση του όρου "re-entrant" μπορεί να προκαλέσει πρόβλημα σε σχέση με τη χρήση της σε συζητήσεις σχετικά με τον πολυνηματικό προγραμματισμό.
Ένα περιορισμένο είδος είναι η συνέχεια διαφυγής που μπορεί να χρησιμοποιηθεί για να μεταφερθούμε από το τρέχον περιβάλλον εκτέλεσης σε ένα άλλο που το περικλείει. Πολλές γλώσσες που δεν επιτρέπουν ρητά συνέχειες επιτρέπουν χειρισμό εξαιρέσεων, ο οποίος είναι ισοδύναμος με τις συνέχειες διαφυγής και μπορεί να χρησιμοποιηθεί για τους ίδιους σκοπούς. Οι setjmp/longjmp της C είναι επίσης ισοδύναμες: μπορούν να χρησιμοποιηθούν μόνο για να ξετυλίξουν τη στοίβα. Οι συνέχειες διαφυγής μπορούν επίσης να χρησιμοποιηθούν για να υλοποιήσουν βελτιστοποίηση κλήσης ουράς.

Μειονεκτήματα

Οι συνέχειες είναι η συναρτησιακή έκφραση της εντολής GOTO, και χαρακτηρίζεται από κάποια παρόμοια με αυτήν προβλήματα. Αν και είναι χρήσιμες σε πολλές περιπτώσεις (π.χ. προγραμματισμός εφαρμογών Ιστού), η συχνή χρήση τους μπορεί να οδηγήσει κώδικα με πολύπλοκη ροή ελέγχου που είναι δύσκολος στην κατανόηση. Για παράδειγμα, η γλώσσα προγραμματισμού Unlambda χρησιμοποιεί την κλήση-με-τρέχουσα-συνέχεια λόγω της δυσκολίας της στην κατανόηση. Οι παρακάτω εξωτερικοί σύνδεσμοι αναφέρουν περισσότερες πληροφορίες για το θέμα.

Γλωσσολογία

Κάποια φαινόμενα στις φυσικές γλώσσες έχουν περιγραφεί με τη χρήση φραγμένων συνεχειών (delimited continuations).
Ανεπίσημα, οι συνέχειες μπορούν να εξηγήσουν την ομοιότητα μεταξύ των προτάσεων "η Alice βλέπει τον Bob"—τυπικά, —και "η Alice βλέπει όλους τους ανθρώπους", δηλ.. .
Δείτε τη δημοσίευση του Chris Barker Continuations in Natural Language για λεπτομέρειες. Δείτε επίσης τη γραμματική Montague.