Lisp

Published on February 2017 | Categories: Documents | Downloads: 72 | Comments: 0 | Views: 699
of 602
Download PDF   Embed   Report

Comments

Content

λ

Πανεπιστήμιο

Αθηνών

Τμήμα

Πληροφορικής aaa

Συναρτησιακός
Προγραμματισμός
και Lisp
Γιώργος Γυφτοδήμος

Aθήνα, Γενάρης 2000

Σημείωμα
Θυμάμαι ένα παραμύθι από τα παιδικά μας χρόνια, που έλεγε για ένα πορτοφόλι, που κάθε
φορά που το άνοιγε αυτός που το είχε, εύρισκε ένα νόμισμα. Το συνάντησα ξανά, αληθινό
αυτή τη φορά, στα ρεύματα - λίστες παραγωγής στη Lisp, που ενώ περιέχουν ένα μόνο
στοιχείο, κάθε φορά που το διαβάζουμε είναι νέο, και έτσι η λίστα φαίνεται άπειρη.
Θυμάμαι ακόμα, στα γυμνασιακά μας χρόνια, που συζητούσαμε γιατί ο υπολογισμός κατά
κάποιο τρόπο ήταν έξω από τα μαθηματικά νοήματα: "πώς γίνεται οι ισοδυναμίες που
οδηγούν στην επίλυση ενός συστήματος, μαθηματικά να είναι αντιμεταθέσιμες αλλά σαν
βήματα του υπολογισμού να έχουν ορισμένη σειρά;". Η εξήγηση που παίρναμε ήταν: "ο
υπολογισμός είναι αυτό που κάνουμε εμείς για να ανακαλύψουμε τις μαθηματικές αλήθειες,
δεν είναι οι μαθηματικές αλήθειες". Αλλά τότε τί ήταν ο υπολογισμός; Μόνον αργότερα,
όταν συνάντησα τον λ-λογισμό, διαπίστωσα πως υπάρχει η ζητούμενη Μαθηματική θεωρία
που θεμελιώνει τον υπολογισμό, που αν και απλή παραμένει μακρυά από τις μέσες
εκπαιδευτικές βαθμίδες.
Κάποια στιγμή ένας σπουδαστής ήρθε με ένα "δύσκολο" πρόβλημα: να γράψει πρόγραμμα
που να εκτελεί υπολογισμούς πάνω σε κάποια αστρονομική παρατήρηση, με μεγέθη από τάξη
10-22 μέχρι 1022, με ζητούμενο τελικό αποτέλεσμα που να δίνει ακρίβεια στο δέκατο τέταρτο
δεκαδικό. Του είπα: "να, προσδιόρισε τί ζητάς στη Lisp, σε μορφή πράξεων ακεραίων με
τελική έξοδο δεκαδικό, και τελείωσες". "Και το πρόγραμμα πού είναι;" μου είπε, ψάχνοντας
για τις συνήθεις σε άλλες γλώσσες, εγγραφές δηλώσεων, συντακτικών συμβόλων, αλλά και
για τον αλγόριθμο εξασφάλισης της ζητούμενης ακρίβειας.

Το βιβλίο αυτό γράφτηκε με την ευκαιρία του μαθήματος "Συναρτησιακός
Προγραμματισμός" του Τμήματος Πληροφορικής ΕΚΠΑ , αλλά έχει απώτερο σκοπό να
δείξει οτι ο υπολογισμός, κατά το νόημα του λ-λογισμού, είναι το μοντέλο που γεφυρώνει τη
Μαθηματική επιστήμη με την Πληροφορική∙ και ακόμα πιο βαθειά, οτι ο υπολογισμός
εκφράζει ένα θεμελιακό τρόπο με τον οποίο ο άνθρωπος συλλογίζεται, εκφράζεται και
επικοινωνεί.
Θέλω να ευχαριστήσω τον καθηγητή μου κ. Γεώργιο Φιλοκύπρου για την πολύτιμη
επιστημονική συμπαράστασή του∙ τον συνάδελφο Δημήτρη Θεοτόκη για τις συζητήσεις ορόσημα που είχαμε, μέσα από τις οποίες ξεκαθαρίστηκαν πολλά πληροφορικά ζητήματα,
και ελπίζω οτι θα συνεχίζονται για πολύ καιρό∙ τον πληροφορικό Ηλία Γυφτοδήμο, για την
επιστημονική βοήθειά του.
Γ. Ε. Γ.
Γενάρης 2000

Περιεχόμενα
ΜΕΡΟΣ Α'
Προκαταρκτικές Εννοιες και Εισαγωγή στη Γλώσσα Lisp
ΜΕΡΟΣ Β'
Συναρτησιακή Δόμηση και Εφαρμογές

Εισαγωγή
Βιβλιογραφία

6
623

Εισαγωγή

Επιχειρώντας να περιγράψει κανείς τί ακριβώς σημαίνει Συναρτησιακός Προγραμματισμός,
μπορεί να δώσει την εξής απλή περιγραφή:
προγραμματισμός εκφρασμένος μέσω συνθέσεων συναρτησιακών εκφράσεων και εφαρμογών τους
Οι έννοιες συναρτησιακή έκφραση και εφαρμογή έρχονται από ένα θεωρητικό μοντέλο, το λλογισμό, το οποίο θέτει το φορμαλισμό του υπολογισμού με τρόπο που "στέκεται δίπλα" στη
Μαθηματική Λογική: πχ. στο μοντέλο της Προτασιακής Λογικής έχουμε τις αρχικές λογικές
πράξεις and , or , not , και με τον κανόνα Modus Ponens κτίζεται ο λογικός συμπερασμός∙ στο λλογισμό έχουμε τρεις κανόνες (α-conversion, β-reduction και η-reduction) μέσω των οποίων
πετυχαίνουμε οποιαδήποτε συναρτησιακή σύνθεση, ακόμα και τη σύνθεση των λογικών πράξεων,
και την πράξη της εφαρμογής μέσω της οποίας πετυχαίνουμε την παραγωγή αποτελέσματος.
Ένα ζήτημα που προκύπτει κατά την ανάπτυξη του Συναρτησιακού μοντέλου, είναι οτι,
προσπαθώντας να αποδώσουμε με κάποιο κατανοητό φορμαλισμό τις υπολογιστικές έννοιες,
βρισκόμαστε μπροστά στην ιδιόμορφη κατάσταση:
να προσπαθούμε να περιγράψουμε ένα μοντέλο υπολογισμού, χρησιμοποιώντας κατ' ανάγκη μια
-ιδεατή ή πραγματική- υπολογιστική γλώσσα έκφρασης, η οποία βέβαια οφείλει να υπακούει σε ένα
μοντέλο υπολογισμού

Η κατάσταση αυτή είναι παρόμοια με αυτή στην οποία βρισκόμαστε όταν προσπαθούμε να
θεμελιώσουμε τη Μαθηματική Λογική: και τότε έχουμε ανάγκη από μια γλώσσα έκφρασης η
οποία βέβαια οφείλει να υπακούει εκ των προτέρων στη Λογική που πρόκειται να αναπτύξουμε.
Από την πλευρά της Λογικής, το ζήτημα αυτό λύνεται με την παραδοχή ενός σαφώς
οριοθετημένου μεταχώρου της Λογικής (χώρος που δεν ορίζεται Λογικά αλλά στον οποίο οφείλει
να υπακούει η Λογική) και μιας ελαχιστοποιημένης μεταγλώσσας (αποδεκτή κατά σύμβαση
επειδή είναι κατανοητή, και έτσι κι αλλιώς δεν μπορούμε να κάνουμε τίποτα άλλο). Από την
πλευρά της χρήσης στην πράξη μιας συναρτησιακής θεωρίας υπολογισμού όπως είναι ο λλογισμός, έχουμε διάφορους δυνατούς δρόμους να ακολουθήσουμε:
– ένας είναι, να θεωρήσουμε ως δεδομένο ένα μοντέλο υπολογισμού που θεωρείται γνωστό
στον αναγνώστη, όπως είναι μια ψευδογλώσσα που περιλαμβάνει ένα μικρό αριθμό από αρχικές
έννοιες (primitives) και βάσει αυτών να κτίσουμε τις συναρτησιακές δομές
– άλλος είναι, είτε να κάνουμε χρήση αυτής καθ' εαυτής της θεμελιακής λειτουργικότητας του
υπολογιστή (πχ. με τη γλώσσα Assembly) και να εκφράσουμε τα συναρτησιακά νοήματα μέσω
τεχνικών υλοποίησής τους
– άλλος είναι, να θέσουμε ορισμένες αρχικές απαιτήσεις, υπολογιστικά υλοποιήσιμες, που να
εκφράζουν τα θεμέλια της θεωρίας, το οποίο στην πράξη σημαίνει να χρησιμοποιήσουμε μια
συναρτησιακή υπολογιστική γλώσσα, και με βάση αυτές να οργανώσουμε τον χώρο του
συναρτησιακού υπολογισμού, με χρήση της γλώσσας αυτής.

Ο πρώτος δρόμος έχει αρκετά προβλήματα, θεωρητικά και πρακτικά:

Δεδομένου οτι, όταν αναφερόμαστε σε μοντέλα υπολογισμού, μιλάμε για ισοδύναμα μοντέλα
(σύμφωνα με τη θέση του Church), σε τελική ανάλυση αυτό που κάνουν οι περιγραφές που
κάνουν αναφορά σε άλλο μοντέλο υπολογισμού, είναι να "μεταφράζουν" το ένα μοντέλο στο
άλλο. Πράγματι, συχνά βλέπουμε σε πολύ σημαντικά εισαγωγικά συγγράμματα, τις
συναρτησιακές έννοιες εκφρασμένες μέσω μιας "Pascal-like" διαδικαστικής ψευδογλώσσας, που
χρησιμοποιείται με ένα και μοναδικό σκοπό: να συνδεθούν οι έννοιες με γνώσεις που ήδη έχει ο
αναγνώστης, γνώσεις που πράγματι είναι απλές, αλλά δεν παύουν να προϋποθέτουν ένα μοντέλο
αναφοράς για τον υπολογισμό. Αυτό εμπεριέχει μια πολύ μεγάλη αδυναμία έκφρασης: επιχειρεί
κανείς να εκφράσει πρωτογενείς έννοιες πλουσιότερες από αυτές που διαθέτει το "διαδικαστικό
λεξιλόγιο", και έτσι αρχίζει μια ολοένα αυξανόμενη εκφραστική πολυπλοκότητα, άνευ λόγου,
και επί πλέον πρέπει να ακολουθήσει χωριστά η φάση υπολογιστικής υλοποίησης.
Ο δεύτερος παρουσιάζει το πρόβλημα οτι, αν και οι δομές υλοποιούνται άμεσα υπολογιστικά, το
αποτέλεσμα δεν είναι εύχρηστο και παραμένει κατάλληλο μόνο για πολύ απλούς πειραματισμούς.
Για τους παραπάνω λόγους, στο παρόν ακολουθούμε τον τρίτο δρόμο:
Προσπαθούμε να δώσουμε την ερμηνεία τού τί σημαίνει Συναρτησιαακός Προγραμματισμός
προσεγγίζοντας απ' ευθείας το μοντέλο του υπολογισμού στο οποίο υπακούει, χωρίς εξάρτηση
από άλλο μοντέλο υπολογισμού που θα υποθέταμε γνωστό. Η προσέγγιση αυτή συνδέει από δύο
όψεις την ιδέα της συναρτησιακής σύνθεσης: κατ' αρχήν με το θεωρητικό μοντέλο του λλογισμού, και στη συνέχεια με τη γλώσσα Lisp. Αναφορές σε άλλα μοντέλα γίνονται σποραδικά,
για λόγους ευκρίνειας, σύγκρισης ή μεταφοράς αντιστοίχων εννοιών.
Κεντρικός στόχος είναι να γεφυρώσουμε το νοηματικό με το φορμαλιστικό επίπεδο,
χρησιμοποιώντας ως γλώσσα έκφρασης την ίδια τη γλώσσα προγραμματισμού.

Προσεγγίσεις του Συναρτησιακού Προγραμματισμού (ΣΠ)

Ο ΣΠ μελετά τη σύνθεση και εκτέλεση αλγορίθμων κάτω από το φως της εφαρμογής
συναρτησιακών εκφράσεων. Αντιμετωπίζουμε τον ΣΠ από διάφορες όψεις όπου τίθενται
ορισμένες αρχές που διασταυρώνονται "ορθογώνια", σε θεωρητικό ή πρακτικό επίπεδο:
– ποιά είναι η τάξη υπολογισμού, δηλ. αν ξεκινά "από μέσα προς τα έξω" ή αντίθετα
– ποιο είναι το θεμελιακό νόημα: η συναρτησιακή έκφραση, πάνω στο οποίο θα κτίζουμε τη
συναρτησιακή σύνθεση και την έννοια της εφαρμογής, ή η εφαρμογή, και μέσω αυτής θα
κτίζουμε τη συναρτησιακή έκφραση και τη σύνθεση
– πώς παρουσιάζεται το μοντέλο: με σταδιακά αυξητικό τρόπο, παράλληλα εμπλουτιζόμενο με
την εμπειρία του μελετητή, ή αντίθετα, κατ' αρχήν με θεωρητικά ολοκληρωμένο τρόπο ο
οποίος στη συνέχεια θα εφαρμόζεται στην πράξη
– πώς εκφράζονται τα νοήματα: με τυπικό φορμαλισμό, με χρήση μιας εμπειρικής
ψευδογλώσσας, ή με απ' ευθείας χρήση της επιλεγόμενης προγραμματιστικής συναρτησιακής
γλώσσας

– ποιός είναι ο ρόλος της αλληλεπίδρασης και πώς αξιοποιείται
– σε τί βαθμό ολοκλήρωσης θα φθάσει η θεωρητική ανάπτυξη και αντίστοιχα η πρακτική.

Η προσέγγιση που ακολουθούμε εδώ, είναι προσαρμοσμένη στις ανάγκες του μαθήματος
"Συναρτησιακός Προγραμματισμός" του Τμήματος Πληροφορικής ΕΚΠΑ αλλά έχει δύο ακόμα
στόχους: πρώτο, να καθιστά δυνατή τη μελέτη χωρίς προγενέστερες γνώσεις Πληροφορικής και
παρ' όλα αυτά να προχωρά σε αρκετά "βαθιά νερά"∙ δεύτερο, να δείξει οτι ο ΣΠ είναι δυνατό να
χρησιμεύσει ως γέφυρα μεταξύ Πληροφορικής και Γνωσιολογίας, αντίθετα με τη συνήθη
πρακτική όπου η Γνωσιολογία χρησιμοποιεί "έτοιμο λογισμικό" (γέφυρα που έγιναν πολυετείς
προσπάθειες να στηθεί με τη χρήση της γλώσσας LOGO, αλλά αποδείχτηκε οτι δεν επαρκεί να
καλύψει όλες τις απαιτήσεις έκφρασης και επίτευξης υπολογισμού).
Στο πλαίσιο αυτό, μελετάμε τον ΣΠ από τις εξής απόψεις:
– θεωρητική θεμελίωση των συναρτησιακών εννοιών και (σε κάποιο μέτρο απλότητας)
αντίστοιχη υπολογιστική υλοποίησή τους
– τρόποι, τεχνικές και κατευθύνσεις αξιοποίησης του ΣΠ
– αντιμετώπιση σε ενιαίο πλαίσιο, τόσο των επιτακτικών όσο και των δηλωτικών εννοιών,
εκφράζοντάς 'τες ομοιόμορφα με φόρμες που περιγράφουν υπολογισμό ή εφαρμόζουν
υπολογισμό
– σύγκριση και σύνδεση του ΣΠ με άλλα προγραμματιστικά μοντέλα, στο βαθμό που κάνει
σαφέστερο το ρόλο του ΣΠ
– μελέτη της βαθύτερης σχέσης "αλληλεπίδρασης" και "συναρτησιακής έκφρασης".

Τα χαρακτηριστικά της Lisp και του ΣΠ

H Lisp είναι μια γλώσσα περιγραφής και εκτέλεσης υπολογισμού εκφρασμένου μέσω
συναρτήσεων και εφαρμογών τους, με εξαιρετικά πλατειές δυνατότητες αφαίρεσης και αντίστοιχα
συγκεκριμενοποίησης. Πρέπει να διευκρινίσουμε εξ αρχής οτι η έννοια της συνάρτησης στα
πλαίσια του ΣΠ είναι σε πολλά διαφορετική από την έννοια της "μαθηματικής συνάρτησης", διότι
είναι δυνατό να εμπεριέχει την έννοια της διαδικασίας, δηλαδή διαδοχής βημάτων υπολογισμού,
και η εφαρμογή συνάρτησης εμπεριέχει την έννοια της διεργασίας.
Τα χαρακτηριστικά της Lisp δίνουν μεγάλη ευελιξία στην έκφραση και τη μοντελοποίηση, μέσω
όρων προσδιορισμού και εφαρμογής υπολογισμού. Ειδικότερα:
α'. Οι όροι της είναι φόρμες, απλές ή σύνθετες. Η σύνθεση αλγόριθμου προσδιορίζεται πάντοτε
σε μορφή διαδοχής ή συσχετισμού από τέτοιες φόρμες, κατ' επέκταση από το μαθηματικό νόημα
της σύνθεσης συναρτήσεων. Η εκτέλεση αλγόριθμου προσδιορίζεται ως εφαρμογή τέτοιων
συναρτησιακών εκφράσεων.
β'. Δεν υπάρχει εντολή της γλώσσας που να μην είναι συνάρτηση. Τα συντακτικά σύμβολα είναι
μόνον οι παρενθέσεις και το κενό διάστημα, και σε ειδικές περιπτώσεις οι εξής χαρακτήρες:

. (τελεία)

, (κόμμα)
τελεία)

` (βαρεία)

# (δίεση)

' (οξεία)
@ ("at")

: (άνω-κάτω

γ'. Ο χρήστης της γλώσσας δεν είναι υποχρεωμένος να υποτάξει τις εκφραζόμενες έννοιες, σε
ένα φορμαλισμό ξένο του νοηματικού χώρου που επεξεργάζεται, όπως συνήθως συμβαίνει με
άλλες γλώσσες.
δ'. Σε κάποιο μέτρο, που θα μελετήσουμε στα επόμενα, ο χρήστης της γλώσσας έχει την
ευχέρεια να οργανώσει τη συμπερασματική συλλογιστική που θα ακολουθήσει, υλοποιώντας το
δικό του λογικό μοντέλο.
ε'. Οι σύνθετες φόρμες είναι διαχειρίσιμες με την ίδια ευκολία που είναι και μια απλή φόρμα∙
αυτό σημαίνει οτι και η πολυπλοκότερη φόρμα (αλγόριθμος ή εφαρμογή αλγόριθμου) μπορεί να
χρησιμοποιηθεί σαν να είναι μια απλή στοιχειώδης έκφραση.
ς'. Ο καθορισμός των υπολογιστικών "οντοτήτων", δηλ. των ολοκληρωμένων και διακριτών
νοημάτων που υλοποιούνται, γίνεται είτε με αναφορά στο όνομά τους είτε με εγκλεισμό μέσα σε
ζεύγη παρενθέσεων της σύνθεσης που τις καθορίζει. Αυτό δίνει απόλυτη σαφήνεια στους
συσχετισμούς: τίποτα δεν "εννοείται", τίποτα δεν εκφράζεται σε "άλλα επίπεδα". Μόνο σε ειδικής
μορφής συναρτήσεις όπως οι macros μπορεί να είναι κρυμμένος κάποιος συσχετισμός, και αυτό
γίνεται επίτηδες για να καλύψει ειδικές και καλά προσδιορισμένες ανάγκες απλοποίησης της
έκφρασης και διαχείρισης του υπολογισμού.
ζ'.
Η δυνατότητα σύνθεσης όσο θέλουμε πολύπλοκων οντοτήτων, μας δίνει την ευκαιρία
υλοποίησης σε μια ενιαία υπολογιστική έκφραση αυτού που νοητικά αντιλαμβανόμαστε ως
ενιαίο νόημα, και αυτό σε όσα επίπεδα απαιτηθούν. Παράλληλα, η δυνατότητα σύνδεσης
οντοτήτων, μας επιτρέπει να αυτονομήσουμε τμήματα υπολογισμού, τόσο για λόγους
απλούστευσης της έκφρασης όσο και επαναχρησιμοποίησης.
η'. Κάθε υπολογισμός αποδίδεται είτε ως σύνθεση συναρτησιακής έκφρασης, είτε ως εφαρμογή
συναρτησιακής έκφρασης πάνω σε ορίσματα∙ δεν υπάρχει ανάγκη (ούτε τρόπος) να
προσδιορίσουμε υπολογισμό διαφορετικά, είτε πρόκειται για συνήθεις διαδικασίες είτε για
μαθηματικές συναρτήσεις είτε για χαμηλού επιπέδου λειτουργίες είτε για οντότητες που
σχετίζονται με χρήση αντικειμένων.
θ'. Ο χρήστης της γλώσσας, μέσω της αλληλεπίδρασης με το χώρο του προγράμματος, έχει την
ευχέρεια σύνθεσης συμβόλων (συναρτήσεων, μεταβλητών) τα οποία μπορεί να χρησιμοποιεί
αδιάκριτα από τα πρωτογενή σύμβολα της γλώσσας. Έτσι είναι δυνατή η απεριόριστη επέκταση
της γλώσσας, και μέσω αυτής η σύνθεση οποιουδήποτε αλγόριθμου, με τον τρόπο που επιθυμεί ο
χρήστης. Η επέκταση αυτή μπορεί να είναι προς διάφορες κατευθύνσεις: "user interface",
υλοποίηση γνώσης, προετοιμασία από πολλούς μικρούς χρήσιμους αλγόριθμους, σύνθεση
πολύπλοκων διεργασιών με σκοπό την πολλαπλή αξιοποίησή τους, από τον ίδιο τον
προγραμματιστή ή τον "επόμενο χρήστη", κά..
ι'. Έχουμε πάντοτε τη βεβαιότητα οτι: αν ο συλλογισμός μας θα μπορούσε να αποδοθεί ως
αλγόριθμος σε κάποια άλλη προγραμματιστική γλώσσα, τότε μπορεί να αποδοθεί και σε Lisp, και
αν θέλουμε, με παρεμφερή εκφραστικό τρόπο (σε κάποια πλαίσια βέβαια).
ια'. Οι αναγκαίες για την έκφραση αλγόριθμου βασικές συναρτήσεις είναι ελάχιστες (όχι πάνω

από 20-30), αλλά η γλώσσα διαθέτει επιπρόσθετα ένα μεγάλο πλήθος ενσωματωμένων συμβόλων
(οι διαθέσιμες συναρτήσεις περνούν τη χιλιάδα, και οι ειδικές μεταβλητές το ίδιο) που
διευκολύνουν και κάνουν αποτελεσματικότερο τον προγραμματισμό.
ιβ'. Εντολές και συναρτήσεις είναι ένα και το αυτό. Ο χρήστης της γλώσσας μπορεί να ορίζει
νέες εντολές, απλώς συνθέτοντας τις διαθέσιμες συναρτήσεις.
ιγ'. Ο τελικός χρήστης, αν δεν εμποδίζεται από την ίδια την κατασκευή του προγράμματος (δηλ.
αν δεν είναι επίτηδες κατασκευασμένο έτσι ώστε να γίνεται αντιληπτό ως ολοκληρωμένη
διεργασία και μόνον) το βλέπει ως σύνολο συναρτήσεων, όπου, από πλευράς χρήσης, ειδικότερες
ομάδες συναρτήσεων είναι δυνατό να παίζουν ρόλο από:
– "λευκό κουτί", με την έννοια οτι στοχεύουν να δώσουν στο χρήστη τη δυνατότητα να
αναγνωρίζει την εσωτερική λειτουργικότητά τους, και κατά συνέπεια να επεμβαίνει αν έχει
κάποια εμπειρία στον ΣΠ
– "γκρίζο κουτί", με την έννοια οτι στοχεύουν να δώσουν στο χρήστη την παραπάνω
δυνατότητα αλλά σε συγκεκριμένα κομβικά σημεία και μόνον, σημεία ιδιαίτερης σημασίας για τη
λειτουργικότητα αλλά και επαρκώς ψηλού επιπέδου ώστε να είναι κατανοητά.

– "μαύρο κουτί", με την έννοια οτι ο χρήστης βλέπει μόνο το λειτουργικό αποτέλεσμα και τις

παραμέτρους του, και αφορά ορισμένες συναρτήσεις που παίζουν ρόλο "ξεκινήματος" εφαρμογής.
Αυτό δεν σημαίνει οτι ο χρήστης δεν έχει την ευχέρεια συνθετικής εκμετάλλευσης αυτών των
συναρτήσεων: απλώς δεν χρειάζεται να γνωρίζει το πώς λειτουργούν εσωτερικά.

ιδ'. Εξετάζοντας μια συνάρτηση κάτω από το πρίσμα του τί εκφράζει ως συλλογισμός, μπορούμε
να την δούμε διττά: i) ως δήλωση συσχετισμών και παραγωγή αποτελέσματος βάσει αυτών
(αποτέλεσμα που προκύπτει σαν λογικό συμπέρασμα), ή ii) ως διαδικαστική περιγραφή των
βημάτων ενός αλγόριθμου (αποτέλεσμα που προκύπτει αν ακολουθήσουμε στα τυφλά τα βήματα
του αλγόριθμου). Αν και ειδικά συνθετικά στοιχεία και προγραμματιστικές προσεγγίσεις
εξυπηρετούν τη μια ή την άλλη άποψη, πάντα είναι δυνατό να εκτιμήσει κανείς και να
αξιοποιήσει συνθετικά μια δοσμένη συνάρτηση ως στοιχείο της μιας ή της άλλης άποψης,
ανεξάρτητα του πώς και γιατί συντέθηκε.
ιε'. Εκτός ακραίων περιπτώσεων, κάθε εφαρμογή συνάρτησης (με τους κατάλληλους
περιορισμούς) είναι δυνατό να εκτιμηθεί από τον χρήστη είτε ως εξειδίκευση αυτής της
συνάρτησης στο ειδικό πλαίσιο των ορισμάτων που δίνει, είτε ως επιτακτική εντολή προς
εκτέλεση. Αυτό εξαρτάται περισσότερο από το νοητικό μοντέλο που έχει σχηματίσει ο χρήσης για
τον επεξεργαζόμενο χώρο και από τον τρόπο που σκέφτεται, παρά από τον φορμαλισμό.
ις'. Οι συναρτήσεις που προσφέρει το πρότυπο της Common Lisp (CL), χωρίς να
διαφοροποιούνται τυπικά, κατανέμονται σε: i) σημασιολογικού επιπέδου, που έχουν σκοπό να
διευκολύνουν την έκφραση και τη σύνθεση (τουλάχιστον κατά την άποψη του κατασκευαστή της
έκδοσης της γλώσσας) και παρέχουν ευρύτερους και ευκολότερους τρόπους αναπαράστασης, και
ii) τεχνικού επιπέδου, που έχουν σκοπό να επιτρέψουν διαχείριση του συστήματος σε
χαμηλότερα επίπεδα, τόσο για καλύτερη εκμετάλλευση των πόρων όσο και για βαθύτερη
διαχείριση των συσχετισμών.
ιζ'. Το υπολογιστικό μοντέλο που ακολουθεί ο ΣΠ και ειδικότερα η Lisp βασίζεται στο λ-

λογισμό, που είναι μια αυστηρά και καλά προσδιορισμένη μαθηματική δομή, η οποία αποδίδει το
νόημα του υπολογισμού με φυσικό για την αντίληψή μας τρόπο. Έτσι, σε αντίθεση με άλλες
γλώσσες προγραμματισμού:
δεν πετυχαίνουμε τον υπολογισμό που θέλουμε ως έμμεσο αποτέλεσμα από αυτά που γράφουμε,
αλλά προσδιορίζουμε ευθέως αυτό που θέλουμε, ως υπολογισμό, και αρκεί γι' αυτό να εκφράσουμε
τη σκέψη μας με όρους σύνθεσης και εφαρμογής συνάρτησης
Μπορεί να δει κανείς την τεράστια διαφορά ανάμεσα στα δύο, απλώς προσπαθώντας να
υλοποιήσει σε διαδικαστική προγραμματιστική γλώσσα έναν αλγόριθμο, του οποίου η εκτέλεση
να παράγει αλγόριθμο, του οποίου η εκτέλεση να παράγει αλγόριθμο, του οποίου η εκτέλεση να
παράγει αλγόριθμο που να δίνει σταθερό αποτέλεσμα, όπως στο εξής πρόβλημα:
Μια συνάρτηση μιας αριθμητικής μεταβλητής, που όταν εφαρμοστεί σε αριθμό n να δώσει ως τιμή
της συνάρτηση μιας αριθμητικής μεταβλητής, που όταν εφαρμοστεί σε αριθμό m να δώσει ως τιμή
της συνάρτηση μιας αριθμητικής μεταβλητής, που όταν εφαρμοστεί σε αριθμό k να δώσει ως
αποτέλεσμα συνάρτηση μιας αριθμητικής μεταβλητής, που όταν εφαρμοστεί σε αριθμό a να δώσει
ως τιμή της την έκφραση (m*k+n) a .

Με το παραπάνω, έχουμε "άπειρες" δυνατότητες: να δούμε τα αποτελέσματα των διαδοχικών
εφαρμογών, ως σταδιακή εξιδίκευση του νοήματος (m*k+n) a , να κατασκευάσουμε αυτόνομες
συναρτήσεις από τα ενδιάμεσα αποτελέσματα, να δούμε κάποιες μεταβλητές ως μεταβλητές της
συνάτησης και άλλες ως παραμέτρους, κλπ.
ιη'. Είναι δυνατό να οργανώσουμε την αλληλεπίδραση σε οποιοδήποτε επίπεδο υπολογισμού,
περιορίζοντας ή αποδεσμεύοντας, στο βαθμό που επιθυμούμε, τις δυνατότητες επέμβασης του
χρήστη στο πρόγραμμα, και φιλτράροντας τα στοιχεία εισόδου που δίνει.

ιθ'. Η Lisp είναι μια γλώσσα που είναι δυνατό να αρχίσει να τη χρησιμοποιεί κανείς αμέσως με
απλή γνώση της συντακτικής μορφής των συμβόλων της και λίγων βασικών συναρτήσεων,
επεκτείνοντας σταδιακά τις γνώσεις του μέσα από την προγραμματιστική εμπειρία, αντίθετα από
άλλες γλώσσες όπου είναι αναγκαίο να εκπαιδευτεί ο χρήστης καλά πριν την ελάχιστη χρήση
τους.

κ'.
Η Lisp μπορεί να χρησιμοποιηθεί πλατύτερα, ως γλώσσα έκφρασης, τόσο λόγω των
συνθετικών δυνατοτήτων που υποστηρίζει όσο και των εννοιολογικά σαφών συναρτήσεων που
προσφέρει (ως πρωτογενείς). Αυτές παρέχουν στον χρήστη την ευχέρεια να εκφράζει
συλλογισμούς απ' ευθείας μέσω της γλώσσας αυτής, χωρίς ανάγκη χρήσης κάποιας ενδιάμεσης
μορφής έκφρασης που θα γεφυρώνει τη σκέψη του χρήστη με την οργάνωση του προγράμματος
(όπως ψευδοκώδικα, δομοδιαγράμματα, λογικά διαγράμματα). Έτσι αξιοποιείται η γλώσσα
ακόμα και στη φάση σχεδιασμού. Η εκφραστική δυνατότητα δεν αφορά μόνο τον
προγραμματιστή αλλά και τον τελικό χρήστη, και μπορεί να υποστηριχθεί με κατάλληλο user
interface.
Με όλα αυτά, ο χρήστης της Lisp έχει περισσότερο την εντύπωση οτι:
χρησιμοποιεί μια νέα φυσική γλώσσα μέσω της οποίας μαθαίνει στον υπολογιστή τί σημαίνουν
ορισμένες έννοιες και πώς προσδιορίζονται κάποιοι υπολογισμοί
παρά οτι χειρίζεται ένα τρόπο με τον οποίο "βάζει τον υπολογιστή να εκτελέσει κάτι".

Δυνατότητες του ΣΠ

Τα παραπάνω χαρακτηριστικά παρέχουν πολλές δυνατότητες, επιτρέποντας στον χρήστη:
α'.
Να κάνει άμεση, εννοιολογικά σαφή και 1:1 συσχέτιση των νοημάτων που
συλλαμβάνει νοητικά, προς τις υπολογιστικές οντότητες (προϋπάρχουσες ή που συνθέτει ο ίδιος).
Αυτό γίνεται σε περισσότερες της μιας φάσεις: κατ' αρχήν, αναγνώριση της σημασίας και του
ρόλου των πρωτογενών στοιχείων της γλώσσας για το νοηματικό χώρο (δηλ. των συμβόλων της
ιδεατής Common Lisp)∙ στη συνέχεια, συνθετική αξιοποίηση αυτών των πρωτογενών στοιχείων,
προς σταδιακή δημιουργία νέων συμβόλων, που αντιστοιχούν σε νοηματικές οντότητες. Τα νέα
σύμβολα χρησιμοποιούνται πλέον αδιάκριτα από τα πρωτογενή.
β'.
Να κάνει κατακερματισμό του ζητήματος που αντιμετωπίζει σε επί μέρους απλούστερα
ζητήματα, εύκολα επιλεγόμενα και σχεδιαζόμενα ώστε να αποτελούν: i) "μικρά και εύκολα"
βήματα προς τον τελικό στόχο, ii) αυτόνομες και ολοκληρωμένες ενότητες, iii) συνθέσιμα
μεταξύ τους τμήματα. Με άλλα λόγια, αποτελεί ιδεώδες περιβάλλον για εφαρμογή της τεχνικής
"διαίρει και βασίλευε".
γ'.
Να αντιμετωπίζει το (κάθε) υπό ανάπτυξη ζήτημα ως ολοκλήρωση (integration) τέτοιων
μικρών ενοτήτων, αξιοποιώντας τα ήδη σχηματισμένα σύμβολα, προερχόμενα από το ίδιο
πρόγραμμα ή άλλο.
δ'.
Να αλληλεπιδρά είτε με το συνολικό χώρο του προγράμματος, "εκτελώντας και
συνθέτοντας", είτε με τον κόσμο που δημιουργείται από κάποια εκτέλεση, είτε με τον πηγαίο
κώδικα, που αναγνωρίζεται ως λεκτική αποτύπωση εννοιών, με τον ίδιο τρόπο που χρησιμοποιεί
κανείς ένα κείμενο.
Μπορούμε να πούμε για την Lisp πως αποτελεί μια γλώσσα μέσω της οποίας αρχίζει κανείς να
εκφράζει και να συνθέτει έννοιες και οντότητες παρόμοια με τον τρόπο που ένα μικρό παιδί κάνει
το ίδιο με τη φυσική γλώσσα: με ένα περιορισμένο σύνολο λέξεων και μια περιορισμένη γνώση
των μηχανισμών έκφρασης, αρχίζει να κάνει σωστούς συλλογισμούς και να επικοινωνεί, και
σταδιακά επεκτείνει τις ικανότητές του για σύνθεση και έκφραση.
Όμως, ο χρήστης της Lisp οφείλει να έχει διαρκώς στο νου του, οτι είναι μια γλώσσα της οποίας
μπορεί πολύ εύκολα να κάνει καλή ή κακή χρήση, από δύο πλευρές: i) ως προς το κατά πόσο η
επίλυση που συνθέτει είναι κατανοητή, επεκτάσιμη, δυναμικά αναλύσιμη και ανασυνθέσιμη, και
ii) ως προς την αλγοριθμική αποτελεσματικότητα των υπολογισμών που συνθέτει. Αυτές οι δύο
πλευρές συχνά θέτουν διαφορετικές προτεραιότητες, διότι η πρώτη εξυπηρετείται καλύτερα από
"ψηλά" επίπεδα προγραμματισμού, ενώ η δεύτερη από "χαμηλά", και οφείλει ο χρήστης της
γλώσσας να βρει τη χρυσή τομή οφέλους έναντι κόστους σε σχέση με τη βαρύτητα κάθε
πλευράς.
Ένα σημείο που πρέπει να προσέξει ο μελετητής, είναι οτι η ορολογία του ΣΠ και της Lisp
μερικές φορές συμπίπτει με την ορολογία άλλων τεχνικών και γλωσσών προγραμματισμού, αλλά
σε κάποιες περιπτώσεις έχει διαφορετικό νόημα.

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

Το μεγάλο πρόβλημα που παρουσιάζεται όταν γίνεται περιγραφή και θεμελίωση του υπολογισμού
μέσω του λ-λογισμού, είναι οτι ο λογισμός αυτός είναι δυσνόητος στην πρώτη μελέτη του, αλλά η
αιτία γι' αυτό είναι περισσότερο οτι αποτελεί "κάτι καινούργιο" για το μελετητή, που συνήθως
πιέζεται χρονικά για να καλύψει την ύλη, παρά στο οτι έχει κάποια δυσκολία∙ στην ουσία είναι
πολύ απλούστερος από άλλες μαθηματικές δομές που διδάσκονται στη δευτεροβάθμια
εκπαίδευση.
Στη βιβλιογραφία που αφορά τον ΣΠ, το πρόβλημα αυτό παρακάμπτεται άλλοτε με το να
αποφεύγονται οι αναφορές στη θεωρία του λ-λογισμού, δίνοντας μια πρακτική ερμηνεία των λεκφράσεων, σε μορφή κάποιας τεχνικής της χρήσης τους μέσω της υπολογιστικής γλώσσας, και
άλλοτε με την ανάπτυξη πρώτα της θεωρίας και μόνο μετά την ολοκλήρωσή της ακολουθούν
κάποια πολύ περιορισμένη αξιοποίησή της. Και στις δύο περιπτώσεις η ανάπτυξη είναι
ανισοβαρής. Για να αντιμετωπίσουμε αυτό το πρόβλημα, προσεγγίζουμε στο παρόν τον λ-λογισμό
και τις συναρτησιακές συνθέσεις "ανεβαίνοντας" τέσσερα, ανεξάρτητα μεταξύ τους,
"σκαλοπάτια":
– περιγράφουμε τις αρχικές βασικές έννοιες του υπολογισμού και του ΣΠ με σχηματικό τρόπο
(τετραγωνίδια και βέλη που τα συνδέουν), με αναφορά στις λ-εκφράσεις μόνο ως προς το
ρόλο που παίζουν
– δίνουμε κάποια σημασιολογικά απλά δείγματα υπολογισμού με βάση τους θεμελιώδεις
κανόνες του λ-λογισμού, με την παραδοχή οτι είναι προσδιορισμένες οι συνήθεις μαθηματικές
πράξεις και συναρτήσεις και συντάσσονται κατά τα γνωστά
– αναπτύσσουμε τις βασικές θεωρητικές αρχές του λ-λογισμού, μέχρι του σημείου που να
επαρκούν για να αντιληφθεί ο μελετητής την έννοια και τον τρόπο σύνθεσης και εφαρμογής
αλγορίθμων, εκφρασμένων αντίστοιχα με λ-εκφράσεις και εφαρμογές λ-εκφράσεων
– αφού αναπτυχθούν οι τεχνικές προγραμματισμού με τη γλώσσα Lisp, ανακαλούμε άμεσα ή
έμμεσα τα προηγούμενα μέσα από τη γλώσσα Lisp και εκμεταλλευόμαστε εκφραστικά και
υπολογιστικά τις λ-εκφράσεις.
Το περιεχόμενο του βιβλίου διαμορφώνεται ως εξής:
– Αρχίζει με τις κεντρικές έννοιες και τα θεμέλια του ΣΠ. Γίνεται μια σύντομη αναφορά στο
λ-λογισμό, τη σχέση του με τον ΣΠ και τις τεχνικές υλοποίησης λ-εκφράσεων με τη Lisp. Στη
συνέχεια επικεντρώνεται στη σημασιολογική και χρηστική διάσταση, περιγράφοντας απλές
τεχνικές υλοποίησης υπολογισμών με τη Lisp. Δίνονται οι βασικές πρωτογενείς συναρτήσεις της
Lisp, πρωτογενείς ή οριζόμενες, και η τεχνική σύνθεσης συνάρτησης, καθώς και οι τυπικές
φόρμες που έχουν στόχο την ευχερέστερη έκφραση συνθέτων διαδικασιών.
– Κατόπιν, εισάγονται οι βασικές συναρτήσεις δημιουργίας συνθέτων οντοτήτων και
παρέμβασης στη δομή των οντοτήτων. Με βάση αυτές, περιγράφονται οι μέθοδοι δημιουργίας

συναρτήσεων ανωτέρου επιπέδου, δηλ. συναρτήσεις που επεξεργάζονται συναρτήσεις, καθώς και η
δυνατότητα θεώρησης και ανάπτυξης αφηρημένων συναρτησιακών δομών.
– Στη συνέχεια, γίνεται αναφορά στις δυνατότητες του ΣΠ για προσανατολισμό στα
αντικείμενα και στους τρόπους υλοποίησής τους με τη Lisp, σε δύο στάδια: πρώτα των
ιεραρχικών δομών, και κατόπιν των κλάσεων στο πλήρες νόημά τους (σύμφωνα με το πρότυπο
του CLOS).
– Τέλος, περιγράφονται κάποιοι αντιπροσωπευτικοί τομείς εφαρμογών του ΣΠ, και αναλύεται
η σημασία τους από τη γνωσιολογική άποψη.
Τα παραδείγματα και οι τεχνικές προγραμματισμού που αναφέρονται, μένουν σε πολύ απλά
επίπεδα, για να είναι αμέσως κατανοητά. Σε επόμενο τεύχος θα δώσουμε αναλυτικά μεγαλύτερες
εφαρμογές, που καλύπτουν προβλήματα του πραγματικού χώρου.
Συνδέοντας τα νοήματα του ΣΠ με τη γλώσσα Lisp και τους πρακτικούς τρόπους υπολογιστικής
υλοποίησής τους, χρησιμοποιούμε τη γλώσσα αυτή όχι μόνο ως γλώσσα προγραμματισμού, δηλ.
υπολογιστικής υλοποίησης των νοημάτων, αλλά και ως γλώσσα έκφρασης για σύνθεση των ίδιων
των νοημάτων. Σε μερικά σημεία που υπάρχει ανάγκη συγκεκριμενοποίησης της ιδεατής CL, με
χρήση κάποιου πραγματικού compiler, γίνεται αναφορά στην Allegro CL, σε δύο εκδόσεις της,
την v.3.0 και την v.5.0.1 , όπου η πρώτη είναι ταχύτερη αλλά περιορισμένων δυνατοτήτων, και η
δεύτερη πολύ πιό επαρκής θεωρητικά και πλούσια στην υποστήριξη αντικειμένων, παραθύρων
και γραφικών.
Ο αναγνώστης που θέλει μια πιο πρακτική εισαγωγή στη Lisp και τον ΣΠ μπορεί να μελετήσει
πρώτα: τα κεφάλαια 2-4 σε πρώτη φάση, τα 5-6 σε δεύτερη και τα 7-9 σε τρίτη. Τα κεφάλαια 1
και 10 είναι ανεξάρτητα των πρακτικών χρήσεων της Lisp.

Εξερεύνηση στο Internet για θέματα σχετικά με ΣΠ
Στο Internet παρέχονται εκτεταμένες πληροφορίες για τις διάφορες εκδόσεις της Lisp που
κυκλοφορούν, καθώς και τα "συμβαίνοντα" γύρω από το Συναρτησιακό Προγραμματισμό και το
λ-λογισμό, καθώς και άλλες συναρτησιακές γλώσσες, όπως την ML (θεμελιακή γλώσσα
συναρτησιακής έκφρασης), την Scheme ("διάλεκτος" της Lisp) και την Haskell (η "νέα θεώρηση
των πραγμάτων στον υπολογισμό", σε πειραματικό στάδιο ακόμα).
Βρίσκει κανείς δωρεάν διαθέσιμες εκδόσεις της Lisp, περιορισμένου μεν χρόνου χρήσης ή
μεγέθους προγράμματος, αλλά επαρκείς για τη μελέτη, όπως:
– την Allegro CL - Lite v.5.1 της Franz Inc., για Windows και για Unix. Bρίσκεται στη
διεύθυνση www.franz.com
– την Free Lisp (Lite version), για Windows, εύχρηστη και σύντομη, χωρίς αυστηρότητα
απέναντι στο χρήστη, ιδανική για παραδείγματα, για περιορισμένης έκτασης εφαρμογές
– την CLisp, σε έκδοση για DOS και για Unix, κά.
Ιδιαίτερα σημαντικό είναι οτι διατίθενται στο δίκτυο αλληλεπιδρώντα μαθήματα (tutorials) για τη
Lisp, πολύ εντυπωσιακά από πλευράς λειτουργικότητας και αλληλεπίδρασης, αν και απλά

εισαγωγικά, όπως στη διεύθυνση:
http://www.cs.tulane.edu/www/Villamil/lisp/lisp1.html "LISPTUTOR"
καθώς και ένα εισαγωγικό μάθημα στη διεύθυνση: http://www.apl.jhu.edu/~hall/lisp.html "An
Introduction and Tutorial for Common Lisp"
Πολύ ενδιαφέρων είναι και ο BNF κώδικας του πυρήνα της Lisp, που σε λίγες γραμμές
προσδιορίζει αυστηρά το ίδιο το νόημα του υπολογισμού, στη διεύθυνση:
http://cuiwww.unige.ch/db-research/Enseignement/analyseinfo/LISP/BNFindex.html
Μια πλήρης ιστορία της Lisp δίνεται στη διεύθυνση:
http://www8.informatik.uni-erlangen.de/html/lisp-enter.html "Lisp History"
Το νεότερο τυπικό πρότυπο της Common Lisp (ANSI-standard) δίνεται στη διεύθυνση:
http://www.stat.ucla.edu/develop/lisp/common/docs/ansi/index.html
Και, πάνω απ' όλα, είναι πρωταρχικής σημασίας μια επίσκεψη της προσωπικής web σελίδας του
δημιουργού της Lisp, John McCarthy, στη διεύθυνση:
http://www-formal.stanford.edu/jmc/index.html
Απ' όπου και να ξεκινήσει κανείς, έχει την ευκαιρία μέσω "links" να ακολουθήσει μεγάλες
διαδρομές σχετικά με τον ΣΠ, και να εντοπίσει άμεσα ή έμμεσα: γεγονότα, συνέδρια, άρθρα,
βιβλία, λίστες συζητήσεων, μαθήματα από το διαδίκτυο, πληροφορίες για Πανεπιστημιακά
μαθήματα, προπτυχιακές και μεταπτυχιακές σπουδές που σχετίζονται με το χώρο του
Συναρτησιακού Προγραμματισμού.

ΜΕΡΟΣ Α
Προκαταρκτικές Εννοιες
και
Εισαγωγή στη Γλώσσα Lisp

16

1 Γενικά Στοιχεία για τον ΣΠ

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

Αυτό σημαίνει: i) δεν έχουμε ανάγκη να εκφράζουμε τους αλγόριθμους σε μια ενδιάμεση
γλώσσα με την οποία να αποδίδουμε τις έννοιες που συλλαμβάνουμε (όπως ψευδοκώδικα, λογικά
διαγράμματα, δομοδιαγράμματα) και μετά να τη μεταφράζουμε σε μια άλλη γλώσσα, την
υπολογιστική∙ ii) ξεκινάμε τη συγγραφή κώδικα παράλληλα με την καταγραφή της λύσης του
προβλήματος, διαμορφώνοντας σταδιακά, και κυρίως "εν δυνάμει" τους αλγόριθμους, με την
έννοια οτι ξεκινάμε την υλοποίηση από μια κεντρική ιδέα και συγκεκριμενοποιούμε ή
διορθώνουμε τις λεπτομέρειες κατά την πορεία (αυτή είναι η εξελικτική προσέγγιση στον
προγραμματισμό).
Με λίγα λόγια, οτιδήποτε εκφράσουμε τοποθετείται κάτω από το στόχο:
να δώσουμε στο χρήστη δυνατότητες να αναδιαμορφώνει εξελικτικά τις εκφράσεις που έχουν ήδη
υλοποιηθεί και βάσει αυτών να διαμορφώνει νέες
17

1.1 Συγκριτική τοποθέτηση του ΣΠ
Στην ενότητα αυτή θα δώσουμε μερικά εισαγωγικά στοιχεία και θα θέσουμε ορισμένα
προβλήματα που, όπως θα δούμε, δείχνουν το εύρος των θεμάτων που μπορούμε να
αντιμετωπίσουμε. Σημειώνουμε οτι ο όρος "συνάρτηση" είναι συνώνυμος των: "αλγόριθμος",
"διαδικασία"∙ αντίστοιχα, ο όρος "εφαρμογή συνάρτησης" είναι συνώνυμος των: "εκτέλεση
αλγόριθμου", "εφαρμογή αλγόριθμου", "διεργασία" (με κάποιες ειδικότερες νοηματικές ή τεχνικές
διαφορές, που θα δούμε).

1.1.1 Γιατί να χρησιμοποιήσουμε ΣΠ ;
Σε ένα άρθρο του, ο D. W. Rasmus (Object Magazine, June 1996) τοποθετεί την αξία του
Συναρτησιακού Προγραμματισμού με χρήση της Lisp και ειδικότερα του CLOS, στην
υποστήριξη από τα ακόλουθα: i) του γνωστικού έργου του χρήστη μέσω της νοήμονος
συμπεριφοράς και ολοκλήρωσης (integration) του προγράμματος, ii) της μάθησης της μηχανής,
iii) της υλοποίησης και παρακολούθησης μεταβολών, iv) της επαναχρησιμοποίησης, και v) των
αναπαραστάσεων που επιτρέπουν οι συμβολικές εφαρμογές. Οι Sinclair και Moone, στο άρθρο
τους "The Philosophy of Lisp" (CACM, Sept.1991) δίνουν μια ολοκληρωμένη άποψη των
δυνατοτήτων της Lisp συνδυάζοντάς 'τες με αξιολόγηση των προγραμματιστικών στόχων.
Σε όλη την έκταση του παρόντος βιβλίου, άμεσα ή έμμεσα δείχνουμε αυτές τις δυνατότητες και
τους στόχους που εξυπηρετούν. Ως παράδειγμα, τα παρακάτω θέματα είναι πολύ εύκολο να
αντιμετωπιστούν συναρτησιακά, αλλά θα ήταν πολύ δύσκολο να αντιμετωπιστούν με τις συνήθεις
διαδικαστικές γλώσσες, ακόμα και από πολύ έμπειρους προγραμματιστές.
α'. Να δώσουμε τιμή σε μεταβλητή, έστω a, κάποια έκφραση που χρησιμοποιεί άλλες μεταβλητές,
έστω b και c, και η a να μπορεί να παρακολουθεί τις μεταβολές που εκ των υστέρων υφίστανται
οι b και c , με τρόπο ώστε μέσω της μεταβλητής a να μπορούμε είτε να καλέσουμε την έκφραση τιμή αυτούσια είτε το αποτέλεσμα του υπολογισμού της. Γενικότερα, να προσδιορίσουμε αλυσίδα
εξαρτήσεων που να παρακολουθεί τις μεταβολές των τιμών των αναφερομένων μεταβλητών σε
οποιοδήποτε βάθος.
β'. Να ορίσουμε συνάρτηση της οποίας οι μεταβλητές να εφαρμόζονται σε συναρτήσεις, χωρίς να
γνωρίζουμε εκ των προτέρων ποιες. Γενικότερα, να χρησιμοποιούμε, σε φόρμες υπολογισμού,
μεταβλητές που να παίρνουν ως τιμή μια συνάρτηση άγνωστη στην φάση προσδιορισμού της
φόρμας. Ακόμα γενικότερα, να αναφερθεί ο υπολογισμός σε άγνωστη συνάρτηση η οποία να καλεί
συνάρτηση επίσης άγνωστη στη φάση προσδιορισμού της αναφοράς αυτής, και αυτό σε όσα
βήματα σύνθεσης θέλουμε.
γ'. Να ορίσουμε συνάρτηση της οποίας η εφαρμογή να εξειδικεύει / προσαρμόζει τη
συμπεριφορά της, ανάλογα με το είδος των τιμών στις οποίες εφαρμόζεται, ενώ η εξειδίκευση /
προσαρμογή αυτή να μπορεί να επεκτείνεται δυναμικά, όσο λαμβάνουμε νέες πληροφορίες ή
σχηματίζουμε νέες ιδέες σχετικά με τα είδη (τύπους) των τιμών.
18

δ'. Να δώσουμε συνάρτηση της οποίας η εφαρμογή να προκαλεί μερική συγκεκριμενοποίησή
της, επιστρέφοντας πάλι συνάρτηση που να αποτελεί εξειδίκευση της αρχικής. Και ακόμα, η
εφαρμογή να οδηγεί σε συνάρτηση περισσοτέρων μεταβλητών από την αρχική, που σημαίνει, να
πετύχουμε γενίκευση μέσω της εξειδίκευσης.
ε'. Να δώσουμε μεθοδολογία που να επιτρέπει τον έλεγχο του πότε θα εκτελεστεί ένας
προκαθορισμένος υπολογισμός στα πλαίσια ενός ευρύτερου υπολογισμού, και γενικότερα να μας
επιτρέπει να αποφασίζουμε κατά την εκτέλεση ποιά τμήματα του υπολογισμού θα εκτελεστούν.
ς'. Να δώσουμε συνάρτηση που να μας επιτρέπει να αλλάξουμε τον ορισμό μιας περιεχόμενης
διαδικασίας, κατά τη φάση εκτέλεσής της. Γενικότερα, να αφήσουμε σε εκκρεμότητα τον ορισμό
διαδικασίας Α στη φάση εκτέλεσης διαδικασίας Β που χρησιμοποιεί την Α, μέχρι τη στιγμή που θα
κληθεί η Α.
ζ'. Να ορίσουμε συνάρτηση που ο υπολογισμός της να δίνει ως αποτέλεσμα συνάρτηση, και
γενικότερα, συνάρτηση που ο υπολογισμός της να δίνει ως αποτέλεσμα συνάρτηση η οποία, σε
εφαρμογή της, να δίνει ξανά συνάρτηση, και αυτό, αν θέλουμε, απεριόριστα.
η'. Να δώσουμε ένα σύνολο από διαδικασίες ως τιμή μεταβλητής, εξαρτώντας το ποια ή ποιες
θα εκτελεστούν από δεδομένα, είτε του περιβάλλοντος της μεταβλητής είτε εσωτερικά της
μεταβλητής.
θ'. Να ορίσουμε σύμβολο που να περιέχει έναν αλγόριθμο ταυτόχρονα σε μορφή πηγαίου
κώδικα και εκτελέσιμου κώδικα, και αν θέλουμε επί πλέον το σύμβολο να "θυμάται" τα
αποτελέσματα που έχουν προκύψει από τις εκτελέσεις του αλγόριθμου.
ι'. Να δώσουμε ως είσοδο ανάγνωσης κατά τη φάση εκτέλεσης διαδικασίας, μια ολόκληρη
φόρμα υπολογισμού ή μια νέα συνάρτηση που θα χρησιμοποιηθεί στα πλαίσια του ίδιου
υπολογισμού. Γενικότερα, να δώσουμε ως είσοδο μια συναρτησιακή έκφραση και επίσης ως
είσοδο ανάγνωσης τα ορίσματα στα οποία θα εφαρμοστεί.
ια'. Να ορίσουμε αντίγραφα καταστάσεων που να παρακολουθούν ή να μη παρακολουθούν την
εξέλιξη των πρωτοτύπων τους, όπου ως κατάσταση να εννοούμε είτε ένα απλό σύνολο
μεταβλητών και αντίστοιχων τιμών είτε, μαζί, και ένα σύνολο διαδικασιών, οι οποίες να είναι
δυνατό να υφίστανται εξελικτικές μεταβολές.
ιβ'. Να προσδιορίσουμε διεργασίες που να εκτελούνται "παράλληλα", επικοινωνώντας μεταξύ
τους κατά την πορεία της εκτέλεσής τους, και αυτό χωρίς να χάνει ο χρήστης, κατά τη διάρκεια
της εκτέλεσης, τη δυνατότητα χρήσης της γλώσσας, για επικοινωνία με τον υλοποιημένο χώρο
γνώσης ή ακόμα και και για άσχετα προ το πρόγραμμα θέματα.

Σε γλώσσες διαδικαστικού προγραμματισμού (Pascal, C, C++ ...) η απάντηση σε ερωτήματα
όπως τα παραπάνω, όταν δεν είναι πρακτικά αδύνατη, περνάει μέσα από τη συγγραφή από
πολύπλοκα προγράμματα, και το κυριότερο, εκφρασμένα με τρόπο μακρυά από αυτόν με τον
οποίο θα σκεφτόταν ο οποιοσδήποτε επιλυτής του προβλήματος. Όμως, αυτό που θα περίμενε
κανείς από ένα ιδεατό προγραμματιστικό περιβάλλον, και αυτό προσπαθούμε να πετύχουμε με
τον ΣΠ, είναι:
τα νοήματα σε προβλήματα όπως τα παραπάνω είναι απλά∙ γιατί να μην είναι το ίδιο
απλός ο τρόπος υπολογιστικής αντιμετώπισής τους;
19

Αυτός ο στόχος τίθεται συχνά στην Πληροφορική: αποτελεί την κεντρική επιδίωξη των εργαλείων
ανάπτυξης (authorware), όπου πετυχαίνουμε εύκολα την υπολογιστική υλοποίηση από
πολυσύνθετα νοήματα, θυσιάζοντας όμως την ευρύτητα των επιδιώξεων∙ πάντοτε είναι
περιορισμένα σε συγκεκριμένες δρομολογήσεις, που δεν επιτρέπουν παρεκκλίσεις, όπου το
"επιθυμητό" περιορίζεται από το "εφικτό", και το "πραγματικά αποτελεσματικό" το πετυχαίνουμε
με περιορισμό των επιδιώξεών μας στις δυνατότητες του εργαλείου.

1.1.2 Ο ΣΠ σε σχέση με άλλα είδη προγραμματισμού
Ο ΣΠ ως Συμβολικός Προγραμματισμός και ως Αλληλεπιδρών Προγραμματισμός

O ΣΠ κατατάσσεται στον λεγόμενο Συμβολικό Προγραμματισμό (symbolic programming), όπου
οι υπολογιστικές οντότητες είναι σύμβολα ή συναρτησιακές συνθέσεις συμβόλων. Τα σύμβολα
εκφράζουν νοήματα συναρτησιακής σύνθεσης και εφαρμογής κατά το μοντέλο του λ-λογισμού.

Σε σύγκριση με τον Αριθμητικό Προγραμματισμό, ο ΣΠ είναι πολύ πλουσιότερος διότι δέχεται
μέσα σε υπολογισμό, τον συσχετισμό οντοτήτων που δεν είναι καταληκτικές (σταθερές), και
δεν τίθεται υποχρέωση προσδιορισμού αποκλειστικά υπολογισμών οι οποίοι, εκτελούμενοι, να
δίνουν καταληκτική τιμή. Στον ΣΠ μπορούμε να χρησιμοποιούμε μέσα σε λογικές εκφράσεις (ή
και κατ' ευθείαν ως λογικές εκφράσεις) σύμβολα που εκφράζουν συναρτησιακές σχέσεις ή
υπολογισμό. Η αντιπαράθεση αυτών των δύο ειδών προγραμματισμού αναφέρεται συχνά στη
βιβλιογραφία, και οφείλεται ιστορικά στην περίοδο όπου αναπτύχθηκαν τα δύο είδη, στη
δεκαετία του '60, όπου κυριαρχούσε ο Αριθμητικός Προγραμματισμός με τη Fortran.

Σε σύγκριση με τον Διαδικαστικό Προγραμματισμό, ο ΣΠ έχει το πλεονέκτημα αφ' ενός της
ενιαίας έκφρασης για οτιδήποτε, οσοδήποτε σύνθετο και αν είναι, χωρίς διαφοροποίηση
δηλώσεων, ορισμών, κλήσεων, και αφ' ετέρου της αφαίρεσης στο επιθυμητό επίπεδο. Η έκφραση
λύσης προβλήματος στον ΣΠ δεν χρειάζεται να περιγραφεί με βήματα διαταγών προς τον
υπολογιστή, αλλά με βήματα εξάρτησης και συσχετισμού, που συνθέτουν επί μέρους
αλγόριθμους και από αυτούς τελικά συνθέτουμε τον αλγόριθμο της λύσης. Έτσι είναι πολύ
ευκολότερο να αποδώσουμε υπολογιστικά τον "φυσικό τρόπο" επίλυσης, δηλ. αυτόν που
εφαρμόζεται στον πραγματικό χώρο όπου ανήκει το πρόβλημα. Αυτό δεν αποκλείει την ανάπτυξη
καθαρά επιτακτικού κώδικα, τοπικά ή συνολικά στις συναρτησιακές δομές.
Αν και κάποιοι συγγραφείς τοποθετούν τον ΣΠ στα πλαίσια του Διαδικαστικού
Προγραμματισμού, αποτελεί ιδιαίτερο και αυτόνομο κλάδο για τους εξής λόγους:

α'.
Η φιλοσοφία του και η εφαρμογή της για τη θέση, συμβολική περιγραφή και επίλυση
πραγματικών προβλημάτων είναι φορμαλιστικά και νοηματικά διαφορετική από αυτή των άλλων
ειδών προγραμματισμού. Μια διάσταση στην οποία δίνεται όλο και περισσότερη βαρύτητα, είναι
η αλληλεπίδραση: ο ΣΠ είναι κατά βάση αλληλεπιδρών προγραμματισμός, αποτελώντας ένα
από τα δύο βασικά σκέλη του (το δεύτερο είναι ο Λογικός Προγραμματισμός).
β'.
Οι συναρτησιακές συνθέσεις, με την ομοιομορφία έκφρασής τους, παρέχουν στο χρήστη
τη δυνατότητα να τις ερμηνεύσει όπως θέλει, ανάλογα με την περίσταση: ως περιγραφές
προστακτικών μεθόδων (: "κάνε αυτό…"), ως δηλωτικές περιγραφές καταστάσεων (: "ισχύει
20

αυτό…"), ως συναρτησιακές περιγραφές με περισσότερο βάρος προς τη διαδικαστική ή τη
δηλωτική κατεύθυνση (: "υπολόγισε αυτό… βάσει των εξής γνώσεων…"), ως συναρτησιακές
περιγραφές κόσμων αντικειμένων (: "η δομή και οι μέθοδοί της έχουν ως εξής… και βάσει αυτών
υπολόγισε αυτό…"), και όλα αυτά με έναν απλό φορμαλισμό.
γ'.
Η ανάπτυξη του προγράμματος μπορεί, όχι μόνο να πάρει την κατεύθυνση και το νόημα
που διευκολύνει την επίτευξη των στόχων, αλλά και να ακολουθήσει κατά την πορεία άλλες
μεθοδεύσεις προγραμματισμού, προσωρινά ή οριστικά (πχ. μεταστροφή σε λογικό, διαδικαστικό,
ή προγραμματισμό προσανατολισμένο στα αντικείμενα).

δ'.
Το τυπικό υπόβαθρο (μοντέλο) του ΣΠ, ο λ-λογισμός, είναι άμεσα ορατό, και ακόμα
περισσότερο, είναι προγραμματιστικά χρησιμοποιήσιμο. Το "χρησιμοποιήσιμο" είναι
ουσιαστικό, σε σύγκριση με άλλα τυπικά μοντέλα υπολογισμού. Δεδομένου οτι ο λ-λογισμός
μπορεί να αποτελέσει τη θεωρητική βάση έκφρασης για οποιοδήποτε πληροφορικό νόημα, ως
ισοδύναμος της αφηρημένης μηχανής Turing, είναι προφανές πως διαθέτουμε ένα τρόπο
υπολογιστικής υλοποίησης κάθε πληροφορικής έννοιας και υπολογισμού. Το υπόβαθρο αυτό
είναι θεωρητικά επαρκές και πρακτικά εύχρηστο για τη συμβολική έκφραση όσο θέλουμε
συνθέτων νοημάτων. Σε σύγκριση, η ιδέα "να γράψουμε τον κώδικα σε μορφή Μηχανής Turing"
εκτός του οτι αυτό είναι δυσχερέστατο, μόνο σε Assembly θα ήταν "αρκετά κοντά" να
υλοποιηθεί.

ε'.
Μέσω του ΣΠ μπορεί να υποστηριχθεί, έστω και με κατάλληλη ανάπτυξη κώδικα, όχι
μόνο οποιαδήποτε υπολογιστική κατεύθυνση ή τεχνική, αλλά και οποιοσδήποτε τρόπος
συλλογιστικής:
– συμπερασματική κίνηση και αιτιολόγηση προς τα εμπρός (forward reasoning)
– συμπερασματική κίνηση και αιτιολόγηση προς τα πίσω (backward reasoning)
– κάθε είδους μονότονη συλλογιστική (monotonic reasoning), ακόμα και κάτω από πλειότιμη
λογική (multi-valued logic)
– κάθε είδους μη μονότονη συλλογιστική (non monotonic reasoning), όπως πιθανοθεωρητική
συλλογιστική (probabilistic reasoning), ασαφής λογική (fuzzy logic)
– και όλα αυτά, σε προτασιακό ή κατηγορηματικό λογικό πλαίσιο

Η συναρτησιακή δόμηση, από το ιδεατό μοντέλο ως την πράξη
Αυτό που πρέπει να έχει υπ' όψη του ο μελετητής του ΣΠ, και θα το διαπιστώσει σε πολλά σημεία
με τη Lisp, είναι οτι ακόμα και οι γλώσσες που είναι ειδικά κατασκευασμένες για συναρτησιακή
ανάπτυξη προγράμματος, υποχρεώνουν το χρήστη να ακολουθεί πιο συγκεκριμένες μεθοδεύσεις
απ' όσο θα περίμενε στηριγμένος μόνο στο θεωρητικό μοντέλο του λ-λογισμού. Συχνά είναι
πρακτικότερο να κινηθεί διαδικαστικά, όχι εξ αιτίας του θεωρητικού μοντέλου αλλά εξ αιτίας της
δαιδαλώδους διαδρομής όπου οδηγεί η συναρτησιακή εξάρτηση εννοιών. Από την άλλη πλευρά
όμως, του προσφέρουν ένα πολύ πιο πλούσιο χώρο από το τυπικό μοντέλο υπολογισμού, με
προετοιμασμένο έδαφος, σε μορφή από πολλές εκατοντάδες ή και χιλιάδες έτοιμες συναρτήσεις.
Οι συνηθέστερες δεσμεύσεις που πρακτικά επιβάλλουν οι διαδικαστικές γλώσσες
προγραμματισμού σε σχέση με το τυπικό συναρτησιακό μοντέλο υπολογισμού είναι:
– εφαρμογή συγκεκριμένης τάξης υπολογισμού (διαδοχή εκτέλεσης) την οποία ακολουθεί η
21

γλώσσα προγραμματισμού (συνήθως την κανονική, δηλ. "από μέσα προς τα έξω")
– να απαιτείται προκαθορισμός του τύπου κάθε μεταβλητής
– να υπακούει σε περιορισμούς ως προς την είσοδο του υπολογισμού αλλά και ως προς την
κατάληξή του (πχ. να έχει νόημα μόνο αν καταλήγει σε σταθερά)
– η μεταβλητή κλήση υπολογισμού είτε να είναι αδύνατη είτε να πετυχαίνεται με
τεχνικάπολυσύνθετες μεθοδεύσεις.
Αντίστοιχα, οι ελευθερίες που συνήθως προσφέρουν στον χρήστη οι συναρτησιακές γλώσσες
είναι πολλές, όπως να μπορεί:
– να χρησιμοποιεί πακέτα συναρτήσεων - βιβλιοθήκες απ' όπου δυναμικά να επιλέγει τις
συναρτήσεις που χρειάζεται, είτε για την επέκταση απ' ευθείας του προγράμματός του είτε για
την διαμόρφωση ενός προγραμματιστικού περιβάλλοντος
– να δημιουργεί πρόγραμμα που να μένει ανοικτό για περαιτέρω επέκταση / διαμόρφωση,
ακόμα και από τον τελικό χρήστη
Ο Συναρτησιακός Προγραμματισμός, βαισμένος στο θεωρητικό μοντέλο του λ-λογισμού και
υλοποιημένος με τη γλώσσα Lisp, παρέχει τις εξής ουσιαστικές δυνατότητες:
– να υπερκαλύπτει (άμεσα ή με ανάπτυξη κατάλληλου κώδικα) όλα τα χρησιμοποιούμενα
σήμερα είδη προγραμματισμού, όπως διαδικαστικό, λογικό, προσανατολισμένο στα
αντικείμενα, δηλωτικό, σχεσιακό
– να υλοποιούνται υπολογιστικά με πολύ αποτελεσματικό τρόπο και απ' ευθείας οι θεωρητικά
εκφραζόμενες σχέσεις∙ μπορούμε να δούμε την αποτελεσματικότητα και από την άποψη της
ευχέρειας στη σύνθεση αλγορίθμων, τόσο λόγω των απλών κανόνων σύνθεσης όσο και της
γενικότητας και του πλούτου των πρωτογενών συναρτήσεων, αλλά και από την άποψη της
εκτέλεσης, λόγω της ταχύτητας των σχετικών compilers.
Έχει όμως και κάποια πρακτικά μειονεκτήματα:
– Αν και η σύνθεση συναρτησιακού προγράμματος που αποδίδει μια λύση προβλήματος είναι
απλή στη σύλληψη και απαιτεί ελάχιστη προγραμματιστική εμπειρία, εν τούτοις αν χρειαστεί
να κατέβουμε χαμηλότερα "προς τη μηχανή", τα πράγματα δυσκολεύουν πάρα πολύ, σε ότι
αφορά την έκφραση. Έτσι, ανάμεσα στην εκφραστικά απλή υλοποίηση και την
αποτελεσματική εκμετάλλευση των υπολογιστικών πόρων υπάρχει κάποια απόσταση, που
προς το παρόν καλύπτεται μόνο με την απόκτηση εμπειρίας.
– Η πολυπλοκότητα των συναρτησιακών συνθέσεων μπορεί να φθάσει σε πολύ μεγάλα βάθη,
χωρίς να χάνεται η υπολογιστική αποτελεσματικότητα αν τηρηθούν ορισμένοι κανόνες. Αλλά
η ανθρώπινη νόηση μπορεί να συλλάβει και να επεξεργαστεί άμεσα ένα σχετικά μικρό αριθμό
εννοιών, οπότε ο μη έμπειρος προγραμματιστής δύσκολα μπορεί να σχηματίσει μια εν των
προτέρων καθολική αντίληψη των συναρτήσεων που θα ήταν δυνατό να συνθέσει, ή
αντίστοιχα όταν διαβάζει ένα σύνθετο πηγαίο κώδικα.
– Οι δυνατότητες του ΣΠ είναι διαφορετικής κατεύθυνσης από του Διαδικαστικού (ΔΠ) και
δεν συγκρίνονται "1:1" . Γι' αυτό ο έμπειρος σε ΔΠ προγραμματιστής δύσκολα περνά στην
πλευρά του ΣΠ ώστε να εκμεταλλευτεί αυτές τις δυνατότητες (συνηθισμένο είναι να
προσπαθεί να αποδώσει σε Lisp αυτούσιο το διαδικαστικό κώδικα που συλλαμβάνει και
συνθέτει εκ πείρας, κάτι που δεν βλάπτει μεν αλλά δεν έχει λόγο να το κάνει).
22

– Η υπολογιστική υλοποίηση σχέσεων σε μορφή συναρτησιακών εκφράσεων και εφαρμογών
τους, δεν είναι πάντα ευχερής: μόνο επιλυμένες μορφές σχέσεων είναι δυνατό να αποδοθούν,
και κατά συνέπεια πρέπει κάθε σχέση να περιγράφεται ως ένα σύνολο κατάλληλα επιλυμένων
σχέσεων ώστε να είναι αποδεκτές στη Lisp, κάτι που είναι αρκετά δυσχερές στην πράξη, στην
περίπτωση συνθέτων κατηγορηματικών σχέσεων.

1.2 Μια πρώτη προσέγγιση των συναρτησιακών εννοιών
Στην ενότητα αυτή, δίνουμε μια σχηματική αναπαράσταση των βασικών συναρτησιακών εννοιών,
ακολουθώντας μια σημειολογία που συναντάται συχνά. Είναι βοηθητική και μπορεί ο μελετητής
να την παρακάμψει.
Πρόκειται για μια άτυπη (informal) περιγραφή του ρόλου και των τρόπων χρήσης των βασικών
εννοιών του ΣΠ, χρησιμοποιώντας δύο "δομικά υλικά", το τετραγωνίδιο και το βέλος που συνδέει
τετραγωνίδια.
Στη συνέχεια θα δούμε έναν άτυπο αλλά κατανοητό τρόπο περιγραφής του νοήματος της λέκφρασης και του υπολογισμού που επιτελείται μέσω εφαρμογής λ-εκφράσεων.

1.2.1 Σχηματική περιγραφή συναρτησιακών συνθέσεων

Μια συναρτησιακή οντότητα είναι είτε άτομο είτε σύνθετη οντότητα. Ατομο είναι είτε σταθερά
είτε σύμβολο. Σύνθετη οντότητα είναι μια κατασκευή που συντίθεται από απλούστερες (άτομα ή
σύνθετες) μέσω ενός βασικού μηχανισμού σύνθεσης, του κατασκευαστή (constructor), που θα
δούμε.
Δεχόμαστε οτι οι οντότητες μπορούν να περιέχουν εσωτερικές μεταβλητές, τις λεγόμενες λμεταβλητές, οι οποίες συσχετίζονται μέσω μιας ή περισσοτέρων υπολογίσιμων πράξεων, και ο
συσχετισμός αυτός αποτελεί το σώμα υπολογισμού.
Ας παραστήσουμε ένα σύμβολο με όνομα a , με ένα τετραγωνίδιο:

ή ταυτόσημα:

Η "κλήση" ή "υπολογισμός" του συμβόλου a έχει το νόημα αναφοράς στο περιεχόμενό του (δηλ.
όταν καλέσουμε το a, στη θέση του νοείται το περιεχόμενό του).
Μια πρωτογενής έννοια είναι η σύνδεση δύο οντοτήτων. Ας παραστήσουμε μια οντότητα η οποία
παριστά τη σύνδεση δύο οντοτήτων, έστω των a και b , με ένα τετραγωνίδιο που έχει δύο μέρη,
όπου το πρώτο έχει περιεχόμενο a και το δεύτερο έχει περιεχόμενο b :
23

Τη σύνδεση αυτή αποκαλούμε ζεύγος ή cons (από το construction). Στο παραπάνω ζεύγος, το a
αποτελεί την "κεφαλή" της σύνδεσης και το αποκαλούμε "car" ή "first", και το b αποτελεί την
"ουρά" της σύνδεσης, και το αποκαλούμε "cdr" ή "rest".
Το ζεύγος, ως οντότητα και αυτό, μπορεί να ονομαστεί:

Αποκαλούμε κατασκευαστή, και έχει όνομα cons, την πρωτογενή πράξη που δέχεται δύο
ορίσματα και δημιουργεί το ζεύγος τους. Είναι η θεμελιώδης συνάρτηση του ΣΠ, και θα τη
μελετήσουμε διεξοδικά.
Ένα ειδικό σύμβολο είναι το "κενό", που θα το παριστούμε με ένα τετραγωνίδιο με διαγώνιο:
Αυτό το σύμβολο, το αποκαλούμε κενή λίστα, και συμβατικά θεωρούμε οτι το περιεχόμενο του
κενού είναι το κενό (δηλ. ως σχήμα, το ορθογώνιο με διαγώνιο και η διαγώνιος είναι ένα και το
αυτό). Παριστάνεται με το όνομα nil ή συμβολικά με: ( ) .
Το ζεύγος με κεφαλή a και ουρά το κενό, το αποκαλούμε λίστα με ένα στοιχείο, το a :

Παριστάνουμε αυτή τη λίστα με (a) .
Κατασκευάζοντας ξανά ζεύγος, από νέα οντότητα, έστω d , με ήδη υφιστάμενο ζεύγος, έστω το (a
. b) , πετυχαίνουμε να αποδώσουμε το νόημα της τριάδας:

Επαναλαμβάνοντας τέτοιες συνδέσεις, αποδίδουμε το νόημα της τετράδας, πεντάδας, κοκ.
Αν ειδικά το δεύτερο μέλος ενός ζεύγους είναι λίστα με ένα στοιχείο, έστω b , τη σύνδεση που
προκύπτει την αποκαλούμε λίστα δύο στοιχείων, που τη συμβολίζουμε με (a b) :

Κατασκευάζουμε την λίστα (a b) εφαρμόζοντας τον κατασκευαστή ζεύγους πάνω στις οντότητες
a και (b) . Αποκαλούμε αυτή την πράξη επισύναψη νέου στοιχείου, του a, στη λίστα (b) . Τα a
και b αποτελούν τους όρους ή στοιχεία της λίστας που προκύπτει.
Με διαδοχική επισύναψη όρων, κατασκευάζουμε λίστα k όρων, για k φυσικό αριθμό:
Αν η ήδη υφιστάμενη κατασκευή είναι λίστα με k όρους α 1, α2, …, ακ, επισυνάπτοντας σ' αυτήν
τον ακ+1, όρο, κατασκευάζουμε λίστα που πλέον έχει k+1 στοιχεία:

Κεφαλή λίστας είναι η κεφαλή του τελευταία δημιουργημένου ζεύγους (δηλ. το αριστερότερο
24

στοιχείο) και ουρά ή σώμα λίστας είναι η ουρά αυτού του ζεύγους, δηλ. η λίστα - δεύτερο
στοιχείο του τελευταία δημιουργημένου ζεύγους.
Διαγραφή του πρώτου στοιχείου από λίστα δεν είναι τίποτα άλλο παρά η πράξη που επιστρέφει το
σώμα της λίστας.
Αναστροφή λίστας είναι η πράξη που δημιουργεί λίστα εφαρμόζοντας τον κατασκευαστή με
τρόπο που να παίρνει το πρώτο στοιχείο της λίστας και να το ενώνει σε ζεύγος με το κενό,
ακολούθως το πρώτο του σώματος που απομένει, σε ζεύγος με το προηγούμενο ζεύγος, κοκ. αυτό
για κάθε σώμα του σώματος που προκύπτει σε κάθε βήμα, μέχρι το σώμα να μείνει κενό.
Δεχόμαστε οτι μπορούμε να ορίσουμε αναδρομική κλήση πράξης (όπως η παραπάνω της
αναστροφής). Θεωρητικά, η αναδρομή ορίζεται και αυτή ως αλγοριθμική πράξη, όπως θα δούμε.
Συνένωση από δύο λίστες είναι η λίστα που προκύπτει επισυνάπτοντας διαδοχικά τα στοιχεία της
αναστροφής της πρώτης (αρχίζοντας από το πρώτο αριστερά) στη δεύτερη. Εκφράζεται ως μια
αναδρομικά ορισμένη πράξη επανειλημμένης εφαρμογής του κατασκευαστή, πχ. παίρνοντας (και
διαγράφοντας) κάθε φορά το πρώτο στοιχείο της αναστροφής της πρώτης λίστας και ενώνοντάς
το με τη δεύτερη λίστα, μέχρι να μείνει κενή η πρώτη λίστα.
Ένα σύμβολο (ή μια σύνθετη οντότητα) μπορεί να συμμετέχει σε περισσότερα του ενός ζεύγη,
είτε ως σώμα είτε ως κεφαλή:

Για λόγους συμβατότητας με τη γλώσσα Lisp, ορίζουμε οτι: λ-έκφραση είναι μια λίστα με πρώτο
στοιχείο το σύμβολο lambda , δεύτερο μια λίστα ονομάτων, των λεγομένων λ-μεταβλητών (γι'
αυτό λέγεται και λ-λίστα), και ακολουθεί το σώμα της λ-έκφρασης ή σώμα υπολογισμού, τα οποίο
καθορίζει τους συσχετισμούς (περιορισμούς, υποχρεώσεις, πράξεις…) των λ-μεταβλητών.
Το νόημα λ-έκφρασης είναι: περιγραφή από ορισμένες σχέσεις, πάνω σε μια ν-άδα ελεύθερων
μεταβλητών (από καμμία μέχρι ν) που είναι οι λ-μεταβλητές, οι οποίες όταν δεσμευτούν θα
προκύψει υπολογίσιμη έκφραση, αυτή που προσδιορίζει το σώμα όταν αντικατασταθούν σ' αυτό
οι λ-μεταβλητές με τις αντίστοιχες τιμές στις οποίες έχουν δεσμευτεί. Δηλαδή, μια λ-έκφραση
προσδιορίζει έναν υπολογισμό, ή ταυτόσημα, έναν αλγόριθμο.
Η περίπτωση λ-έκφρασης με καμμία λ-μεταβλητή είναι δεκτή, και αντιστοιχεί στην περιγραφή
διαδικαστικής αλγοριθμικής διαδικασίας, αυτής που καθορίζει το σώμα.

Η παρακάτω λ-έκφραση αποδίδει τη μαθηματική παράσταση x+y+z+5 . Στο σχήμα, η λίστα με τα
τρία στοιχεία x , y , z , είναι η λίστα των λ-μεταβλητών. Η λίστα με τα πέντε στοιχεία + , x , y ,
z , 7 , είναι το σώμα της λ-έκφρασης.

25

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

Η σειριακή έκφραση που έχει το σώμα λ-έκφρασης, επιβάλλει αμφιμονότιμη σύνδεση. Επομένως,
κάθε ζεύγος που αναφέρεται, εντάσσεται σε μια κατευθυνόμενη αλυσίδα "από - σε", που δεν
μπορεί να περιλαμβάνει περισσότερες της μίας συνδέσεις του αυτού στοιχείου (αλλά μπορεί
βέβαια να περιλαμβάνει πολλαπλές αναφορές στο αυτό όνομα στοιχείου, σε διάφορες θέσεις).
Εφαρμογή λ-έκφρασης σε συγκεκριμένα ορίσματα: είναι η αντικατάσταση των λ-μεταβλητών
μέσα στο σώμα της λ-έκφρασης με τις συγκεκριμένες τιμές που έχουν τα ορίσματα αυτά,
ακολουθώντας τον κανόνα: κάθε εμφάνιση της λ-μεταβλητής αντικαθίσταται με την αυτή τιμή
παντού μέσα στο σώμα.
Δεν είναι δυνατό η ίδια λ-μεταβλητή να αποτελεί και λ-μεταβλητή εσωτερικής λ-έκφρασης: αν
συμβεί μια λ-έκφραση με λ-μεταβλητή με όνομα x, να περιέχει στο σώμα της άλλη λ-έκφραση η
οποία έχει επίσης μια λ-μεταβλητή με το ίδιο όνομα x, η εσωτερικά αναφερόμενη λ-μεταβλητή
δεν σχετίζεται με την εξωτερική, και η δέσμευση της εξωτερικής δεν επηρεάζει την εσωτερική.
Η αντικατάσταση λόγω δέσμευσης προκαλεί μια συγκεκριμενοποίηση της αρχικής λ-έκφρασης.
Η συγκεκριμενοποίηση αυτή μπορεί να είναι είτε καταληκτική, με το νόημα οτι δεν επιδέχεται
περαιτέρω συγκεκριμενοποίηση, λόγω του οτι η έκφραση - αποτέλεσμα είναι σταθερά (δηλ. δεν
περιέχει λ-μεταβλητές), είτε ενδιάμεση, με το νόημα οτι προκύπτει πάλι λ-έκφραση (η οποία
μπορεί να εφαρμοστεί ξανά).
Η τιμή δέσμευσης λ-μεταβλητής μπορεί να είναι:
– Σταθερά, κατάλληλου τύπου για τις σχέσεις που περιγράφονται μέσα στο σώμα.

– λ-έκφραση, η οποία οφείλει να προσδιορίζει υπολογισμό συμβατό με τον υπολογισμό της
αρχικής λ-έκφρασης. Δηλαδή, η λ-έκφραση που είναι αντικείμενο εφαρμογής γίνεται δεκτή
όταν προκαλεί αποδεκτή δέσμευση της αντίστοιχης λ-μεταβλητής, άρα οι οριζόμενες στο
(αρχικό) σώμα πράξεις δέχονται την αντικατάσταση αυτή.

– Έκφραση η οποία είναι εφαρμογή λ-έκφρασης σε ορίσματα. Τέτοια έκφραση οδηγεί σε ένα από
τα δύο προηγούμενα, όταν η "φορά υπολογισμού" (η λεγόμενη τάξη του υπολογισμού) είναι
ορισμένη "από μέσα προς τα έξω". Διότι, πρώτα υπολογίζεται το αντικείμενο εφαρμογής (που
δίνει αποτέλεσμα είτε σταθερά είτε λ-έκφραση) και μετά γίνεται η εφαρμογή. Παίζει
26

διαφορετικό ρόλο όταν προσδιορίζουμε την φορά υπολογισμού αντίθετα, δηλ. "από έξω προς
τα μέσα".
– Όνομα συνάρτησης, που δεν είναι τίποτα άλλο από "παραπομπή μέσω ονόματος" στην
αντίστοιχη λ-έκφραση.
– Συνάρτηση εφαρμοσμένη σε κατάλληλα ορίσματα, δηλαδή εφαρμογή της λ-έκφρασης που
αντιστοιχεί στο όνομα της συνάρτησης, πάνω στα ορίσματα αυτά
– όνομα μεταβλητής, που δεν είναι τίποτα άλλο από "παραπομπή μέσω ονόματος" στην
αντίστοιχη τιμή της μεταβλητής (η τιμή αυτή μπορεί να είναι οτιδήποτε από τα παραπάνω)
Μπορούμε να δούμε την εφαρμογή λ-έκφρασης πάνω σε ορίσματα, ως κατασκευή μιας
αλυσιδωτής σύνδεσης, δηλ. λίστας με πρώτο στοιχείο τη λ-έκφραση και επόμενα τα ορίσματα
πάνω στα οποία θα γίνουν οι αντίστοιχες των λ-μεταβλητών δεσμεύσεις:

Αποτέλεσμα εφαρμογής λ-έκφρασης είναι το αποτέλεσμα του υπολογισμού του σώματός της μετά
την εφαρμογή της σε συγκεκριμένες τιμές για τις λ-μεταβλητές της.
Έστω οτι έχουμε τη λ-έκφραση που παριστά το x+y+z+5 : Έχει τρεις λ-μεταβλητές, τις x,y,z, και
σώμα το x+y+z+5 . Εφαρμόζοντας την λ-έκφραση αυτή στις τιμές 33, 77, 66 παίρνουμε την
έκφραση εφαρμογής:

που ο υπολογισμός της, με αντικατάσταση των μεταβλητών, δίνει αποτέλεσμα 181 .
Αποτέλεσμα υπολογισμού είναι αυτό που προκύπτει από τον υπολογισμό του σώματος μετά την
αντικατάσταση των λ-μεταβλητών με τα αντίστοιχα ορίσματα.

Συνάρτηση δεν είναι τίποτα άλλο από μια λ-έκφραση με όνομα. Το όνομα συνάρτησης είναι ένα
σύμβολο (εδώ σημειώνουμε το όνομα πάνω από το τετραγωνίδιο από το οποίο θα ξεκινήσει η
διαδικασία αντικατάστασης και ο υπολογισμός). Θεωρούμε εδώ οτι κάθε μαθηματική πράξη είναι
μια τέτοια συνάρτηση.
Συμβατικά, και για αποφυγή άλλων συμβολισμών, θεωρούμε οτι: μια λίστα εκφράζει πάντα
έννοια εφαρμογής, του πρώτου όρου που είναι συναρτησιακή έκφραση (δηλ. συνάρτηση ή λέκφραση) πάνω στους επόμενους που είναι τα ορίσματα της συναρτησιακής έκφρασης, και με
απλή αναγραφή τέτοιας έκφρασης εννοούμε οτι ζητάμε την εκτέλεσή της. Σε περίπτωση
εγκλεισμού (κιβωτισμένων) υπολογισμών, θεωρούμε πως ξεκινά "από μέσα προς τα έξω".
Αν θέλουμε να αναιρέσουμε αυτή την έννοια "υποχρεωτικού υπολογισμού" σε μια λίστα, είτε για
να δώσουμε στη λίστα νόημα ν-άδας ή συνόλου, είτε για να μεταθέσουμε χρονικά την εκτέλεση
"για αργότερα", εφαρμόζουμε πάνω στη λίστα που θέλουμε να μείνει "ανέγγιχτη από τον
υπολογισμό" μια θεμελιακή συνάρτηση, την quote, που καθυστερεί τον υπολογισμό και
επιστρέφει αυτούσια τη φόρμα που δέχεται ως όρισμα.
27

Το νόημα της διαδοχής υπολογισμών συναντάμε σε δύο διαφορετικές καταστάσεις:

α'. Διαδοχή κλήσεων που προκαλούνται από συνάρτηση προς όρισμά της, το οποίο καλεί
συνάρτηση, κοκ. Τότε θεωρούμε οτι ο υπολογισμός αρχίζει από μέσα προς τα έξω, δηλ. από την
κλήση του τελευταίου εσωτερικά επιπέδου. Η διαδοχή υπακούει στις γνωστές προτεραιότητες
των πράξεων και παρενθέσεων. Πχ:

O παραπάνω υπολογισμός παριστά το 15/5 + (7+4+2) + (5-2) , που δίνει αποτέλεσμα 19 :
β'. Στην εφαρμογή μιας συνάρτησης που δέχεται ως ορίσματα μια πεπερασμένη ακολουθία
εφαρμογών, και προκαλεί διαδοχική εκτέλεσή τους. Τότε η διαδοχή εκτελείται από αριστερά
προς τα δεξιά. Σε περίπτωση όπου έχουμε δενδροειδή διαδοχή, (δηλ. όταν κάποιοι όροι εφαρμογές είναι πάλι εφαρμογές τέτοιων συναρτήσεων) εκτελείται "από τα φύλλα και πρώτα
αριστερά", εκτός αν αλλιώς ορίζει η εφαρμοζόμενη συνάρτηση.
Μια τέτοια συνάρτηση - πρωτογενής πράξη είναι η progn, που δέχεται ως ορίσματα μια σειρά
από σώματα υπολογισμού (δηλ. καλείται ως φόρμα - λίστα με κεφαλή την progn και επόμενα
στοιχεία κάποια σώματα που προσδιορίζουν υπολογισμούς) και προσδιορίζει οτι τα σώματα αυτά
εκτελούνται διαδοχικά, κατά τη σειρά γραφής τους.
Η progn δέχεται ορίσματα οποιουδήποτε πεπερασμένου πλήθους. Αποτέλεσμα της φόρμας
εφαρμογής της progn, όταν κληθεί για υπολογισμό, είναι η τιμή του υπολογισμού του τελευταίου
σώματος - ορίσματος της progn . Για παράδειγμα, ο υπολογισμός στο παρακάτω ξεκινά από το
πρώτο αριστερά σώμα (δηλ. 15/3), και ακολουθούν τα επόμενα (δηλ. 7+5 , 5-1) .

Σημαντικό είναι οτι ο αποτέλεσμα υπολογισμού κάθε όρου στη διαδοχή, ενδέχεται να καθορίζει
τις συνθήκες απ' όπου ξεκινά ο υπολογισμός του επομένου όρου (στο παράδειγμα αυτό είναι
28

ανεξάρτητοι). Συνεπώς, η παραπάνω εφαρμογή τής progn έχει τρία διαδοχικά σώματα
υπολογισμού, τις πράξεις 15/3 , 7+5 , 5-1 , και όταν κληθεί θα δώσει αποτέλεσμα 4 , που είναι το
αποτέλεσμα του τελευταίου σώματος – φόρμας υπολογισμού, αλλά θα έχει εκτελέσει και όλες τις
ενδιάμεσες φόρμες.
Το υπολογιστικό νόημα του progn είναι οτι, είναι δυνατό κάποιοι από τους διαδοχικούς
υπολογισμούς να προκαλούν δεσμεύσεις ή αλλαγές δεσμεύσεων συμβόλων ή λ-μεταβλητών που
χρησιμοποιούνται σε επόμενους υπολογισμούς, ή ακόμα και σε καταστάσεις εκτός του
υπολογισμού, οπότε οι διαδοχικοί υπολογισμοί υπακούουν στις μεταβολές που έχουν επέλθει.
Εννοούμενο progn : συμβατικά θεωρούμε οτι σε μια λ-έκφραση, όταν το σώμα είναι εφαρμογή
της progn (δηλ. λίστα με κεφαλή progn) τότε μπορεί να αναγραφεί απ' ευθείας η σειρά των όρων ορισμάτων του progn, χωρίς αναφορά της progn. Οι όροι αυτοί αποτελούν φόρμες προς διαδοχικό
υπολογισμό όταν κληθεί η λ-έκφραση προς εφαρμογή.
Μεταβλητή, ή σύμβολο-μεταβλητή, είναι ένα όνομα στο οποίο μπορούμε να αποδώσουμε
περιεχόμενο και με το οποίο μπορούμε να δημιουργήσουμε ορισμένες συνδέσεις (από / προς).
Τα σύμβολα - μεταβλητές διαφοροποιούνται από τις λ-μεταβλητές κατά το οτι τα πρώτα είναι
οντότητες που υφίστανται ανεξάρτητα του οποιουδήποτε υπολογισμού, ενώ οι δεύτερες
υφίστανται ως σύμβολα μόνο στα πλαίσια κάποιου υπολογισμού.

Ενδιάμεσα ως σημασία, τοποθετούνται οι τοπικές μεταβλητές, που προσδιορίζονται ως λμεταβλητές σε ένα υπολογισμό (δηλ. σε μια εφαρμοζόμενη λ-έκφραση), και αξιοποιούνται με
τρόπο ώστε οτιδήποτε ορίζεται εσωτερικά (δηλ. στα πλαίσια αυτού του υπολογισμού), μπορεί να
δει αυτές τις μεταβλητές ως σύμβολα.
Ένα σύμβολο είναι ορατό από παντού, εκτός αν έχουν τεθεί περιορισμοί τόπου (δηλ. εγκλεισμός
του συμβόλου στο σώμα λ-έκφρασης της οποίας είναι λ-μεταβλητή). Αυτό σημαίνει οτι η
αναφορά του σε μια φόρμα υπολογισμού είναι δεκτή χωρίς αναγραφή σύνδεσης βέλους με αυτό,
και η αναφορά του παρακολουθεί τις συνδέσεις που ήδη αυτό έχει ή αποκτά∙ "Α παρακολουθεί
Β" σημαίνει οτι, όταν Α κληθεί προς υπολογισμό, θα λάβει υπ' όψη του ο υπολογισμός τις
τελευταίες μεταβολές που επήλθαν στο Β.
Κλήση από φόρμα προς εκτέλεση δεν είναι τίποτα άλλο από εφαρμογή λ-έκφρασης ή συνάρτησης
σε ορίσματα, και υπολογισμό της έκφρασης που προκύπτει. Μπορούμε να διακρίνουμε αυτά τα
δύο βήματα, παρεμβάλλοντας ενδιάμεσες και ελεγχόμενες φάσεις υπολογισμού, καθυστερώντας
έτσι τον τελικό υπολογισμό (delayed evaluation).
Αποκαλούμε φόρμα οποιαδήποτε έκφραση που είναι είτε σταθερά, είτε σύμβολο, είτε εφαρμογή
συνάρτησης, είτε λ-έκφραση. Ατομο είναι μια φόρμα που είναι είτε σταθερά, είτε σύμβολο.

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

29

Τα σύμβολα a, b, c ονομάζουν τις παραπάνω φόρμες. Κλήση (υπολογισμός) του συμβόλου a θα
επιστρέψει 3, και κλήση του συμβόλου c θα επιστρέψει το ζεύγος των στοιχείων a και b , δηλ. (a .
b).
Αποδίδουμε το παραπάνω ζεύγος c και με τη μέθοδο κατασκευής του, που γράφεται συμβολικά
(cons a b) . H κλήση αυτής της έκφρασης θα κατασκευάσει το ζεύγος.

Υπολογισμός που καλεί το σύμβολο c κάνει έμμεση χρήση των συμβόλων a και b, και "μπορεί να
κατεβεί" στο περιεχόμενό τους. Μεταβολή του περιεχομένου των συμβόλων a ή/και b θα
μεταβάλλει το αποτέλεσμα του τελευταίου.
Δημιουργία συμβόλου-μεταβλητής: Η πρωτογενής συνάρτηση setq δημιουργεί σύμβολο μεταβλητή, με όνομα το πρώτο όρισμά της και τιμή (περιεχόμενο) το αποτέλεσμα του
υπολογισμού του δεύτερου ορίσματος.
Εδώ, η αναγραφή ονόματος συμβόλου έξω από το τετραγωνίδιο σημαίνει: ονομασία του
αναφερόμενου περιεχομένου. Η αναγραφή ονόματος συμβόλου μέσα στο τετραγωνίδιο σημαίνει:
κατά τον υπολογισμό θα γίνει υπολογισμός του συμβόλου, δηλ. κλήση του περιεχομένου του
συμβόλου που αναφέρεται.

Στο παραπάνω, η συνάρτηση "+" εφαρμόζεται, στo σύμβολo a και την σταθερά 2 . Κατά τον
υπολογισμό, πρώτα τη θέση του συμβόλου a θα πάρει το περιεχόμενό του, που είναι 3 (από μέσα
προς τα έξω, έχουμε κλήση του a, άπου σημαίνει υπολογισμός του a, άρα επιστροφή της τιμής
του, στη θέση του), άρα τελικό αποτέλεσμα είναι 5 .
Εάν πρόκειται για συνθετότερη φόρμα υπολογισμού, ο υπολογισμός θα ακολουθήσει την
ανάστροφη σειρά (τάξη) των συνδέσεων που αναφέρονται, και αυτή είναι η λεγόμενη κανονική
τάξη υπολογισμού (normal order), και έχει το νόημα του "από μέσα προς τα έξω υπολογισμού".
Όπως θα δούμε αργότερα, μπορούμε να οργανώσουμε και υπολογισμό που ακολουθεί την τάξη
της εφαρμογής συνδέσεων (applicative order) που έχει το νόημα του "απ' έξω προς τα μέσα
υπολογισμού".

Συνάρτηση επιλογής: Μια συνάρτηση, με όνομα cond , προκαλεί επιλογή μεταξύ περισσοτέρων
της μιας κατευθύνσεων υπολογισμού. Δέχεται ως ορίσματα μια σειρά από λίστες, των οποίων το
πρώτο στοιχείο αποτελεί μπουλιανή έκφραση (τιμής "αληθές" ή "ψευδές"), και τα επόμενα
αποτελούν το σώμα (ως εννοούμενο progn) που θα υπολογιστεί αν η μπουλιανή έκφραση έχει
τιμή "αληθές". Κατά τον υπολογισμό του cond εκτελούνται διαδοχικά τα στοιχεία (φόρμες) των
μπουλιανών εκφράσεων, και εκτελείται το σώμα (ως εννοούμενο progn) της πρώτης λίστας που
θα συμβεί να έχει μπουλιανή έκφραση τιμής "αληθές"∙ τότε ο υπολογισμός τερματίζεται και
επιστέφεται ως τιμή της συνάρτησης cond η τιμή του σώματος, δηλαδή η τιμή του τελευταίου
υπολογισμού στο σώμα. Σε αποτυχία όλων, η cond επιστρέφει "ψευδές" .
Στο παρακάτω σχήμα, τα b1, b2, b3 πρέπει να είναι οπωσδήποτε μπουλιανές εκφράσεις (δηλ.
30

φόρμες που όταν υπολογιστούν θα δώσουν τιμή "αληθές" ή "ψευδές"), και τα c1, c2, c3 μπορούν
να είναι οποιεσδήποτε φόρμες υπολογισμού, ή σειρά από τέτοιες φόρμες (ως εννοούμενα progn),
ή ακόμα και να μην υπάρχουν.

Η ακόλουθη φόρμα αποδίδει την έκφραση "αν είναι 1>3 τότε υπολόγισε το 7+4+5 αλλιώς, αν
είναι 3<7 τότε υπολόγισε το 12/2" . Το conditional αυτό, επιστρέφει 6 .

Κλήση συμβόλου:
– Εάν σε κάποιο τετραγωνίδιο στο σώμα έχει τεθεί ως περιεχόμενο το όνομα μεταβλητής, τότε
κατά την κλήση προς εκτέλεση, η μεταβλητή αυτή υπολογίζεται, δηλ. αντικαθίσταται από την
τιμή της.

– Εάν σε κάποιο τετραγωνίδιο έχει τεθεί το όνομα συνάρτησης, αυτό κατά την κλήση της
φόρμας προς υπολογισμό, θα αποτελέσει κλήση εφαρμογής τής εν λόγω συνάρτησης, οπότε αυτή
πρέπει να ακολουθείται από τα κατάλληλου πλήθους και τύπου ορίσματα, πάνω στα οποία θα
εφαρμοστεί. Πχ, στην παραπάνω φόρμα "cond…", η φόρμα (+ 7 4 5) είναι η εφαρμογή της
συνάρτησης "+" με ορίσματα τα 7, 4, 5 .
Οι διακλαδώσεις επιλογής δεν αποτελούν πολλαπλές συνδέσεις, αλλά τεχνική που οργανώνει το
κατάλληλο σώμα υπολογισμού κατά τη στιγμή της εκτέλεσης, βάσει των ισχυουσών συνθηκών.
Την τεχνική αυτή θα δούμε στην επόμενη ενότητα.
Όλες οι πράξεις που αναφέραμε προηγουμένως προσδιορίζονται και ως συναρτήσεις, με σώμα
την αντίστοιχη δράση και επιστρεφόμενη τιμή το κατάλληλο αποτέλεσμα.
Συνάρτηση διαγραφής όρου από λίστα: Η διαγραφή του πρώτου όρου λίστας ισοδυναμεί με
31

επιστροφή του σώματος της λίστας. Η διαγραφή του ν-οστού όρου ισοδυναμεί με διαδοχική
διαγραφή ν όρων, και επανεπισύναψη των διαγραμμένων όρων μέχρι τάξης ν-1 αρχίζοντας από
τον τελευταίο. Άρα μπορεί να οριστεί ως διπλά αναδρομική συνάρτηση.

1.2.2 Η έννοια της λ-έκφρασης και της εφαρμογής

Παραθέτουμε εδώ έναν άτυπο, απλό τρόπο γραφής και χρήσης των βασικών εννοιών του λλογισμού, με ένα συμβολισμό που απλουστεύει τις έννοιες και τη χρήση τους. Ο τρόπος αυτός
αντιστοιχεί αμφιμονοσήμαντα στον τρόπο απόδοσης και χρήσης εκφράσεων της Lisp, και
παρατίθεται ανεξάρτητα του προηγούμενου.
Μια λ-έκφραση είναι μια παράσταση που αρχίζει με το χαρακτήρα λ , ακολουθεί η λεγόμενη λμεταβλητή, και στη συνέχεια, μετά από μια τελεία, αναγράφεται το λεγόμενο σώμα της λέκφρασης, που είναι η παράσταση που προσδιορίζει τις εφαρμοζόμενες πράξεις ή τις σχέσεις στις
οποίες υπόκειται η λ-μεταβλητή:
λx.B
Για παράδειγμα, θεωρώντας οτι αριθμοί, μαθηματικές πράξεις και σύνταξη είναι προσδιορισμένα
στο λ-λογισμό με το συνήθη τρόπο (κάτι που δεν συμβαίνει στον τυπικό συμβολισμό, αλλά δεν
βλάπτει αυτή η παραδοχή εδώ) η μαθηματική παράσταση 3x+2 θα γράφεται:
λx.(3x+2)
Μια κατά το μαθηματικό νόημα "ελεύθερη μεταβλητή", έστω z , δεν είναι τίποτα άλλο από την λέκφραση:
λz.z
η οποία ακριβέστερα είναι η ταυτοτική λ-έκφραση, και λέμε "η" διότι ταυτίζεται με κάθε
έκφραση που προκύπτει από αυτήν με αλλαγή του ονόματος της λ-μεταβλητής.
Σε μια λ-έκφραση μπορούμε να αντικαταστήσουμε τη λ-μεταβλητή με άλλη, μη αναφερόμενη
στην ίδια έκφραση (ο λεγόμενος "κανόνας α-conversion"):
λx.(3x+2) ταυτίζεται με λy.(3y+2)
Το νόημα του υπολογισμού εκφράζεται από τον κανόνα οτι μια λ-έκφραση μπορεί να εφαρμοστεί
πάνω σε "κάτι". Αυτό το "κάτι" είναι το όρισμα που της δίνουμε, το οποίο αντικαθιστά τη λμεταβλητή παντού μέσα στο σώμα τής λ-έκφρασης, και μετά τις πράξεις στο σώμα προκύπτει το
αποτέλεσμα αυτής της εφαρμογής. Το όρισμα οφείλει να είναι αποδεκτό από τις πράξεις του
σώματος. Αποκαλούμε αυτή την αντικατάσταση δέσμευση της λ-μεταβλητής (ο λεγόμενος
"κανόνας β-reduction").

Γράφουμε την εφαρμογή μιας συναρτησιακής έκφρασης Σ πάνω σε όρισμα Μ (κατάλληλο βέβαια
για να δεσμευτεί σ' αυτό η λ-μεταβλητή) ως εξής:
(Σ M)
Με τον τρόπο αυτό, η εφαρμογή λ-έκφρασης, έστω της λx.B , πάνω στο όρισμα Μ, θα γράφεται:
(λx.B M)
Πχ, έστω η λ-έκφραση λx.(3x+2) και έστω οτι την εφαρμόσουμε πάνω στο 4 . Από τη μορφή του
σώματος της λ-έκφρασης εννοείται ότι η x είναι αριθμητική οντότητα, άρα μπορεί να δεσμευτεί
32

σε αριθμό, άρα το όρισμα οφείλει να είναι αριθμός:
(λx.(3x+2) 4) = 3*4 + 2 = 14
Σε μια εφαρμογή, έστω την (Α Β) , αποκαλούμε το Α συναρτησιακό μέρος και το Β μέρος
ορίσματος ή αντικείμενο εφαρμογής ή απλά όρισμα.
Βλέπουμε οτι το αποτέλεσμα της εφαρμογής μιας λ-έκφρασης (μιας λ-μεταβλητής, όπως οι
παραπάνω εκφράσεις) πάνω σε σταθερά δεν είναι πλέον λ-έκφραση αλλά σταθερά. Είναι
πανομοιότυπο με αυτό που κάνουμε όταν παίρνουμε την τιμή μαθηματικής συνάρτησης μιας
μεταβλητής για σταθερή τιμή τής ελεύθερης μεταβλητής. Είναι δυνατό όμως η εφαρμογή να γίνει
πάνω σε συνθετότερη έκφραση, όπως θα δούμε.
Το όρισμα μπορεί να είναι σύνθετη πράξη (συνάρτηση) που δίνει αριθμητικό αποτέλεσμα. Το
συναρτησιακό μέρος μπορεί να είναι είτε απλή λ-έκφραση είτε συνάρτηση (δηλ. όνομα λέκφρασης) είτε σύνθεση εφαρμογής, η οποία να δίνει ως αποτέλεσμα σταθερά ή πάλι μια λέκφραση.
Στην περίπτωση όπου το όρισμα είναι λ-έκφραση έχουμε οτι: Η εφαρμογή της λ-έκφρασης, έστω
a, πάνω σε λ-έκφραση, έστω b, ακολουθεί τον κανόνα της απλοποίησης (ο λεγόμενος "κανόνας
η-reduction"), με το νόημα οτι το αποτέλεσμα είναι λ-έκφραση που σχηματίζεται ως εξής: το
σώμα της b, ολόκληρο, αντικαθιστά τη λ-μεταβλητή στην a , και το όνομα της λ-μεταβλητής της
b αποτελεί τη λ-μεταβλητή του αποτελέσματος, το οποίο είναι πάλι λ-έκφραση.
Για παράδειγμα:
α'. Η εφαρμογή της λx.(3x+2) πάνω στην ταυτοτική λz.z δίνει:
(λx.(3x+2) (λz.z)) = λz.(3z+2) = λx.(3x+2)
β'. Η εφαρμογή της λx.(3x+2) πάνω στην λx.(cos(x 2+1)) δίνει:
(λx.(3x+2) λx.(cos(x2+1))) = λx.(3cos(x2+1) + 2)
γ'. Η εφαρμογή τής λx.(sinx) πάνω στην λy.(5y–2) δίνει:
(λx.(sinx) λy.(5y–2)) = λy.(sin(5y–2))
Στα Μαθηματικά, αντίστοιχη έννοια της εφαρμογής λ-έκφρασης πάνω σε λ-έκφραση είναι η
σύνθεση δύο συναρτήσεων f(x) και g(y) όπου η δεύτερη παίρνει τη θέση της μεταβλητής της
πρώτης, και το αποτέλεσμα είναι συνάρτηση της y : f(g(y))
Τυπικά μια λ-έκφραση έχει ακριβώς μια λ-μεταβλητή. Είναι δυνατό όμως, το σώμα λ-έκφρασης
να είναι πάλι λ-έκφραση, δηλ. της μορφής:
λx.(λy.B)
όπου το σώμα Β περιέχει (δυνητικά) αναφορές και στις δύο μεταβλητές, πχ.
λx.(λy.(2x+3y))
Η εφαρμογή της παράστασης αυτής σε όρισμα, σημαίνει εφαρμογή ως προς τη x λ-μεταβλητή, η
οποία απαλείφεται (υποκαθίσταται μέσα στο σώμα, από το όρισμα). Επομένως το αποτέλεσμα
είναι λ-έκφραση με λ-μεταβλητή το y. Αυτό σημαίνει οτι μπορεί να εφαρμοστεί ξανά σε όρισμα,
άρα μπορούμε να κάνουμε δύο διαδοχικές εφαρμογές της αρχικής έκφρασης, η πρώτη σε όρισμα
που θα αντικαταστήσει την x και η δεύτερη σε όρισμα που θα αντικαταστήσει την y .
Για λόγους απλούστευσης, όταν έχουμε μια έκφραση που συντίθεται από λ-εκφράσεις με την
33

παραπάνω διαδοχή, με προϋπόθεση οτι θα γίνονται πάντα όλες ο διαδοχικές εφαρμογές, αντί να
γράψουμε την έκφραση σε διαδοχικά βήματα σύνθεσης, όπως:
λx.λy.(2x+3y)
τη γράφουμε ως ενιαίο βήμα περισσοτέρων λ-μεταβλητών:
λ(x y).(2x+3y)
Η γραφή αυτή απλώς υποδηλώνει την προηγούμενη.
Σε δύο βήματα εφαρμογής, δεσμεύονται διαδοχικά οι x και y :
( ( λx.λy.(2x+3y) 4 ) 5 ) = ( λy.(2∙4+3y) 5 ) = 2∙4+3∙5 = 23
και αυτό ισοδυναμεί με την σε ένα βήμα εφαρμογή της δεύτερης, που δεσμεύει "ταυτόχρονα" και
τις δύο λ-μεταβλητές:
( λ(x y).(2x+3y) 4 5) = 2∙4+3∙5 = 23
Σύμφωνα με το παραπάνω, θα γράφουμε μια λ-έκφραση δύο μεταβλητών:
λ(x y).A
και την εφαρμογή της σε δύο ορίσματα, Μ και Ν, ως εξής:
(λ(x y).A Μ Ν)
Γενικότερα, για περισσότερες μεταβλητές, θα γράφουμε αντίστοιχα:
λ(x y z …w).A
Μια λ-έκφραση n μεταβλητών προφανώς θα πρέπει να εφαρμοστεί σε n ορίσματα.

Η παρένθεση που περιέχει τις λ-μεταβλητές ονομάζεται λ-λίστα. Για να υπάρξει συμβατότητα
γραφής, χρησιμοποιούμε την παρένθεση της λ-λίστας ακόμα και για μία μεταβλητή, δηλ.
γράφουμε λ(x).(x+3) αντί για λx.(x+3) .
Έστω η λ-έκφραση λ(x y).(x+y) Έχουμε τις εξής περιπτώσεις εφαρμογής:
– Αν όλα τα ορίσματα είναι σταθερές (πχ. αριθμοί) και το σώμα ορίζεται ως τύπος
μαθηματικής συνάρτησης, το αποτέλεσμα θα είναι σταθερά:
(λ(x y).(x+y) 3 4) = 3+4 = 7
– Αν το πρώτο όρισμα είναι σταθερά, έστω 3, και το δεύτερο λ-έκφραση, έστω η λzz, η πρώτη
μεταβλητή δεσμεύεται στο 3 και η δεύτερη στο λz.z , άρα (με εφαρμογή του η-κανόνα):
(λ(x y).(x+y) 3 λz.z) = λz.(3+z)
Βλέπουμε οτι προκύπτει λ-έκφραση μιας λ-μεταβλητής.
– Αν και τα δύο ορίσματα είναι λ-εκφράσεις, έστω η λw.w και λz.(1+z) αντίστοιχα, η καθεμία
θα αντικαταστήσει την αντίστοιχη λ-μεταβλητή, οπότε:
(λ(x y).(x+y) λw.w λz.(1+z))
=
[εφαρμογή α-κανόνα]
= (λ(x y).(x+y) λx.x λy.(1+y)) =
[εφαρμογή β-κανόνα]
= λ(x y).(λx.x + λy.(1+y))
=
[εφαρμογή η-κανόνα]
= λ(x y).(x+(1+y))
Βλέπουμε οτι προκύπτει λ-έκφραση πάλι δύο λ-μεταβλητών.
Αν, κατά την εφαρμογή μιας λ-έκφρασης μιας μεταβλητής το όρισμα είναι λ-έκφραση
34

περισσοτέρων λ-μεταβλητών, η δέσμευση της αρχικής λ-μεταβλητής οδηγεί σε λ-έκφραση
περισσοτέρων λ-μεταβλητών από την αρχική έκφραση. Αυτό είναι μια εκπληκτική δυνατότητα
του υπολογισμού που βασίζεται στον λ-λογισμό, διότι πετυχαίνει γενίκευση μέσω εφαρμογής.
Αν συμβεί, σε μια λ-έκφραση μιας μεταβλητής, η λ-μεταβλητή να μην αναφέρεται στο σώμα της
(dummy μεταβλητή) τότε η λ-έκφραση απλώς εκφράζει τον υπολογισμό που περιγράφει το σώμα.
Ο υπολογισμός όμως του σώματος, περιμένει εφαρμογή της έκφρασης σε όρισμα αντίστοιχο της
λ-μεταβλητής (χωρίς να επιδρά στο σώμα).
Πχ, έστω οτι δίνεται η έκφραση λd.(2-3) . Το σώμα της, είναι μια εκτελέσιμη πράξη, που βέβαια
δεν εκτελείται, διότι δεν έχει εφαρμοστεί η λ-έκφραση πουθενά. Αν δώσουμε μια εφαρμογή της,
έστω στον αριθμό 7 , προχωρά η δέσμευση της λ-μεταβλητής, και λόγω του οτι δεν εμπεριέχεται
στο σώμα, δεν υπάρχει τίποτα να το αντικαταστήσει. Ο υπολογισμός λοιπόν θα εκτελεστεί
ανεξάρτητα της τιμής εφαρμογής:
(λd.(2-3) 7) = (2-3) = -1

Μια τέτοια "dummy" λ-μεταβλητή παίζει ρόλο καθυστέρησης στον υπολογισμό που εκφράζει το
σώμα, επιτρέποντάς μας να ελέγξουμε τη στιγμή της εκτέλεσης: πρέπει να εφαρμοστεί η λέκφραση "κάπου", αδιάφορο πού, για να γίνει ο υπολογισμός.
Επειδή το όνομα "dummy" λ-μεταβλητής δεν υπεισέρχεται στον υπολογισμό, συμβατικά το
"αγνοούμε", και αναγράφουμε μια έκφραση με "dummy" λ-μεταβλητή, με κενή τη λ-λίστα, όπως:
λ( ).(3+4-5)
Δηλαδή η έκφραση αυτή εξ ορισμού είναι ταυτοτικά ισοδύναμη της:
(λd.(3+4-5) a)
για οποιαδήποτε υπολογίσιμη έκφραση a .
Θεωρούμε οτι μπορούμε να δώσουμε όνομα σε μια έκφραση. Έτσι αποκτάμε ένα τρόπο κλήσης
της έκφρασης, "δείχνοντάς την με το όνομά της". Ας χρησιμοποιήσουμε προσωρινά τον όρο "def"
για τη λειτουργία που δίνει όνομα σε συναρτησιακή έκφραση, και ας δεχθούμε οτι συντάσσεται
ως υπολογισμός με τη φόρμα:
(def <όνομα> <έκφραση>)
Αν "βαφτίσουμε" μια λ-έκφραση με όνομα, αυτό αποδίδει το νόημα της συνάρτησης:
(def square λx.(x∙x))
Από την ονομασία υπολογισμών (ή σταθερών, ως υπολογισμών όπου δεν υπάρχει τίποτα να
εκτελεστεί) δημιουργείται το νόημα των συμβόλων-μεταβλητών, και η αναφορά τέτοιων
μεταβλητών στο σώμα λ-έκφρασης σημαίνει απλώς οτι αντικαθίστανται από την αντίστοιχη
έκφραση. Ας χρησιμοποιήσουμε προσωρινά τον όρο set για τέτοια ονομασία, θεωρώντας οτι set
είναι συνάρτηση που δέχεται δύο ορίσματα, <όνομα συμβόλου> και <περιεχόμενο συμβόλου> :
(set <όνομα συμβόλου> <σταθερά>)
(set <όνομα συμβόλου> (<λ-έκφραση> όρισμα>) )
(set <όνομα συμβόλου> (<λ-έκφραση> όρισμ1 όρισμ2 … όρισμk>))
Για παράδειγμα:
(set a 2)
(set b 3)
35

(def f λx.(ax+b))
(def g λ(x y).(x-y))
Η ονομασία συναρτησιακών εκφράσεων και υπολογισμών δίνει πολύ μεγάλη ευελιξία για το
σχηματισμό συνθέτων υπολογισμών, πχ:
(f 6) = 15
(g a b) = -1
(g (f (g (a+1) (b-1)) ) (f b) ) = (g (f 0) 9) = (g 3 9) = -6
Θα δούμε στην επόμενη ενότητα την αντιμετώπιση των εννοιών της συνάρτησης, της μεταβλητής
και του υπολογισμού με φορμαλιστικό τρόπο.

1.3 "Φιλοσοφία" και πρακτική του ΣΠ
Στην ενότητα αυτή θα εξετάσουμε τις δυνατότητες που ανοίγει η θεώρηση του υπολογισμού,
προσδιορισμένου κάτω από το μοντέλο του λ-λογισμού, σε συνδυασμό με τις δυνατότητες για
αποτελεσματική υλοποίηση των θεωρητικών υπολογιστικών εννοιών.

1.3.1 Κεντρικά χαρακτηριστικά του ΣΠ
Μεταβαίνοντας από τη διαδικαστική αντίληψη στη συναρτησιακή
H διάκριση του ΣΠ από τα άλλα είδη προγραμματισμού υπεισέρχεται σε όλα τα επίπεδα: ξεκινά
από τον φορμαλισμό, σε λογικό και συντακτικό επίπεδο, προχωρά στο εννοιολογικό βάθος των
νοημάτων - οντοτήτων που υλοποιούμε, και φθάνει στη δυναμική αποτύπωση της εξέλιξης των
οντοτήτων και τρόπων επεξεργασίας τους, μέσω της αλληλεπίδρασης.

Με τον ΣΠ υλοποιούμε, σε σημαντικό βαθμό, την πραγματική σημασία των οντοτήτων. Για
παράδειγμα,. δεν ονομάζουμε απλά μια μεταβλητή "ΔΥΝΑΜΗ" επειδή χρησιμοποιείται σε ένα
τύπο σε αντιστοιχία με το φυσικό μέγεθος "δύναμη", όπως θα κάναμε σε ένα διαδικαστικό
πρόγραμμα προσδιορίζοντας:
ΔΥΝΑΜΗ := ΜΑΖΑ * ΕΠΙΤΑΧΥΝΣΗ
αλλά, ο όρος ΔΥΝΑΜΗ μπορεί να είναι οντότητα (entity) που έχει βαθύτερο περιεχόμενο και
να περικλείει ένα πλήθος συσχετισμών, ερμηνειών, διεργασιών, και όλα αυτά με ποικιλία
εκφραστικών μορφών: εξειδικευμένο τύπο, τιμή, ιδιότητες, συμμετοχή σε συνδέσεις, σύνδεση
μεθόδων με τον τύπο του. Έτσι είναι δυνατό να περιγράψουμε εύκολα με διασυνδεδεμένες
εκφράσεις "οτιδήποτε γνωρίζουμε σχετικά με δυνάμεις", όπως "η δύναμη ασκείται σε σώμα, αυτό
ακριβώς που έχει την αναφερόμενη στον τύπο μάζα, και προκαλεί σ' αυτό την αντίστοιχη
επιτάχυνση"∙ δηλαδή, το ίδιο το αντικείμενο "σώμα" με την "εφαρμογή της δύναμης" έχει
αποκτήσει την σχετική "επιτάχυνση".
Η δυνατότητα να δώσουμε περιεχόμενο σε οντότητα μας δίνει την ευκαιρία να τη συνδέσουμε με
36

άλλες οντότητες ή με διεργασίες. Το περιεχόμενο είναι δυνατό να μεταβάλλεται, και επομένως
είναι δυνατό η οντότητα να συμμετέχει σε εξελισσόμενες καταστάσεις. Έτσι μπορούμε να
αποδώσουμε, ως οντότητες, σύνθετες έννοιες, όπως: "το υλικό σώμα έχει μάζα, ανά πάσα στιγμή
μια θέση στον τριδιάστατο χώρο, εξαρτημένη από τα τάδε στοιχεία υπολογισμού, έχει κινητική
κατάσταση", συσχετισμούς όπως: "είναι τοποθετημένο σε πεδίο βαρύτητας το οποίο μεταβάλλεται
με το ύψος", και λειτουργίες, όπως "η ελεύθερη πτώση", εξειδικευόμενες, όπως: "η πτώση ενός
σώματος από το διάστημα πάνω στη Γη".
Η σε βάθος υλοποίηση της πραγματικής σημασίας στην υπολογιστική υλοποίηση εννοιών αποκτά
ακόμα μεγαλύτερο ενδιαφέρον όταν οργανώνουμε σύμβολα - συναρτήσεις που υλοποιούν δράσεις
αντίστοιχες με τις πραγματικές, διότι από το ένα μέρος η σύνθεση συναρτήσεων απεικονίζει την
σύνθεση των αντιστοίχων πραγματικών δράσεων και το αντίθετο (στο μέτρο της εφικτής
λεπτομέρειας), και από το άλλο μέρος τα αποτελέσματα των υπολογιστικών διεργασιών
αντιστοιχούν στα αποτελέσματα των προτύπων δράσεων στον πραγματικό κόσμο (στο ίδιο
μέτρο).

Θεμελιακά χαρακτηριστικά του ΣΠ

Στον ΣΠ θεωρούμε οτι τα πάντα αποτελούν είτε συναρτησιακή έκφραση (νόημα ταυτόσημο του
αλγόριθμου) είτε εφαρμογή συναρτησιακής έκφρασης, και μια εφαρμογή διακρίνεται από το αν
αναφερόμαστε σε προσδιορισμό ή σε εκτέλεσή της. Οποιαδήποτε εκτέλεση υπολογισμού (νόημα
ταυτόσημο της εκτέλεσης αλγόριθμου) είναι υπολογισμός της τιμής που παίρνει μια συναρτησιακή
έκφραση όταν εφαρμοστεί σε συγκεκριμένα ορίσματα. Με τον όρο "υπολογισμός" συχνά
εννοούμε οτιδήποτε από τα παραπάνω, δηλ. συναρτησιακή έκφραση, περιγραφή εφαρμογής,
εκτέλεση εφαρμογής.

Tο αποτέλεσμα εφαρμογής μπορεί να είναι συναρτησιακή έκφραση ή σταθερά. Μπορούμε να
βλέπουμε τις σταθερές ως συναρτησιακές εκφράσεις που δεν δέχονται ορίσματα και όταν
κληθούν, δίνουν αποτέλεσμα πάντοτε τον εαυτό τους. Οι σταθερές στη Lisp είναι οι αριθμοί, οι
χαρακτήρες και οι συμβολοσειρές (strings)

Η τυπική θεωρία του υπολογισμού είναι τόσο γενική που μπορεί να εκφράσει οποιαδήποτε
νοήματα, αρκεί αυτά να είναι υπολογίσιμα (με οποιοδήποτε τρόπο και αν προσδιορίσουμε το
νόημα του υπολογίσιμου). Ο συναρτησιακός φορμαλισμός επιτρέπει την περιγραφή και
επεξεργασία από οσοδήποτε σύνθετα νοήματα, που πάντοτε είναι δυνατό να εκφραστούν ως
ενιαίες οντότητες και να χρησιμοποιηθούν ως απλές οντότητες. Η υπολογιστική σύνθεση που
αποδίδει ένα σύνθετο νόημα, αποτελεί επίσης ενιαία υπολογιστική οντότητα, είτε έχει όνομα είτε
όχι, πχ: "το τραπέζι που είναι από ξύλο, έχει χρώμα καφέ, στηρίζεται σε 4 πόδια, έχει σχήμα
τετράγωνο πλευράς 1.40 μ, και το πλήθος των θέσεων υπολογίζεται με τη μέθοδο τάδε ως προς την
πλευρά".
Τα συντιθέμενα νοήματα (συνθέσεις από πρωτογενή σύμβολα και ήδη συντεθειμένες εκφράσεις)
σχετίζονται άμεσα με τον τρόπο που σκέφτεται ο άνθρωπος. Αυτό σημαίνει οτι δεν χρειάζεται να
αποδώσουμε τις έννοιες του πραγματικού προβλήματος και της μεθόδου επίλυσής του με ένα
τρόπο σκέψης τον οποίο επιβάλλει η υπολογιστική μεθόδευση και διαφορετικό του συνήθους
στον πραγματικό κόσμο, όπως συμβαίνει με τις διαδικαστικές γλώσσες∙ η μοντελοποίηση
37

προβλήματος μπορεί να ακολουθεί εύκολα τους πραγματικούς συσχετισμούς όπου εντάσσεται το
πρόβλημα, και η υλοποίηση της επίλυσής του τη συλλογιστική που εφαρμόζεται στον
πραγματικό χώρο.

Εκτός από συνθέσεις με πεπερασμένα βήματα, είναι δυνατό να εκφράσουμε ως ενιαίες
υπολογιστικές οντότητες και απεριόριστες δομές, όπου το νόημα του "απεριόριστου" εκφράζεται
μέσα από τη δυναμική διάσταση που προσθέτει στις οντότητες μια περιεχόμενη διεργασία ή η
αλληλεπίδραση, και παίρνει τη μορφή άλλοτε της απεριόριστης παραγωγής οντοτήτων, ως πηγή
που τροφοδοτεί τη ζήτηση, άλλοτε της δυνατότητας απεριόριστης επέκτασης μέσω δημιουργίας
νέων οντοτήτων, αυτόματα ή από το χρήστη, συνθετικά / εξειδικευτικά / παραγωγικά.
Οι αφαιρετικές δυνατότητες του υπολογισμού είναι απεριόριστες: μπορούμε να δώσουμε
αφαιρετικές εκφράσεις, τόσο με τη μορφή γενίκευσης δεδομένων ειδικών περιπτώσεων όσο και
με τη μορφή απ' ευθείας δημιουργίας αφηρημένων οντοτήτων.
Μια εφαρμογή μπορεί να συμπεριλαμβάνει την είσοδο μεθόδων υπολογισμού που είναι ακόμα
άγνωστες στη φάση συγγραφής του κώδικα. Μπορούμε να αποδώσουμε έννοια αγνώστου
υπολογισμού σε μεταβλητή, ή να τοποθετήσουμε με "φυσικό" τρόπο στον αρχικό υπολογισμό,
έναν υπολογισμό που έρχεται κατά την εφαρμογή. Ακόμα, η ίδια η εκτέλεση μπορεί να παράγει
τις συναρτησιακές εκφράσεις που απαιτούνται σε επόμενα βήματα εφαρμογής.
Η υπολογιστική λογική (computational logic) του ΣΠ καθορίζεται από το μοντέλο του λλογισμού πολύ βαθύτερα από τα μοντέλα δίτιμης λογικής (προτασιακής και κατηγορηματικής),
διότι το λογικό μοντέλο που ακολουθείται, εκφράζεται με όρους σύνθεσης και εφαρμογής και
κυριολεκτικά συντίθεται εκ του μηδενός στα πλαίσια του μοντέλου του υπολογισμού. Αν και
είναι δυνατό να κτίσει ο χρήστης της συναρτησιακής γλώσσας το δικό του λογικό μοντέλο,
παρέχεται υλοποιημένο το σύνηθες δίτιμο προτασιακό, που αποτελεί τη βάση για κάθε γλώσσα
προγραμματισμού. Ο λογικός έλεγχος περνά εξ ολοκλήρου μέσα από συναρτησιακές συνθέσεις
και εφαρμογές, και εξ αιτίας από αυτό είναι πολύ γενικότερος του συνήθους διαδικαστικού (θα
δούμε αναλυτικά το νόημα αυτής της γενικότητας).
Μια συναρτησιακή σύνθεση (συναρτησιακή έκφραση ή εφαρμογή) μπορεί να παρακολουθεί
δυναμικά τις μεταβολές των συμβόλων (συναρτήσεων, μεταβλητών) που καλεί, με την έννοια οτι,
όταν η σύνθεση κληθεί προς εκτέλεση, θα αναφερθεί στο τρέχον περιεχόμενο των συμβόλων. Οι
όροι "εφαρμογή" και "εκτέλεση" διαφοροποιούνται κατά το οτι μπορούμε να διαχωρίσουμε τη
φάση προσδιορισμού μιας έκφρασης που είναι εφαρμογή συνάρτησης πάνω σε ορίσματα, από τη
φάση παραγωγής του αποτελέσματος (καθυστερημένος υπολογισμός).
Η σύνθεση συναρτήσεων υπόκειται σε ένα και μοναδικό περιορισμό, τη διατήρηση ενός
πρωτοκόλλου επικοινωνίας μεταξύ των συντιθεμένων μερών, δηλ. την καταλληλότητα: i) του
πλήθους των στοιχείων εισόδου, ii) των τύπων τους, iii) της σχετικής μεταξύ τους θέσης τους, για
κάθε επί μέρους υπολογισμό (κόμβο) της σύνθεσης (στη περίπτωση των αντικειμένων, το
πρωτόκολλο αυτό περιλαμβάνει και άλλα στοιχεία). Είναι δυνατή η επίτευξη σύνθεσης
υπολογιστικών οντοτήτων μέσω εκτέλεσης συνάρτησης, και η σύνθεση που προκύπτει να
καθορίζεται να είναι δυναμική, δηλ. τέτοια που να παρακολουθεί την εξέλιξη των συστατικών
μερών της, ή στατική, δηλ. τέτοια που να που συνδέεται με συγκεκριμένη κατάσταση των
οντοτήτων τις οποίες χρησιμοποιεί, ανεξάρτητα του τί μεταβολές έχουν επέλθει.
38

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

1.3.2 Συναρτησιακές γλώσσες προγραμματισμού
Αν και είναι δυνατό, βέβαια σε κάποιο βαθμό αποτελεσματικότητας και ευρύτητας, να εφαρμόσει
κανείς τεχνικές Συναρτησιακού Προγραμματισμού σε κάθε γλώσσα προγραμματισμού, υπάρχουν
διάφορες γλώσσες που σχεδιάστηκαν με σκοπό να υποστηρίξουν ειδικά αυτό το είδος
προγραμματισμού. Η υπολογιστική υλοποίηση ακολουθεί παρεμφερείς προσεγγίσεις, με την
ανάπτυξη από αντίστοιχους compilers, όπως οι παρακάτω:
α'.
Η γλώσσα ML: Είναι μια καθαρά συναρτησιακή γλώσσα, βασισμένη στον λ-λογισμό.
Είναι χρήσιμη από την άποψη οτι αποτελεί καθαρή υλοποίηση του λ-λογισμού. Θεωρείται η
βασικότερη γλώσσα Συναρτησιακού Προγραμματισμού. Παρουσιάζει το πλεονέκτημα οτι πολλοί
θεμελιώδεις συναρτησιακοί συσχετισμοί όπως η αναδρομή, το ταίριαγμα φορμών (pattern
matching), η μεταβλητή κλήση συνάρτησης, τα δένδρα και τα "tuples", αποτελούν για τη γλώσσα
θεμελιακές υπολογιστικές δυνατότητες και δομές, και όχι απλά "υποστηριζόμενες" μέσω τεχνικών
προγραμματισμού που χρησιμοποιούν τις άλλες διαθέσιμες δομές. Ο λ-λογισμός υποστηρίζεται
άμεσα, και όχι μέσω εφαρμογών, όπως στη Lisp που χρησιμοποιεί έμμεσους δρόμους για την
υποστήριξη λ-εκφράσεων. Είναι μια γλώσσα πολύ χαμηλού επιπέδου σε σχέση με τη
μοντελοποίηση του υπολογισμού, και για το λόγο αυτό είναι θεωρητικά μεν ιδανική, αλλά πολύ
δύσχρηστη για την ανάπτυξη πραγματικού προγράμματος. Οι διαθέσιμοι compilers επιτρέπουν
υλοποίηση αλγορίθμων μόνο σε επίπεδο παραδειγμάτων.

β'.
Η γλώσσα Lisp: Επί πλέον από το συναρτησιακό τρόπο απόδοσης νοημάτων, υποστηρίζει
άμεσα το διαδικαστικό και τον προσανατολισμένο στα αντικείμενα, και έμμεσα το λογικό.
Διαθέτει εξαιρετικά πλούσιες και ευέλικτες δομές υλοποίησης και διαχείρισης τύπων,
αντικειμένων, συνδέσεων, και όλα εκφρασμένα με ένα ομοιόμορφο πλαίσιο φορμαλισμού, απλό
και πραγματικά αποτελεσματικό, τόσο για την υλοποίηση συνθέτων αλγορίθμων όσο και για την
εκτέλεσή τους. Διαθέτει μεγάλο πλήθος ενσωματωμένων συναρτήσεων που επιτρέπουν ανάπτυξη
πραγματικού προγράμματος και όχι απλώς "μικρά μοντέλα προς επίδειξη". Το γεγονός οτι ο λλογισμός υποστηρίζεται δια μέσου εφαρμογής συναρτήσεων και όχι θεμελιακά, δεν είναι
σημαντικό μειονέκτημα, διότι δεν επιβάλλει απαράβατους περιορισμούς στην έκφραση. Το κύριο
πλεονέκτημά της είναι οτι έχει αναπτυχθεί μια ιδεατή γλώσσα - πρότυπο, η Common Lisp (CL)
39

και ο κώδικας που γράφεται σ' αυτήν είναι μεταφέρσιμος σε κάθε compiler που υποστηρίζει την
CL, σε κάθε πλατφόρμα.
γ'.
Η γλώσσα Scheme: Ουσιαστικά είναι μια μετεξέλιξη της Lisp. Προσφέρει απλούστερη
σύνταξη σε ότι αφορά την κλήση μεταβλητών συναρτήσεων και λ-εκφράσεων. Χρησιμοποιείται
τόσο για εκπαιδευτικούς σκοπούς (κυρίως στις ΗΠΑ) όσο και για ανάπτυξη πραγματικών
εφαρμογών. Το μειονέκτημά της είναι οτι αποτελεί περισσότερο εμπορικό προϊόν παρά ένα
τυπικό ιδεατό θεωρητικό μοντέλο, οπότε δεν έχουμε την ευχέρεια της μεταφερτότητας.
δ'.
Η γλώσσα Miranda: Διαθέτει σειρά ευκολιών, τόσο ως εντολές όσο και ως μεθοδολογία
για την ανάπτυξη συσχετισμών. Δεν είναι πολύ αποτελεσματική, και κινείται σε ενδιάμεσα
επίπεδα, ανάμεσα στον λ-λογισμό και τις ευκολίες για το χρήστη. Χρησιμοποιείται καθαρά για
εκπαιδευτικούς σκοπούς (κυρίως στη Μ. Βρετανία) και δεν έχει χώρους πραγματικών
εφαρμογών. Η λέξη "μεταφερτότητα" δεν έχει καμμία έννοια στα πλαίσιά της.
ε'.
Η γλώσσα Haskell: Είναι νεότερη από πλευράς έρευνας, αλλά ακόμα σε επίπεδα
πειραματισμού. Εκτός των χαρακτηριστικών της Lisp διαθέτει και άλλα, που θεωρείται από
πολλούς οτι καθιστούν τη γλώσσα αυτή το θεωρητικά πληρέστερο υπολογιστικό πλαίσιο.
Υποστηρίζει ταίριαγμα μεταβλητών, "pattern matching", "tuples", απ' ευθείας μεταβλητή κλήση
συνάρτησης. "Βαριά" συμβολική, επιτρέπει συγγραφή κώδικα σε εκπληκτικά μικρά μεγέθη.
Διαφαίνεται μια τάση να αντικαταστήσει τη Miranda για εκπαιδευτικούς σκοπούς.
ς'.
Στις συναρτησιακές γλώσσες μπορούμε να κατατάξουμε και το μαθηματικό πακέτο
ΜΑTLAB (ΜΑΤrix LABoratory), που επιτρέπει τη διαμόρφωση και σύνθεση συναρτησιακών
εκφράσεων, ακολουθώντας μια προσέγγιση μέσω πινάκων, εξαιρετικά αποτελεσματική για τον
υπολογισμό τιμών και τη γραφική απεικόνιση μαθηματικών σχέσεων, περιορισμένη όμως σε ότι
αφορά την εν γένει έκφραση νοημάτων.

1.3.3 Συναρτησιακός Προγραμματισμός με τη Lisp

Στο παρόν θα χρησιμοποιήσουμε τη Lisp, και πιο συγκεκριμένα το ιδεατό μοντέλο της Common
Lisp (CL) που είναι μια γλώσσα που είναι οργανωμένη κάτω από τη "φιλοσοφία" του ΣΠ ,
εκφρασμένη με όρους λ-λογισμού. Υποστηρίζει την πρακτική της μεταφερτότητας (portability)
ανάμεσα σε ένα αρκετά ευρύ πλήθος εδικών για κάθε πλατφόρμα εκδόσεων. Είναι γλώσσα
εξαιρετικά αποτελεσματική σχεδόν για κάθε είδους υπολογισμό.

Βασικές συναρτήσεις "εκκίνησης με τη Lisp"
Πλεονέκτημα για τη μελέτη αυτής της γλώσσας είναι οτι, παρά το τεράστιο πλήθος διαθεσίμων
συναρτήσεων και συμβόλων (πάνω από χιλιάδα στο "common" μέρος, άλλη μια πεντακοσάδα στο
CLOS και τα γραφικά, όπου πρέπει να προσθέσουμε και τα εξειδικευμένα σύμβολα της έκδοσης
του compiler, της τάξης πολλών εκατοντάδων) η συναρτησιακή ανάπτυξη κώδικα μπορεί να
βασιστεί αρχικά σε απλούς κανόνες σύνταξης, απλούστερους από οποιαδήποτε άλλη γλώσσα, και
σε ένα πολύ μικρό αριθμό βασικών συναρτήσεων, όπως:
SETQ και SETF που είναι οι συναρτήσεις καταχώρησης∙ CONS που είναι η συνάρτηση
40

σύνδεσης∙ COND που είναι η συνάρτηση επιλογής∙ AND , OR , NOT που είναι οι βασικές
συναρτήσεις λογικής∙ οι συναρτήσεις που εισχωρούν σε σύνδεση: CAR που δίνει την κεφαλή
της σύνδεσης και CDR το σώμα∙ DEFUN που επιτρέπει ορισμό νέων συναρτήσεων∙ QUΟΤΕ
και EVAL που είναι οι συναρτήσεις παρεμπόδισης και πρόκλησης υπολογισμού∙ FUNCALL
που προκαλεί εφαρμογή συνάρτησης∙ PROGN που προκαλεί εκτέλεση ακολουθίας
συναρτήσεων∙ LIST που συνενώνει οντότητες σε λίστα.

Το πρότυπο της CL απέναντι στις υλοποιήσεις του
Tο πρότυπο της CL περιλαμβάνει ένα σύνολο από αρχικά (primitive) σύμβολα, χωρίς οτιδήποτε
που να αφορά συγκεκριμένη πλατφόρμα. Στην πράξη όμως, οι διάφορες υλοποιήσεις της CL
παρουσιάζουν κάποιες λειτουργικές ιδιομορφίες, οφειλόμενες στις επιλογές των κατασκευαστών:
συχνά λειτουργούν κάτω από απλουστεύσεις του ιδεατού μοντέλου, ή/και με τρόπο εφαρμογής
υπολογισμού που "ερμηνεύει" το θεωρητικό μοντέλο του λ-λογισμού.

Κάποιες, μικρές ή μεγαλύτερες, ασυμβατότητες που θεωρητικά δεν θάπρεπε να υφίστανται,
μεταξύ εκδόσεων που αποκαλούνται "συμβατές με Common Lisp", προκαλούν δυσχέρεια
μεταφοράς κώδικα μεταξύ διαφόρων compilers.

Το πρόβλημα αυτό έχει δύο ρίζες: i) υπάρχει μια εγγενής δυσχέρεια στην ίδια τη σύλληψη της
Lisp: οι λ-εκφράσεις τοποθετούνται κάτω από εφαρμογή συνάρτησης (και όχι το αντίθετο, όπως
θέτει η θεωρία: στη Lisp: το σύμβολο λ είναι συνάρτηση)∙ ii) κάποιοι compilers τοποθετούν την
τυποποίηση ψηλότερα από τον υπολογισμό και άλλοι χαμηλότερα (γι' αυτό και σε κάποιους Lisp
compilers η σχέση λx.x δεν είναι δεκτή πριν να καθοριστεί, άμεσα ή έμμεσα, ο τύπος του x, ενώ
σε άλλους δεν ενοχλεί).
Η "απαλλαγή" από την πλατφόρμα προσδίδει στην CL ευελιξία, αλλά αφήνει ένα κενό, που
αφορά ακριβώς τις δυνατότητες διαχείρισης του υπολογιστικού συστήματος στο οποίο
υλοποιείται. Το κενό αυτό πρέπει να καλύψει ο χρήστης διερευνώντας και αξιοποιώντας τις πέραν
της CL επεκτάσεις του compiler που χρησιμοποιεί. Για το σκοπό αυτό, οι compilers που
υποστηρίζουν το πρότυπο της CL διαθέτουν, εκτός από τα primitives της CL, και ένα σημαντικό
πρόσθετο πλήθος από πρωτογενείς συναρτήσεις που διαχειρίζονται αρχεία, μνήμη, γραφικά,
παράθυρα, multimedia, επικοινωνία, σύνθετες συναρτήσεις – προετοιμασμένους αλγόριθμους,
που διευκολύνουν τον προγραμματισμό από την άποψη στην οποία εντοπίζει το ζήτημα ο
κατασκευαστής του compiler, καθώς και ένα μεγάλο πλήθος μεταβλητών του συστήματος που
καθορίζουν το περιβάλλον και τη συμπεριφορά του compiler.
Στους Lisp compilers, κατά κανόνα, ορισμένες θεμελιακές συναρτήσεις είναι υλοποιημένες σε
επίπεδο δυαδικού κώδικα (όχι ακριβώς οι ίδιες για κάθε compiler) ενώ οι λοιπές αποτελούν
προγράμματα σε Lisp τα οποία απλώς απαλλάσσουν τον προγραμματιστή από τον κόπο να τα
συνθέσει. Διαφέρουν οι συναρτήσεις του πρώτου είδους από τις του δεύτερου κατά το οτι δεν
είναι δυνατή οποιαδήποτε μεταβολή τους, ενώ οι δεύτερες έχουν "ορατό" κώδικα και είναι
επαναπροσδιορίσιμες από το χρήστη (αν ο κατασκευαστής το επιτρέπει, που συνήθως
μεταφράζεται σε "να αγοραστεί το δικαίωμα").
Λόγω του μεγάλου πλήθους αυτών των συναρτήσεων δίνονται συνήθως ομαδοποιημένες σε
"πακέτα" που λειτουργούν ως πλαίσια εργασίας για συγκεκριμένη κατηγορία προβλημάτων. Τα
41

πακέτα αυτά αποτελούν διακεκριμένες κατηγορίες συναρτήσεων που φορτώνονται επιλεκτικά,
και διακρίνονται μεταξύ τους με κάποια τεχνική συνδεδεμένη με μηχανισμούς της Lisp
(συναρτήσεις διαχείρισης πακέτων) ενώ σε φτωχότερους compilers δίνονται απλά ως
ταξινομημένα αρχεία.

Οι παραβάσεις σε σχέση με το θεωρητικό μοντέλο του λ-λογισμού, και της ιδεατής CL από τις
διάφορες υπολογιστικές υλοποιήσεις της Lisp, είναι τριών ειδών, που τις αντιμετωπίζει ο χρήστης
αντίστοιχα:
α'.
Να προσφέρει ο compiler λιγότερες βασικές συναρτήσεις σε σχέση με τις θεωρούμενες
ως πρωτογενείς συναρτήσεις της CL. Τότε η γλώσσα προγραμματισμού είναι φτωχή αλλά αυτό
διορθώνεται σχετικά, με την ανάπτυξη των καταλλήλων συναρτήσεων ως προγραμμάτων, στα
πλαίσια βέβαια των δυνατοτήτων τής συγκεκριμένης διαλέκτου. Ήταν η συνηθισμένη κατάσταση
στην επoχή των "μικρών υπολογιστών" (Intel 8088, Motorola 68000…) αλλά και των πρώτων
εκδόσεων για μεγαλύτερους υπολογιστές.
β'.
Να συνυπάρχουν συναρτήσεις εκτός των προβλεπομένων από την CL. Τότε έχουμε
αυξημένη αποτελεσματικότητα (λόγω της εκμετάλλευσης του συγκεκριμένου συστήματος) αλλά
λιγότερη συμβατότητα με άλλους compilers. Ο χρήστης έχει να επιλέξει ανάμεσα στον
προγραμματισμό με χρήση μόνον των "common" συναρτήσεων (οπότε διαθέτει φτωχή βάση
συναρτήσεων) ή με εκμετάλλευση ολόκληρου του πακέτου (οπότε χάνει τη μεταφερτότητα).
Παράδειγμα αυτής της κατηγορίας είναι η MU-LISP (Microsoft, για DOS)
γ'.
Η υλοποίηση να υποστηρίζει το θεωρητικό μοντέλο του λ-λογισμού με κάποιο
"ιδιόμορφο" τρόπο. Τότε έχουμε μια γλώσσα που σε ακραίες περιπτώσεις υπολογισμού δεν
ακολουθεί τα αναμενόμενα βάσει του λ-λογισμού και η μεταφορά κώδικα σε άλλο compiler είναι
δυσχερής (χρειάζεται, εκτός από κάποιους ορισμούς συναρτήσεων, και βασικές μετατροπές στον
κώδικα). Τυπική υλοποίηση της CL, που αποτέλεσε την πρότυπη υλοποίηση της CL και
παραμένει, είναι η LISP της Digital (τρέχει σε VAX κάτω από VMS) [Stee86].

Εφαρμογές του Συναρτησιακού Προγραμματισμού
Ο Συναρτησιακός Προγραμματισμός δεν αποτελεί μόνο ένα πειραματικό μοντέλο
προγραμματισμού, αλλά έχει αξιοποιηθεί πολύπλευρα. Ένα σημαντικό μέρος του λογισμικού που
κατατάσσεται στο χώρο της Τεχνητής Νοημοσύνης έχει συντεθεί με τεχνικές ΣΠ, κυρίως
χρησιμοποιώντας κάποια έκδοση της Lisp: Έμπειρα συστήματα διάγνωσης ασθενειών (πχ.
MYCIN), επιλυτές μαθηματικών προβλημάτων (πχ. MUMATH), γεωγραφικά συστήματα,
αλληλεπίδραση χρήστη - μηχανής με χρήση φυσικής γλώσσας (θεμελιακές είναι οι εργασίες του
Schank [Sch+81]) και γενικότερα διαχείριση γνώσης, αλληλεπίδραση ανθρώπου - μηχανής με
χρήση συμβόλων, μάθηση μηχανής, εκπαίδευση, επεξεργασία φυσικής γλώσσας, επεξεργασία
εικόνας, όραση (vision) και γενικότερα ρομποτική, σχεδιαστικά συστήματα (πχ. AUTOCAD), κά.
Το ΚΕΕ (για SUN workstations) είναι ένα πακέτο ανάπτυξης εμπείρων συστημάτων, ανεπτυγμένο
σε Lisp και ανοικτό προς τη Lisp, με ενδιαφέρουσες δυνατότητες.
Στα επόμενα πιο αναλυτικά τις δυνατότητες και τους περιορισμούς που συνθέτουν τη
"φιλοσοφία" του ΣΠ. Ιδιαίτερο ενδιαφέρον παρουσιάζει το θεμελιακό άρθρο των Sinclair και
Moone "The Philosophy of Lisp" [Sin+91] που αναφέρεται γενικότερα στη φιλοσοφία της Lisp.
42

Κεντρικό χαρακτηριστικό του ΣΠ είναι οτι, με τη δυνατότητα εισόδου συναρτησιακής έκφρασης
(δηλ. αλγόριθμου) ή εφαρμογής (δηλ. εκτέλεσης αλγόριθμου) από το χρήστη σε οποιοδήποτε
επίπεδο (αρκεί να δέχεται είσοδο), επαναπροσδιορίζει το ίδιο το νόημα της αλληλεπίδρασης. Θα
αναφερθούμε διεξοδικά στο χαρακτηριστικό αυτό.

43

1.4 Ασκήσεις
Διαγράμματα με "ορθογώνια και βέλη" και άτυπη μορφή λ-εκφράσεων:
1. Να δώσετε διάγραμμα το οποίο να παριστά συνάρτηση με όνομα F δύο ορισμάτων που να
είναι λίστες ίσου μήκους, και να επιστρέφει ως αποτέλεσμα τη λίστα των ζευγών των
αντίστοιχης τάξης στοιχείων.

2. α) Να παραστήσετε με διάγραμμα: τη συνάρτηση αντιστροφής λίστας, τη συνάρτηση
συνένωσης λιστών, την παράσταση x+2.
β) Να παραστήσετε τη φόρμα υπολογισμού που προκύπτει από την εφαρμογή τής λ-έκφρασης
που παριστά το x+2 πάνω στον αριθμό 3 .
3. Να δώσετε διάγραμμα το οποίο να παριστά ως λ-έκφραση δύο μεταβλητών την παράσταση
x+y . Στη συνέχεια:
α) Να παραστήσετε την έκφραση που προκύπτει από την εφαρμογή αυτής της λ-έκφρασης
πάνω στα ορίσματα 3 και 4 .
β) Να δώσετε την έκφραση που προκύπτει από την εφαρμογή της λ-έκφρασης πάνω στα
ορίσματα 3 και λ(x).(4-x) .
4. Να δώσετε τη λ-έκφραση που δηλώνει την μαθηματική παράσταση: 3cos 2(2x) και να την
εφαρμόσετε διαδοχικά πάνω στις ακόλουθες τρεις τιμές: π , 3x+1 , siny

5. Να δώσετε τη λ-έκφραση που δηλώνει την μαθηματική παράσταση (x-1)(y+1) και να την
εφαρμόσετε διαδοχικά πάνω στις ακόλουθα ζεύγη ορισμάτων: (1 , 2) , (z , w) , (1 , x) , (3x+1 ,
2x-1) , (x-1 , y+1) , (2 , y-3)

44

2 Ανάπτυξη Συναρτησιακών Συνθέσεων
Στο κεφάλαιο αυτό θα δούμε τους κύριους τρόπους ανάπτυξης συναρτησιακών συνθέσεων,
χρησιμοποιώντας τις βασικές δομές της γλώσσας Lisp.
H ανάπτυξη συναρτησιακών συνθέσεων είναι τυπικά ταυτόσημη με την ανάπτυξη αλγορίθμων,
και η εφαρμογή συναρτησιακής έκφρασης σε ορίσματα είναι τυπικά ταυτόσημη με την εφαρμογή
αλγόριθμου σε συγκεκριμένη συμβολοσειρά εισόδου. Διαφέρει όμως σημαντικά το περιεχόμενο
που δίνουμε στην έννοια "ανάπτυξη κώδικα" σε ένα συναρτησιακό περιβάλλον, διότι
συμπεριλαμβάνει ως στοιχειώδη δυνατότητα τη διαρκή εξέλιξη του χώρου των εκτελέσιμων
συνθέσεων, γεγονός που δίνει μια δυναμική διάσταση στην έννοια "πρόγραμμα".

2.1 Εισαγωγικά στοιχεία για τη γλώσσα Lisp
Ιστορικά στοιχεία
Η Lisp είναι μια γλώσσα προγραμματισμού που θεμελιώθηκε το 1960 από τον McCarthy
[McCa60], [McCa80]και θεωρείται ως η πληρέστερη γλώσσα ανάπτυξης συναρτησιακού
προγράμματος. Αποτελεί μια προσέγγιση υλοποίησης του λ-λογισμού, τον οποίο θεμελίωσε ο
Church το 1937 ως μοντέλο του υπολογισμού [Chur47].

Η αρτιότητα και η ευκολία σύνταξης συναρτησιακών εκφράσεων και εφαρμογών τους στη Lisp
είναι τέτοια που από το 1963 η γλώσσα έχει πάρει την τελική της μορφή και παραμένει από τότε
αναλλοίωτη στα κύρια χαρακτηριστικά της. Βελτιώσεις και επεκτάσεις έχουν γίνει πολλές από
τότε, σε διάφορες διαλέκτους, που αφορούν τον εμπλουτισμό των διαθεσίμων συναρτήσεων, τη
διαχείριση του υπολογιστικού συστήματος και την επικοινωνία, την υποστήριξη από παράλληλα
συστήματα, τις τεχνικές προγραμματισμού καθώς και παροχή των συνήθων σε άλλες γλώσσες
ευκολιών (παραθυρικό σύστημα, προετοιμασμένα buttons, βοήθεια, γραφικά, διαχείριση χώρου
συμβόλων, κά). Ουσιαστικότερη εξέλιξη απετέλεσε η υποστήριξη αντικειμένων, όπου μετά από
μια χαοτική ανάπτυξη διαφόρων προσεγγίσεων, θεμελιώθηκε κατά το 1986 το πρότυπο "CLOS"
(Common Lisp Object System), αρχικά σαν επέκταση της CL και αργότερα ενσωματωμένο στην
CL.
Οι υπολογιστές τής πρώτης εποχής έθεταν κάποια τεχνικά όρια στη Lisp πολύ στενά για τους
προγραμματιστικούς στόχους και η χρήση της Lisp περιορίστηκε στην μοντελοποίηση, την
τεχνητή νοημοσύνη και τα έμπειρα συστήματα, τομείς όπου οι άλλες γλώσσες ήταν συγκριτικά
πολύ αδύναμες. Για εμπορικές εφαρμογές αριθμητικού υπολογισμού βέβαια, ούτε σκέψη δεν
μπορούσε να γίνει.
Με την εμφάνιση της Prolog, για πρώτη φορά το '72 σε περιορισμένη έκταση πειραματισμών και

πλατειά μετά το '80, άρχισε μια επιστημονική διαμάχη γύρω από το ποια από τις δύο γλώσσες
είναι καταλληλότερη για την Τεχνητή Νοημοσύνη. Οι οπαδοί της Lisp παρέτασσαν το βάθος που
μπορεί να φθάσει ο προγραμματισμός χωρίς να χάνει την αποτελεσματικότητά του, και οι οπαδοί
της Prolog αντέτασσαν πιο απλουστευμένες λογικές εκφράσεις με την ίδια λειτουργικότητα.
Προς τα τέλη της δεκαετίας του '80, όπου οι νεότερες "προσωπικές" μηχανές μπορούσαν να
ανταποκριθούν στις απαιτήσεις υπολογισμού και μνήμης της Lisp, και ταυτόχρονα οργανώθηκε η
υποστήριξη προγραμματισμού προσανατολισμένου στα αντικείμενα, η Lisp έγινε ξανά το
επίκεντρο μελετών, και πληθώρα σχετικών δημοσιεύσεων εμφανίζονται από τις αρχές της
δεκαετίας του '90.
Η σημερινή μορφή της Common Lisp (πρότυπο ANSI CL) είναι συμβατή με τις προηγούμενες,
αλλά διαφέρει στο οτι περιλαμβάνει το CLOS, υποστηρίζει το Unicode set χαρακτήρων,
περιλαμβάνει πακέτο γραφικών (packages "common-graphics" και "common-graphics-user"), και
τείνει να υπακούσει αυστηρότερα στις προδιαγραφές X3J13 [Bob+89].

Ευχέρεια προγραμματισμού και χρησιμότητα του ΣΠ
Η συναρτησιακή προσέγγιση έχει ορισμένα χαρακτηριστικά που διευκολύνουν κάθετα τον
προγραμματισμό, και, χωρίς να είναι αναντικατάστατα, ανοίγουν δρόμους χρήσης του
υπολογιστή που πολύ δύσκολα θα μπορούσαν να ανοιχθούν μέσα από άλλες προσεγγίσεις.
Αναφέραμε ήδη τέτοια χαρακτηριστικά, όπως:
– ακολουθεί το θεμελιακό θεωρητικό μοντέλο του υπολογισμού, το λ-λογισμό, επιτρέποντας την
προγραμματιστική αξιοποίησή του
– ανοίγει εύκολους δρόμους για κάθε κατηγορίας μοντελοποίηση
– προσφέρεται για την ανάπτυξη αλληλεπιδρώντων περιβαλλόντων με κάθε έννοια και βάθος
που μπορούμε να δώσουμε στη λέξη "αλληλεπίδραση"
– η ανάπτυξη κώδικα είναι πάντα σε μορφή σύνθεσης και εφαρμογής συναρτήσεων

– κάθε πρόγραμμα που αναπτύσσεται, έχει πάντοτε δύο όψεις: υλοποίηση αλγόριθμων
επίλυσης ενός συνόλου προβλημάτων, και πλαίσιο περαιτέρω ανάπτυξης κώδικα με
συνθέσεις των ήδη ανεπτυγμένων τμημάτων∙ αυτό καθ' εαυτό το περιβάλλον ανάπτυξης
αποτελεί ένα σταδιακά προγραμματιζόμενο χώρο
– θα μπορούσαμε να παρομοιάσουμε τις συνθέσεις συναρτήσεων με αυτές που κάνει ο χρήστης
από ένα επιστημονικό κομπιουτεράκι, αλλά εδώ σε ένα εκπληκτικά συνθετότερο επίπεδο,
χωρίς να είναι ουσιαστικά δυσκολότερο.
– οι οντότητες που επεξεργαζόμαστε στον ΣΠ μπορούν να έχουν συνθετότητα (βάθος, πλάτος,
διασυνδέσεις) πρακτικά ανέφικτη για άλλες προσεγγίσεις, διατηρώντας πάντα την απλότητα
χρήσης των πρωτογενών οντοτήτων
– μπορούμε να πούμε με βεβαιότητα πως δεν υπάρχει αλγόριθμος που δεν μπορεί να υλοποιηθεί
σε Lisp∙ αν και αυτό θεωρητικά μπορούμε να το πούμε για κάθε καλά οργανωμένη
υπολογιστική γλώσσα, με τη Lisp έχουμε τη διαφορά οτι συνδυάζει το κατανοητό, το

αποτελεσματικό, το εύχρηστο, το ευέλικτο και το πρακτικό.

2.2 Tύποι, φόρμες και υπολογισμός

Oι καλά σχηματισμένες εκφράσεις της Lisp αποκαλούνται φόρμες, οι οποίες και μόνον
αποτελούν τις υπολογιστικές οντότητες. Τα πάντα στη Lisp είναι φόρμες, όπου απλές φόρμες
είναι τα σύμβολα και οι σταθερές, και σύνθετες φόρμες είναι οντότητες που συντίθενται πάλι από
φόρμες. Οι χαρακτήρες σύνταξης είναι μόνον οι παρενθέσεις και το κενό διάστημα, και μόνο σε
ειδικές περιπτώσεις, που θα δούμε λεπτομερώς παρακάτω, χρησιμοποιούνται και οι εξής εννέα
χαρακτήρες: ' # , ` : \ " @ ;
Oι τύποι από φόρμες, τους οποίους διαθέτει η γλώσσα, οργανώνονται σε μια δενδροειδή δομή,
που καθορίζει τους τύπους δεδομένων (data types). Στο δένδρο αυτό εντάσσονται δυναμικά και οι
(υπο-)τύποι που δημιουργεί ο χρήστης. Η δενδροειδής διακλάδωση δεν είναι απόλυτη, διότι σε
ορισμένες περιπτώσεις είναι δυνατό να παραβιαστεί, επιτρέποντας προς τα κάτω συνενώσεις
κλάδων.
Ειδική περίπτωση υπολογιστικής οντότητας είναι το NIL που είναι ταυτόχρονα τύπου atom (είναι
η λογική τιμή "ψευδές"), τύπου list (είναι η κενή λίστα), και είναι το μοναδικό αντικείμενο του
τύπου null, που είναι ο μοναδικός υποτύπος των πάντων (και χαρακτηρίζει το γεγονός οτι "είναι
αλήθεια πως η κενή λίστα είναι κενή", χρήσιμο στον έλεγχο). Σε κάθε περίπτωση (με εξαίρεση το
null) ο χρήστης της γλώσσας μπορεί να επεκτείνει το δέντρο των τύπων, διακλαδίζοντας ή
συνενώνοντας υπάρχοντες κόμβους.

2.2.1 Το δένδρο των τύπων
Το παρακάτω δένδρο είναι απεριόριστα επεκτάσιμο κατά πλάτος, βάθος και έχουμε τη
δυνατότητα συνένωσης κλάδων.

Άτομα (τύπος atom) είναι τα απλά δομικά στοιχεία της Lisp. Άτομα είναι οι αριθμοί, οι
χαρακτήρες και τα σύμβολα.

Ακολουθία (τύπος sequence) είναι μια φόρμα που συντίθεται από απλούστερες, με υποτύπους:
πίνακα (τύπος array) και σύνδεση (τύπος cons , από το "construction"). Λίστα (τύπος list) είναι
μια σύνδεση από φόρμες, με ειδικό τρόπο ορισμένη.

Υποτύπος του πίνακα είναι το διάνυσμα (τύπος vector), του οποίου υποτύπος είναι η
συμβολοσειρά ή αλφαριθμητική ακολουθία (τύπος string), που είναι μια ακολουθία από
χαρακτήρες, δοσμένη με ειδικό συμβολισμό (ανάμεσα σε διπλά εισαγωγικά).

Αριθμός (τύπος number) είναι τύπος που έχει τους υπο-τύπους: πραγματικός (τύπος real),
μιγαδικός (τύπος complex). Ο πραγματικός έχει τους υποτύπους: ακέραιος (τύπος integer),
πηλίκο (τύπος ratio), δεκαδικός (τύπος float). Ο τύπος float έχει υποτύπους single-float και double-

float (το πλήθος δεκαδικών είναι συνήθως 7 και 14 αντίστοιχα). Ο τύπος integer έχει υποτύπους
fixnum (≤215) και bignum (>215)

Οι μιγαδικοί (τύπος complex) αναγράφονται ως διανύσματα δύο πραγματικών ( real)
συντεταγμένων, με τη φόρμα:
#c(<πραγματικό μέρος> <φανταστικό μέρος>)
(προσοχή: εδώ, τα σύμβολα ανισότητας στη γραφή <…string…> δεν είναι της Lisp αλλά τα
γράφουμε απλώς για να περικλείσουμε την προσδιοριζόμενη οντότητα).

Ο τύπος complex είναι υποτύπος του number. Οι συντεταγμένες μιγαδικού είναι τύπου real , του
υποτύπου που προκύπτει από τις πράξεις ή δηλώνεται (προσοχή: "real" σημαίνει "πραγματικός
αριθμός", όχι υποχρεωτικά "δεκαδικός", όπως σε άλλες γλώσσες).
Δομή (τύπος structure) είναι ένας ιδιαίτερος τύπος που διαφέρει από τους άλλους στο οτι, ενώ οι
τύποι κατά κανόνα χαρακτηρίζουν δεδομένες οντότητες, μια οντότητα τύπου structure
χρησιμοποιείται για το κτίσιμο από νέες οντότητες αυτού του τύπου. Χαρακτηρίζει οντότητες
που είναι ιεραρχικές δομές. Σημειώνουμε οτι ο όρος "δομή" χρησιμοποιείται και γενικότερα για
να χαρακτηρίσει ορισμένες σύνθετες οντότητες, που ανήκουν σε άλλους τύπους, και κατά
συνέπεια απαιτείται η κατάλληλη διευκρίνιση στην περίπτωση που σημαίνει τύπο.
O υποτύπος standard-class του τύπου structure περιλαμβάνει τις οντότητες τύπου κλάσης και
είναι το επίκεντρο του CLOS, που θα δούμε σε επόμενο κεφάλαιο.
Το δένδρο των τύπων έχει (στα κεντρικά σημεία του) ως εξής:

real
number
atom
form

complex

symbol

variable

standard-class

array
cons
list

ratio

character

sequence
structure

float
integer

vector

function

string

"δένδρο" των τύπων

Τα παραπάνω αποτελούν τους χρησιμότερους τύπους της CL και το δένδρο (που δεν είναι
αυστηρά δένδρο, διότι είναι δυνατό να συνενωθούν κλάδοι) δεν εξαντλείται με αυτούς.
Παρουσιάζονται διαφοροποιήσεις στις διάφορες διαλέκτους της Lisp (πχ. σε μερικούς compilers,
ο τύπος structure είναι κάτω από τον symbol). Ένα σύνολο τύπων, που αναγνωρίζονται ως

βασικοί από την CL, δίνεται στα επόμενα. Το πλήρες δένδρο που υποστηρίζει ο κάθε
χρησιμοποιούμενος compiler είναι σκόπιμο να αναζητηθεί στο εγχειρίδιό του.

Οι τύποι, ανάλογα με τη θέση τους στην ιεραρχία, χαρακτηρίζονται ως υποτύποι ή υπερτύποι
άλλων. Κάθε οντότητα ορισμένου τύπου έχει και όλους τους υπερτύπους της. Είναι δυνατό να
προσδιορίζουμε απεριόριστα σε βάθος και πλάτος νέους υποτύπους, αλλά εν γένει όχι
υπερτύπους.
Οι οντότητες τύπου symbol χαρακτηρίζονται ως δεσμευμένες (bound) ή αδέσμευτες (unbound),
ανάλογα με το αν έχουν –κατά τη στιγμή όπου ελέγχονται– περιεχόμενο (τιμή) ως μεταβλητές ή
όχι. Αντίστοιχα, μια λ-μεταβλητή χαρακτηρίζεται ως δεσμευμένη ή αδέσμευτη σε κάποια φάση
του υπολογισμού, ανάλογα με το αν η εκτέλεση της συνάρτησης στην οποία ανήκει, έχει
προκαλέσει απόδοση τιμής στη μεταβλητή αυτή ή όχι, τη στιγμή του ελέγχου.

Παρατηρήσεις (1)

► Το όνομα που δόθηκε στη ρίζα του δένδρου, "form" , δεν αποτελεί όνομα τύπου, και γράφηκε
για να γίνει σαφές οτι "τα πάντα στη Lisp είναι φόρμες". Για την ακρίβεια, ρίζα είναι η T (λογική
τιμή "αληθές"), που σημαίνει οτι "τα πάντα είναι αληθή" με εξαίρεση την κενή λίστα NIL
(ταυτόχρονα και λογική τιμή "ψευδές") . Ο συσχετισμός αυτός αναγνωρίζεται από την CL ως "τα
πάντα, ακόμα και το NIL , είναι υπερτύπου T ".
δηλαδή, με άλλα λόγια, το "ψευδές" είναι τύπου "αληθές" !!!
► Ο τύπος real δηλώνει πραγματικό αριθμό (σύμφωνα με το μαθηματικό νόημα) και όχι
δεκαδικό κινητής υποδιαστολής, όπως σε άλλες γλώσσες. Έτσι έχει υποτύπους του, τόσο τον
integer όσο και τον float.

► Αν και η μορφή μιγαδικού συντακτικά είναι αυτή του array και ειδικότερα του vector,
κατατάσσεται κάτω από τον τύπο number και όχι κάτω από τον vector.

► Συχνά αναφέρεται ο χαρακτηρισμός "macro" για κάποιες συναρτήσεις. Δεν πρόκειται για τύπο,
αλλά για διαφορετικό τρόπο ορισμού της συνάρτησης, τρόπο που απλώς προκαλεί ειδική
επεξεργασία των ορισμάτων της.
► Ο χαρακτήρας “;” σημειώνει οτι από κει και πέρα, στην ίδια γραμμή του κώδικα, ακολουθεί
σχόλιο. Χρησιμοποιούμε περισσότερα ";;;" για να διακρίνουμε επίπεδα μεταξύ των σχολίων,
κατά την ανάγνωση. Το σύμβολο "→" δεν είναι της Lisp αλλά το χρησιμοποιούμε για να
σημειώσουμε το επιστρεφόμενο από τη φόρμα αποτέλεσμα του υπολογισμού.

Παρατηρήσεις (2)
Ονόματα που δεν έχουν οριστεί ως σύμβολα ούτε είναι συμβολοσειρές, γίνονται δεκτά σε μορφή
συμβόλου, με τον όρο οτι θα αποτραπεί ο υπολογισμός τους (πχ. θέτοντάς τους το πρόθεμα
"quote", όπως θα δούμε). Διακρίνουμε δύο κατηγορίες από compilers, με διαφορετική
συμπεριφορά στο ζήτημα αυτό:
α'.

Σε νεότερους compilers τέτοια ονόματα αποτελούν αυτόματα σύμβολο με την απλή

αναφορά τους, αλλά δεν έχουν τιμή και ο υπολογισμός τους θα προκαλέσει σφάλμα. Κάθε εκ των
υστέρων προσδιορισμός του ονόματος ως συμβόλου, σε τέτοιο compiler, επιδρά στις αναφορές
που έχουν ήδη γίνει σ' αυτό, δηλ. όταν αργότερα θα "περάσει" ο υπολογισμός από κάποια φόρμα
που περιέχει το όνομα αυτό, θα το αντιμετωπίσει ως σύμβολο, με τη νέα του τιμή και τις λοιπές
συσχετίσεις που έχει λάβει (πχ. ιδιότητες). Μπορούμε να χειριστούμε εκ των υστέρων ως
σύμβολο ένα τέτοιο όνομα:
(setq Jean 'French)
; το όνομα Jean ορίζεται ως σύμβολο με τιμή French
; το όνομα French δεν έχει οριστεί ως σύμβολο
(eval Jean) → Error …

Το σύμβολο Jean στην τελευταία έκφραση οδηγεί αυτόματα στην τιμή του, όπως οποιαδήποτε
αναγραφή συμβόλου, δηλ. στη θέση του Jean τίθεται κατά την "από μέσα προς τα έξω" εκτέλεση
αυτόματα η τιμή French. Η εφαρμογή της EVAL οδηγεί σε υπολογισμό του ορίσματος (που από
μόνο του υπολογίζεται πριν τον υπολογισμό τής EVAL) δηλ. αναζητά την τιμή της τιμής τού
Jean, που φυσικά δεν έχει, και προκύπτει σφάλμα.
Αν τώρα δώσουμε:
(setq French 'European)
; το όνομα French ορίζεται ως σύμβολο με τιμή European
(eval Jean) → European

Δηλαδή, η τιμή του συμβόλου Jean , που είναι French , αναγνωρίστηκε ως το σύμβολο French
που ορίστηκε, και ο υπολογισμός οδήγησε στην τιμή του, που είναι European .

β'.
Αντίθετα, σε παλαιότερους compilers η αναφορά ονόματος (ως τιμή συμβόλου) χωρίς να
έχει προηγουμένως οριστεί αυτό το όνομα ως σύμβολο, δεν παρακολουθεί μεταγενέστερους
προσδιορισμούς του ως συμβόλου, οπότε πρέπει να τηρείται αυστηρά η αναγκαία διαδοχή γραφής
των φορμών, σύμφωνα με τη διαδοχή των αναφερομένων σ' αυτές κλήσεων των συμβόλων. Αυτό
σημαίνει απλά οτι, αν προσδιοριστεί εκ των υστέρων κάποιο όνομα ως σύμβολο, πρέπει να
επανοριστεί κάθε φόρμα που το περιέχει. Για το λόγο αυτό, σε τέτοιο compiler, η παρακάτω
διαδοχή γραφής στον πηγαίο κώδικα δεν οδηγεί σε σύνδεση του όρου man με τον όρο Greek:
(setq man 'George)
(setq George 'Greek)
ο όρος - τιμή Greek εδώ δεν είναι προσπελάσιμος μέσω του man , διότι όταν δόθηκε η τιμή
George αυτό δεν ήταν σύμβολο:
(eval man) → Error…

Αν όμως τα παραπάνω δοθούν στην ακόλουθη διαδοχή γραφής:

(setq George 'Greek)
(setq man 'George)
το σύμβολο man έχει τιμή το σύμβολο George το οποίο έχει ήδη τιμή το σύμβολο Greek , και
έτσι από την αλυσιδωτή σύνδεση έχουμε πρόσβαση από το man στο
με δύο βήματα
υπολογισμού (ένα το αυτόματο "man → George" και ένα της εκτέλεσης του EVAL ) :

→ Greek
Επομένως είναι αναγκαίο να ελέγξει ο χρήστης αν ο compiler που διαθέτει είναι του πρώτου ή
του δεύτερου είδους, διότι η αλληλουχία των συνδέσεων από τιμή σε τιμή της τιμής κοκ. σε
περισσότερα βήματα θα προκαλέσει διαφορετικό αποτέλεσμα μετά από κάποια παρέμβαση σε
ενδιάμεσο βήμα.
(eval man)

Στην Allegro CL v.5 (Franz Lisp), που ανήκει στην πρώτη κατηγορία, έχουμε:
(setq a 'b)
; προσδιόρισε σύμβολο a με τιμή b
a
; υπολόγισε την τιμή του α
→ B
(απαντά οτι το a έχει τιμή b )
(eval a)
; υπολόγισε την τιμή της τιμής του a
→ Error: Unbound variable B in EVAL
(setq b 3)
; δώσε στο σύμβολο b τιμή 3
(eval a) → 3

Παρατηρήσεις (3)
► Είναι απαραίτητο να αντιδιαστείλουμε τις έννοιες "δέσμευση συμβόλου - μεταβλητής σε
τιμή", και "δέσμευση λ-μεταβλητής σε τιμή":

α'.
H δέσμευση συμβόλου - μεταβλητής σε τιμή έχει τα εξής χαρακτηριστικά:
– Απόδοση τιμής σε σύμβολο, αν η τιμή είναι σταθερά, σημαίνει καταχώρηση αντιγράφου της
σταθεράς.
– Αν η τιμή είναι σύμβολο ή έκφραση υπολογισμού, υπολογίζεται πρώτα, λόγω της από μέσα
προς τα έξω τάξης που ακολουθεί η Lisp, και το αποτέλεσμα του υπολογισμού αποδίδεται ως
τιμή στο σύμβολο. Αν η τιμή είναι σύμβολο ή έκφραση υπολογισμού και έχει τεθεί
καθυστέρηση, το σύμβολο ή ο υπολογισμός καταχωρούνται αυτούσια. Με τον τρόπο αυτό,
στην τελευταία περίπτωση η τιμή του συμβόλου παρακολουθεί τις μεταβολές της οντότητας
που έχει ως τιμή, και έτσι πετυχαίνουμε σύνδεση της πρώτης με τη δεύτερη.
– Η σύνδεση αυτή είναι μόνιμη, μέχρι να ζητηθεί αλλαγή της.
– Εάν η οντότητα - τιμή είναι επίσης σύμβολο - μεταβλητή, μπορεί επίσης να δεσμευτεί, και
έτσι μπορούμε διαδοχικά να δημιουργήσουμε αλυσίδα συνδέσεων.
– Πιο πλατειά, εάν η οντότητα - τιμή είναι λίστα συμβόλων - μεταβλητών, και αυτά τα σύμβολα
με τη σειρά τους παίρνουν τιμή λίστες συμβόλων - μεταβλητών, κοκ. σε πεπερασμένο βάθος,
σχηματίζουμε ένα δένδρο.
– Δεν απαγορεύεται "κύκλος", δηλαδή το δεύτερο σύμβολο που δίνουμε ως τιμή του πρώτου
συμβόλου να έχει ως τιμή το πρώτο (σε μορφή υπό καθυστέρηση υπολογισμού).
– Επίσης, δεν απαγορεύεται σύμβολο να πάρει τιμή (υπό καθυστέρηση) τον εαυτό του.
β'.
Η δέσμευση λ-μεταβλητής σε τιμή έχει τα εξής χαρακτηριστικά:
– Η έννοια της λ-μεταβλητής αναφέρεται οπωσδήποτε σε συναρτησιακή έκφραση: η λμεταβλητή είναι μεταβλητή κάποιας συνάρτησης ή λ-έκφρασης. Δέσμευση λ-μεταβλητής σε

τιμή - οντότητα σημαίνει οτι η λ-μεταβλητή αντικαθίσταται από ένα αντίγραφο της
πρωτότυπης οντότητας στην οποία δεσμεύεται η λ-μεταβλητή (δηλ. δεν δεσμεύεται σε αυτή
καθ' εαυτή την οντότητα). Έτσι δεν υφίσταται περαιτέρω σχέση του υπολογισμού με την
αρχική οντότητα: κάθε μεταβολή του αντίγραφου, που τυχόν θα προκαλέσει ο υπολογισμός,
δεν επιδρά στην πρωτότυπη οντότητα.
Κάποια πολυπλοκότητα, που θα μελετήσουμε, γεννά το γεγονός οτι διαθέτουμε τεχνικές
πρόσβασης στην πρωτότυπη οντότητα μέσα από τον υπολογισμό (όπως στις macro
συναρτήσεις), που σημαίνει οτι τότε είναι ανάγκη να έχουμε θέσει ξεκάθαρα τη σχέση της λμεταβλητής και της αντίστοιχης πρωτότυπης τιμής (δηλ. αν παραμένει με κάποιο τρόπο
συνδεδεμένη ή έχουμε αντίγραφο).
– Η δέσμευση λ-μεταβλητής πάνω σε τιμή παύει να υφίσταται με τον τερματισμό του
υπολογισμού που την προκάλεσε.
– Υπολογισμός που περιέχει διαδοχικά επίπεδα σύνθεσης (εγκλεισμό συναρτήσεων) δεσμεύει
τις αναφερόμενες λ-μεταβλητές με τη διαδοχή των επιπέδων τους, "απ' έξω προς τα μέσα" ως
προς τον εγκλεισμό των επιπέδων (θα το δούμε αναλυτικά στις λ-εκφράσεις). Επειδή η
εκτέλεση του υπολογισμού γίνεται "από μέσα προς τα έξω", ο υπολογισμός "περιμένει" να
ολοκληρωθούν οι διαδοχικές δεσμεύσεις πριν εκτελεστεί, και αυτό αποτελεί ένα ισχυρότατο
μέσον - εργαλείο διαχείρισης του υπολογισμού (καθυστέρηση, ελεγχόμενη διαδοχή).

Παρατηρήσεις (4)
► "Οντότητα της Lisp" μπορεί να είναι οποιαδήποτε έκφραση (σταθερά, σύμβολο - μεταβλητή,
σύμβολο - συνάρτηση, φόρμα υπολογισμού, λ-έκφραση), αλλά και οποιοσδήποτε καθορισμός
που "δείχνει" στο περιεχόμενο κάποιας "φυσικής θέσης" μέσω αναφερομένων συνδέσεων. Μια
τέτοια φυσική θέση, είναι αρκετό να εντοπίζεται ως αποτέλεσμα κάποιου υπολογισμού, δηλ. δεν
είναι απαραίτητο να έχει όνομα. Αποκαλούμε τις οντότητες της τελευταίας περίπτωσης, "φυσικά
αντικείμενα" (η λέξη "φυσικό" εδώ αναφέρεται στην οργάνωση του κώδικα και όχι σε θέσεις
μνήμης).

Για παράδειγμα: "τα νύχια του μπροστινού αριστερού ποδιού της γάτας του Θανάση": η έκφραση
αυτή προσδιορίζεται με την κατάλληλη τυπική συσχέτιση των λέξεων, αξιοποιώντας σύμβολα,
ιδιότητες, τιμές και αποτελεί ένα φυσικό αντικείμενο. Μπορούμε να χειριστούμε ένα φυσικό
αντικείμενο με τον ίδιο τρόπο που χειριζόμαστε ένα σύμβολο, πχ. να του αποδώσουμε
χαρακτηριστικά, όπως: "τα νύχια του μπροστινού αριστερού ποδιού της γάτας του Θανάση είναι
σπασμένα" (ένας τρόπος καταχώρησης τέτοιων προσδιορισμών περιγράφεται στις "ιδιότητες
συμβόλου", και ένας άλλος στις "κλάσεις αντικειμένων").

2.2.2 Από το άτυπο έως το ισχυρά τυποποιημένο
Η γλώσσα αναγνωρίζει τον τύπο της φόρμας που δίνεται, με περισσότερους από ένα τρόπους:
– άμεσα από σχετική δήλωση, πχ, η μεταβλητή var1 είναι διπλής ακρίβειας δεκαδικός
(χρησιμοποιείται μόνο για λόγους αποφυγής περιττών ελέγχων):

(declare var1 'double-float)

– έμμεσα από τον τρόπο ορισμού της φόρμας, πχ, η μεταβλητή var2 είναι ακέραιος:
(setq var2 (+ 2 3))

– ακόμα πιο έμμεσα, από τη χρήση της φόρμας σε υπολογισμό, πχ, όπως θα δούμε σε επόμενα:
(funcall '(lambda (x) x) 3)

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

Η μοναδική οντότητα που ανήκει σε δύο ξένους τύπους, είναι η NIL (η κενή λίστα, και το άτομο λογική τιμή "ψευδές"). Σε περίπτωση λογικού ελέγχου που σχετίζεται με το αν είναι λίστες ή
άτομα τα ελεγχόμενα αντικείμενα, αυτό χρειάζεται προσοχή.
Η αυτόματη αναγνώριση του τύπου φόρμας, απέναντι στη δυνατότητα ελέγχου του τύπου
φόρμας, και τα δύο σε συνδυασμό με τη δυνατότητα προσδιορισμού νέων υποτύπων απεριόριστα,
μας δίνει την ευχέρεια να θεωρήσουμε τη Lisp με τον τρόπο που θέλουμε, δηλαδή ως:
– μια γλώσσα ελεύθερη τύπων (typeless) , ή
– μια γλώσσα ισχυρά τυποποιημένη (strongly typed),
και αυτό εξαρτάται μόνον από τον τρόπο που προγραμματίζουμε και δεν είναι απαίτηση της
γλώσσας. Πιο συγκεκριμένα, ισχύει:
– αν ο υπερτύπος καλύπτει τις απαιτήσεις εισόδου συνάρτησης, τότε και κάθε υποτύπος του, τις
καλύπτει
– στις συναρτήσεις που διαθέτει η γλώσσα (built-in functions) κατά κανόνα προβλέπεται η
εξειδίκευση της συμπεριφοράς τους ανάλογα με τον υποτύπο εισόδου, σε συμφωνία με τα
ισχύοντα από τον πραγματικό χώρο απ' όπου προέρχεται η συνάρτηση.
Για τους λόγους αυτούς, στον ορισμό συνάρτησης ο έλεγχος του τύπου της τιμής εισόδου πρέπει
να κατευθύνεται στο να καλύπτει τον γενικότερο δυνατό τύπο στοιχείου που πρόκειται να δοθεί
ως είσοδος (πχ. number για μια μαθηματική συνάρτηση), ανακατευθύνοντας στο σώμα τις
ειδικότερες περιπτώσεις. Σε ένα συναρτησιακό πρόγραμμα, δεν πρέπει να παρουσιάζεται η
ανάγκη να χρησιμοποιηθεί συνάρτηση με κάποιον ειδικό υποτύπο εισόδου μόνο και μόνο διότι
αυτό απαιτεί ο φορμαλισμός.
Παράδειγμα: Αν ζητηθεί η τετραγωνική ρίζα του αριθμού –1 αναγνωρίζεται αυτόματα η είσοδος
ως ο μιγαδικός –1 + 0∙i διότι μόνο τότε έχει νόημα η τετραγωνική ρίζα, και ο υπολογισμός
οδηγείται στον υπολογισμό της ρίζας αυτού του μιγαδικού. Το επιστρεφόμενο αποτέλεσμα είναι
η φανταστική μονάδα i , άρα μιγαδικού τύπου:
(sqrt -1) → #c(0 1)
(το αποτέλεσμα είναι η συμβολική έκφραση του μιγαδικού αριθμού i )
Ο τύπος οντότητας μετατρέπεται αυτόματα σε απλούστερο, αν είναι δυνατό: πχ. μια οντότητα

complex, μετατρέπεται σε real αν το φανταστικό της μέρος είναι μηδέν∙ μια οντότητα ratio
μετατρέπεται σε integer, αν η διαίρεση είναι τελεία. Γι' αυτό, ο μιγαδικός #c(4 0) μετατρέπεται

αυτόματα στον ακέραιο αριθμό 4 :
(typep #c(4 0) 'complex) → NIL
(typep #c(4 1) 'complex) → T
(typep #c(4 0) 'integer)
→ T
(typep #c(4 1) 'integer)
→ NIL

(όπως θα δούμε αναλυτικά , TYPEP είναι η συνάρτηση εξακρίβωσης τύπου∙ ακολουθεί η
ελεγχόμενη φόρμα, και ο τύπος, που δίνεται "quoted", διότι είναι κάτι που δεν υπολογίζεται∙ NIL
σημαίνει ψευδές και Τ αληθές).
Αντίθετα, είναι δυνατό να προσδιορίσουμε συνάρτηση με απόλυτη εξάρτηση από τον (υπο-)τύπο
εισόδου: μπορούμε να απαιτήσουμε να έχει το όρισμα ειδικό (υπο-)τύπο, ή να εξαρτήσουμε τη
λειτουργικότητα της συνάρτησης από το είδος του (υπο-)τύπου εισόδου. Σε συνδυασμό με τη
δυνατότητα απεριόριστου ορισμού νέων υποτύπων και υποτύπων τους, διαθέτουμε ένα ισχυρό
τρόπο εξειδίκευσης της συμπεριφοράς συνάρτησης ανάλογα με τον τύπο εισόδου.
Είναι δυνατό να ζητηθεί από τη γλώσσα να μετατρέψει τον τύπο οντότητας σε άλλο τύπο από
αυτόν στον οποίο ανήκει (με τις κατάλληλες βέβαια προϋποθέσεις συμβατότητας στη μεταβολή).
H ανάμειξη τύπων αριθμών σε μια πράξη ή συνάρτηση επιτρέπεται ελεύθερα, και το αποτέλεσμα
ακολουθεί την προτεραιότητα ( ">" σημαίνει οτι υπερτερεί για το αποτέλεσμα ο αριστερά
αναγραφόμενος τύπος, όταν συνυπάρχουν σε μια πράξη):
δεκαδικός > πηλίκο > ακέραιος

2.2.3 Απεριόριστη αριθμητική και ακρίβεια πράξεων
H τάξη ακεραίας πράξης είναι απεριόριστη (infinite arithmetic). Έτσι, μπορούμε να δώσουμε προς
αριθμητικό υπολογισμό οποιεσδήποτε ακέραιες τιμές, οσοδήποτε μεγάλες (στα όρια του compiler
και του συστήματος), με βεβαιότητα οτι το ακέραιο αποτέλεσμα ακέραιης πράξης δεν θα έχει
στρογγύλευση μετά από κάποια τάξη. Δεδομένου οτι η διαίρεση ακεραίων δίνει αποτέλεσμα με
σχετική ακρίβεια, ο υπολογισμός διαίρεσης ακεραίων σταματά στο ανάγωγο πηλίκο που
προκύπτει. Για να υπολογιστεί το πηλίκο, χρειάζεται ειδική οδηγία (συνάρτηση μετατροπής
τύπου).
Εκμεταλλευόμαστε την απεριόριστη ακέραιη αριθμητική για να πετύχουμε συγκεκριμένη
ακρίβεια αποτελέσματος, για κάθε υποτύπο αριθμητικών δεδομένων. Για το σκοπό αυτό, είναι
αρκετό να γράψουμε κάθε δεκαδικό ως πηλίκο (ή να ζητήσουμε από τη γλώσσα να το
μετατρέψει), οπότε η πράξη εκτελείται ως ακεραία, ακρίβειας μονάδας, για οποιοδήποτε μέγεθος
τιμών, ενώ η διαίρεση εκτελείται μόνο στο μέτρο που είναι δυνατές ακέραιες απλοποιήσεις. Έτσι
αποφεύγουμε την αυτόματη μετατροπή των αριθμητικών τιμών σε κινητής υποδιαστολής και τη
συνεπαγόμενη στρογγύλευση όταν το πλήθος των δεκαδικών ψηφίων που προκύπτουν είναι πολύ
μεγάλο, όπως συμβαίνει σε άλλες γλώσσες (Basic, Fortran, Pascal, C,…), και δεν χάνουμε την

απόλυτη ακρίβεια (απαίτηση η οποία σ' αυτές τις γλώσσες μας οδηγεί σε πολύπλοκες μεθόδους
ελέγχου και διατήρησης της ακρίβειας).

Οι αριθμητικές πράξεις γράφονται συμβολικά στη Lisp πάντοτε σε προθεματική μορφή (prefix)
των τελεστών, διότι και αυτοί είναι συναρτήσεις:
(+ 2 3 4)
; είναι το άθροισμα των αριθμών, επιστρέφει 9
Από τα παραπάνω γίνεται σαφές οτι, για να μη γίνουν στρογγυλεύσεις σε έναν υπολογισμό λόγω
δεκαδικών πράξεων, πρέπει να μετατραπούν όλοι σε ακεραίους. Πχ. η σειρά των μαθηματικών
πράξεων:
0.000000000034 / 1230000000 * (-0.0000000000000234567)
πρέπει να γραφεί στη Lisp ως:
(* ( / (/ 34 10000000000) 1230000000) (/ -234567 10000000000000))

ή, με χρήση της εκθετικής συνάρτησης EXPT :
(* (/ (* 34 (expt 10 -10)) (* 123 (expt 10 7))) (* -234567 (expt 10 -13)))
→ -1329213 / 20500000000000000000000000000000

Άρα έχουμε τον κανόνα: το αποτέλεσμα που θα προκύψει από πράξεις ακεραίων, επιστρέφεται
πάντοτε είτε ως ακέραιος είτε ως πηλίκο ακεραίων. Tο πηλίκο επιστρέφεται πάντοτε ανηγμένο,
απόλυτης ακρίβειας για αριθμητή και παρονομαστή. Αν ξεπεράσουμε τα όρια του συστήματος,
θα προκληθεί σφάλμα, όχι στρογγύλευση.

Για να υπολογιστεί τελικά ως δεκαδικός το αποτέλεσμα που εξάγεται ως πηλίκο, πρέπει να
εξαναγκαστεί η μετατροπή του σε δεκαδικό. Aυτό γίνεται μέσω οποιασδήποτε συνάρτησης που
παίρνει είσοδο αριθμό και δίνει έξοδο δεκαδικό, όπως είναι η FLOAT που δέχεται όρισμα τύπου
number και επιστρέφει τιμή υποτύπου float : αρκεί λοιπόν να δώσουμε το συνολικό υπολογισμό
ως όρισμα στη συνάρτηση FLOAT :
(float
(* ( / (* 34 (expt 10 -10)) (* 123 (expt 10 7)))
(* -234567 (expt 10 -13))))

-6.48396585365854E-26

Αν υπάρχει έστω και ένας όρος δεκαδικός (τύπου float) σε μια σύνθετη αριθμητική πράξη, η
συνολική έκφραση θα προσδιορίσει αποτέλεσμα δεκαδικό, όπου οι πράξεις ακολουθούν πλέον
την ακρίβεια υπολογισμού δεκαδικού. Για να μη χάσουμε τον έλεγχο της ακρίβειας,
μετατρέπουμε το δεκαδικό σε τύπου ratio, πχ. με χρήση της συνάρτησης RATIONALIZE :
(* 1234567890 0.75 ) → 9.259259175E8
(* 1234567890 (rationalize 0.75 )) → 1851851835 / 2
διότι ισχύει:
(rationalize 0.75)
→ ¾
Είναι δυνατή η γραφή δεκαδικών είτε σε μορφή κινητής υποδιαστολής (πχ. 0.2345Ε-12) είτε
σταθερής υποδιαστολής. Μπορούμε να γράψουμε και απ' ευθείας ως πηλίκο έναν ρητό αριθμό,
παραβαίνοντας τον γενικό κανόνα της προθεματικής γραφής, δηλ. να γράψουμε:
234/567

αντί:
(/ 234 567)

Tα αλφαριθμητικά διακρίνονται σε δύο τύπους, character και string, που όπως φαίνεται από το
δένδρο των τύπων, έχουν διαφορετική θέση στην ιεραρχία. Τα strings γράφονται μεταξύ διπλών
εισαγωγικών (πχ. “Λεωνίδας”) ενώ οι χαρακτήρες σημειώνονται με το πρόθεμα #\ (πχ. #\a).
Είναι σκόπιμο οι απλοί χαρακτήρες να μη δίνονται ως συμβολοσειρές, για λόγους ταχύτητας.

Tα σύμβολα είναι ονόματα οντοτήτων, που από σημασιολογική και τυπική άποψη μπορούν να
έχουν κάποιο περιεχόμενο, κάποια συναρτησιακή λειτουργικότητα, κάποιες ιδιότητες και να
συμμετέχουν σε μια ή περισσότερες συνδέσεις, όπως θα δούμε αναλυτικά

Τα σύμβολα διακρίνονται σε: συναρτήσεις και μεταβλητές. Σε πλαίσια που οριοθετούν την ισχύ
των μεταβλητών, μπορούμε να διακρίνουμε τα σύμβολα - μεταβλητές σε τοπικές, που υφίστανται
σε ένα τόπο, με περιορισμό εύρους στο "ποιός τις βλέπει" και χρόνου υπόστασης, και καθολικές,
που είναι ορατές από ολόκληρο το πρόγραμμα και διαρκείς.
Κατά το πρότυπο της CL τα σύμβολα και οι μεταβλητές είναι διαφορετικοί υποτύποι του τύπου
symbol. Στις πληρέστερες εκδόσεις της CL, ένα σύμβολο μπορεί να είναι ταυτόχρονα μεταβλητή
και συνάρτηση, με το νόημα οτι περιέχει διακεκριμένα μέρη που αφορούν τους ρόλους του
συμβόλου ως μεταβλητή και ως συνάρτηση (η διαδοχή των μερών δεν είναι σημαντική). Εξ
άλλου και η ίδια η δομή συμβόλου βασίζεται σε διαδοχικές συνδέσεις στοιχειωδέστερων
"σωματιδίων", εν γένει κάτω από το ακόλουθο σχήμα (που ενδέχεται να διαφοροποιείται,
ανάλογα με τον compiler):

Εσωτερικό όνομα είναι αυτό με το οποίο ο compiler βλέπει το σύμβολο. Εξωτερικό όνομα είναι
αυτό με το οποίο το βλέπει ο χρήστης και με αυτό το όνομα αναγράφεται παντού στο πρόγραμμα.
Το περιεχόμενο μεταβλητής, είναι η τιμή που δίνουμε στο σύμβολο ή διαβάζουμε από το
σύμβολο, προσδιορίζοντάς το ως μεταβλητή.
Το συναρτησιακό περιεχόμενο, είναι ο ορισμός του συμβόλου ως συνάρτηση. Ένα σύμβολο
συνήθως το χρησιμοποιούμε μόνο ως μεταβλητή ή ως συνάρτηση, δηλ. αναφερόμαστε μόνο σε
ένα από τα δύο αυτά μέρη, αλλά δεν αποκλείεται η ταυτόχρονη χρήση και των δύο μερών (εν
γένει τέτοια διπλή δυνατότητα εξαρτάται από τον compiler και θα τη δούμε λεπτομερέστερα στα
επόμενα).
Η υποδοχή σύνδεσης επιτρέπει τη μόνιμη σύνδεση του συμβόλου με οντότητα, ανεξάρτητα της
σύνδεσης με τιμή. Θεμελιακή σύνδεση είναι η λίστα ιδιοτήτων του συμβόλου (symbol property
list), αλλά διατίθεται και ένας διαφορετικός τρόπος σύνδεσης, με ένα πίνακα κατακερματισμού
(hash table). Η υποδοχή αυτή επιτρέπει την κίνηση από το σύμβολο προς τα διασυνδεδεμένα
στοιχεία, μέσω ειδικών συναρτήσεων που διαχειρίζονται αυτή τη σύνδεση.

Οι όροι μιας λίστας μπορούν να είναι οποιεσδήποτε φόρμες, χωρίς υποχρέωση ομοιομορφίας
τύπου. Το ίδιο συμβαίνει και για όρους - στοιχεία πίνακα ή διανύσματος, αλλά μπορούμε να
απαιτήσουμε ομοιομορφία τύπου στην περίπτωση αυτή.

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

β'. Όταν θέλουμε να κρατήσουμε τον υπολογισμό για αργότερα:
(setq a '(+ 3 4))

Χρήση:

a
→ (+ 3 4)
(eval a) → 7

(υπολογισμός της τιμής του a)
(υπολογισμός της τιμής της τιμής του a)

Ένα πρόγραμμα είναι είτε μια μοναδική φόρμα είτε μια ακολουθία από διαδοχικά δοσμένες
φόρμες. Νέες τέτοιες φόρμες μπορούν να προστίθενται εκ των υστέρων, επεκτείνοντας το
πρόγραμμα.

2.2.4 Φόρμες και υπολογισμός
Οι φόρμες-συναρτησιακές εκφράσεις και οι φόρμες-εφαρμογές
Κάθε φόρμα που δεν είναι άτομο, εκφράζει είτε κάποιο αλγόριθμο είτε κάποια εφαρμογή
αλγόριθμου για συγκεκριμένη είσοδο. Αντίστοιχα, κάθε αλγόριθμος ή εφαρμογή του, εκφράζεται
με μια φόρμα. Επίσης, κάθε διαδοχή εκτέλεσης από φόρμες μπορεί να εκφραστεί ως ενιαία
φόρμα, και αντίθετα, μια ενιαία πολυσύνθετη φόρμα μπορεί να αποδοθεί με μια πεπερασμένη
ακολουθία από απλούστερες φόρμες.
Εκτέλεση σημαίνει κλήση μιας συγκεκριμένης φόρμας εφαρμογής προς υπολογισμό, και μέσω
αυτής ενδεχομένως κλήση και υπολογισμό από άλλες φόρμες κοκ. Η κλήση φόρμας υπολογισμού
μέσα σε άλλη φόρμα υπολογισμού, μπορεί να είναι προκαθορισμένη από τη συγγραφή του
προγράμματος (πχ. ορισμό συνάρτησης η οποία στο σώμα της καλεί άλλες) ή να καθορίζεται από
το χρήστη (στο prompt εισόδου).

Κατά το φόρτωμα αρχείου που περιλαμβάνει περισσότερες διαδοχικές φόρμες, εκτελούνται όλες
με τη διαδοχή που είναι αναγραμμένες, και με την εκτέλεση κάθε μιας, διαμορφώνεται ανάλογα ο
χώρος των δεδομένων του προγράμματος. Αν επαναπροσδιορίζεται κάτι, υπερισχύει πάντα η
τελευταία εγγραφή.

Θα δούμε οτι στη Lisp, εφαρμογές και συναρτησιακές εκφράσεις δίνονται πανομοιότυπα, σε
μορφή εφαρμογών. Παρακάμπτουμε αυτή τη δυσχέρεια, απλώς χρησιμοποιώντας συναρτήσεις
των οποίων η εφαρμογή σε αλγόριθμο επιστρέφει τον αλγόριθμο, δηλαδή δέχονται ως όρισμα
συναρτησιακή έκφραση και επιστρέφουν την έκφραση αυτή.

Χαρακτήρες ονομάτων συμβόλων και συμβολοσειρών
Oι χαρακτήρες που διακρίνει η CL είναι οι ASCII 0-255 . Τα κεφαλαία διαφοροποιούνται από τα
πεζά σε οντότητες τύπου character ή string, αλλά όχι σε ονόματα συμβόλων.
Tα ονόματα συμβόλων σχηματίζονται με ελεύθερο τρόπο ως προς το πλήθος και το είδος των
χαρακτήρων∙ οι φτωχότεροι compilers δέχονται μόνο λατινικούς χαρακτήρες στα ονόματα
συμβόλων, πλέον ορισμένα σύμβολα, όπως το “–” και το “_” , ενώ οι πλουσιότεροι υποστηρίζουν
το πλήρες Unicode set. Εδώ θεωρούμε οτι υποστηρίζεται το πλήρες set χαρακτήρων.
Κανόνας: Στα ονόματα συμβόλων, τα πεζά και κεφαλαία είναι ισοδύναμα.

Ο "ατέρμων κύκλος υπολογισμού" στη Lisp
Η Lisp παλαιότερα προσφερόταν χωριστά ως interpreter και ως compiler. Οι νεότερες εκδόσεις
είναι τύπου compiler ο οποίος εκμεταλλεύεται την μερική μεταγλώττιση και διαθέτει ένα επίπεδο
–χώρο αλληλεπίδρασης με το χρήστη– το λεγόμενο "toploop", που φαινομενικά λειτουργεί ως
interpreter. Στο toploop μπορεί ο χρήστης να εισάγει οτιδήποτε, κάνοντας χρήση από κάθε
διαθέσιμο σύμβολο της γλώσσας ή του προγράμματος που έχει συνθέσει ή φορτώσει.

Θεμελιακό χαρακτηριστικό της Lisp είναι ο αναγνώστης (reader). Ο "αναγνώστης" είναι ένας
μηχανισμός που διαβάζει ότι δοθεί στην είσοδό του, το αναγνωρίζει συντακτικά, και το εκτελεί
μόλις ολοκληρωθεί ως έκφραση. Αν η έκφραση δίνεται στο ανώτατο επίπεδο (δηλαδή,
φορμαλιστικά, να μην έχουν ανοίξει παρενθέσεις που εκκρεμούν), απ' ευθείας από την κυρία
είσοδο ή από ανάγνωση αρχείου, επιστρέφει το αποτέλεσμα του υπολογισμού στην κύρια έξοδο.
Αν έκφραση δίνεται σε χαμηλότερο επίπεδο (δηλαδή, φορμαλιστικά, να έχουν ανοίξει
παρενθέσεις που εκκρεμούν), επιστρέφει το αποτέλεσμα του υπολογισμού στο αμέσως ανώτερο
επίπεδο, απ' όπου κλήθηκε ο υπολογισμός.
Με το πέρας των υποχρεώσεων υπολογισμού, ο "αναγνώστης" επιστρέφει πάντοτε σε κατάσταση
ανάγνωσης, από την κυρία είσοδο. Αν κυρία είσοδος (αντίστ. έξοδος) είναι το πληκτρολόγιο
(αντίστ. η οθόνη), που είναι η "default" κατάσταση, δίνεται "prompt" εισόδου στο "toploop", όπου
ο χρήστης μπορεί "να κάνει τα πάντα": να δηλώσει νέα σύμβολα ή να επαναπροσδιορίσει
υπάρχοντα, να ζητήσει εφαρμογή συνάρτησης ή την τιμή μεταβλητής, να φορτώσει αρχείο, να
φορτώσει και να εκτελέσει συνολικά ένα πρόγραμμα, και γενικά να ζητήσει οποιαδήποτε
λειτουργία που υποστηρίζει η Lisp, κά.
Ακριβέστερα, το toploop εκτελεί μια ατέρμονα ανακύκλωση εφαρμογής, το λεγόμενο "read-evalprint loop", που περιμένει να διαβάσει μια φόρμα υπολογισμού που μόλις δοθεί την υπολογίζει
και επιστρέφει το αποτέλεσμα. Η φόρμα μπορεί να είναι οποιαδήποτε φόρμα δέχεται η Lisp,
ορισμού ή εφαρμογής, οσοδήποτε σύνθετη και αν είναι.

Η εκτέλεση ακολουθεί την κανονική τάξη υπολογισμού, δηλ. εκτελείται ο αναφερόμενος
υπολογισμός από μέσα προς τα έξω (όπου, παίζει ρόλο και η λειτουργικότητα των συναρτήσεων
που καλούνται, διότι είναι δυνατό να προσδιορίζουν τη σειρά υπολογισμού διαφορετικά από το
φαινομενικό εγκλεισμό). Το ανώτατο επίπεδο, το λεγόμενο "toplevel", είναι το επίπεδο όπου
προσδιορίζεται μια αυτόνομη φόρμα προς εκτέλεση, και συνεπώς είναι το επίπεδο εκκίνησης
κάθε υπολογισμού που δεν προκαλείται από άλλον υπολογισμό. Είναι το επίπεδο όπου ανοίγει η
πρώτη παρένθεση, και αντίστοιχα κλείνει Στο toploop, ο χρήστης ξεκινά γράφοντας στο toplevel,
εκτός αν έχει ξεκινήσει διεργασία (δηλαδή εκτέλεση εφαρμογής) που δίνει prompt εισόδου, οπότε
ο χρήστης γράφει φόρμες σε χαμηλότερο επίπεδο.

2.3 Μορφοποίηση και εκτέλεση προγράμματος
Κατά κανόνα, διακρίνουμε τις εξής φάσεις σε ένα πρόγραμμα ΣΠ , από την συγγραφή μέχρι την
εκτέλεσή του:
α'. Φορτώνουμε τα αρχεία που θα χρησιμεύσουν ως βιβλιοθήκες (αποκαλούνται "patches", διότι
παίζουν τον ίδιο ρόλο που παίζουν τα "κομμάτια από πανιά" σε μια ραφή "patchwork")∙ ανά
πάσα στιγμή μπορούμε να φορτώσουμε πρόσθετα.
β'. Ξεκινάμε τη δόμηση του προγράμματος, συνήθως σε μορφή "από πάνω προς τα κάτω"
σύνθεσης των συμβόλων που αποτελούν το στόχο κλήσης. Οι συνθέσεις αυτές είναι ορισμός
από μία ή περισσότερες μεταβλητές ή/και συναρτήσεις, και σύνθεση από μια ή περισσότερες
φόρμες υπολογισμού.
β'. Χρήσιμο είναι να εντοπίσουμε τα βασικά σύμβολα (αλγόριθμους και εφαρμογές τους που θα
χρειαστούν) διαχωρίζοντάς τα ως: εργαλεία, δομικά στοιχεία, και στοιχεία κλήσης από το
χρήστη. Η διάκριση είναι καθαρά νοηματική, αλλά παίζει ρόλο τόσο στην ανάπτυξη όσο και
στη χρήση του προγράμματος.
δ'. Επιλέγουμε από το σύνολο των καταχωρημένων συμβόλων (ανάμεσα σε πολλά άλλα που
έχουν μείνει από δοκιμές, από φόρτωμα αρχείων μερικής χρησιμότητας, κά) αυτά που τελικά
συνθέτουν το πρόγραμμα - στόχο. Οι δυνατότητες επιλογής, ομαδοποίησης και αποθήκευσης
εξαρτώνται από τον compiler (πχ. μερικοί compilers διαθέτουν "σώσιμο χώρου εργασίας")
αλλά οπωσδήποτε σε επίπεδο τυπικής CL διαθέτουμε τις θεμελιακές δυνατότητες διαχείρισης
του χώρου των συμβόλων χώρου: καταχώρηση μεμονωμένων ορισμών ή ομάδων σε αρχείο,
τήρηση αντίγραφου από οτιδήποτε γράφουμε στο λεγόμενο "dribble file" και "ξεκαθάρισμα"
μέσα από έναν text editor.
ε'. Καταχωρούμε χωριστά όσα, από τα σύμβολα που ορίσαμε, μπορούν να χρησιμεύσουν ως
εργαλεία, σε μια ή περισσότερες βιβλιοθήκες, για επαναχρησιμοποίηση. Ιδιαίτερη σημασία
έχουν σύμβολα που εξυπηρετούν τη συγγραφή κώδικα (πχ. να φτιάξουμε μια συνάρτηση
"mydefun" που αφού ορίσει την επιθυμητή συνάρτηση, στη συνέχεια να τη σώσει σε
αντίστοιχο αρχείο).
ς'. Διευκρινίζουμε με σαφήνεια τον προβλεπόμενο ρόλο του τελικού χρήστη, και συνθέτουμε

φόρμες που θα τον διευκολύνουν στη συνήθη εργασία του ("user interface"). Ετοιμάζουμε
συναρτήσεις για κλήση από το χρήστη, ενδεχομένως για διάφορα προφίλ χρηστών, με
κατάλληλο όνομα και εύχρηστο τρόπο κλήσης: η συνθετική χρήση συμβόλων είναι
αποδοτικότερη όταν το περιβάλλον είναι φιλικό.
ζ'. Εντοπίζουμε πιθανά σημεία, οφειλόμενα σε ενδεχόμενες δράσεις του χρήστη, που μπορούν
να φέρουν την εκτέλεση σε κατάσταση: i) ασάφειας, δηλ. να χαθεί ο χρήστης ανάμεσα σε
επίπεδα ή ανάμεσα σε λειτουργικές φάσεις όπου έχει κάποιες υποχρεώσεις, ii) ριζικού
σφάλματος, που προκαλεί καταστροφή της τρέχουσας κατάστασης αλλά εν τω μεταξύ έχουν
προκύψει παραμένουσες μεταβολές από σώσιμο αρχείων, iii) συνεχιζόμενου σφάλματος
(continuable error: σφάλμα που μπορεί να παρακαμφθεί, με διόρθωση ή αγνόηση) όπου ο
χρήστης οφείλει να δράσει κατάλληλα για την άρση του σφάλματος.
η'. Διερευνούμε τις ευκαιρίες για περαιτέρω αξιοποίηση του προγράμματος, όπως: επέκταση /
διακλάδωση / προσαρμογή / διόρθωση / επαναχρησιμοποίηση κώδικα από τον ίδιο τον
κατασκευαστή ή άλλον, ακόμα και από τον τελικό χρήστη, και ανάλογα μορφοποιούμε τα
σύμβολα, ώστε να καλύπτουν και τις ευρύτερες ανάγκες.
θ'. Αποφασίζουμε αν ο τελικός χρήστης που θα κάνει συνθετική χρήση κάποιων συμβόλων,
χρειάζεται ή όχι τον πηγαίο κώδικά τους, και επισυνάπτουμε σχετικά σχόλια.

2.3.1 Λειτουργικότητα ενός συναρτησιακού προγράμματος

Πρώτα απ' όλα πρέπει να διευκρινίσουμε οτι έχουμε δύο κατηγοριών συναρτησιακά
προγράμματα: τα εκτελέσιμα αυτόνομα, που είναι προγράμματα δυαδικού κώδικα που
φορτώνονται ανεξάρτητα της Lisp και καμμία μη προβλεπόμενη ενέργεια του χρήστη δεν είναι
δυνατή, και τα προγράμματα (πηγαίου ή δυαδικού κώδικα) που φορτώνονται μέσω του
compiler. Στο παρόν αναφερόμαστε αποκλειστικά σε προγράμματα της δεύτερης κατηγορίας.

Τί περιλαμβάνει ένα πρόγραμμα
Ένα συναρτησιακό πρόγραμμα συνταγμένο σε Lisp, περιλαμβάνει εν δυνάμει: i) όλα τα
πρωτογενή σύμβολα του compiler, ή πιο συγκεκριμένα του χρησιμοποιούμενου πακέτου
(package), ii) όσα σύμβολα ορίζονται στον κώδικα που φορτώνεται ως αρχείο(-α), iii) όσα
σύμβολα ορίζονται απ' ευθείας στο prompt εισόδου του toploop. Δηλαδή, αποτελεί ένα χώρο που
συνενώνει τη λειτουργικότητα των συμβόλων του χρησιμοποιούμενου πακέτου (τμήμα της Lisp)
με αυτή των ορισμένων στο πρόγραμμα και με αυτή των συμβόλων που ορίζονται από τον τελικό
χρήστη του προγράμματος.
Στα επόμενα, θα αντιμετωπίζουμε ένα συναρτησιακό πρόγραμμα σαν μια τέτοια συνένωση. Αυτή
η συνένωση μπορεί να προκαλέσει πολύ εύκολα κάποιο σφάλμα, και γι' αυτό οι Lisp compilers
διαθέτουν εξελιγμένο μηχανισμό αποσφαλμάτωσης (debugging).
Ουσιαστικό είναι να έχουμε υπ' όψη οτι ο χώρος των συμβόλων ενδεχομένως αναδιαμορφώνεται
μετά από: i) το φόρτωμα αρχείου (διότι εκτελούνται όλες οι φόρμες του), ii) μετά από επέμβαση
του χρήστη, iii) μετά από εκτέλεση εφαρμογής.

Συναρτησιακή έκφραση και εφαρμογή

Στη Lisp, μια συναρτησιακή έκφραση είναι:
– είτε το όνομα συνάρτησης
– είτε ως φόρμα υπολογισμού που δίνει έξοδο συναρτησιακή έκφραση (δηλ. συνάρτηση ή λέκφραση)
– είτε ως απ' ευθείας δοσμένη λ-έκφραση.
Ο ορισμός συμβόλου - συνάρτησης δεν είναι τίποτα άλλο από ονομασία αλγόριθμου, και γίνεται
μέσω εφαρμογής ειδικών συναρτήσεων, δηλαδή είναι αποτέλεσμα εκτέλεσης. Η έννοια του
αλγόριθμου συμπίπτει με της συναρτησιακής έκφρασης, και η έννοια της εφαρμογής
συναρτησιακής έκφρασης συμπίπτει με της εκτέλεσης αλγόριθμου με συγκεκριμένη είσοδο.
Έχουμε διαφόρων κατηγοριών ορισμούς. Οι βασικοί είναι δύο, ο ορισμός συμβόλου - μεταβλητής
και ο ορισμός συμβόλου - συνάρτησης. Ειδικότερα:
Ο ορισμός συμβόλου - μεταβλητής αποτελεί ονομασία μιας έκφρασης, που μπορεί να είναι: i)
σταθερά (άτομο ή ακολουθία), ii) υπολογισμός που δηλώνεται και εκτελείται "επί τόπου" για να
δώσει την τιμή εξόδου του ως τιμή στη μεταβλητή, iii) υπολογισμός που δηλώνεται ως μη
εκτελέσιμος (καθυστερημένος) και καταχωρείται αυτούσιος ως τιμή της μεταβλητής με σκοπό να
εκτελεστεί όποτε θελήσουμε, iv) λίστα (που δεν διαφέρει συντακτικά και υπολογιστικά από το
προηγούμενο, αλλά διαφέρει εννοιολογικά), v) οποιαδήποτε σύνθεση εκφράσεων εφαρμογής
μέσω συνάρτησης, vi) συναρτησιακή έκφραση, με σκοπό το σύμβολο - μεταβλητή να παίξει ρόλο
συνάρτησης, vii) όνομα συνάρτησης, με σκοπό να περάσουμε δοσμένη συνάρτηση στο επίπεδο
των μεταβλητών (που είναι ταυτόσημο με το vi).
Η Lisp παρέχει μηχανισμούς "παράκαμψης" της χρήσης λ-εκφράσεων για τις συνήθεις εκφράσεις,
για λόγους ευκολίας∙ έτσι μόνο σε προχωρημένα επίπεδα προγραμματισμού χρειαζόμαστε
εισαγωγή από λ-εκφράσεις.

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

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

Υπολογισμός, σύμβολα και τοπικότητα

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

Ένα σύμβολο, είτε είναι συνάρτηση είτε μεταβλητή, όταν "κληθεί", υπολογίζεται και επιστρέφει
την τιμή του "σ' αυτόν που την κάλεσε" , δηλ. το χρήστη ή τη συνάρτηση στην οποία δόθηκε ως
όρισμα. Αν το καλούμενο σύμβολο είναι συνάρτηση που απαιτεί ορίσματα, τα αναζητά
αναγραμμένα "απ' ευθείας δεξιά του" :
pi → 3.14159265358979
(cos pi) → -1.0
Κάθε κλήση συνάρτησης είναι εφαρμογή που κλείνεται σε παρενθέσεις.

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

Επίσης, μια συνάρτηση μπορεί να εφαρμοστεί στον εαυτό της (δηλ. μια λ-μεταβλητή της
συνάρτησης να δεσμευτεί στην ίδια τη συνάρτηση), αρκεί να προβλέπεται τυπικά μέσα στο σώμα
οτι η λ-μεταβλητή θα δεσμευτεί σε συνάρτηση (καταλληλότητα τύπου εισόδου).
Ο ορισμός συμβόλου - μεταβλητής που αναφέρεται τον εαυτό του είναι δυνατός, και συναντάται
σε δύο περιπτώσεις:
– κλήση της προηγούμενης τιμής της μεταβλητής για διαμόρφωση της νέας τιμής: το αντίστοιχο
των: " a := 3 ; a := a+1 ; " της Pascal, σε Lisp αναγράφεται ως:
(setq a 3)
(setq a (+ 1 a))

αν ζητήσουμε την τιμή του a , θα λάβουμε:
a → 4 (εννοείται οτι το δώσαμε στο propmpt εισόδου του toploop)
– κλήση του ίδιου του ονόματος της μεταβλητής, πχ.
(setq a 'a)

που έχει νόημα ταυτοποίησης της μεταβλητής με το περιεχόμενό της, και το χρησιμοποιούμε όταν
θέλουμε να αποφύγουμε τη σύγχυση ανάμεσα στο "τί πρέπει να καλέσουμε, το όνομα ή το
περιεχόμενο".
a
→ a
(eval a) → a
Τέτοιες αναφορές από σύμβολο σε σύμβολο, μπορούν να ακολουθούν διαδρομές περισσοτέρων
βημάτων, μέσω άλλων συμβόλων ή κλήσης υπολογισμών, όπως θα δούμε.
Τα σύμβολα - μεταβλητές είναι τύπου variable και τα σύμβολα - συναρτήσεις είναι τύπου
function, που είναι υποτύποι του τύπου symbol. Εκτός από αυτά έχουμε και τα σύμβολα - ειδικές
μεταβλητές, που είναι τύπου special, επίσης υποτύπου του symbol. Έχουμε ακόμα και τις
ακόλουθες κατηγορίες συμβόλων:
– τα σύμβολα - macro συναρτήσεις, που είναι τύπου function αλλά προσδιορίζονται και
υπολογίζονται διαφορετικά από τις συνήθεις συναρτήσεις
– τις κλάσεις, που είναι ονόματα με περιεχόμενο μια κλάση
– τις μεθόδους, που είναι συναρτήσεις που προσδιορίζονται ειδικά για κλάσεις και στιγμιότυπά
τους
– τα σύμβολα που προσδιορίζουν αρχέτυπες συναρτήσεις, που είναι συναρτήσεις ανωτέρου
επιπέδου γενικότητας και αφορούν μεθόδους με το ίδιο όνομα, εξειδικευμένης
λειτουργικότητας ανάλογα με τον τύπο εισόδου.

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

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

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

2.3.2 Κανόνες υπολογισμού
Παραπάνω είδαμε περιγραφικά κάποιους κανόνες που αφορούν τον υπολογισμό στη Lisp. Ας τα
δούμε συγκεντρωμένα:
– Υπολογίζονται διαδοχικά όλες οι φόρμες αρχείου-ων που φορτώνονται, αλλά και κάθε φόρμα
που θα συντεθεί και δοθεί για υπολογισμό από το χρήστη. Τα αποτελέσματα υπολογισμών
επιδρούν στο χώρο του προγράμματος σύμφωνα με τον προδιαγραμμένο ρόλο τους, και με τη
διαδοχή που εκτελούνται.
– Μια φόρμα είναι είτε άτομο είτε λίστα. Η σύνταξη λίστας είναι: αριστερή παρένθεση, οι όροι
της λίστας χωρισμένοι με τουλάχιστον ένα κενό διάστημα, δεξιά παρένθεση.
– Η γραφή εφαρμογής των συναρτήσεων είναι προθεματική, σε μορφή λίστας. Πχ. η πρόσθεση
3+4 , που είναι η συνάρτηση + με ορίσματα τους αριθμούς 3 και 4 , γράφεται::
(+ 3 4)

που κατά τον υπολογισμό επιστρέφει 7 . Η τιμή που προκύπτει από έναν υπολογισμό, παίρνει
πάντοτε τη θέση της φόρμας που υπολογίστηκε.
– Η φόρμα κλήσης ή αλλιώς εφαρμογής συνάρτησης είναι λίστα, με πρώτο όρο το όνομα της
συνάρτησης και επόμενους τα ορίσματά της.
– Μεταβλητή μπορεί να κληθεί με απλή αναφορά του ονόματός της. Κλήση μεταβλητής σημαίνει
υπολογισμό της τιμής της. Αν η τιμή μεταβλητής είναι κάτι που υπολογίζεται, μπορούμε να
ζητήσουμε υπολογισμό του, κοκ.

– Ο υπολογισμός γίνεται κατά την κανονική τάξη (normal order evaluation) εκτός αν ειδικά
προσδιορίζεται αλλιώς, που σημαίνει από μέσα προς τα έξω και αν οι φόρμες είναι στο ίδιο
επίπεδο ακολουθείται η διαδοχή από αριστερά προς τα δεξιά (με υπακοή στην προτεραιότητα
που δίνουν οι παρενθέσεις):
(+ (/ 4 2) (* 3 5)) → (+ 2 (* 3 5)) → (+ 2 15) → 17
Εξαίρεση από τον κανόνα "από μέσα προς τα έξω και από αριστερά προς τα δεξιά" αποτελούν
συναρτήσεις που η κατασκευή τους επεξηγηματικά προσδιορίζει διαφορετική αντιμετώπιση
των ορισμάτων τους (τέτοια είναι η COND).

– Τα ορίσματα συνάρτησης μπορούν να είναι οποιεσδήποτε φόρμες εφαρμογής που δίνουν έξοδο
τιμή κατάλληλου τύπου, δηλ. που να ταιριάζει η έξοδός τους με την αναμενόμενη είσοδο.
Βάσει του προηγούμενου κανόνα υπολογίζονται πρώτα τα ορίσματα ως φόρμες και στη θέση
κάθε φόρμας τίθεται η τιμή του αποτελέσματός της. Αυτές τις τιμές παίρνει η συνάρτηση ως

τιμές εισόδου.
Επομένως, οι συναρτησιακές φόρμες είναι λίστες που έχουν τη μορφή:
( <συνάρτηση> <όρισμα1> <όρισμα2> … <όρισμαΝ> )
με πλήθος ορισμάτων (από κανένα μέχρι Ν, όπου Ν ακέραιος θετικός). Κάθε όρισμα είναι είτε
τιμή (του απαιτούμενου από τα συμφραζόμενα τύπου) είτε εφαρμογή που επιστρέφει τιμή
κατάλληλου τύπου.

Στην προηγούμενη φόρμα, αν αντικατασταθεί το όρισμα1 με τη φόρμα:
( <συνάρτηση1> <όρισμα11> <όρισμα12> … <όρισμα1Κ> )
παίρνουμε τη σύνθεση:
( <συνάρτηση>
(<συνάρτηση1> <όρισμα11> <όρισμα12> … <όρισμα1Κ> )
<όρισμα2> … <όρισμαΝ>)
– Κάθε φόρμα - λίστα θεωρείται από τη Lisp ως εφαρμογή συνάρτησης, με πρώτο όρο το όνομα
της συνάρτησης και επόμενους τα ορίσματά της. Ακόμα και εντολές που θεωρούνται απλά
διαδικαστικές λειτουργίες, όπως η PRINT, είναι συναρτήσεις: η φόρμα (print 3) θα εκτελέσει
τη ζητούμενη εκτύπωση, αλλά επίσης ως συνάρτηση θα επιστρέψει τιμή 3 .
Το "quote" αποτρέπει τη Lisp από το να θεωρήσει μια φόρμα ως συνάρτηση προς εκτέλεση,
θέτοντας "φραγή" στον υπολογισμό, και αυτός είναι ένας τρόπος περιγραφής ομάδας οντοτήτων
μέσω λίστας (θα δούμε πως και αυτό είναι συνάρτηση).

– Η Lisp υπολογίζει οτιδήποτε συναντήσει, και τα πάντα έχουν τιμή. Δεν υπάρχει εντολή στη
γλώσσα που να μην παίζει και ρόλο συνάρτησης (πλην της PPRINT). Αποτρέπεται αυτός ο
αυτόματος υπολογισμός όταν η φόρμα είναι "quoted". Διευκρινίζουμε οτι αυτός ο "αυτόματος"
υπολογισμός είναι ενός επιπέδου, δηλ. αναζητά την τιμή συμβόλου αλλά όχι και την τιμή της
τιμής (που είναι δυνατό να προσδιοριστεί) κοκ.

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

– Το αποτέλεσμα του υπολογισμού φόρμας που δίνεται στο πρώτο επίπεδο, επιστρέφεται στο
κύριο ρεύμα εξόδου (αρχική κατάσταση κύριου ρεύματος εξόδου είναι η οθόνη, αλλά
μπορούμε να το ανακατευθύνουμε, πχ. σε αρχείο).

– Αν μια συνάρτηση έχει και κάποια παράπλευρη, ως προς τη συναρτησιακή της υπόσταση,
λειτουργική δράση, αυτή φυσιολογικά δεν παρεμποδίζει τη συναρτησιακή αλληλουχία των
κλήσεων στη δομή όπου εντάσσεται η συνάρτηση. Πχ η εντολή PRINT είναι ταυτόχρονα και
συνάρτηση, με τιμή αυτό που τυπώνει, και μπορεί να παρεμβάλλεται στη συναρτησιακή
αλληλουχία:
(+ (print (+ 3 4)) (print (+ 4 5)) )

Ο υπολογισμός της φόρμας αυτής, από μέσα προς τα έξω, δίνει σε διαδοχικά βήματα τα εξής:
τιμή - έξοδος τού (+ 3 4) είναι 7 , του (print 7) είναι 7 με παράπλευρο αποτέλεσμα την
εκτύπωση του 7 , του (+ 4 5) είναι 9 , του (print 9) είναι 9 με παράπλευρο αποτέλεσμα την
εκτύπωση του 9 , τιμή του (+ 7 9) είναι 16 . Άρα, αν κληθεί στο πρώτο επίπεδο (toplevel) θα
δούμε στην οθόνη:
7
9
16

– Kαταληκτικές οντότητες, που είναι οι σταθερές (αριθμοί, strings και χαρακτήρες) δίνουν τιμή
τον εαυτό τους.

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

– Οι συναρτήσεις ("built-in" ή που κατασκευάζουμε) μπορούν να είναι ανωτέρου επιπέδου, δηλ.
συναρτήσεις που δέχονται ως όρισμα όνομα συνάρτησης.
– Κάποιες συναρτήσεις είναι της κατηγορίας macro, που συντίθενται με ειδικό τρόπο και
υπολογίζονται με ειδικό τρόπο, και ανοίγουν πιο "ευέλικτους δρόμους" στον υπολογισμό, διότι
η λειτουργικότητά τους μπορεί να συμπεριλαμβάνει δράσεις με μόνιμο αποτέλεσμα.

– Είναι δυνατό κάποια συνάρτηση, "built-in" ή που κατασκευάζουμε, να δέχεται προαιρετικές
παραμέτρους - ορίσματα, είτε απροσδιορίστου πλήθους.
– Κάποιες συναρτήσεις έχουν πολλαπλή έξοδο, όπου η πρώτη τιμή παίζει τον κύριο ρόλο (όπως
στις κοινές συναρτήσεις) αλλά είναι δυνατό να εκμεταλλευτούμε όλες τις εξόδους.

– Κάποιες συναρτήσεις έχουν και παράπλευρες δράσεις, που μπορούμε να τις εκμεταλλευτούμε
τόσο διαδικαστικά όσο και συναρτησιακά.

– Tα κεφαλαία και τα πεζά γράμματα είναι ισοδύναμα στα ονόματα συμβόλων, αλλά
διαφοροποιούνται σε οντότητες τύπου string ή character. Σε μετατροπή των τελευταίων σε
σύμβολα, ισχύει το προηγούμενο.

Παραδείγματα κλήσης υπολογισμού από φόρμες εφαρμογής συνάρτησης
- H φόρμα (+ 3 4 5 6)
-

ως συνάρτηση, επιστρέφει τον ακέραιο
αριθμό 18
H φόρμα ( / 3 4 )
επιστρέφει το κλάσμα ¾
H φόρμα ( / 6 2 )
επιστρέφει τον ακέραιο 3
H φόρμα ( / 12 16 )
επιστρέφει το (ανάγωγο) κλάσμα ¾
H φόρμα ( / 12.0 16 )
επιστρέφει τον δεκαδικό 0.75
H φόρμα ( * 3 1.5)
επιστρέφει 4.5
H φόρμα ( * 3 2.0)
επιστρέφει 6.0
H φόρμα (print (/ 3 4 )
τυπώνει και επιστρέφει: ¾
H φόρμα (print (/ 3 4.0) ) τυπώνει και επιστρέφει: 0.75

Διαμόρφωση εκτύπωσης
Είδαμε τη συνάρτηση PRINT : δέχεται ένα ακριβώς όρισμα, το τυπώνει και το επιστρέφει. Αν
θέλουμε εκτύπωση από περισσότερα στοιχεία, δεν έχουμε παρά να τα κλείσουμε σε λίστα, και
αυτό γίνεται με χρήση της συνάρτησης LIST :
(setq
(setq
(setq
(print

χρόνος 'σήμερα)
τόπος 'Αθήνα)
καιρός 'βροχερός)
(list χρόνος 'στην τόπος 'o 'καιρός 'είναι καιρός)) →
→ (σήμερα στην Αθήνα ο καιρός είναι βροχερός)

Οι "quoted" όροι τυπώνονται αυτούσιοι, ενώ οι μη-"quoted" αποτελούν σύμβολα, των οποίων
υπολογίζεται η τιμή.
Εκτός της PRINT , διατίθενται και άλλες συναρτήσεις εκτύπωσης:
prin1 : Επειδή η PRINT τυπώνει, σε κάθε κλήση της, σε νέα γραμμή (πρώτα προκαλεί αλλαγή
γραμμής και μετά τυπώνει), αν θέλουμε να συνεχιστεί η εκτύπωση στην ίδια γραμμή,
χρησιμοποιούμε τη συνάρτηση – εντολή PRIN1 , η οποία δεν προκαλεί αλλαγή γραμμής.
Η επιστροφή τιμής που δίνει ως συνάρτηση, είναι πανομοιότυπη με της PRINT :
(print “το αποτέλεσμα είναι:”) (prin1 (* (/ 8 4) 2 3)
→ “το αποτέλεσμα είναι:” 12

princ : Παρόμοια με την PRIN1 , αλλά προκαλεί εκτύπωση και επιστροφή μόνον των
αναγνώσιμων χαρακτήρων. Δεν τυπώνει τα εισαγωγικά των strings, ούτε το πρόθεμα #\ των
χαρακτήρων. Η επιστροφή τιμής που δίνει ως συνάρτηση, είναι πανομοιότυπη με της PRINT
(… (princ “το αποτέλεσμα είναι: ”) (prin1 (* (/ 8 4) 2 3) …)
→ το αποτέλεσμα είναι: 12

Η PRINC χρησιμοποιείται περισσότερο για εκτύπωση προς ανάγνωση από τον χρήστη, ενώ η
PRIN1 για εκτύπωση που πρόκειται να διαβάσει η Lisp (πχ. σε αρχείο, όπως περιγράφεται σε
επόμενη ενότητα) .
terpri : Χωρίς ορίσματα. Τερματίζει την μη αλλαγή γραμμής των PRIN1 , PRINC .
pprint : (από το "pretty print"). Προκαλεί εκτύπωση με αλλαγή γραμμής και tabs κατά επίπεδα.
Χρησιμοποιείται για καλύτερη εμφάνιση του κώδικα. Είναι η μόνη εντολή της CL που δεν είναι
συνάρτηση (δεν επιστρέφει τιμή). Η αναφορά της μέσα σε μια συναρτησιακή δομή δεν επιδρά
στον υπολογισμό της (εκτελείται ως παράπλευρη δράση που δεν συσχετίζεται με τον
υπολογισμό).

format : Ειδικής μορφής συνάρτηση που διαμορφώνει την εκτύπωση. Δέχεται μεγάλο πλήθος
από οδηγίες εκτύπωσης (κλειδιά) και χρησιμοποιείται αντί για την PRINT για εξειδικευμένο και
καθοδηγούμενο τρόπο εκτύπωσης (ξεφεύγει από το σκοπό του παρόντος).
write : Αφορά κυρίως την εκτύπωση σε printer. Δέχεται, μετά το προς εκτύπωση όρισμα,
παραμέτρους που αφορούν τον εκτυπωτή. Χωρίς παραμέτρους λειτουργεί όπως η PRINT .

Υπολογισμοί και δράσεις
Οι σχέσεις εφαρμογής καθορίζονται μεν σε κάθε περίπτωση συναρτησιακά, αλλά είναι νοηματικά
διαφορετικό το να προκαλούν υπολογισμό από το να δημιουργούν ή να μεταβάλλουν
καταστάσεις στο περιβάλλον. Για παράδειγμα:
– Μια Μαθηματική συναρτησιακή σχέση, όπως ο μαθηματικός τύπος cos 23π + 2
(+ (square (cos (* 3 pi))) 2)

Η φόρμα αυτή προκαλεί υπολογισμό τιμής σύνθετης συνάρτησης αλλά όχι άλλη δράση.
– Μια σχέση που προκαλεί διαμόρφωση του περιβάλλοντος: "θέσε τιμή στη μεταβλητή a, τη
λίστα που θα προκύψει μετά την επισύναψη αυτού που θα διαβάσεις από την είσοδο, στην τιμή
που είχε η λίστα list1"
(setq a (cons (read) list1))

Στη φόρμα αυτή οι συναρτήσεις λειτουργούν κυρίως ως δράσεις.
– Μια συνάρτηση που συμπεριλαμβάνει μαθηματικές συναρτήσεις και διαδικαστικές δράσεις:
(square (setq b (+ 4 5)))

Η φόρμα αυτή δηλώνει την εκτέλεση της συνάρτησης SQUARE η οποία εδώ επιστρέφει 81
διότι παίρνει είσοδο 9 που είναι η έξοδος της συνάρτησης SETQ , αλλά παράλληλα η SETQ
θέτει στο b την τιμή 9 , που είναι η έξοδος τής "+" . Eδώ έχουμε ανάμεικτα μαθηματικό
υπολογισμό και δράση.

2.4 Σύμβολα - μεταβλητές
2.4.1 Προσδιορισμός συμβόλου ως μεταβλητής
Ένα σύμβολο μπορεί να δεχθεί τιμή, η οποία εννοιολογικά αποτελεί το περιεχόμενό του. Ένας
τρόπος απόδοσης τιμής σε σύμβολο είναι με χρήση της συνάρτησης SETQ , που εφαρμόζεται με
πρώτο όρισμα το όνομα του συμβόλου (νεοπροσδιοριζόμενου ή ήδη υπάρχοντος) και δεύτερο την
τιμή που του αποδίδεται:
(setq symb1 3)
; ορίζεται το σύμβολο symb1 και παίρνει τιμή 3
(setq symb2 (+ 4 5)) ; ορίζεται το σύμβολο symb2 και παίρνει τιμή 9

Η συνάρτηση SETQ ορίζει (ή επανορίζει) ως σύμβολο - μεταβλητή (ακριβέστερα ως τύπου
symbol, υπο-τύπου variable) το όνομα που αναγράφεται ως πρώτο όρισμά της, και επιστρέφει ως
τιμή της, την αποδιδόμενη στο σύμβολο τιμή (δηλ. το δεύτερο όρισμα).
Το πρώτο όρισμα είναι το όνομα του συμβόλου – μεταβλητή και αναγράφεται ως έχει (χωρίς
οξεία), ενώ το δεύτερο όρισμα είναι η αποδιδόμενη στο σύμβολο τιμή, και υπολογίζεται πριν να
δοθεί ως τιμή στο σύμβολο. Γι' αυτό:
– Αν η τιμή είναι σταθερά, αναγράφεται ως έχει:
(setq newvar1 123)

– Αν η τιμή είναι string αναγράφεται μέσα σε εισαγωγικά:
(setq newvar2 “abcd” )

Τα εισαγωγικά δεν αποτελούν μέρος του string, αλλά δηλώνουν τον τύπο, την αρχή και το
τέλος του.
– Αν η τιμή είναι σύμβολο το οποίο δεν πρέπει να υπολογιστεί, αυτό αναγράφεται με πρόθεμα
οξεία ( ' , "quote"):
(setq newvar3 'newvar1)
(print newvar3)

newvar1
(εκτύπωση)
newvar1
(επιστροφή τιμής)

Δηλαδή, πρώτα γίνεται υπολογισμός του newvar3, που έχει αποτέλεσμα newvar1 . Ακολουθεί
εκτύπωση του newvar1 και επιστροφή της τιμής της συνάρτησης PRINT , που είναι newvar1 .
Το πρόθεμα "οξεία" δεν είναι μέρος του συμβόλου.
– Αν η τιμή που δίνουμε στο σύμβολο είναι τιμή συμβόλου, το τελευταίο πρέπει να υπολογιστεί
πριν την εκτέλεση της SETQ , και γι' αυτό αναγράφεται ως έχει (δηλ. χωρίς οξεία):
(setq newvar3 newvar1)
; δώσε στο newvar3 την τιμή του newvar1
(print newvar3)
→ 123

– Αν ως τιμή δοθεί μια "quoted" λέξη (δηλ. με πρόθεμα οξεία), είναι δεκτή έστω και αν η λέξη
αυτή δεν είναι ορισμένη ως σύμβολο. Όπως είπαμε, σε ορισμένους compilers προσδιορίζεται
αυτόματα η "quoted" λέξη ως σύμβολο, ενώ σε άλλους όχι. Σε κάθε περίπτωση, η απόπειρα

αναζήτησης της τιμής τέτοιας λέξης προκαλεί σφάλμα:
(setq name 'Γιάννης)
(eval name)
→ Error: <μήνυμα σφάλματος>

Στο prompt εισόδου του "toploop" είναι δυνατό να δοθεί και κατ’ ευθείαν κάποιο όνομα
συμβόλου που έχει οριστεί ως μεταβλητή. Tότε επιστρέφεται (που σημαίνει οτι τυπώνεται στην
οθόνη / κυρία έξοδο) η τιμή της. Αντίστοιχα, αν δοθεί μια καταληκτική τιμή, επιστρέφεται η ίδια:
newvar3
→ 123
4
→ 4
"Κώστας" → "Κώστας"
Ένα όνομα, σημαίνει πάντοτε για τον compiler όνομα μεταβλητής, η οποία υπολογίζεται και στη
θέση του ονόματος επιστρέφεται η τιμή της (εκτός αν είναι ο πρώτος όρος μη-"quoted" λίστας,
οπότε σημαίνει όνομα συνάρτησης):
(print newvar3) → 123
Αντίθετα, αν δοθεί ένα όνομα σε παρένθεση, υπακούει στον κανόνα από τις φόρμες-λίστες (δηλ.
ότι ο πρώτος όρος λίστας είναι όνομα συνάρτησης), άρα η Lisp «καταλαβαίνει» ότι πρόκειται για
συνάρτηση χωρίς ορίσματα. Πχ.
(read)
; προκαλεί ανάγνωση φόρμας από την κυρία είσοδο
η φόρμα που θα διαβαστεί από την εκτέλεση του (read) αποτελεί την τιμή της συνάρτησης
φόρμας (read) , και παίρνει τη θέση της:
(setq v1 (read)) <κατάσταση εισόδου∙ πληκτρολογούμε:>
4
v1 → 4

Ορισμός μεταβλητής και απόδοση περιεχομένου

Μπορούμε να δούμε ένα σύμβολο - μεταβλητή ως ένα όνομα που υφίσταται ως οντότητα, ή να το
δούμε ως ένα όνομα που "βαφτίζει" έναν υπολογισμό ή μια σταθερά. Πάντοτε αποτελεί μια
υπολογιστική οντότητα τύπου variable, και προαιρετικά παίρνει περιεχόμενο (τιμή). Η τιμή
μεταβλητής έχει δικό της τύπο, που προσδιορίζεται άμεσα ή έμμεσα. Η τιμή μπορεί να αλλάξει,
όπως και ο τύπος της τιμής (προσαρμόζεται πάντα στη νέα τιμή). Σύμβολο - μεταβλητή ορίζουμε
είτε με τη συνάρτηση SETQ είτε με τη συνάρτηση SETF (που αναφέρεται σε επόμενη ενότητα) οι
οποίες ως προς αυτό το ρόλο έχουν πανομοιότυπη σύνταξη και λειτουργικότητα.

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

Μπορούμε να χρησιμοποιήσουμε μια μεταβλητή και με ευρύτερο νόημα, ως όνομα δοχείου όπου
είναι δυνατό να θέσουμε ως περιεχόμενο οποιαδήποτε φόρμα και γενικά οντότητα της Lisp. Το
"δοχείο" αυτό είναι ορατό από ολόκληρο το πρόγραμμα, και γι' αυτό οι μεταβλητές συχνά
αποκαλούνται καθολικές (global variables). Η φόρμα που δίνουμε ως τιμή μεταβλητής μπορεί να

περιέχει υπολογισμό που περιέχει άλλες μεταβλητές. Μπορούμε να θέσουμε ως τιμή της
μεταβλητής αυτόν καθ' εαυτό τον υπολογισμό (δίνοντας "quoted" τη φόρμα) ή το αποτέλεσμά του
(δίνοντας απ΄ ευθείας τη φόρμα).
Προσοχή: επίσης συχνά, ο όρος "καθολική μεταβλητή" χρησιμοποιείται για ένα άλλο είδος
μεταβλητών, που θα δούμε αργότερα, τις ειδικές μεταβλητές (special variables).

Περιγράφονται στη συνέχεια λεπτομερώς οι βασικές συναρτήσεις QUOTE και EVAL . Η πρώτη
αποτρέπει την εκτέλεση υπολογισμού, και η δεύτερη επιβάλλει την εκτέλεση.

2.4.2 Καθυστέρηση και εξαναγκασμός υπολογισμού
Αποτροπή από υπολογισμό φόρμας (φραγή, καθυστέρηση υπολογισμού)
Δεδομένου οτι η Lisp "υπολογίζει ότι βρει", είναι απαραίτητο να μπορούμε να εμποδίσουμε τον
υπολογισμό αν θέλουμε να αναφερθούμε σ' αυτή καθ' εαυτή τη φόρμα ενός υπολογισμού, ως
οντότητα και όχι να υπολογιστεί η τιμή της.

quote : Πρόκειται για μια βασική συνάρτηση που αποτρέπει τη Lisp από το να "μπει μέσα" στο
όρισμά της προς υπολογισμό. H συνάρτηση αυτή έχει όνομα QUOTE , που συντάσσεται
κανονικά ως συνάρτηση, αλλά για συντομία μπορεί να γραφεί σαν μια απλή οξεία ′ ακριβώς πριν
τη φόρμα, όπως είδαμε. Αναιρείται από την EVAL , που θα δούμε παρακάτω.
Παραδείγματα:

(+ 3 4)
;; επιστρέφει: 7
(quote (+ 3 4))
;; επιστρέφει αυτούσια την παράσταση: (+ 3 4)
'(+ 3 4)
;; είναι το ίδιο με το προηγούμενο, επιστρέφει (+ 3 4)
(print (quote (+ 3 4)))

;; τυπώνει (+ 3 4) ως παράπλευρη δράση της PRINT, και
; επιστρέφει (+ 3 4) ως αποτέλεσμα της συνάρτησης PRINT

Λόγω της συχνότητας χρήσης τής QUOTE χρησιμοποιείται γενικά η ισοδύναμη μορφή με το
πρόθεμα “οξεία”.
H QUOTE έχει εφαρμογή σε νοηματικά διαφορετικές περιπτώσεις, όπως:
α'. Για να αναφερθούμε στο ίδιο το αναφερόμενο αντικείμενο – όνομα συμβόλου, και όχι στην
τιμή του (περιεχόμενό του), που αλλιώς θα υπολογιζόταν αυτόματα από τη Lisp:
(setq Νίκος 'μηχανικός)
'Νίκος
→ Νίκος
Νίκος
→ μηχανικός

β'. Για να επιτρέψουμε τη χρήση φόρμας ως λίστας από στοιχεία που συνθέτουν μια ομάδα ή
ένα σύνολο:
'(cat dog mouse)

; επιστρέφει το όρισμα της QUOTE , ήτοι: (cat dog mouse)
γ'. Για να αποτρέψουμε τη Lisp από το να υπολογίσει αυτό που ακολουθεί∙ είτε διότι θέλουμε

να αναφερθούμε ειδικά στον όρο που ακολουθεί και όχι στο αποτέλεσμα υπολογισμού του,
είτε διότι θέλουμε να καθυστερήσουμε αυτό τον υπολογισμό, ο οποίος είναι δυνατό να
προκληθεί αργότερα. Η εκτέλεση του καθυστερημένου υπολογισμού γίνεται με χρήση της
EVAL :
(setq
(setq
(setq
force
(eval

mass 4)
acceleration 10)
force '(* mass acceleration))
→ (* mass acceleration)
force)
→ 40

► Σε περίπτωση σφάλματος διακόπτεται η εκτέλεση. Αν και το τί ακριβώς είναι δυνατό να
ακολουθήσει μετά από σφάλμα εξαρτάται από τον compiler, εν γένει για ορισμένα σφάλματα,
που χαρακτηρίζονται ως συνεχιζόμενα σφάλματα (continuable errors) παρέχεται δυνατότητα
διόρθωσής τους από τον τελικό χρήστη.

Εξαναγκασμός σε εκτέλεση
α'. Εκτέλεση υπό καθυστέρησης εφαρμογής:
Είναι δυνατό να χρειαστούμε υπολογισμό τον οποίο να προσδιορίζουμε σε κάποια φάση και να
"διατάζουμε" να εκτελεστεί σε επόμενη φάση. Διευκρινίζουμε οτι, στην περίπτωση όπου έχει
νόημα ο "υπολογισμός του αποτελέσματος υπολογισμού", όπως όταν έχουμε σύνθεση με
περισσότερα του ενός επίπεδα, ο εξαναγκασμός σε εκτέλεση αφορά μόνο το πάνω επίπεδο.

eval : H συνάρτηση EVAL προκαλεί το αντίθετο από την QUOTE : υπολογίζει αυτό που
ακολουθεί. Eπομένως αναιρεί την QUOTE αν τεθεί πριν από αυτή, διότι η QUOTE επιστρέφει
παράσταση μη υπολογισμένη, την οποία ακριβώς η EVAL υπολογίζει:
(eval '(+ 3 4))
; επιστρέφει 7
(eval (print (quote (+ 3 4)))) ; τυπώνει (+ 3 4) και επιστρέφει 7
► Η συνάρτηση EVAL είναι αντίστροφη της QUOTE μόνο κατά την παραπάνω φορά, διότι
αντίθετα, η φόρμα ′(eval (+ 3 4)) επιστρέφει, χωρίς υπολογισμό λόγω του QUOTE, τη λίστα
δύο στοιχείων, όπου πρώτο είναι το όνομα - σύμβολο EVAL και δεύτερο η λίστα των τριών
στοιχείων (+ 3 4) .
Δεδομένου οτι το κάθε τί υπολογίζεται, το όνομα μεταβλητής υπολογίζεται, και η εφαρμογή της
EVAL πάνω σε όνομα θα προκαλέσει δεύτερο υπολογισμό, δηλ θα επιστρέψει την τιμή της τιμής
του, όπου βέβαια για να έχει η τιμή δική της τιμή, πρέπει να είναι σύμβολο ή "quoted" φόρμα
υπολογισμού. Οι σταθερές έχουν τιμή τον εαυτό τους και γι' αυτό δεν δίνονται "quoted".
β'. Κατέβασμα σε σκαλοπάτια σύνδεσης, μέσω υπολογισμού τιμής
Μπορούμε να σχηματίσουμε αλυσίδες από περιεχόμενη τιμή σε περιεχόμενη τιμή, και να
κινηθούμε στους "κρίκους της αλυσίδας" με την EVAL:
(setq dog 'Bill)
(setq mammal 'dog)
(setq animal 'mammal)

dog
→ Bill
mammal
→ dog
animal
→ mammal
(eval mammal)
→ Bill
(eval (eval animal)) → Bill

► Χρειάζεται προσοχή στη χρήση της EVAL , διότι προκαλεί υπολογισμό με διαδικαστικό
τρόπο (επιτακτικά) και μπορεί να δημιουργήσει προβλήματα όταν τη χρησιμοποιήσουμε μέσα σε
μια συναρτησιακή δομή που "δεν περιμένει" υπολογισμό. Ιδιαίτερα, αν πρόκειται για λμεταβλητή που χρησιμοποιούμε διαδικαστικά μέσα σε υπολογισμό.

Μερική αποτροπή υπολογισμού: καθυστέρηση τμημάτων του υπολογισμού
Συχνά η έκφραση που συνθέτουμε συμπεριλαμβάνει κάποια ανάμειξη όρων που θέλουμε να
υπολογιστούν και όρων που θέλουμε να μείνουν ως έχουν (δηλ. χωρίς να υπολογιστούν, μέχρι
επόμενη κλήση). Αυτό αντιμετωπίζεται πολύ εύκολα με μια στοιχειώδη συνάρτηση, παρεμφερή
της QUOTE , την BACKQUOTE :
(backquote x)

Συντομογραφικά (αντίστοιχα με το πρόθεμα "οξεία" αντί της QUOTE) θέτουμε αντί της
BACKQUOTE το πρόθεμα "βαρεία" ( ` ) πριν τη φόρμα, η οποία υποχρεωτικά πρέπει να έχει
μορφή λίστας (δηλ. δεν εφαρμόζεται σε άτομα). Οι όροι της λίστας δεν θα υπολογιστούν,
παρόμοια με το να θέταμε το πρόθεμα "οξεία" (quote) αλλά τώρα έχουμε τη δυνατότητα να
εξαιρέσουμε από την απαγόρευση υπολογισμού κάποιους όρους, θέτοντας σε καθένα από αυτούς
το πρόθεμα "comma" ( , ) το οποίο επίσης είναι συνάρτηση (με όνομα COMMA ):
(setq animal1 'cat)
→ cat
(setq animal2 'dog)
→ dog
(setq animals `(mouse bird ,animal1 ,animal2))
→ (mouse bird cat dog)

Αν τεθεί κόμμα σε κάθε όρο, επιστέφεται η λίστα των τιμών των όρων. Αυτό δεν είναι ισοδύναμο
του να μη τεθεί καθόλου το πρόθεμα και τα κόμματα, διότι τότε ο πρώτος όρος θάπρεπε να είναι
όνομα συνάρτησης.

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

►Παρ' όλο που ο συνδυασμός BACKQUOTE και COMMA είναι πολύ απλός, με αυτόν
πετυχαίνουμε κάτι εξαιρετικά σύνθετο: να περιγράψουμε σε μορφή συνάρτησης έναν
υπολογισμό όπου διακρίνουμε τη διαδοχή από κάποια βήματα εκτέλεσης, με δυνατότητα
παρέμβασης σ' αυτά, από την ίδια την εκτέλεση ή περνώντας τον έλεγχο στο χρήστη. Θα
συναντήσουμε ξανά το συνδυασμό αυτό.

Το συνδυασμένο πρόθεμα ,@
Συχνά θέτουμε σε λίστα μια ομάδα στοιχείων για να τα παραθέσουμε σε μια διατεταγμένη σειρά,
και να τα πάρουμε αν "κοιτάξουμε μέσα στη λίστα". Πχ:
(setq άνθρωπος '(ο Κώστας που εργάζεται στη ΔΕΗ))

Η κλήση της μεταβλητής "άνθρωπος" θα επιστέψει τη λίστα, που για μας έχει νόημα φράσης. Οι
παρενθέσεις δεν μας ενοχλούν ιδιαίτερα διότι είναι έξω από τη φράση:
άνθρωπος → (ο Κώστας που εργάζεται στη ΔΕΗ)
αλλά οπωσδήποτε ενοχλούν όταν παρεμβάλλονται σε άλλη φράση, όπως στο ακόλουθο:
(setq δράση 'έρχεται)
→ έρχεται
(setq τόπος 'εδώ)
→ εδώ
(setq χρόνος 'σήμερα)
→ σήμερα
(setq είδηση `( έμαθα οτι ,χρόνος ,δράση ,τόπος ,άνθρωπος ) )
είδηση →
(έμαθα οτι σήμερα έρχεται εδώ (ο Κώστας που εργάζεται στη ΔΕΗ))

Το πρόβλημα της εσωτερικής παρένθεσης, που ενοχλεί, λύνεται με θέση του συνδυασμού των
προθεματικών χαρακτήρων ,@ αντί για απλό κόμμα στον όρο που έχει τιμή με τις ανεπιθύμητες
παρενθέσεις. Αν δώσουμε:
(setq μορφοποιημένη_είδηση
`( έμαθα οτι ,χρόνος ,δράση ,τόπος ,@άνθρωπος ))

παίρνουμε:

μορφοποιημένη_είδηση →
(έμαθα οτι σήμερα έρχεται εδώ ο Κώστας που εργάζεται στη ΔΕΗ)

Βλέπουμε οτι το @ προκαλεί απλή παράθεση των στοιχείων της λίστας, χωρίς τις παρενθέσεις της
λίστας. Μπορούμε να αξιοποιήσουμε αυτή την αναίρεση των παρενθέσεων για επιπεδοποίηση
σύνθετης λίστας (δηλ. μορφής λίστα από λίστες κοκ).
Η πιο σημαντική αξιοποίηση των "backquoted" λιστών γίνεται στις macro συναρτήσεις, που θα
δούμε σε επόμενη ενότητα.

Η ταυτοτική συνάρτηση
identity
:
Επιστρέφει απλώς το όρισμά της (αν όρισμα. είναι φόρμα υπολογισμού, το
υπολογίζει πριν να το επιστρέψει):
(identity 3)
→ 3
(identity (+ 4 5)) → 9
Χρησιμοποιούμε τη συνάρτηση IDENTITY όταν η εφαρμοζόμενη συνάρτηση απαιτεί ως είσοδο
όνομα συνάρτησης αλλά δεν θέλουμε αυτή η συνάρτηση να δράσει (θα δούμε παρακάτω τέτοιες
συναρτήσεις).

2.4.3 Από τη μεταβλητή με τιμή, στο σύμβολο με περιεχόμενο
Εδώ θα συνοψίσουμε όσα αναφέραμε για τις μεταβλητές, παραθέτοντας τις διάφορες σημασίες
που είναι δυνατό να δώσουμε στην απόδοση τιμής σε μεταβλητή.

Απόδοση τιμής σε μεταβλητή
Είδαμε πως τα σύμβολα - μεταβλητές μπορούν να πάρουν “τιμή”, με το σύνηθες νόημα που έχει
αυτό σε άλλες γλώσσες:

setq : Η συνάρτηση SETQ εφαρμόζεται με πρώτο όρισμα το όνομα του συμβόλου
(νεοπροσδιοριζόμενου ή ήδη υπάρχοντος) και δεύτερο την τιμή που του αποδίδεται (η οποία
υπολογίζεται). Ως συνάρτηση, η SETQ επιστρέφει την αποδιδόμενη τιμή της μεταβλητής.
(setq myvar 64)

; ορίζει σύμβολο-μεταβλητή myvar με τιμή 64, και επιστρέφει 64

(print myvar)

; τυπώνει και επιστρέφει το περιεχόμενο της myvar: 64
(setq myvar (+ 1 myvar)) → 65
(print myvar)
→ 65
(setq newvar myvar)
→ 65
(print newvar)
→ 65
(setq anothervar ′myvar) → myvar
(print anothervar)
→ myvar
(eval anothervar)
→ 65
Στο τελευταίο, ο υπολογισμός "από μέσα προς τα έξω" προκαλεί κατ’ αρχήν υπολογισμό της
τιμής του συμβόλου anothervar , που είναι το σύμβολο newvar , και η εφαρμογή της
συνάρτησης EVAL πάνω στην newvar μας δίνει την τιμή της, 65 .

Η τιμή συμβόλου με το νόημα του "περιεχομένου" του συμβόλου
H απόδοση τιμής σε σύμβολο ουσιαστικά αποδίδει "περιεχόμενο" στην οντότητα που παριστά το
σύμβολο, και το περιεχόμενο μπορεί να είναι επίσης σύμβολο, ή γενικότερα οποιαδήποτε φόρμα:
(setq calc '(+ (* 2 3) 4)) → (+ (* 2 3) 4)
(setq symb1 'calc)
→ calc
(setq symb2 calc) → 24
Aυτό δίνει τη δυνατότητα θεώρησης "αλυσίδων οντοτήτων":
(setq Ελληνας 'Ευρωπαίος)
(setq Ιταλός 'Ευρωπαίος)
(setq Λαρισαίος 'Ελληνας)

; προσοχή: η οξεία δεν είναι του χαρακτήρα Ε

(setq
(setq
(setq
(setq

Πατρινός 'Ελληνας)
Αθηναίος 'Ελληνας)
Κώστας 'Πατρινός)
Νίκος 'Πατρινός)

(setq Γιώργος 'Αθηναίος)
(setq Γιάννης 'Λαρισαίος)

Έχουμε:

(print Κώστας)

→ Πατρινός (εκτύπωση)
Πατρινός

(επιστροφή)

διότι πριν την εκτέλεση του PRINT θα υπολογίσει το σύμβολο Κώστας , που επιστρέφει τιμή:
Πατρινός .
(eval Κώστας)

Ελληνας

διότι ο αρχικός υπολογισμός του συμβόλου Κώστας επιστρέφει Πατρινός , το οποίο είναι
σύμβολο, και λόγω της EVAL υπολογίζεται, και επιστρέφει Ελληνας . Για τον ίδιο λόγο:
(eval (eval Κώστας)) → Ευρωπαίος
Βλέπουμε οτι αν εφαρμοστεί η EVAL σε σύμβολο ήδη υπολογισμένο, δηλαδή στην τιμή του,
προκαλεί υπολογισμό του επομένου επιπέδου, δηλ. τής τιμής τής τιμής. Αν η πρώτη τιμή είναι
καταληκτική, ο δεύτερος υπολογισμός της, δίνει την ίδια τιμή:
(setq myvar 4)
; επιστρέφει 4
(print myvar)
; τυπώνει 4 και επιστρέφει 4
(eval myvar)
; επιστρέφει 4
(eval (eval myvar) ) ; επιστρέφει 4

Όπως θα δούμε παρακάτω, τα σύμβολα δεν έχουν μόνο περιεχόμενο, αλλά και ιδιότητες, οι
οποίες διαφοροποιούνται τυπικά και ουσιαστικά από το περιεχόμενο.

Παράλληλη και σειριακή καταχώρηση
H συνάρτηση SETQ μπορεί να χρησιμοποιηθεί και με περισσότερα ορίσματα, και για την
ακρίβεια δυάδες ορισμάτων, για οικονομία γραφής όταν πρόκειται να δοθούν πολλές δηλώσεις
ανεξαρτήτων μεταξύ τους μεταβλητών. Τότε γράφουμε:
(setq var1 'val1 var2 'val2 var3 'val3)

αντί για τρεις χωριστές δηλώσεις:
(setq var1 'val1)
(setq var2 'val2)
(setq var3 'val3)

Διαφέρει η ομαδική γραφή από τη χωριστή, κατά το οτι με διαδοχικά SETQ μπορούμε να
χρησιμοποιούμε τις ήδη ορισμένες μεταβλητές (και σε νεότερους compilers να χρησιμοποιούμε
σε ορισμό άλλες μεταβλητές, ανεξάρτητα της σειράς ορισμού τους). Με "χωριστά" διαδοχικά
SETQ μπορούμε να προσδιορίσουμε κάποια αλληλεξάρτηση μεταβλητών αξιοποιώντας τη
δυνατότητα απόδοσης τιμής οποιασδήποτε μορφής∙ αντίθετα, μέσα σε ένα ομαδικό SETQ οι
καταχωρήσεις εννοούνται ανεξάρτητες μεταξύ τους, άρα η αναφορά μεταβλητής σε άλλες
μεταβλητές του ίδιου SETQ δεν είναι δυνατή. Για το λόγο αυτό, η συνάρτηση SETQ αποκαλείται
παράλληλης καταχώρησης. Χρησιμεύει για οικονομία γραφής, αλλά κυρίως για την εξασφάλιση
του οτι αναφερόμαστε στις αυτές αρχικές συνθήκες, ανεξάρτητα των δράσεων που προκαλούνται

από τον υπολογισμό των τιμών που αποδίδονται.

setq*
: Αντίθετα, για να περιγράψουμε πολλαπλή, μαζική καταχώρηση μεταβλητών που
ενδεχομένως έχουν αλληλεξάρτηση, και ειδικότερα κατά τη διαδοχή γραφής τους,
χρησιμοποιούμε τη συνάρτηση SETQ* , η οποία "βλέπει" τις καταχωρήσεις που έχει ήδη κάνει
η ίδια:
(setq* v1 3 v2 (+ v1 4))
v2 → 7

Για το λόγο αυτό, η συνάρτηση SETQ* αποκαλείται σειριακής καταχώρησης.

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

2.4.4 Διάφορες έννοιες που παίρνει η χρήση μεταβλητών
Δημιουργία συμβόλου - μεταβλητής
Όταν έχουμε σκοπό να δημιουργήσουμε ένα σύμβολο για να μπορούμε να αναφερθούμε σ' αυτό
(πχ. για να του αποδώσουμε ιδιότητες, όπως θα δούμε παρακάτω), ακόμα και αν δεν μας
ενδιαφέρει ως μεταβλητή (δηλ. σύμβολο με τιμή), μπορούμε να χρησιμοποιήσουμε την SETQ .
Επειδή η SETQ απαιτεί να δώσουμε κάποιο περιεχόμενο - τιμή στο σύμβολο, αρκεί να δώσουμε
οτιδήποτε, αλλά είναι σκόπιμο να αποφύγουμε την τιμή "κενό" ( NIL) διότι η τιμή αυτή σε λογικό
έλεγχο θα εκτιμηθεί από τη Lisp ως "false". Εξυπηρετεί πρακτικά να δώσουμε ως περιεχόμενο το
ίδιο το όνομα, για να μπορούμε να καλούμε αδιάκριτα το σύμβολο ή την τιμή του
(setq dog 'dog)→ dog
Αν θέλουμε να διακρίνουμε τα επίπεδα "σύμβολο" - "τιμή", είναι πρακτικό να δώσουμε την τιμή
"true", η οποία έχει το πλεονέκτημα οτι είναι του γενικότερου δυνατού τύπου:
(setq dog t)

→ Τ

Προσδιορισμός συμβόλου - μεταβλητής ορισμένου τύπου
Εάν γνωρίζουμε τον τύπο του περιεχομένου ενός συμβόλου - μεταβλητής, είναι αρκετό να
δώσουμε αρχικά μια τιμή του κατάλληλου τύπου. Η μεταβολή της τιμής, αργότερα, σε άλλη ίδιου
τύπου είναι πολύ ταχύτερη από μεταβολή σε τιμή διαφορετικού τύπου. Η αλλαγή τύπου του
περιεχομένου μεταβλητής είναι αυτόματη, με την απόδοση του νέου περιεχομένου:
(setq horse “jonny”)
; η τιμή της μεταβλητής horse ορίζεται ως τύπου string
→ jonny
(setq horse 32)
; η τιμή της μεταβλητής horse μετατρέπεται σε τύπου integer

→ 32

Αρχικοποίηση μεταβλητής

Η απόδοση μιας αρχικής τιμής σε μεταβλητή αποκαλείται αρχικοποίηση (initialization) και
μπορεί να φανεί χρήσιμη τόσο σε επίπεδο προκαθορισμού του τύπου όσο και σε επίπεδο
"ξεκινήματος" για κάποιον υπολογισμό, πχ. για να προσδιορίσουμε την τιμή μεταβλητής με τρόπο
εξαρτημένο από την προηγούμενη τιμή:
(setq var 2)
(setq var '(* var (read)))
(eval var) → <φέρνει σε κατάσταση εισόδου∙ δίνουμε τιμή>
5 <enter>
→ 10
(eval var) → <φέρνει σε κατάσταση εισόδου∙ δίνουμε τιμή>
4 <enter>
→ 40

Σύνοψη των χρήσεων της SETQ
Aπό τα παραπάνω φαίνεται η αξιοποίηση της SETQ σε πολλές, εννοιολογικά διαφορετικές,
καταστάσεις:
α'. Για τον ορισμό καθολικών μεταβλητών με καταληκτική τιμή:
(setq money 20) → 20
money
→ 20
β'. Για τον ορισμό αντικειμένου και τον προσδιορισμό αυτού που θεωρείται ως το
αντιπροσωπευτικό χαρακτηριστικό του, όπως το όνομα ενός ζώου:
(setq dog 'jack)

γ'. Για τον ορισμό ονόματος και αυτού που θεωρείται οτι αντιπροσωπεύει:
(setq George 'mathematician)

δ'. Για την έμμεση περιγραφή ιεραρχικών κατηγοριοποιήσεων, αξιοποιώντας διαδοχικές
αποδώσεις συμβόλων ως περιεχόμενο άλλων:
(setq mathematician 'scientist
physician 'scientist)
(setq Giannis 'mathematician
George 'mathematician
Kostas 'physician)

Εφαρμογή:
Giannis
→ mathematician
(eval Giannis) → scientist
Kostas
→ physician
(eval Kostas)→ scientist

physician

→ scientist

ε'. Για την ονομασία ν-άδων αντικειμένων όπου το αποδιδόμενο όνομα νοείται ως το κοινό
χαρακτηριστικό τους, η κατηγορία τους, ή το όνομα της συλλογής τους:
(setq animals '(cat mouse bird))
animals
→ (cat mouse bird)

ς'. Για τον επαναπροσδιορισμό της τιμής μεταβλητής∙ στο δ’:
(setq George 'informatician)
; η προηγούμενη τιμή mathematician χάνεται

ζ'. Για επαναπροσδιορισμό τιμής με χρήση της προηγούμενης:

(setq money ( * money 3))
money →
60
(το money είχε οριστεί προηγουμένως)

η'. Για κατ’ ευθείαν περιγραφή μιας σύνθετης δομής, η οποία δεν αναγνωρίζεται υποχρεωτικά
ως τέτοια από τη Lisp, αλλά είναι κατανοητή από το χρήστη ή από τον τρόπο που καλείται
από άλλες συναρτήσεις:
(setq like-an-array '((1 2 3 4) (2 3 4 5) (3 4 5 6)) )

Το σύμβολο like-an-array έχει δομή πίνακα 3x4, και μπορεί πολύ καλά να χρησιμοποιηθεί ως
πίνακας∙ οι συναρτήσεις CAR , CDR , SETQ και συνδυασμοί αυτών, επαρκούν για
προσδιορισμό οποιασδήποτε πράξης πινάκων ή διαμόρφωσης πίνακα∙ παρ' όλα αυτά, έχουμε
ιδιαίτερο τύπο πίνακα, που διαθέτει δείκτη "τρέχουσας θέσης", τύπο στοιχείων, και
διατίθενται ειδικές συναρτήσεις επεξεργασίας πινάκων.
Η τιμή συμβόλου είναι δυνατό να καθορίζεται πολυεπίπεδα. Ένας απλός τρόπος γι' αυτό είναι
να χρησιμοπoιήσουμε τη δομή τής λίστας:
(setq animals
'((mammals
(humanoids canines felines))
(reptiles (lizards snakes))))

Στο παράδειγμα αυτό μέσω της δομής "λίστα από λίστες" αποδόθηκε η κατηγοριοποίηση του
νοηματικού χώρου: πρώτος όρος κάθε όρου είναι το όνομα της κατηγορίας, δεύτερος τα
συγκεκριμένα είδη που περιέχει (θα δούμε και άλλους τρόπους έκφρασης
κατηγοριοποιήσεων).
θ'. Για τον προσδιορισμό του τύπου μεταβλητής. Όταν δημιουργούμε ένα σύμβολο – μεταβλητή
με σκοπό να χρησιμοποιηθεί αργότερα, είναι σκόπιμο να προσδιορίζεται εξ αρχής με τον
κατάλληλο τύπο∙ αρκεί γι' αυτό να δοθεί μια αρχική τιμή του τύπου αυτού:
(setq mychar #\a)
→ #\a
; η μεταβλητή mychar είναι τύπου character, με τιμή το a
ι'.

Για την καταχώρηση φόρμας που θέλουμε να υπολογιστεί αργότερα. Αν δώσουμε:
(setq a1 2 a2 4)
(setq v1 (> a1 a2))

τότε η v1 παίρνει τιμή NIL (ψευδές), που είναι το αποτέλεσμα του υπολογισμού (> a1 a2) .

Αν όμως δώσουμε:

(setq v2 '(> a1 a2))

η v2 παίρνει τιμή την ίδια τη φόρμα (> a1 a2) , η οποία θα υπολογιστεί όταν καλέσουμε την
v2 με EVAL , και θα δώσει αποτέλεσμα σύμφωνα με τις τρέχουσες τιμές των a1 και a2 (δηλ.
αυτές που θα έχουν κατά τη στιγμή της κλήσης):
(setq a1 5 a2 2) → 2
(επιστρέφεται ο τελευταίος υπολογισμός)
v2
→ (> a1 a2)
(επιστρέφεται η έκφραση υπoλογισμού)
(eval v2)
→ T
(η τιμή της έκφρασης, που είναι "αληθές")
ια'. H κυκλική αναφορά όρων κατά τον ορισμό περιεχομένου δεν απαγορεύεται αλλά πρέπει να
έχουμε ειδικό λόγο γι' αυτό, διότι συχνά είναι αιτία σύγχυσης.
(setq dog 'jack) →
jack
(setq jack dog) →
jack
dog)

jack
jack

jack
(γιατί;)
Οι έννοιες χρήσης της SETQ δεν εξαντλούνται εδώ.

2.5 Σύμβολα - συναρτήσεις και η έννοια της λ-μεταβλητής
Στην ενότητα αυτή θα μελετήσουμε την έννοια της συνάρτησης ως ειδικό υποτύπο function του
τύπου symbol και την έννοια της λ-μεταβλητής, που είναι αντίστοιχη της μαθηματικής έννοιας
της μεταβλητής μιας συνάρτησης.

2.5.1 Διάκριση συμβόλων σε μεταβλητές και συναρτήσεις

Oι συναρτήσεις είναι σύμβολα, που είναι συνδεδεμένα με την ειδική συναρτησιακή
λειτουργικότητα που τους αποδίδεται. Οι συναρτήσεις είναι οντότητες τύπου symbol αλλά
διαφορετικού υποτύπου από τις μεταβλητές. Σε ορισμένες διαλέκτους της Lisp διαφοροποιούνται
ως τύποι οι "καθαρές" συναρτήσεις από τις διαδικασίες, και σε άλλες όχι (κατά το πρότυπο της
CL δεν διαφοροποιούνται). Δεν αποκλείεται, ένα σύμβολο να ορίζεται ως συνάρτηση και ως
μεταβλητή, και να παίζει και τους δύο ρόλους, ανεξάρτητα.
Στην CL οι συναρτήσεις διαφοροποιούνται μεν από τις μεταβλητές ως προς τον υπολογισμό,
αλλά συναρτήσεις και μεταβλητές υπόκεινται σε κοινή διαχείριση ως σύμβολα (πχ. μπορούμε να
καταχωρήσουμε ιδιότητες σε συνάρτηση, ανεξάρτητα από τον υπολογισμό που προσδιορίζει,
όπως και σε μεταβλητή∙ επίσης, να εφαρμόσουμε συναρτήσεις που επεξεργάζονται ονόματα
συμβόλων).
Θα δούμε την διαφοροποίηση "κλήσης συνάρτησης προς εφαρμογή", από την "κλήση ονόματος
συνάρτησης".

λ-μεταβλητές έναντι συμβόλων - μεταβλητών
Κάνουμε εδώ μια επανάληψη των βασικών χαρακτηριστικών των συμβόλων - μεταβλητών, με
σκοπό τη διαφοροποίησή τους από τις λ-μεταβλητές, αλλά και το συσχετισμό τους με λμεταβλητές, διότι όπως θα δούμε αργότερα, ο ρόλος λ-μεταβλητής μεταπίπτει σε ρόλο
συμβόλου - μεταβλητής μέσα σε υπολογισμό, και καθ' όλη τη διάρκεια εκτέλεσης του
υπολογισμού.
Όπως προαναφέραμε, λ-μεταβλητές είναι οι μεταβλητές συνάρτησης, δηλ. αυτές που τυπικά
αναγράφονται στη λίστα των λ-μεταβλητών κατά τον ορισμό της, που θα δούμε αμέσως μετά (και
έχουν νόημα πανομοιότυπο με αυτό των ανεξαρτήτων μεταβλητών των μαθηματικών
συναρτήσεων, όπως η x όταν σημειώνουμε: “έστω y=f(x)” ).
Τα σύμβολα - μεταβλητές (που θα τα αναφέρουμε και απλά ως μεταβλητές ή ως καθολικές
μεταβλητές) ορίζονται αυτόνομα ως σύμβολα, και έχουν καθολική ισχύ, ανεξάρτητα της
εφαρμογής που τις καλεί. Οι τοπικές μεταβλητές, είναι σύμβολα που έχουν ισχύ σε συγκεκριμένο
τόπο, και δεν είναι τίποτα άλλο από λ-μεταβλητές κάποιας συναρτησιακής έκφρασης, κατά την
εφαρμογή της οποίας οι λ-μεταβλητές της "λειτουργούν" ως σύμβολα στα πλαίσια του
εκτελούμενου υπολογισμού.
Μπορούμε να δούμε τα σύμβολα - μεταβλητές ως οντότητες με αυτόνομη και διαρκή ύπαρξη
(ανεξάρτητα των εφαρμογών), δική τους τιμή - περιεχόμενο, δικές τους ιδιότητες, και που
συμμετέχουν ενδεχομένως σε κάποιες συνδέσεις που προσδιορίζουν συνθετότερες οντότητες.

Ορισμός συμβόλου - μεταβλητής μπορεί να γίνει είτε απ' ευθείας στο πρώτο επίπεδο, είτε κατά
την εκτέλεση υπολογισμού (οπότε, αναφέρεται ο ορισμός του στο σώμα της αντίστοιχης
συνάρτησης, και ενεργοποιείται με την εφαρμογή της συνάρτησης). Προϋπόθεση ορισμού
συμβόλου - μεταβλητής μέσα στο σώμα συνάρτησης, είναι οτι δεν συμπίπτει το όνομα αυτού του
συμβόλου με το όνομα λ-μεταβλητής της συνάρτησης.

Κατά την κλήση εφαρμογής μιας συνάρτησης (είτε πρόκειται για συνάρτηση που ορίζει ο
χρήστης είτε για πρωτογενή συνάρτηση της Lisp), οι μεν λ-μεταβλητές της δεσμεύονται πάνω στις
αντίστοιχες τιμές των ορισμάτων που δίνονται στη συνάρτηση κατά την κλήση, οι δε καλούμενες
(χωρίς quote) μεταβλητές - σύμβολα υπολογίζονται και τη θέση τους παίρνει η τιμή που έχουν
κατά τη στιγμή της κλήσης τους.

Η ιδιαίτερη κατηγορία των ειδικών μεταβλητών (special variables), που είναι μεταβλητές που
ορίζονται με την DEFPARAMETER ή την DEFVAR ή δηλώνονται με τη συνάρτηση SPECIAL ,
αναφέρονται στο παρόν αποκλειστικά με τον όρο "ειδικές μεταβλητές" (περιγράφονται στα
επόμενα). Ο όρος "μεταβλητή" δεν πρέπει να χρησιμοποιείται για λ-μεταβλητές. Στη
βιβλιογραφία, μερικές φορές ο όρος "καθολικές μεταβλητές" (global variables) χρησιμοποιείται
για τις "ειδικές μεταβλητές", ενώ οι αναφερόμενες εδώ ως σύμβολα - μεταβλητές συναντώνται
και ως "λεξικογραφικές μεταβλητές" (lexical variables), εξ αιτίας του οτι εντοπίζονται
λεξικογραφικά. Ο μελετητής που διασταυρώνει στοιχεία από περισσότερες πηγές, πρέπει να
αποσαφηνίσει το πώς αναφέρονται σε κάθε πηγή οι αντίστοιχοι όροι.

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

Απόψεις κατάταξης των συναρτήσεων
α'.
Από την άποψη της αλληλεπίδρασης, διακρίνουμε τις συναρτήσεις σε αναπλάσιμες σε
επίπεδο επέμβασης στον πηγαίο κώδικα, και σε απλώς χρησιμοποιήσιμες, σε μεταγλωττισμένη
μορφή.
– Οι primitive συναρτήσεις της γλώσσας, περιεχόμενες στον δυαδικό πυρήνα του compiler
παρέχουν μόνο πρόσβαση χρήσης σ' αυτές. Δεν αναθεωρούνται.
– Οι συναρτήσεις που φορτώνονται από πακέτα ή βιβλιοθήκες που προσφέρει ο
κατασκευαστής, είναι μεταγλωττισμένες. Δεν αναθεωρούνται. Παρ' όλα αυτά, ο χρήστης έχει
πρόσβαση στον πηγαίο κώδικα μόνο με ειδική άδεια του κατασκευαστή (ειδική option αγοράς
του compiler), οπότε είναι αναθεωρήσιμες με κατάλληλο editing, αλλά όχι στο επίπεδο χρήσης
της γλώσσας.
– Οι συναρτήσεις που ορίζονται κατά τη συγγραφή προγράμματος (runtime ή με φόρτωμα
αρχείου) είναι αναθεωρήσιμες (δηλ. είναι δυνατός ο επανορισμός τους). Είναι δυνατό να
προσφερθούν σε "επόμενο χρήστη" είτε σε μεταγλωττισμένη μορφή (αναθεωρήσιμες μόνο με
επανορισμό) είτε σε πηγαία (αναθεωρήσιμες στις λεπτομέρειες του κώδικα).
β'.
Από την άποψη του υπολογισμού που επιτελούν, είναι δύο βασικών κατηγοριών:
i) συναρτήσεις, κατά το Μαθηματικό νόημα (αριθμητικές συναρτήσεις ή γενικότερα,
μονοσήμαντες απεικονίσεις - μετασχηματισμοί της εισόδου)
ii) συναρτήσεις - αλγόριθμοι (κάθε αλγόριθμος γράφεται ως συνάρτηση στη Lisp)∙ η
κατηγορία αυτή έχει δύο υποκατηγορίες, όχι απαραίτητα ξένες μεταξύ τους:
– συναρτήσεις που υλοποιούν έναν επιτακτικά διαδικαστικό αλγόριθμο∙ στη χρήση, τις
βλέπουμε σαν διαταγές - εντολές προς τον υπολογιστή
– συναρτήσεις που υλοποιούν ένα δηλωτικά προσδιορισμένο αλγόριθμο (ορίζουν ή
τροποποιούν μια κατάσταση)∙ στη χρήση, τις βλέπουμε σαν υλοποιημένη γνώση.

γ'.
Από την άποψη του σκοπού που εξυπηρετούν έχουμε τις εξής κατηγορίες (τις
αναφερόμενες συναρτήσεις, θα τις μελετήσουμε σταδιακά):
i) Συναρτήσεις ορισμού οντότητας, όπως οι DEFUN, SETQ, SETF, DEFTYPE,
DEFMACRO, DEFMETHOD, DEFSTRUCT, DEFCLASS, DEFVAR .
ii) Συναρτήσεις θεμελιακής λειτουργικότητας που επιτρέπουν εμβάθυνση στην ίδια την
έννοια του υπολογισμού , όπως οι LAMBDA, FUNCALL, ΑPPLY .

iii)
Συναρτήσεις βοήθειας του χρήστη σε ότι αφορά αφ' ενός τη λειτουργικότητα συμβόλων,
όπως οι APROPOS , DOCUMENTATION, και αφ' ετέρου την πορεία ή την αποτελεσματικότητα
υπολογισμού, όπως οι TRACE, TIME .
iv)
Συναρτήσεις διευκόλυνσης του χρήστη∙ τέτοιες συντάσσει κατά κανόνα ο
προγραμματιστής για να καταστήσει εύκολη τη διαχείριση του προγράμματος από τον τελικό
χρήστη∙ η Lisp διαθέτει επίσης πολλές συναρτήσεις διευκόλυνσης του προγραμματιστή, όπως οι
FIRST, SECOND, κλπ, TENTH, LAST, BUTLAST, 1+ , 1– .
v) Συναρτήσεις που χωρίς να είναι θεμελιακές, ωστόσο βοηθούν στην ανάπτυξη κώδικα,
όπως οι LET, LET*, MAPCAR, MAP, MAPCAN .
vi)
Συναρτήσεις για την ανάπτυξη διαδικαστικού κώδικα, που χρησιμοποιούνται ως
"σύνθετες διαδικαστικές εντολές"∙ αν και όλες οι συναρτήσεις είναι δυνατό να χρησιμοποιηθούν
ως τέτοιες εντολές, ιδιαίτερα αξιοποιούνται οι PROG, PROGN, DOLIST, DO, DO*, TIMES .

vii) Συναρτήσεις ελέγχου (τα λεγόμενα κατηγορήματα της Lisp), όπως τα αριθμητικά
συγκριτικά (>, =, κλπ), του συσχετισμού ( κλπ), της επιλεκτικής διαφοροποίησης ή ομοιότητας
(SOME, EVERY, κλπ)∙ ιδιαίτερο χαρακτηριστικό είναι οτι οποιαδήποτε συνάρτηση μπορεί να
χρησιμοποιηθεί ως συνάρτηση ελέγχου, διότι τα πάντα στη Lisp είναι αληθή ή ψευδή.
viii)

Θεμελιακές συναρτήσεις που επιτελούν απ' ευθείας ένα πολυσύνθετο έργο, όπως οι
APPLY, ASSOC, RASSOC .
ix)

Συναρτήσεις επεξεργασίας του "εσωτερικού οντοτήτων", όπως οι CAR, CDR,
REPLACE, RPLACA, RPLACD .
x)

Συναρτήσεις διαχείρισης αρχείων / εισόδου / εξόδου, όπως οι OPEN, WITH-OPEN-FILE,
READ, PRINT, WRITE .
xi)

Συναρτήσεις που η χρήση τους έχει νόημα μόνο μέσα σε άλλες συναρτήσεις, όπως οι
RETURN, GO, CATCH, THROW .
xii) Συγκεκριμένες συσχετίσεις συναρτήσεων, που με το συνδυασμό τους επιτελείται ένα
ειδικό έργο, όπως ο συνδυασμός (SETF (GET …) …)
xiii) Συναρτησιακές εκφράσεις που προσδιορίζονται έμμεσα, ως αποτέλεσμα εφαρμογής, και
είναι δυνατό να εφαρμοστούν ως συναρτήσεις, όπως η ακόλουθη (θα μελετήσουμε εκτενώς την
FUNCALL σε επόμενο κεφάλαιο):
(funcall (defun cube (x) (* x x x)) 3) → 27

xiv) Συναρτήσεις προσδιορισμού φυσικής οντότητας, όπως οι GET, GET-DECODED-TIME
και μεταβολής φυσικής οντότητας, όπως η SETF

Επίδραση στη λειτουργικότητα συναρτήσεων έχουν κατά περίπτωση κάποια κλειδιά: με
χαρακτηριστικό οτι έχουν το πρόθεμα "άνω-κάτω τελεία" ( : ) χρησιμοποιούνται αποκλειστικά σε
ορισμένες θέσεις για να προσδιορίσουν είτε κάποια κατάσταση είτε το ρόλο της οντότητας που
ακολουθεί.
δ'.
Από την άποψη του τρόπου σύνταξης ή/και χρήσης συναρτήσεων, διακρίνουμε
περιπτώσεις που υπακούουν σε ειδικούς νόμους σύνθεσης και χρήσης, και είναι: i) οι

συναρτήσεις ορισμού, ii) οι macro συναρτήσεις, iii) οι συναρτήσεις που επιστρέφουν
περισσότερες της μιας τιμές, iv) οι συναρτήσεις που δεν έχουν προκαθορισμένο πλήθος
ορισμάτων, v) οι αρχέτυπες συναρτήσεις, vi) οι μέθοδοι.
Θα μελετήσουμε αναλυτικά τις περιπτώσεις αυτές.

2.5.3 Ορισμός και εφαρμογή συνάρτησης
Ο χρήστης της γλώσσας μπορεί να ορίζει συναρτήσεις βασισμένες σε πρωτογενώς ορισμένες
συναρτήσεις της γλώσσας ή/και σε συναρτήσεις που έχει ήδη ορίσει, ακόμα και σε συναρτήσεις
που πρόκειται να ορίσει. Μεταξύ άλλων συναρτήσεων που ορίζουν σύμβολα με λειτουργικότητα,
θεμελιακό ρόλο παίζει η συνάρτηση DEFUN :
defun : Συνάρτηση ειδικής μορφής, με πρώτο όρισμα το όνομα του συμβόλου - συνάρτησης,
δεύτερο τη λίστα των λ-μεταβλητών της, και επόμενα ορίσματα αυτά που αποτελούν το σώμα
υπολογισμού της συνάρτησης (ως "εννοούμενο progn", όπως έχουμε αναφέρει στο Κεφ.1) :
(defun <όνομα συνάρτησης> <λίστα λ-μεταβλητών> <σώμα> )
Το όνομα συνάρτησης υπακούει στους περιορισμούς ονόματος συμβόλου. που αναφέραμε στις
μεταβλητές.

Η λίστα των λ-μεταβλητών περιέχει το σύνολο των λ-μεταβλητών της συνάρτησης, στη διάταξη
με την οποία θα δοθούν τα αντίστοιχα ορίσματα κατά την εφαρμογή της. Το μέγιστο πλήθος λμεταβλητών καθορίζεται από την τιμή ειδικής μεταβλητής, με αρχική τιμή συνήθως 50 . Η λίστα
των λ-μεταβλητών μπορεί να είναι κενή.
Μια συνάρτηση περισσοτέρων της μιας μεταβλητών, όπως και στο θεωρητικό μοντέλο,
ισοδυναμεί με συνάρτηση μιας μεταβλητής που η εφαρμογή της έχει ως αποτέλεσμα συνάρτηση
της οποίας η εφαρμογή έχει ως αποτέλεσμα συνάρτηση, κοκ. μέχρι σταθερού αποτελέσματος. Τα
βήματα αυτά των διαδοχικών εφαρμογών είναι ίσου πλήθους με το πλήθος των λ-μεταβλητών.
Η γραφή συνάρτησης με περισσότερες μεταβλητές προϋποθέτει ορισμένη διαδοχή των ορισμάτων
που θα δώσουμε. Αυτό είναι σημαντικό, διότι ο υπολογισμός εφαρμόζεται κατά την κανονική
τάξη: αυτό σημαίνει υπολογισμό πρώτα των ορισμάτων, με τη διαδοχή που δίνονται, και αυτό
σημαίνει οτι το αποτέλεσμα υπολογισμού ενός ορίσματος μπορεί να επιδρά στο αποτέλεσμα
υπολογισμού επομένου ορίσματος, αλλά όχι το αντίθετο.

Tο σώμα είναι μια πεπερασμένη ακολουθία από φόρμες (ενδεχομένως καμμία) οι οποίες
εκφράζουν υπολογίσιμες συνθέσεις των λ-μεταβλητών, ενδεχομένως με κλήσεις άλλων
συναρτήσεων και μεταβλητών. Το σώμα καθορίζει τον υπολογισμό που θα γίνει πάνω στις λμεταβλητές όταν κληθεί η συνάρτηση για εφαρμογή πάνω σε κατάλληλου τύπου ορίσματα.
Επομένως, η παραπάνω φόρμα μπορεί να γραφεί:
(defun όνομα-συνάρτησης (λ-μεταβλητές)
φόρμα1 φόρμα2 φόρμα3 … φόρμαk )

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

μπορεί να επανορίζεται μέσω της DEFUN (εκτός βέβαια αν είναι primitive της γλώσσας).

Οι λ-μεταβλητές συνάρτησης δεν σχετίζονται με καθολικές μεταβλητές που τυχόν έχουν το ίδιο
όνομα, ούτε τις επηρεάζουν. Χρήση ονομάτων λ-μεταβλητών που συμπίπτουν με ήδη ορισμένες
καθολικές μεταβλητές (που έχουν οριστεί πχ. μέσω της SETQ) επιτρέπεται, και στην περίπτωση
αυτή νοούνται κατά προτεραιότητα ως λ-μεταβλητές, διαφορετικές των καθολικών, σε
οποιαδήποτε αναφορά τους μέσα στο σώμα της συνάρτησης, σε όλα τα επίπεδα (όταν έχουμε
εγκλεισμό). Προφανώς δεν είναι δυνατή η αναφορά μέσα στο σώμα συνάρτησης, κάποιων
συμβόλων - μεταβλητών που έχουν όνομα ίδιο με λ-μεταβλητές.
Παράδειγμα
Ορισμός:
(defun f (x)
(setq b 3)
(setq c (+ b 2))
(princ
`("χρησιμοποιούνται καθολικές μεταβλητές, b=" ,b " και c=" ,c) )
(* b c x) )

Εφαρμογή:

(f 4)

(χρησιμοποιούνται καθολικές μεταβλητές, b= 3 και c= 5) (εκτύπωση)
60
(επιστροφή τιμής)

2.5.4 Εφαρμογή συνάρτησης και δέσμευση λ-μεταβλητών
Στη Lisp, η εφαρμογή συνάρτησης γίνεται σύμφωνα με το θεωρητικό μοντέλο του λ-λογισμού, με
τη διαφορά πως είναι δυνατό, μια συνάρτηση να μην έχει καμμία λ-μεταβλητή ή να έχει
περισσότερες της μιας. Το σώμα συνάρτησης μπορεί να είναι απλή φόρμα ή διαδοχή από φόρμες.
Κλήση εφαρμογής συναρτήσεων γίνεται με σύνταξη προθεματικής φόρμας, με πρώτο όρο το
όνομα της συνάρτησης και επόμενους τα ορίσματα στα οποία θα δεσμευτούν οι λ-μεταβλητές της,
κατ' αντιστοιχία αναφοράς τους στη λ-λίστα.
– Κλήση εφαρμογής μπορεί να γίνει είτε στο πρώτο επίπεδο (πχ. στο toploop) οπότε η τιμή της
συνάρτησης επιστρέφεται στο ρεύμα εξόδου (οθόνη), είτε σε χαμηλότερο επίπεδο (μέσα σε
υπολογισμό) οπότε τιμή της συνάρτησης επιστρέφεται στο ανώτερό του επίπεδο (αν το ανώτερό
του επίπεδο είναι το πρώτο επίπεδο, η επιστροφή θα γίνει στο ρεύμα εξόδου).
– Κλήση εφαρμογής σε χαμηλότερο επίπεδο έχουμε σε δύο, διακρινόμενες νοηματικά μεταξύ
τους, περιπτώσεις:
i) όταν η εφαρμογή που καλείται, αποτελεί όρισμα εφαρμογής συνάρτησης, πχ οι εφαρμογές
(* 3 4) και (- 15 7) δίνονται ως ορίσματα στη συνάρτηση +
(+ (* 3 4) (- 15 7))
→ 20

ii) όταν η εφαρμογή αποτελεί αλγοριθμικό βήμα, δηλαδή όρο προς εκτέλεση σε έναν
υπολογισμό με διαδοχικά βήματα. Για παράδειγμα, οι φόρμες εφαρμογής (setq x (+ x 4)) και
(- 3 x) είναι τέτοια βήματα που προσδιορίζονται στο σώμα τής FN00 :
(defun fn00 (x) (setq x (+ x 4)) (- 3 x) )

Εφαρμογή της FN00 πάνω στο 3 :
(fn00 3) → -4
Έστω οι εξής ορισμοί:
(defun fn01
(setq a 4)
(defun fn02
(defun fn10
(defun fn11
(defun fn12
(defun fn30
(defun fn31

( ) "hello" )
( ) (+ a 5))
(x) (* x (+ 3 x)))
(x) (* a (+ 3 x)))
(x) (print a) (setq a (+ a x)) (print a) (* a (+ 3 x)))
(x y z) (+ (* x y) (* y z) (* z x)))
(x y z) (* a (+ (* x y) (* y z) (* z x))))

Κλήσεις εφαρμογής των παραπάνω είναι:
(fn01)
→ "hello"
(fn02)
→ 9
(fn10 2) → 10
(fn11 2) → 20
(fn12 2) →
4
(εκτύπωση λόγω του πρώτου print)
6
(εκτύπωση λόγω του δεύτερου print∙ είναι η νέα τιμή του a)
30
(επιστρεφόμενη τιμή από την εφαρμογή της συνάρτησης)
(fn30 2 3 4) → 26
(fn31 2 3 4) → 156

Η FN31 δέχεται σε τρία ορίσματα∙ την εφαρμόζουμε στα αποτελέσματα που θα πάρουμε από τις
εφαρμογές: της FN02 πάνω σε τίποτα (η FΝ02 δεν δέχεται κανένα όρισμα, άρα η εφαρμογή της
θα δοθεί ως (fn02) ) , της FN10 πάνω στο αποτέλεσμα της εφαρμογής τής FN11 πάνω στο 3
(δηλ. (fn10 (fn11 3)) ) , και της FN12 πάνω στο αποτέλεσμα εφαρμογής της FΝ02 (δηλ. (fn12
(fn02)) ) :
(fn31 (fn02) (fn10 (fn11 3)) (fn12 (fn02))) →
6
(εκτύπωση λόγω του πρώτου print της fn12)
17
(εκτύπωση λόγω του δεύτερου print της fn12 ∙ νέα τιμή του a)
5987638 (επιστρεφόμενη τιμή της fn31)
Συνάρτηση χωρίς σώμα μπορεί να οριστεί, και επιστρέφει τιμή NIL :
(defun nothing (x) )
; οτιδήποτε και να δοθεί ως όρισμα, θα επιστέψει NIL :
(nothing 3)
→ NIL

(defun again-nothing ( ) )
; δεν δέχεται όρισμα, επιστρέφει NIL όταν κληθεί:
(again-nothing) → NIL

Όσο και αν φαίνονται άχρηστη η NOTHING , είναι σε πολλές περιπτώσεις χρήσιμη (πχ. για
τυπική συμπλήρωση όρων - συναρτήσεων σε μια προκαθορισμένης σύνθεσης φόρμα, για
"απορρόφηση" τιμών εισόδου που έρχονται αλλά δεν τις χρειαζόμαστε).

Σύνοψη και συμπεράσματα
Η εφαρμογή συνάρτησης υλοποιείται στη Lisp με γραφή μιας φόρμας - λίστας που έχει πρώτο
όρο το όνομα της συνάρτησης και επόμενους τα ορίσματά της. Τα ορίσματα πρέπει να είναι τιμές,
κατάλληλου τύπου για να δοθούν ως είσοδος στη συνάρτηση. Λόγω της "από μέσα προς τα έξω"
τάξης υπολογισμού της Lisp, τα ορίσματα μπορούν να είναι φόρμες προς υπολογισμό, που
υπολογίζονται πριν τη συνάρτηση, και δίνουν την έξοδό τους ως είσοδο στη συνάρτηση (άρα
πρέπει να είναι φόρμες κατάλληλου τύπου εξόδου).
Η κλήση εφαρμογής συνάρτησης, με εξαίρεση κάποιες ειδικής μορφής, γίνεται πανομοιότυπα,
είτε πρόκειται για συνάρτηση που έχει ορίσει ο χρήστης είτε για πρωτογενή συνάρτηση της
γλώσσας. Η κλήση γίνεται είτε στο ανώτατο επίπεδο είτε σε θέση ορίσματος συνάρτησης η οποία
καλείται προς εκτέλεση, είτε στο σώμα συνάρτησης.
Οι καθαρά μαθηματικές συναρτήσεις έχουν ως σώμα τους μια και μόνο μια φόρμα. Αν το σώμα
περιλαμβάνει περισσότερες της μιας φόρμες, κατά την εφαρμογή εκτελούνται διαδικαστικά κατά
σειρά, και το αποτέλεσμα της τελευταίας είναι το αποτέλεσμα της εφαρμογής της συνάρτησης.
Αν στο σώμα αναφέρεται κάποια σταθερά, κατά την εφαρμογή παραμένει αυτούσια. Αν
αναφέρεται κάποια μεταβλητή, αντικαθίσταται από την τιμή της:
– αν είναι σύμβολο - μεταβλητή, παίρνει την τιμή που ήδη έχει, και αν αυτή η τιμή
μεταβάλλεται λόγω της εφαρμογής, το σύμβολο - μεταβλητή παίρνει μόνιμα τη νέα τιμή
– αν είναι λ-μεταβλητή, παίρνει την τιμή δέσμευσής της, αλλά αν η τιμή αυτή επανορίζεται
διαδικαστικά μέσα στο σώμα (πχ. με χρήση της SETQ) τότε λαβαίνει τη νέα τιμή∙ η λμεταβλητή και η δέσμευσή της "εξαφανίζονται" με το πέρας της εκτέλεσης της εφαρμογής.
Παρaδείγματα
Μαθηματικές συναρτήσεις:

(defun g (x y) (+ x y))
(g 3 4)
→ 7
(g (+ 4 5 6) (– 6 2))
→ 11
(g (g (g 2 2) (g 1 1)) (g (g 2 1) (g 1 2))) → 12
(defun inv-cos (x) (cos (/ 1 x))
(inv-cos (/ 1 pi))
→ -1.0

Διαδικαστική συνάρτηση:

(defun proc1 (x)
(setq x (+ x 1)) (print x) (setq x (* x x)) (print x) x)

(proc1 3) →
4
(εκτύπωση λόγω του πρώτου print)
16
(εκτύπωση λόγω του δεύτερου print)
16
(επιστρεφόμενη τιμή από την εφαρμογή της proc1 )

Συνάρτηση που εφαρμόζεται σε εφαρμογή του εαυτού της:
(square (square (square 2)))

; το τετράγωνο του τετραγώνου του τετραγώνου του 2
→ 256

Κλήση υπολογισμού συνάρτησης απ' ευθείας με τον ορισμό της
Είπαμε οτι η εφαρμογή της συνάρτησης DEFUN επιστρέφει ως αποτέλεσμα το όνομα - σύμβολο
της οριζόμενης συνάρτησης:
(defun cube (x) (* x x x)) → cube
Δεδομένου οτι το αποτέλεσμα αυτού του υπολογισμού είναι συνάρτηση, και το αποτέλεσμα
υπολογισμού παίρνει τη θέση της φόρμας του υπολογισμού, θα περίμενε κανείς πως θα ήταν
δυνατό να εφαρμόσουμε τη φόρμα (defun…) απ' ευθείας στα ορίσματα, σύμφωνα και με το
θεωρητικό μοντέλο του λ-λογισμού:
(f g)
δηλαδή στη Lisp θα έπρεπε, αν δίναμε:
( (defun cube (x) (* x x x)) 3 )
να παίρναμε έξοδο την τελική τιμή 27
Αυτό θα ήταν δυνατό αν η Lisp εκτελούσε τον υπολογισμό που ορίζει ο πρώτος όρος (defun…),
αλλά δεν τον εκτελεί: κατά το πρότυπο της CL, ο πρώτος όρος εφαρμογής πρέπει να δίνεται ως
όνομα συνάρτησης, και όχι φόρμα υπολογισμού.
Όπως θα δούμε αναλυτικά στα επόμενα, παρακάμπτουμε την αδυναμία αυτή εύκολα,
χρησιμοποιώντας μια συνάρτηση που δέχεται ως πρώτο όρισμα μια οποιαδήποτε συναρτησιακή
έκφραση και επόμενα ορίσματα, τα ορίσματα της συναρτησιακής έκφρασης. Τέτοια συνάρτηση
είναι η FUNCALL :
(funcall (defun cube (x) (* x x x)) 3) → 27
Η

φόρμα

αυτή

λέει: "εφάρμοσε τη συνάρτηση που προκύπτει από τον ορισμ ό
(defun cube (x) (* x x x)) , πάνω στην τιμή 3 ". Ταυτόχρονα, ο υπολογισμός αυτός όρισε καθολικά
και τη συνάρτηση CUBE :
(cube 5) → 125
Αντίστοιχα, η παρακάτω φόρμα δίνει το άθροισμα των τετραγώνων των 3 και 4:
(funcall (defun sum-of-squares (x y) (+ (* x x) (* y y))) 3 4)
→ 25

Εφαρμογή της οριζόμενης συνάρτησης με ανάγνωση στοιχείων εισόδου
Σε έναν ορισμό μπορούμε να συμπεριλάβουμε ανάγνωση από την είσοδο. Άρα αν εφαρμόσουμε
την FUNCALL με πρώτο όρισμα έναν ορισμό συνάρτησης ο οποίος περιέχει στο σώμα ανάγνωση

τιμών, κατευθύνουμε τον υπολογισμό έτσι ώστε να λάβει τιμές από την είσοδο.
Παραδείγματα
1. Ορισμός και εφαρμογή συνάρτησης χωρίς λ-μεταβλητές:
(funcall
(defun read-and-add-two-numbers ( )
(+ (read) (read))) )

→ <περιμένει είσοδο δύο τιμών∙ δίνουμε από το πληκτρολόγιο:>
3 <space> 4 <enter>
→ 7

2. Ορισμός και ταυτόχρονη εφαρμογή συνάρτησης με λ-μεταβλητές:
(funcall
(defun two-args (x y)
(* (+ x y) x)
2 3)
→ 10

3. Συνδυασμένη χρήση από λ-μεταβλητές με τιμές εισόδου:
(defun a-procedure (x y)
(setq b (+ y (* x 2) (read) ) ) )

Εφαρμογή:
(a-procedure 4 3)

→ <περιμένει είσοδο μιας τιμής∙ δίνουμε από το πληκτρολόγιο:>

9 <enter>

20
b → 20

Αναφορά συμβόλου το οποίο δεν έχει οριστεί ακόμα, μέσα στο σώμα συνάρτησης
Η αναφορά μη ορισμένου συμβόλου, κατά τον ορισμό συνάρτησης, θα προκαλέσει σφάλμα κατά
την κλήση της συνάρτησης, αλλά όχι στον ορισμό της, διότι στον ορισμό δεν εκτελούνται οι
υπολογισμοί τού σώματος. Είναι λοιπόν δυνατό στον ορισμό συνάρτησης να αναφέρονται
ονόματα συμβόλων που δεν έχουν οριστεί ακόμα, με σκοπό να οριστούν αργότερα: αρκεί να
οριστούν αυτά πριν την πρώτη εφαρμογή της συνάρτησης. Αυτό προσφέρει μεγάλη ευκολία στην
ανάπτυξη του προγράμματος.
Συχνά χρησιμοποιούμε ένα "quoted" όνομα αντί για string. Πχ. γράφουμε:
(setq name 'Ann)

αντί για:
(setq name "Ann")

και μάλιστα συχνά είναι προτιμότερο το πρώτο, διότι αφ' ενός το string καταναλώνει
περισσότερους πόρους (ως ακολουθία διαδοχικά συνδεδεμένων χαρακτήρων) και αφ' ετέρου δεν
προσφέρει τη δυνατότητα να πάρει μελλοντικά περιεχόμενο και ιδιότητες. Ένα "quoted" όνομα

μπορεί να χρησιμοποιηθεί εκ των υστέρων ως σύμβολο, με την προϋπόθεση που αναφέραμε, οτι ο
compiler δέχεται τέτοια σύμβολα.

2.5.5 Εγκλεισμός συναρτήσεων

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

α'.
Οι λ-μεταβλητές εσωτερικά κιβωτισμένου DEFUN που τυχόν συμπίπτουν με λμεταβλητές της εξωτερικής συνάρτησης, δεν σχετίζονται με αυτές: οι λ-μεταβλητές ανήκουν
αποκλειστικά στην εσωτερικότερα οριζόμενη συνάρτηση και έχουν προτεραιότητα υπόστασης
απέναντι σε οποιοδήποτε εξωτερικά προερχόμενο ίδιο όνομα, λ-μεταβλητής ή συμβόλου.

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

γ'.
Οι συναρτήσεις που ορίζονται εσωτερικά μέσα σε συνάρτηση, είναι καθολικά
ορισμένες για το πρόγραμμα. Ο ορισμός όμως της εξωτερικής δεν αρκεί για να ορίσει τις
εσωτερικές, διότι η DEFUN δεν εκτελεί το σώμα, άρα δεν περνά στον εσωτερικό ορισμό
συμβόλων: πρέπει να εκτελεστεί η εξωτερική συνάρτηση τουλάχιστον μια φορά. Προφανώς, σε
κάθε νέα εκτέλεση της εξωτερικής θα επαναπροσδιορίζεται η (κάθε) εσωτερικά οριζόμενη
συνάρτηση. Αυτό είναι βέβαια περιττό όταν δεν μεταβάλλεται ο ορισμός της εσωτερικής, αλλά
είναι πάρα πολύ χρήσιμο στην περίπτωση όπου η εκτέλεση της εξωτερικής προκαλεί μεταβολές
που επηρεάζουν τον ορισμό της εσωτερικής.
Σημαντικό είναι, να δούμε τη συμπεριφορά των λ-μεταβλητών σε τέτοιο εγκλεισμό. Οι λμεταβλητές της εξωτερικής, αποτελούν (τοπικά) σύμβολα για την εσωτερική, εκτός αν έχουν το
ίδιο όνομα: τότε, για τα πλαίσια του εσωτερικού υπολογισμού (δηλ. το σώμα της εσωτερικής)
υπερισχύουν οι εσωτερικές.
Ο εγκλεισμός μπορεί να έχει πολλά επίπεδα, και αν είναι δένδρο είναι σαφές το τί αναφέρεται σε
τί: η λ-μεταβλητή του ανώτερου κόμβου αποτελεί, τοπικά, σύμβολο για τον κατώτερο κόμβο,
εκτός αν ο κατώτερος έχει λ-μεταβλητή με το ίδιο όνομα. Πολυπλοκότερο είναι, αν
δημιουργούνται διασταυρώσεις ή ανακυκλώσεις αναφορών (που δεν αποκλείονται)∙ τότε
υπερισχύει πάντα "ότι υπολογίστηκε τελευταίο".

Παράδειγμα
(defun alpha (x y z)
(defun beta (x w)
(* x y w))
; x w είναι εσωτερικές της beta, y έρχεται από την alpha
(defun gamma (y w)
(* x y w))
; y w είναι εσωτερικές της beta, x έρχεται από την alpha
(+ (beta z x) (gamma y z) ))
; x, y, z έρχονται από την alpha

Εφαρμογή:

→ 48

(alpha 2 3 4)

Περιπτώσεις συναρτήσεων και παραδείγματα
1. Ορισμός συνάρτησης που έχει λ-μεταβλητές.
i) Συνάρτηση square , με είσοδο αριθμό και έξοδο το τετράγωνό του:
(defun square (x) (* x x) )

Εφαρμογή:

(square 4)



16

ii) Συνάρτηση pythagorio , με είσοδο δύο αριθμούς και έξοδο τη ρίζα του αθροίσματος των
τετραγώνων τους:
(defun pythagorio (x y) (sqrt (+ (* x x) (* y y))) )

Εφαρμογή:

(pythagorio 3 4) →

5

2. Ορισμός συνάρτησης με καθολικές μεταβλητές στο σώμα.
i) Kαθολική μεταβλητή a με τιμή 2 και συνάρτηση χωρίς είσοδο, που χρησιμοποιεί την a :
(setq a 2)
(defun do-it ( ) (* a a a) ) → do-it

► Η κενή λίστα μετά το όνομα της συνάρτησης δηλώνει οτι δεν έχουμε λ-μεταβλητές. Όμως,
ως όρισμα της DEFUN πρέπει τυπικά να υπάρχει αυτή η λίστα.
Εφαρμογή:
(do-it)
→ 8
(setq a 3)
(do-it) → 27

ii) Συνάρτηση trinomial που ορίζει ένα τριώνυμο με συντελεστές προσδιορισμένους ως
καθολικές μεταβλητές:
(setq a 3 b 2 c 4)
(defun trinomial (x) (+ (* a x x) (* b x) c) )

Εφαρμογή:

(trinomial 2)

→ 20

3. Συνδυασμένος ορισμός με κλήση συνάρτησης.
Θέλουμε να δώσουμε την έκφραση: «με δεδομένο ότι a=3 και b=2 , να υπολογιστεί το a*x +
b*y , για x=5 και y=4» όπου η έκφραση a*x + b*y να προσδιορίζεται και ως συνάρτηση.
(setq a 3 b 2)
(funcall (defun diofan (x y) (+ (* a x ) (* b y))) 5 4 ) → 23

Η συνάρτηση DIOFAN ορίζεται από τη συνάρτηση DEFUN, και δέχεται δύο ορίσματα. Η
φόρμα ορισμού έχει έξοδο το όνομα-σύμβολο της συνάρτησης, δηλ. DIOFAN . Οι επόμενοι
όροι (δηλ. οι αριθμοί 5 και 4) είναι οι τιμές των ορισμάτων της DIOFAN , που εκτελείται. Το
σύμβολο DIOFAN, που επιστρέφεται από την DEFUN, δίνεται ως είσοδος στη συνάρτηση
FUNCALL .
4. Ορισμοί σε εγκλεισμό.
Ορισμός συνάρτησης που δίνει το άθροισμα των τετραγώνων των δύο μεταβλητών της, μέσω
ορισμού της συνάρτησης SQUARE που δίνει το τετράγωνο:
(defun sum-of-squares (x y)
(defun square (z) (* z z))
(+ (square x) (square y)) )

Εφαρμογές:

(sum-of-squares 3 4) → 25
(square 5)
→ 25

5. Έξοδος της εισόδου αυτούσιας, μετά την εκτέλεση κάποιου υπολογισμού.
Εκτύπωση του κύβου της τιμής εισόδου και επιστροφή της τιμής εισόδου.
(defun testa (x) (print (* x x x)) x )

Η συνάρτηση αυτή εκτελεί έναν υπολογισμό ως ενδιάμεση δράση (που γίνεται ορατή λόγω του
PRINT), και επιστρέφει την είσοδό της αυτούσια.
Εφαρμογή:
(* 7 (testa 2)) ; τυπώνει 8 και επιστρέφει 14 διότι (testa 2) δίνει 2
6. Χρήση της SETQ μέσα σε ορισμό.
Διαχείριση λ-μεταβλητής ως τοπικό σύμβολο
Στην παρακάτω συνάρτηση TESTB αναφέρεται προσδιορισμός τιμής της λ-μεταβλητής x μέσω
της SETQ , ενώ ήδη υπάρχει στο πρόγραμμα σύμβολο - μεταβλητή x :
(setq x 2)
(defun testb (x y) (setq x (* x y)) (testa x) )

Εφαρμογή:

(testb 3 4)
; τυπώνει 1728 και επιστρέφει 12
Το x μέσα στη συνάρτηση testb είναι λ-μεταβλητή, ανεξάρτητη του συμβόλου - μεταβλητής x
που έχει τιμή 2 . Με την κλήση της testb η λ-μεταβλητή x δεσμεύεται στην τιμή 3 και γίνεται
12 λόγω του υπολογισμού της setq μέσα στην testb . Η επιστροφή της testb είναι το

αποτέλεσμα του τελευταίου υπολογισμού της, δηλ, η εκτέλεση της testa για x=12 . Το σύμβολο
- μεταβλητή x δεν επηρεάζεται:

x

→ 2

7. Μετατροπή εξόδου δεδομένης συνάρτησης, με διατήρηση των παραπλεύρων δράσεων της
αρχικής.
Θέλουμε εντολή εκτύπωσης που ως συνάρτηση να μην επιστρέφει την τιμή εισόδου της, όπως η
PRINT, αλλά την τιμή Τ :
(defun my1print (x) (print x) t)

; τυπώνει την είσοδο και επιστρέφει Τ
(defun my2print (x) (print x) nil)

; τυπώνει την είσοδο και επιστρέφει NIL
8. Διάκριση συνάρτησης από μεταβλητή που έχει το ίδιο όνομα.
Ταυτόχρονη χρήση ονόματος ως σύμβολο - μεταβλητή και ως συνάρτηση:
Όπως θα δούμε αναλυτικά σε επόμενη ενότητα, είναι δυνατό να προσδιορίσουμε ένα σύμβολο
ως μεταβλητή και ως συνάρτηση στο ίδιο πρόγραμμα. Αν κληθεί το όνομα του συμβόλου
αυτού σε θέση ή με τρόπο που αφορά μεταβλητή, η κλήση νοείται οτι αναφέρεται στην
μεταβλητή. Αν κληθεί με τρόπο που αφορά συνάρτηση, η κλήση νοείται οτι αναφέρεται στη
συνάρτηση:
(setq a 3)
(defun a ( ) 4)
a
→ 3
(a )
→ 4
(setq b 'a)
(defun b (x) x)
b
→ a
(eval b) → 3
(b 5)
→ 5
(b b)
→ a
(eval (b b)) → 3

9. Επανορισμός / επαναχρησιμοποίηση της ίδιας συνάρτησης μέσα στο σώμα της.
Τί κάνουν οι παρακάτω συναρτήσεις Α , Β , C , D , E , F , G ;
i) (defun a (x) (defun a (x) (+ x 1)))
ii) (defun b (x) (defun c (x) (b x)) )
iii) (setq v 0)
(defun d (x) (defun e (x) (setq v (+ v x)) (d x)) )
iv) (defun f (x) (f x))
(defun g (x) (defun h (x) (setq v (+ v x))) (g x) )

Απαντήσεις:
i) Αν εφαρμόσουμε την A μια φορά πάνω σε αριθμό, παίρνουμε απάντηση το σύμβολο A . Αν
όμως την ξαναεφαρμόσουμε σε αριθμό, παίρνουμε τον επόμενό του (διότι με την πρώτη
εφαρμογή άλλαξε ο ορισμός):
(a 7)
→ a
(a 7)
→ 8

ii) Αν εφαρμόσουμε την B σε οτιδήποτε, επιστρέφει C . Μετά την πρώτη εφαρμογή της Β, αν
καλέσουμε για εφαρμογή την C, επιστρέφει C .

iii) Αν εφαρμόσουμε την D σε όρισμα, παίρνουμε πάντα απάντηση E . Η τιμή της μεταβλητής v
παραμένει 0 . Αν, μετά την πρώτη κλήση της D εφαρμόσουμε την E σε αριθμό, πάλι
παίρνουμε απάντηση E , αλλά η τιμή της v αυξάνεται κάθε φορά κατά το όρισμα της E .
iv) Η συνάρτηση F πέφτει σε ατέρμονα ανακύκλωση λόγω αναδρομής χωρίς τερματισμό. Το
ίδιο και η G .

Το πιο σημαντικό στα παραπάνω είναι οτι η D δεν πέφτει σε ατέρμονα ανακύκλωση, διότι η
"αναδρομική" κλήση γίνεται εσωτερικά στο σώμα της Ε, όπου η Ε ορίζεται εσωτερικά στο
σώμα της D. Κατά κάποιο τρόπο έχουμε ανακύκλωση (και αυτό φαίνεται από την αύξηση της
τιμής της v κάθε φορά που καλούμε την Ε) αλλά μεσολαβεί η κλήση της Ε. Αυτή η
μεσολάβηση, παίζει ρόλο καθυστέρησης για τον υπολογισμό της D .
Βλέπουμε πως μπορούμε να δώσουμε συναρτήσεις που, κατά την εφαρμογή τους, αλλάζουν
τον ίδιο τον ορισμό τους. Διασταυρώνοντας τέτοιες αλλαγές μεταξύ δύο ή περισσοτέρων
συναρτήσεων, θα προκαλέσουμε κυκλική μεταβολή ορισμών, σε κάθε κύκλο κλήσεων.
Άσκηση:
Μελετήστε τη συμπεριφορά των παρακάτω συναρτήσεων A1 , A2 , A3 :
(setq a3 4)
(defun a1 ( ) (defun b1 ( ) (print 1) (a1)) )
(defun a2 ( ) (defun b2 ( ) (print 1)) (a2) )
(defun a3 ( ) (defun b3 ( ) (print 1)) a3 )

2.6 Συναρτήσεις και διαδικασίες
Στην ενότητα αυτή θα μελετήσουμε τον τρόπο απόδοσης διαδικασιών, δηλαδή αλγορίθμων που
συνήθως εκφράζονται με τρόπο "εντολών προς εκτέλεση", σε μορφή συναρτήσεων.

2.6.1 Επιτακτικές διαδικασίες
Προσδιορισμός μια επιτακτικής διαδικασίας ως συνάρτησης
Το σώμα συνάρτησης μπορεί να περιλαμβάνει τα διαδοχικά βήματα αλγόριθμου. Άρα είναι
δυνατό να ορίσουμε μια συνάρτηση με σκοπό τον προσδιορισμό κάποιας αλγοριθμικής
διαδικασίας και όχι με σκοπό την επιστροφή τιμής. Επομένως αρκεί να θεωρήσουμε οτι το όνομα
της συνάρτησης είναι το όνομα της διαδικασίας και το σώμα της συνάρτησης είναι η διαδικασία.
Πρέπει να ληφθεί υπ’ όψη όμως οτι η κλήση της συνάρτησης θα επιστρέψει ούτως ή άλλως
κάποια τιμή, που είναι η τιμή που επιστρέφει ο υπολογισμός της τελευταίας φόρμας στο σώμα

(και πρέπει να προβλέψουμε πού θα πάει η τιμή αυτή).
Παράδειγμα
(defun proc1 (x y z)
(print (+ x y z)) (print (* x y z)) )

Εφαρμογή:
(proc1 2 3 4) →
9
(λειτουργικό αποτέλεσμα, λόγω του πρώτου print)
36
(λειτουργικό αποτέλεσμα, λόγω του δεύτερου print)
36
(έξοδος της proc1 ως υπολογισμός της τελευταίας φόρμας)
(+ (proc1 2 3 4) 5)

9
(λειτουργικό, παράπλευρο αποτέλεσμα)
36
»
»
41
(επιστρεφόμενη τιμή από τη συνάρτηση)

Αν θέλουμε, μπορούμε να θέσουμε ως τελική επιστροφή Τ ή NIL :
(defun proc2 (x y)
(print (* x y)) nil)
(proc2 4 5)

20 (λειτουργικό, παράπλευρο αποτέλεσμα, λόγω της print)
NIL
(επιστρεφόμενη τιμή από τη συνάρτηση)

Διαδικασία που διαβάζει τιμές εισόδου κατά την εκτέλεσή της

Μια διαδικασία ενδέχεται να προσδιορίζεται χωρίς παραμέτρους κλήσης της, αλλά να είναι
τέτοια που να διαβάζει τιμές εισόδου κατά την εκτέλεσή της. Στην περίπτωση αυτή δεν
χρησιμοποιούμε λ-μεταβλητές, αλλά τη συνάρτηση ανάγνωσης READ στο σώμα, εκεί ακριβώς
όπου χρησιμοποιούμε την τιμή ανάγνωσης.

Ουσιαστική διαφορά της ανάγνωσης τιμής σε σχέση με άλλες γλώσσες προγραμματισμού, είναι
οτι μπορούμε να χρησιμοποιήσουμε την τιμή ανάγνωσης με ή χωρίς καταχώρησή της, ως τιμή
μεταβλητής:
(defun myproc ( ) (+ (* (read) (read) 1)) )

Εκτέλεση:

(myproc) → < περιμένει είσοδο δύο αριθμούς∙ δίνουμε: >
3 <space> 4 <enter>
→ 13

Η κλήση της READ μέσα σε υπολογισμό, στη θέση της τιμής που θα διαβαστεί, πετυχαίνει
αμεσότητα γραφής και εκτέλεσης. Αντίθετα, η απόδοση της τιμής ανάγνωσης ως τιμή
μεταβλητής, πετυχαίνει αποθήκευση της τιμής για επαναχρησιμοποίηση.
Αν η αποθήκευση δίνεται ως τιμή δέσμευσης λ-μεταβλητής, έχει "ζωή" όσο διαρκεί η εκτέλεση
της συνάρτησης στην οποία αναφέρεται η λ-μεταβλητή. Αν δίνεται ως τιμή συμβόλου μεταβλητής, η αποθήκευση είναι πλέον ανεξάρτητη της εκτέλεσης και είναι χρησιμοποιήσιμη από

κάθε επόμενη κλήση.

Η τιμή που διαβάζει η READ δεν υπολογίζεται (δηλ. χρησιμοποιείται ως "quoted"). Αυτό είναι
πολύ σημαντικό, διότι αν η (READ) διαβάσει μια έκφραση εφαρμογής, θα περάσει στον
υπολογισμό αυτούσια την έκφραση και όχι το αποτέλεσμά της. Αν πάλι θέλουμε να υπολογίσει
τον όρο που διαβάζει, δεν έχουμε παρά να θέσουμε ένα "EVAL" πριν την ανάγνωση, όπως θα
δούμε εκτενέστερα στα επόμενα:
(… (EVAL (READ)) …)

Παράδειγμα
Να κατασκευαστεί διαδικασία που να διαβάζει τις τιμές των δύο καθέτων πλευρών ορθογωνίου
τριγώνου και να επιστρέφει την υποτείνουσα:
(defun pyth ( )
(setq x (read) y (read)) (sqrt (+ (square x) (square y))))
(pyth) → <κατάσταση εισόδου∙ δίνουμε:>
3 <space> 4 <enter>
→ 5

Θα επανέλθουμε στο θέμα των διαδικασιών και στις χρήσεις της συνάρτησης READ .

2.6.2 Οι έννοιες της αναδρομής και της επανάληψης

Είναι δυνατός ο ορισμός αναδρομικής συνάρτησης, όπου αναδρομή σημαίνει "κλήση για
εφαρμογή του ίδιου του ονόματος της συνάρτησης, μέσα στον ορισμό της" . Αυτός δεν είναι ο μόνος
τρόπος να προκαλέσουμε συναρτησιακά αναδρομή, αλλά είναι ο απλούστερος.
Στην περίπτωση ορισμού αναδρομικής συνάρτησης πρέπει να έχουμε υπ’ όψη οτι η "αναδρομή
στην ουρά" (tail recursion) δεν κτίζει στοίβα (stack), τουλάχιστον σε compilers που "αξίζουν το
όνομά τους", και έτσι μπορεί να εκτελείται χωρίς περιορισμό πλήθους επαναλήψεων, τουλάχιστον
λόγω της αναδρομής. Αντίθετα, η "αναδρομή στο σώμα" (body recursion) κτίζει στοίβα η οποία
θέτει όρια στο πλήθος των δυνατών επαναλήψεων, ανάλογα με το μέγεθος που μπορεί να πάρει η
στοίβα.
Ως αναδρομική στην ουρά χαρακτηρίζουμε μια συνάρτηση όταν η κλήση του ονόματός της είναι ο
τελευταίος υπολογισμός που εκτελείται. Ως αναδρομική στο σώμα χαρακτηρίζουμε μια
συνάρτηση όταν το όνομά της εμφανίζεται μέσα στο σώμα της σε υπολογισμό που τον
ακολουθούν και άλλοι υπολογισμοί.

Η αναδρομή και η επανάληψη είναι άρρηκτα συνδεδεμένες με το λογικό έλεγχο, και θα
επανέλθουμε παρακάτω.
Παράδειγμα
(defun print-continuously (x)
(print x) (print-continuously (+ x 1)) )

Εφαρμογή:

; αναδρομή στην ουρά

(print-continuously 0) →

<τυπώνει ασταμάτητα τους διαδοχικούς φυσικούς αριθμούς>
Από μαθηματική άποψη, ως συνάρτηση η "print-continuously" είναι εσφαλμένη, διότι ποτέ δεν
θα επιστρέψει τιμή. Λειτουργεί όμως ως διαδικασία, παρ’ όλο που δεν τερματίζεται ποτέ. Παρ'
όλα αυτά, χρησιμοποιούμε συναρτήσεις με τέτοια ατέρμονα ανακύκλωση, όταν θέλουμε η ολική
λειτουργία του προγράμματος να δοθεί σε μορφή "διαρκώς επαναλαμβανόμενου κύκλου", όπως
θα δούμε στα επόμενα.
Αντίθετα, η παρακάτω συνάρτηση είναι εσφαλμένη υπολογιστικά, διότι κάποια στιγμή η στοίβα
που κτίζει ο προσδιοριζόμενος υπολογισμός, υπερεκχειλίζει (stack overflow):
(defun is-possible? (x)
(print x) (is-possible? (+ x 1)) (* x 2) )

; αναδρομή στο σώμα

Εφαρμογή:
; μετά από μερικά βήματα, προκαλεί σφάλμα
► Η ικανότητα να μη κτίζει στοίβα μια αναδρομή που προσδιορίζεται "στην ουρά" δεν
διατίθεται από όλους τους Lisp compilers, και πρέπει οπωσδήποτε να το ελέγξει ο χρήστης.
(is-possible? 0)

Επαναληπτικές διαδικασίες
Όπως θα δούμε στα επόμενα, διαθέτουμε αρκετές ειδικές συναρτήσεις προσδιορισμού
διαδικαστικής επανάληψης. Μπορούμε όμως να προσδιορίσουμε επανάληψη απλά
χρησιμοποιώντας αναδρομή στην ουρά. (προσοχή, οι παρακάτω συναρτήσεις δεν έχουν κριτήριο
διακοπής)
1. Χωρίς χρήση λ-μεταβλητών:

(defun do-it-continuously ( )
(setq a (princ (* a a)) (princ " ")) (do-it-continuously) )
(setq a 2)

Εφαρμογή:
(do-it-continuously)

→ 4 16 256 65536 4294967296 …

2. Με χρήση λ-μεταβλητών:
(defun do-it-again-with-var (x)
(princ (* x x)) (princ " ") (do-it-again-with-var (* x x) ))

Εφαρμογή:

(do-it-again-with-var 2)

→ 4 16 256 65536 4294967296 …

Παρατηρήσεις πάνω στον ορισμό και την εφαρμογή συνάρτησης

– Ο ορισμός συνάρτησης μπορεί να περιλαμβάνει στο σώμα του άλλους ορισμούς, ακόμα και σε
μεγαλύτερο "βάθος", ακόμα και της ίδιας της συνάρτησης που εκτελείται. Μια αναδρομική
συνάρτηση μπορεί να επανορίζει τον εαυτό της μέσα στον κύκλο της εκτέλεσης. Μπορούμε να
συνδυάσουμε ορισμούς με κλήσεις εφαρμογής.

– Η κλήση συνάρτησης οφείλει να συνοδεύεται από πλήθος ορισμάτων σε αντιστοιχία με τις λμεταβλητές της, και τον τύπο που απαιτούν. Όπως θα δούμε, συνδυάζοντας: τύπο τιμής εισόδου
και επιλογή (conditional), πετυχαίνουμε προσαρμογή της λειτουργικότητας συνάρτησης ανάλογα
με τον τύπου εισόδου.

– Tο σώμα οριζόμενης συνάρτησης μπορεί να αποτελείται από περισσότερες της μιας φόρμες,
που νοούνται ως διαδοχικές κλήσεις προς εκτέλεση. Δεν εκτελούνται κατά τη φάση του ορισμού
αλλά κατά τη φάση κλήσης εφαρμογής της συνάρτησης, και κάθε μια φόρμα που εκτελείται,
διαμορφώνει τις συνθήκες εκτέλεσης των επομένων. Η έξοδος από τις ενδιάμεσες φόρμες ως
συναρτήσεις δεν δίνεται πουθενά (αν δεν προβλέπεται επίτηδες), ενώ η έξοδος της τελευταίας
αποτελεί την έξοδο της συνάρτησης.
– Η εκτέλεση της DEFUN περνά από μεταγλώττιση τον ορισμό της συνάρτησης, όπου
"τσεκάρεται" η ορθότητα της σύνταξης, αλλά όχι και η ορθότητα του υπολογισμού. Με την
ολοκλήρωση του ορισμού δεν εξασφαλίζουμε οτι η συνάρτηση "θα τρέξει", ούτε οτι θα
τερματίσει. Γι' αυτό χρειάζεται να γίνει τουλάχιστον μια κλήση, δηλ. εφαρμογή της σε ορίσματα
του προβλεπόμενου τύπου (ούτε και αυτό εξασφαλίζει οτι θα τρέχει και θα τερματίζει για κάθε
δυνατή τιμή εισόδου, αλλά τουλάχιστον εξασφαλίζουμε οτι από πλευράς εσωτερικών κλήσεων
λειτουργεί).

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

2.7 Ιδιαίτερες μορφές μεταβλητών
Στην ενότητα αυτή θα μελετήσουμε ένα άλλο είδος καθολικών μεταβλητών, καθώς και τον τρόπο
προσδιορισμού σταθερών, που είναι καθολικές μεταβλητές που δεν σκοπεύουμε να
μεταβάλλουμε την τιμή τους.

2.7.1 Ειδικές μεταβλητές

Eκτός των συνήθων συμβόλων - μεταβλητών και των συμβόλων - συναρτήσεων, η Lisp διαθέτει
και ένα διαφορετικό είδος συμβόλων - μεταβλητών, τις λεγόμενες ειδικές μεταβλητές (special
variables). Στη βιβλιογραφία συναντώνται επίσης και ως δυναμικές μεταβλητές (dynamic
variables), και πιο σπάνια ως καθολικές μεταβλητές.

Πρόκειται για καθολικής μορφής μεταβλητές, τύπου symbol , υποτύπου special , και μπορούμε
να τις χρησιμοποιήσουμε καλώντας 'τες όπως και τις συνήθεις μεταβλητές. Οι ειδικές μεταβλητές
διαφέρουν κατά το οτι δεν υπακούουν απόλυτα στους κανόνες και τρόπους δέσμευσης -

απόδοσης τιμής μεταβλητών, και επίσης παίρνουν ιδιόμορφο ρόλο όταν ταυτοποιούνται με λμεταβλητές.
Συνήθως γράφονται με αστερίσκο στην αρχή και στο τέλος του ονόματός τους για να
διακρίνονται, αλλά δεν είναι υποχρεωτικό (οι ειδικές μεταβλητές του συστήματος, κατά κανόνα
αναγράφονται με τέτοιους αστερίσκους, πχ. *BASE* που καθορίζει τη βάση αρίθμησης). Οι
αστερίσκοι είναι μέρος του ονόματος της ειδικής μεταβλητής.
Υπάρχουν διάφορες σκοπιμότητες χρήσης τέτοιων μεταβλητών:
α'. Να εξυπηρετήσουν την παραμετροποίηση του χώρου του υπολογισμού.
Η παραμετροποίηση αυτή αφορά τον τρόπο που λειτουργεί ο υπολογισμός, προσδιορίζοντας πχ.
το μέγιστο μήκος λίστας που τυπώνεται, το μέγιστο βάθος λίστας που τυπώνεται, το ρεύμα
εξόδου, την αριθμητική βάση, τις τιμές κάποιων σταθερών κλπ. (κάθε compiler διαθέτει πολλές
εκατοντάδες τέτοιων μεταβλητών).
β'. Να δώσουν ευελιξία στον υπολογισμό μέσα στον οποίο αναφέρονται, πχ. επιτρέποντας
απαγκίστρωση από την ανάγκη δέσμευσης των αναφερομένων λ-μεταβλητών σε τιμές που
καθορίζονται από τα ορίσματα. Αυτό αξιοποιείται ιδιαίτερα στις συναρτήσεις που οργανώνονται
σε πλαίσιο προσανατολισμένο στα αντικείμενα.

Προσδιορισμός ειδικών μεταβλητών

Ειδικές μεταβλητές προσδιορίζονται με δύο τρόπους, μέσω της συνάρτησης DEFVAR , και μέσω
της συνάρτησης DEFPARAMETER . Η σύνταξή τους, είναι πανομοιότυπη της SETQ , και τα
σύμβολα που προσδιορίζουν, δεν επηρεάζονται από τις SETQ , SETF ή άλλες συναρτήσεις
επεξεργασίας συμβόλων - μεταβλητών. Διαφέρουν μεταξύ τους κατά το οτι: i) με την DEFVAR
ορισμός και απόδοση τιμής σε ειδική μεταβλητή γίνεται μόνο μια μόνο φορά: δεν μπορεί ο
χρήστης να επαναπροσδιορίσει την ειδική μεταβλητή με την DEFVAR, και αυτό, με σκοπό την
προστασία μεταβλητών που καθορίζουν παραμετρικά τη λειτουργικότητα∙ ii) αντίθετα, με την
defparameter μπορούμε να επαναπροσδιορίσουμε την τιμή ειδικής μεταβλητής (είτε έχει οριστεί
μέσω της DEFVAR είτε μέσω της DEFPARAMETER) :
(defvar *permission* t)

Χρήση ειδικών μεταβλητών
Η αναφορά και εν γένει χρήση ειδικών μεταβλητών μέσα σε φόρμα είναι πανομοιότυπη με την
αναφορά συμβόλων - καθολικών μεταβλητών, με τη διαφορά οτι δεν υπακούουν σε δεσμεύσεις
μέσω των SETQ και SETF. Δεν μπορούν να αποτελέσουν τοπικές μεταβλητές.
Η αναφορά ειδικής μεταβλητής στη λίστα των λ-μεταβλητών έχει ειδικό τρόπο αντιμετώπισής της
κατά τον υπολογισμό:
– Αν προβλέπεται δέσμευση κατά την κλήση, ο υπολογισμός ακολουθεί τη συνήθη πορεία
δέσμευσης των λ-μεταβλητών και η ειδική μεταβλητή συμπεριφέρεται ως λ-μεταβλητή, δηλ. ο
υπολογισμός αγνοεί το οτι έχει οριστεί προηγουμένως το όνομα αυτό ως ειδική μεταβλητή,
ακριβώς όπως κάνει και με τα σύμβολα - μεταβλητές.

– Αν ο υπολογισμός δεν μπορεί να προβεί στην κατάλληλη δέσμευση, τότε καλεί την ειδική
μεταβλητή και τοποθετεί στη θέση της την τιμή της.

2.7.2 Σταθερές
Ορισμός σταθεράς

Μια κατηγορία συμβόλων είναι οι σταθερές (τύπος symbol υποτύπος constant). Ορίζονται μέσω
της DEFCONSTANT , που δεν διαφέρει από την SETQ παρά μόνο στο οτι οι οριζόμενες σταθερές
δεν επηρεάζονται από δράσεις εκτέλεσης (αλλά επανορίζονται με νέα χρήση της
DEFCONSTANT, εκτός αν είναι σταθερές του συστήματος):
(defconstant g 9.81)
(setq g 3) → Error…

Έλεγχος σταθεράς
Ελέγχουμε αν ένα σύμβολο είναι σταθερά, με τη συνάρτηση CONSTANTP
(constantp 'g)
→ T

Φόρμες τύπου number, string και bit-vector είναι σταθερές των οποίων η τιμή είναι ο εαυτός
τους.

2.8 Ιδιαίτερες μορφές συναρτήσεων
Εκτός των συναρτήσεων που είδαμε, στη Lisp διατίθενται και ορισμένες μορφές που διαφέρουν
στον τρόπο ορισμού, στον τρόπο που αντιμετωπίζουν τα ορίσματά τους, στον τρόπο που εκτελούν
τον υπολογισμό, ή/και στον τρόπο επιστροφής αποτελέσματος.

2.8.1 Συναρτήσεις macro

Ένα διαφορετικά οριζόμενο είδος συναρτήσεων, είναι οι λεγόμενες macro συναρτήσεις, που
εξυπηρετούν πολύ σε καταστάσεις όπου οι κανόνες δέσμευσης αποδεικνύονται "μια στενή
πανοπλία" για τον προγραμματισμό. Οι macro συναρτήσεις δεν αποτελούν διαφορετικό τύπο,
αλλά απλώς ορίζονται διαφορετικά.
Συναρτήσεις macro ορίζονται μέσω της συνάρτησης DEFMACRO :
defmacro : Συνάρτηση ειδικής μορφής, με την οποία ορίζονται σύμβολα - συναρτήσεις
"macro". Η σύνταξη ορισμού macro συνάρτησης είναι πανομοιότυπη με τη σύνταξη ορισμού
συνάρτησης μέσω της DEFUN :
(defmacro macroname (v1 v2 …vn) <σώμα>)

Παρ' όλη την ομοιότητα της DEFMACRO με τη DEFUN, διαφοροποιούνται τόσο τεχνικά όσο και
σημασιολογικά. Οι διαφορές είναι οι εξής:

α'. Όταν κληθεί μια συνάρτηση macro, τα ορίσματά της δεν υπολογίζονται. Οι λ-μεταβλητές
της macro δεσμεύονται πάνω στα ίδια τα ορίσματα και όχι στην τιμή τους, όπως στις "κοινές"
συναρτήσεις.

β'. Μετά από αυτή τη δέσμευση των λ-μεταβλητών, ακολουθεί υπολογισμός του σώματος, η
λεγόμενη macro - επέκταση (macro expansion). Αυτός είναι ο πρώτος υπολογισμός, διότι
ακολουθεί και άλλος.

γ'. Μετά τη macro - επέκταση, οι δεσμεύσεις των λ-μεταβλητών εξαφανίζονται, με το νόημα
οτι όλοι οι υπολογισμοί που έγιναν πάνω σε λ-μεταβλητές δεν λαβαίνουν πλέον υπ όψη τους
οτι πρόκειται για λ-μεταβλητές, και στη θέση τους μένουν τα υφιστάμενα αποτελέσματα.
δ'. Μετά την αποδέσμευση, η φόρμα που έχει σχηματιστεί από τη macro - επέκταση,
υπολογίζεται ξανά. Αυτός είναι ο δεύτερος υπολογισμός, που δίνει και το αποτέλεσμα της
συνάρτησης κατά τα γνωστά.
ε'. Αν η φόρμα που προκύψει περιέχει macro συναρτήσεις, η macro - επέκταση
επαναλαμβάνεται.

Χωρίς να επιβάλλεται από κάποιο κανόνα, ο ορισμός macro συναρτήσεων είναι συνυφασμένος με
τη χρήση της BACKQUΟTE , που όπως φαίνεται από το ακόλουθο, εκμεταλλεύεται τα "δύο
περάσματα υπολογισμού" για να διαφοροποιήσει, ως φάσεις επεξεργασίας, το "αντικείμενο
εφαρμογής" από τη "συνάρτηση εφαρμογής".
Συνήθως, με την BACKQUΟTE καθορίζουμε στο πρώτο πέρασμα τα αντικείμενα εφαρμογής,
ανεξαρτητοποιώντας 'τα από τις λ-μεταβλητές, και στο δεύτερο τις συναρτήσεις εφαρμογής.
Παράδειγμα
Να προσδιοριστεί μια συνάρτηση η οποία να επισυνάπτει μόνιμα στοιχείο σε λίστα (δηλ. να
επιτελεί τη λειτουργία της "built-in" συνάρτησης PUSH )
Κατ' αρχήν, ως αντιπαράδειγμα, ας προσπαθήσουμε να προσδιορίσουμε μια τέτοια συνάρτηση
PUSH1 μέσω της DEFUN :
(defun push1 (element stack-list)
(setq stack-list (cons element stack-list)))
(setq ζώα nil)

Εφαρμογή:

(push1 'γάτα ζώα) → (γάτα)
ζώα
→ NIL

Βλέπουμε οτι η τιμή της μεταβλητής "ζώα" δεν επηρεάστηκε από τον υπολογισμό.

Η PUSH1 δεν λειτουργεί με μόνιμο αποτέλεσμα: με το πέρας του υπολογισμού το αντικείμενο
δέσμευσης της stack-list , που είναι η λίστα ζώα , παραμένει κενή, διότι ουσιαστικά η DEFUN
χρησιμοποιεί αντίγραφο του ορίσματος και όχι το ίδιο το όρισμα. Άρα χρειαζόμαστε ένα τρόπο
που να κρατά τις προσδιορισμένες σχέσεις, χωρίς να λαβαίνει υπ όψη του οτι πρόκειται για

δέσμευση από λ-μεταβλητές.
Το πρόβλημα αυτό ξεπερνιέται με τον ορισμό macro συνάρτησης. Δίνουμε:
(defmacro push2 (element stack-list)
`(setq ,stack-list (cons ,element ,stack-list)))

Η συνάρτηση PUSH2 λειτουργεί κατά τα αναμενόμενα (η κλήση είναι πανομοιότυπη με των
κοινών συναρτήσεων):
(setq ζώα nil)
(push2 'σκύλος ζώα)
→ (σκύλος)
(push2 'γάτα ζώα)
→ (γάτα σκύλος)
ζώα
→ (γάτα σκύλος)
(push2 'ποντίκι ζώα)
→ (ποντίκι γάτα σκύλος)
ζώα
→ (ποντίκι γάτα σκύλος)
Βλέπουμε οτι η τιμή της μεταβλητής ζώα διαμορφώθηκε μόνιμα από τον υπολογισμό.

Ανάλυση του υπολογισμού
Ο υπολογισμός τής φόρμας (push2 'σκύλος ζώα) ακολούθησε τα εξής βήματα:
– δέσμευση της λ-μεταβλητής element στην τιμή-σύμβολο 'σκύλος (προσοχή, είναι quoted) και
της stack-list στην τιμή-όνομα ζώα , που δεν υπολογίζεται στο πρώτο πέρασμα, διότι τα
ορίσματα δεν υπολογίζονται στις macros,
– τοποθέτηση των δεσμεύσεων στο σώμα και macro - επέκταση:
`(setq ,ζώα (cons ,'σκύλος ,ζώα )))

– δεύτερος υπολογισμός, απαλλαγμένος τώρα από τις δεσμεύσεις των λ-μεταβλητών και
εφαρμοζόμενος πάνω στα φυσικά αντικείμενα:
(setq ζώα (cons 'σκύλος ζώα)))

Εδώ το σύμβολο ζώα είναι το σύμβολο που δόθηκε στην κλήση, και όχι τιμή λ-μεταβλητής.
Εξ αιτίας τού οτι το σύμβολο ζώα δεν έχει "quote" στην έκφραση που λάβαμε, σημαίνει οτι
στον υπολογισμό που θα ακολουθήσει (δεύτερο), θα πάρουμε την τιμή του, που είναι η λίστα.
Το όνομα σκύλος είναι επίσης σύμβολο, όπου λόγω του "quote", στον δεύτερο υπολογισμό
δεν θα υπολογιστεί, δηλ. θα παραμείνει ως όνομα.
– επιστροφή του αποτελέσματος (σκύλος) ως τιμή της συνάρτησης, ενώ ταυτόχρονα η
μεταβλητή ζώα έχει πάρει τιμή τη λίστα (σκύλος)
Για τον ίδιο λόγο, η δεύτερη κλήση της PUSH2 προκάλεσε συσσωρευτικό αποτέλεσμα στην
αναφερόμενη λίστα, διότι αυτή τη λειτουργικότητα ορίσαμε:
(push2 'γάτα ζώα)
→ (γάτα σκύλος)
Και κάθε νέα κλήση της PUSH2 με δεύτερο όρισμα ζώα θα επισυνάψει το πρώτο όρισμα στη
λίστα των ζώων:
(push2 'ποντίκι ζώα)
→ (ποντίκι γάτα σκύλος)

Παρατηρήσεις πάνω στις macro συναρτήσεις
► Μια macro συνάρτηση μπορεί να μετονομαστεί αλλά μόνον ως macro:

(defmacro another-push (x y)
`(push ,x ,y) )

Δεν μπορούμε να πετύχουμε με άλλο εύκολο τρόπο την παραπάνω μετονομασία.
► Αν macro συνάρτηση κληθεί μέσα στο σώμα κοινής συνάρτησης, τα τυχόν μόνιμα
αποτελέσματα χάνονται (εκτός αν προκαλέσουμε επίτηδες καταχώρηση σε φυσική θέση).
► Σε γενικές γραμμές, αυτό που μπορούμε να πούμε για τις macro συναρτήσεις είναι οτι
προσφέρουν εξαιρετική ευκολία έκφρασης και σύνταξης για πολυσύνθετες καταστάσεις καθώς
και για ανάπτυξη και εφαρμογή άλλων προγραμματιστικών προσεγγίσεων. Όμως αυτό δεν είναι
χωρίς κόστος, διότι η χρήση macros εξασθενεί ένα θεμελιακό χαρακτηριστικό του ΣΠ: τη
διαφάνεια του υπολογισμού μέσα από την έκφραση που τον προσδιορίζει.
Σε κάθε "κοινή" συνάρτηση, βλέποντας τον ορισμό της, έχουμε άμεσα τον αλγόριθμο που
προσδιορίζει, ενώ σε μια macro συνάρτηση, μετατίθεται η περιγραφή σε ένα διαφορετικό τρόπο
έκφρασης "συνθέτων βημάτων". Αυτός ο τρόπος είναι συνήθως πιο κατανοητός απ' όσο ο
συναρτησιακός, αλλά για να γίνει κατανοητή η βασική συναρτησιακή λειτουργία που
αντιπροσωπεύει, χρειάζεται να "μεταφραστεί". Αν μεσολαβήσουν περισσότερα τέτοια βήματα, η
σύνδεση με το βασικό συναρτησιακό μοντέλο γίνεται ολοένα και πιο δύσκολα κατανοητή ή
προσιτή προγραμματιστικά.
► Για να δημιουργήσουμε συναρτησιακές συνθέσεις χρησιμοποιώντας δοσμένες συναρτήσεις,
δεν είναι απαραίτητο να γνωρίζουμε αν πρόκειται για κοινές συναρτήσεις ή macro, αρκεί να
ακολουθούμε τον απαιτούμενο τρόπο σύνταξης και χρήσης τους.
Αντίθετα, για να μελετήσουμε την εσωτερική συναρτησιακή συμπεριφορά μιας σύνθεσης, είναι
αναγκαίο να γνωρίζουμε τον ορισμό των χρησιμοποιουμένων συναρτήσεων, άρα θα έχουμε
πληροφόρηση για το αν πρόκειται για macro συνάρτηση.
Θα ξαναδούμε στα επόμενα τις macro συναρτήσεις και το σημαντικό ρόλο που παίζουν στη
δημιουργία κλάσεων και μεθόδων.

2.8.2 Συναρτήσεις πολλαπλών τιμών εξόδου

Θα συναντήσουμε συναρτήσεις που επιστρέφουν περισσότερες της μιας τιμές, όπως είναι η
FLOOR που δίνει δύο τιμές, την πρώτη ως κύρια (τον αμέσως μικρότερο ακέραιο αριθμό ενός
δεκαδικού) και τη δεύτερη ως δευτερεύουσα (τη διαφορά τους):
(floor 3.7) → 3
0.7

Προσοχή: η επιστροφή πολλαπλών τιμών δεν τις τοποθετεί μέσα σε μια λίστα∙ απλώς
επιστρέφονται διαδοχικά. Αν μια συνάρτηση πολλαπλών τιμών εξόδου χρησιμοποιηθεί για να
δώσει είσοδο σε "κοινή" συνάρτηση, μόνο η κύρια τιμή λαμβάνεται υπ' όψη.
Τέτοιες συναρτήσεις είναι πολύ χρήσιμες σε υπολογισμούς που παράγουν περισσότερα του ενός
αποτελέσματα.

Κατασκευή συναρτήσεων πολλαπλών τιμών
Γίνεται με τη συνάρτηση VALUES , η οποία δέχεται οσαδήποτε ορίσματα - υπολογισμούς, από
κανένα μέχρι ν, και επιστρέφει τα αποτελέσματά τους με την ίδια διαδοχή:
values : Συνάρτηση με τυχαίο πλήθος ορισμάτων. Κάθε όρισμα εκτελείται και το
αποτέλεσμά του επιστρέφεται. Κάθε εκτέλεση επιδρά στις επόμενες, αν συσχετίζονται.
(values (+ 3 5) (- 2 9) (square 3))
→ 8 -7 9
(+ 6 (values (+ 3 5) (- 2 9) (square 3))) → 14

Αξιοποίηση συναρτήσεων πολλαπλών τιμών
Δεν απαγορεύεται να χρησιμοποιήσουμε συναρτήσεις πολλαπλών τιμών, δίνοντάς 'τες
(εφαρμοσμένες στα κατάλληλα ορίσματα) ως είσοδο σε άλλη συνάρτηση. Από τις πολλαπλές
τιμές, μόνο η πρώτη λαμβάνεται υπ' όψη (οι άλλες χάνονται, σαν να μην υπάρχουν), πχ:
(+ (floor 3.7) 2) → 5
Μπορούμε όμως να αξιοποιήσουμε το σύνολο των τιμών τέτοιας συνάρτησης, μέσω της
συνάρτησης MULTIPLE-VALUE-BIND που "βλέπει" όλες αυτές τις τιμές:

multiple-value-bind : Συνάρτηση ομαδικής απόδοσης τιμών που προέρχονται από φόρμα
πολλαπλών τιμών, σε μεταβλητές, μία προς μία. Χρησιμοποιείται μόνο για τοπικές μεταβλητές,
άρα μέσα σε συνάρτηση που κάνει χρήση των μεταβλητών αυτών:
(multiple-value-bind <λίστα μεταβλητών> <φόρμα πολλαπλών τιμών>)
Η χρήση της προφανώς έχει νόημα στα πλαίσια ενός ευρύτερου υπολογισμού.
Παράδειγμα

(defun g ( )
(multiple-value-bind (a b c)
(values (+ 3 5) (- 2 9) (square 3)) )
(list a b c) )
(g ) → (8 -7 9)

2.9 Ασκήσεις
1. Να εκτελεστούν οι πράξεις:
α) 123 * 1034 / 2.345 –27 , β) 99 44 + 0.99 –22
γ) 6755 * 8743 / 9834 , δ) 123456789 / 987654321 ,
ε) 1234*10–54 + 4567*1023 – 1357.246808642 5
2. Έχουμε ένα Lisp compiler που υποστηρίζει εσωτερική παράσταση δεκαδικού αριθμού (float)
με 57 δεκαδικά ψηφία, αλλά τυπώνει δεκαδικά αποτελέσματα με 7 δεκαδικά ψηφία μόνο. Πώς
θα πάρουμε αποτέλεσμα με ακρίβεια 57 δεκαδικών ψηφίων;
3. Να γραφεί συνάρτηση που να δέχεται ένα αριθμητικό όρισμα και να επιστρέφει την τιμή που
δίνεται από τον υπολογισμό a∙cos(b∙x) , για τιμές της εισόδου της:
0 , π/4 , π/3 , π2 , 3π/4 , 2π/3 , π
για a και b μιγαδικές παραμέτρους με τιμές: 0, 1 , i , 1+i , 1–i .
4. Να οριστεί συνάρτηση που να αποδίδει τη μαθηματική συνάρτηση - σύνθεση συναρτήσεων
(sin◦cos◦tan)–1(x2) όπου το σύμβολο "◦" παριστά τη μαθηματική σύνθεση και ο εκθέτης -1 την
αντίστροφη συνάρτηση.

5. α) Να οριστεί συνάρτηση μεταβλητής x , που να αποδίδει την εξίσωση ευθείας y=ax+b , όπου
a και b να οριστούν ως σύμβολα - μεταβλητές. Να οριστούν μεταβλητές που να παριστούν
αντίστοιχα: την κλίση της ευθείας αυτής, το σημείο τομής με τον άξονα των Χ , το σημείο
τομής με τον άξονα των Υ .
β) Να οριστούν μεταβλητές που να παριστούν αντίστοιχα την τετμημένη και την τεταγμένη της
τομής δύο ευθειών y1 και y2 .
6. Να γραφεί συνάρτηση που να δίνει το εμβαδόν τριγώνου με κορυφές τρία δοσμένα σημεία
(θεωρείστε οτι το καθένα σημείο προσδιορίζεται από δύο μεταβλητές, τις καρτεσιανές
συντεταγμένες του).
7. Να οριστεί macro συνάρτηση που να δέχεται δύο ορίσματα, μεταβλητές με τιμή λίστες, και να
προκαλεί συνένωση των λιστών, θέτοντας το αποτέλεσμα μόνιμα ως τιμή της δεύτερης
μεταβλητής.
8. Να δοθεί macro συνάρτηση που να δέχεται όρισμα μεταβλητή με τιμή λίστα ακεραίων, να
υπολογίζει το τετράγωνό τους, και να τοποθετεί τη λίστα των αποτελεσμάτων ως τιμή της
μεταβλητής.
9. Να δοθεί φόρμα πολλαπλών τιμών που να υπολογίζει κατά κύριο λόγο τον τόκο κεφαλαίου για
χρόνο κατάθεσης Χ και επιτόκιο Ε με ετήσιο ανατοκισμό, και κατά δεύτερο λόγο τον φόρο 9%
ετησίως πάνω στον τόκο. Να γραφεί συνάρτηση που να υπολογίζει βάσει της προηγούμενης
φόρμας, για Ν καταθέτες, το ποσόν που θα έχει εισπράξει η εφορία μετά το πέρας του
μεγαλύτερου χρόνου κατάθεσης.
10. Δίνεται το ακόλουθο πρόγραμμα:

(setq b 0 a '(a) )
(defun a ( ) (setq b (+ 1 b)) (print b) a)
Τί θα προκαλέσει η εφαρμογή (eval (eval (a))) αν κληθεί δύο φορές;

3 Υπολογιστική Λογική και Βασικές Συναρτήσεις

Μέχρι τώρα είδαμε τον τρόπο δημιουργίας μαθηματικής συνάρτησης και συνάρτησης που παίζει
ρόλο διαδικαστικής εκτέλεσης σειράς βημάτων. Έλλειπε όμως η δυνατότητα ελέγχου και
επιλογής. Στο κεφάλαιο αυτό θα δούμε το λογικό έλεγχο και γενικότερα τη λογική που ακολουθεί
η Lisp. Θα δώσουμε ιδιαίτερη έμφαση στη λογική αυτή, διότι, αν και καλύπτει τη συνήθη
Προτασιακή Λογική που ακολουθεί ο διαδικαστικός προγραμματισμός, έχει κάποια επιπρόσθετα
χαρακτηριστικά. Με τη δυνατότητα λογικού ελέγχου, ανοίγει ουσιαστικά η δυνατότητα
a
συγγραφής προγράμματος.
περιεχόμενο
Θα δούμε στη συνέχεια το θεμελιώδες για
τον ΣΠ νόημα της λίστας, που είναι υποτύπος της
ακολουθίας∙ τους τρόπους επεξεργασίας λίστας, καθώς και την περίφημη λίστα ιδιοτήτων
συμβόλου.
Θα δούμε την δυνατότητα επεξεργασίας συμβολοσειρών που είναι ειδική περίπτωση της
επεξεργασίας ακολουθίας (sequence), που έχει το ιδιαίτερο χαρακτηριστικό οτι τοποθετείται
ψηλότερα από την επεξεργασία λίστας διότι τόσο ο τύπος list όσο και ο τύπος string είναι
υποτύποι του sequence.
Τέλος, θα μελετήσουμε το νόημα των συνδέσεων ως προς τη δυνατότητα που παρέχουν για
προσδιορισμό μόνιμης και συγκεκριμένης εξάρτησης οντοτήτων, και συγκεκριμένα τον τρόπο
που δύο ανεξάρτητες και ανεξάρτητα εξελισσόμενες οντότητες να προσδιορίζουν τρίτη, η οποία
καί να είναι αυτόνομη καί να παρακολουθεί την εξέλιξη των μερών της.

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

– αληθές είναι οτιδήποτε άλλο, αρκεί να μην είναι "σφάλμα", και έχει συμβολικό όνομα T , που
επίσης είναι η κορυφή των τύπων (υπερτύπος όλων των τύπων) και NIL το μοναδικό στοιχείο
τύπου NULL , που είναι ο τελικός υποτύπος όλων των τύπων (ο ειδικότερος δυνατός).

Σύμφωνα με τα παραπάνω, ο αριθμός 4 ως λογική τιμή ισοδυναμεί με Τ , όπως και ένα string ή
μια οποιαδήποτε φόρμα που υπολογίζεται και η τιμή της δεν είναι NIL . Κατά συνέπεια,
οποιαδήποτε φόρμα που δεν δίνει σφάλμα, μπορεί να θεωρηθεί ως λογική συνάρτηση, όπως:
(print 7)

a

b

; επιστρέφει 7 ∙ λογικά ισοδυναμεί με "αληθές"

(print nil)

; επιστρέφει NIL ∙ είναι η κενή λίστα αλλά και το "ψευδές"
108

(endp 4)

; επιστρέφει Error ∙ δεν αντιστοιχεί σε λογική τιμή
► Οι λογικές τιμές δεν ανήκουν σε τύπο όπως "logical" ή "boolean" που συναντάμε σε άλλες
γλώσσες, για το λόγο οτι αυτή καθ' εαυτή η υπολογιστική συλλογιστική της Lisp βασίζεται στο
οτι οι τιμές αυτές είναι στην ουσία οι ίδιες τύποι.

3.1.1 Λογικά συνδετικά
Tα λογικά συνδετικά AND , OR , NOT υπακούουν στους γνωστούς πίνακες αλήθειας της Δίτιμης
Προτασιακής Λογικής.
Διαφέρουν ριζικά από τα συνήθη σε άλλες γλώσσες λογικά συνδετικά κατά το οτι είναι
συναρτήσεις, που τις χρησιμοποιούμε με τη συνήθη φόρμα των συναρτήσεων της Lisp. Οι
συναρτήσεις AND και OR δέχονται οποιοδήποτε πεπερασμένο πλήθος ορισμάτων.

Η τιμή των συναρτήσεων AND , OR , NOT λογικά είναι Τ ή NIL (με την προϋπόθεση οτι ο
υπολογισμός εφαρμογής πάνω στα ορίσματά τους δεν προκαλεί σφάλμα). Τα ορίσματα που
δέχονται τα λογικά συνδετικά, ή είναι οι τιμές Τ ή NIL ή είναι φόρμες που υπολογιζόμενες
επιστρέφουν αντίστοιχη τιμή, η οποία εκτιμάται σύμφωνα με όσα προαναφέραμε ως Τ ή NIL .
Κατά συνέπεια, δεδομένου οτι οτιδήποτε έχει τιμή που δεν είναι NIL, λογικά έχει τιμή Τ, τα πάντα
(εκτός του σφάλματος) μπορούν να αποτελέσουν είσοδο για τις λογικές συναρτήσεις, άρα
οποιοσδήποτε υπολογισμός μπορεί να ενταχθεί και να εκτελεστεί μέσα σε λογικό έλεγχο.
Όπως θα δούμε, το γεγονός οτι πρόκειται για συναρτήσεις, χαρακτηρίζει τη συλλογιστική που
μπορούμε να εφαρμόσουμε στον ΣΠ, διότι διευρύνει πολύ τις δυνατότητες σύνθεσης
συμπερασματικών σχέσεων
and : Συνάρτηση που δέχεται οποιοδήποτε πεπερασμένο πλήθος ορισμάτων από δύο και πάνω.

+
Επιστρέφει T αν κάθε όρισμα έχει τιμή T (ή "μεταφραζόμενη
λογικά" σε Τ), αλλιώς επιστρέφει
NIL :
(and 3 Τ (+ 1 2) ) → Τ
(or 3 Τ ( ) )
→ NIL
or
: Συνάρτηση που δέχεται οποιοδήποτε πεπερασμένο πλήθος ορισμάτων από δύο και πάνω.

Επιστρέφει T αν κάποιο όρισμα έχει
T (ή "μεταφραζόμενη" σε Τ), αλλιώς επιστρέφει NIL :
a τιμή
(or 3 Τ ( ) )
→ Τ
not : Συνάρτηση ενός ορίσματος. Επιστρέφει NIL αν το (μοναδικό) όρισμα έχει τιμή T , και Τ
αν το όρισμα έχει τιμή NIL :
(not (= 2 3))
→ T
(not (not v)) → <επιστρέφει τη (λογική) τιμή της μεταβλητής v >

Θα δούμε σε επόμενη ενότητα αναλυτικά οτι τα λογικά συνδετικά αποτελούν επίσης τύπους,

βάσει των οποίων μπορούν να οριστούν άλλοι τύποι. Αυτή είναι μια
ακόμα διάσταση των
λογικών συνδετικών στη Lisp, που δεν διαθέτουν
a οιbδιαδικαστικές γλώσσες.

a

109

3.1.2 Αριθμητικά συγκριτικά

Λογικές συναρτήσεις, μεταξύ άλλων, είναι και τα αριθμητικά συγκριτικά:
< , > , <= , >= , = , /=
τα οποία επίσης συντάσσονται ως συναρτήσεις της Lisp, και μπορούν να αναφέρονται σε
οποιοδήποτε πεπερασμένο πλήθος αριθμητικών ορισμάτων:
( > 6 5.1 4 3 2.0 ) → Τ (οι
b αριθμοί είναι σε φθίνουσα διάταξη)
( < 5 6 4 5 6.1 )
→ NIL (οι αριθμοί δεν είναι σε αύξουσα διάταξη)

d
( /= 1 1 5 0.0 )
→ Τ ( οι αριθμοί δεν είναι όλοι ίσοι)
( = 2 2.0 (/ 4 2) )
→ Τ (οι αριθμοί είναι όλοι ίσοι)
Η σύγκριση γίνεται θεωρώντας τις συγκρινόμενες τιμές ως του γενικού τύπου real και δεν
λαβαίνει υπ' όψη της τον ειδικότερο τύπο (προσοχή: η σύγκριση ισότητας μεταξύ αριθμών τύπου
integer, ratio και float μπορεί να οδηγήσει σε διαφορετικά αποτελέσματα από αυτά που έχει
συνηθίσει ο χρήστης άλλων γλωσσών, διότι πάντα ανάγεται στον γενικότερο κοινό υπερτύπο).

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

3.1.3 Κατηγορήματα ελέγχου

15

Ένα σύνολο πρωτογενών λογικών συναρτήσεων, τα λεγόμενα κατηγορήματα (predicates),
ελέγχουν την αλήθεια ορισμένων καταστάσεων, όπως αν το όρισμά τους πληροί συγκεκριμένη
σχέση, αν έχει συγκεκριμένο τύπο, κάποια ιδιότητα κλπ. To σύνολο αυτό μπορεί να επεκταθεί
απεριόριστα, με τον ορισμό νέων κατηγορημάτων ως συναρτήσεων με έξοδο NIL στην περίπτωση
του ψευδούς και οτιδήποτε άλλο στην περίπτωση
. . . του αληθούς.
ak+1



ak



a2



a1

Πολλά κατηγορήματα που εκφράζουν το νόημα "έχει το αντικείμενο αυτό το χαρακτηριστικό;"
τελειώνουν με το γράμμα p (από το property) - δυστυχώς όχι όλα. Ορισμένες εκδόσεις της Lisp
συνηθίζουν να θέτουν στα κατηγορήματα ελέγχου –και πολύ σωστά από σημασιολογική άποψη–
ως τελευταίο χαρακτήρα το ερωτηματικό "?" (δυστυχώς όχι η CL).
Μια άλλη χρήση των κατηγορημάτων, εκτός του λογικού ελέγχου, είναι για τον προσδιορισμό
υποτύπων, όπως θα δούμε στα επόμενα (πχ. ο τύπος "ακέραιος και διαιρετός δια του 4 αλλά όχι
110

δια του 10", ή ο τύπος "μέλος της λίστας Α").


/ ακόλουθα, που επιστρέφουν σε κάθε περίπτωση αποτέλεσμα
Βασικά κατηγορήματα είναι τα
λογικής τιμής Τ ή NIL αποκλειστικά, εκτός ορισμένων που απαιτούν συγκεκριμένο τύπο εισόδου
που αν δεν δοθεί επιστρέφουν σφάλμα. Διευκρινίζουμε ξανά οτι "επιστροφή λογικής τιμής Τ"
μπορεί να σημαίνει δύο καταστάσεις: είτε οτι η φόρμα δίνει "φυσική" τιμή Τ, είτε οτι η φόρμα
δίνει τιμή που λογικά ισοδυναμεί με Τ (σε λογικό έλεγχο είναι το ίδιο, αλλά σε συναρτησιακή
σύνθεση είναι ουσιαστικά διαφορετικό).

Η σχέση "ανήκει"
member : Ελέγχει αν ένα στοιχείο (το πρώτο όρισμα) ανήκει σε κάποια λίστα (το δεύτερο
όρισμα) :
(member 'd '(a b c d e) ) → T

Προσοχή: Σε ορισμένους compilers η επιτυχία της συνάρτησης member επιστρέφει ακριβώς την
τιμή Τ , ενώ σε άλλους επιστρέφει την υπο-λίστα από το εν λόγω στοιχείο μέχρι τέλους της
λίστας, που ως μη κενή λίστα, λογικά ισοδυναμεί με τη τιμή Τ .

Αν το ελεγχόμενο στοιχείο δεν ανήκει στη λίστα, η MEMBER επιστρέφει την κενή λίστα, που
λογικά είναι η τιμή NIL .
(setq animals '(cat dog mouse bird) )
(member 'dog animals)

(dog mouse bird)
(member 'horse animals)

NIL

Επειδή ενδέχεται να χρειαστούμε επιστροφή αυτής καθ' εαυτής της τιμής Τ (ως συμβόλου) και όχι
της υπολίστας, στην περίπτωση αυτή πρέπει να ορίσουμε μια άλλη συνάρτηση, έστω MEMBERT-F (σχετικά με τη συνάρτηση cond βλέπε παρακάτω)
(defun member-t-f (e lis)
(cond
((member e lis) t)
(t nil)))

; αν e είναι μέλος της lis , επίστρεψε Τ
; αλλιώς, επίστρεψε NIL
5

b

Η συνάρτηση MEMBER αποκτά γενικότερη σημασία όταν χρησιμοποιηθεί με μια πρόσθετη
παράμετρο, τη συνάρτηση σύγκρισης, η οποία τοποθετείται:
α'. Μετά από το κλειδί :test το οποίο δηλώνει το κριτήριο χαρακτηρισμού του στοιχείου ως
μέλους. Το όρισμα αυτό είναι όνομα συνάρτησης (με πρόθεμα #' όπως σε κάθε παραμετρική
κλήση ονόματος συνάρτησης) ή λ-έκφραση (η συνάρτηση car-equal ελέγχει ισότητα κεφαλών
λιστών):
(defun car-equal (x w) (eql x (car w)))
(setq ζώο-που-τρώει-ζώο
'( (γάτα (ποντίκι πουλί))
(αλεπού (κότα))
(άνθρωπος (αρνί μοσχάρι κότα λαγός)) ) )
Εφαρμογή:

a
111

d

(member 'άνθρωπος ζώο-που-τρώει-ζώο :test #'car-equal ) → T

β'. Μετά από το κλειδί :test-not , που δηλώνει αρνητικό κριτήριο χαρακτηρισμού του
στοιχείου ως μέλους (δηλ. να μη συμβαίνει για το ελεγχόμενο στοιχείο, ως προς κανένα στοιχείο
της λίστας, αυτό που προσδιορίζει η συνάρτηση - κριτήριο).
Θα δούμε αργότερα και μια άλλη χρήση τής MEMBER , στον προσδιορισμό dυποτύπων.
member-if : Δύο ορισμάτων,
όπου το πρώτο είναι συνάρτηση - κατηγόρημα ελέγχου και το
4c
δεύτερο είναι λίστα. Ελέγχει αν υπάρχει στοιχείο της λίστας που να επαληθεύει το κατηγόρημα
ελέγχου. Επιστρέφει την υπολίστα από το στοιχείο αυτό και πέρα, αν υπάρχει) που ως μη κενή
ισοδυναμεί λογικά με T , αλλιώς NIL .
(setq numbers '(-1 -6 3 -2 7 -1) )
(member-if #'plusp numbers)
b
→ (3 -2 7 -1)
(διότι ο πρώτος θετικός στη λίστα είναι ο 3)
a

Κατηγορήματα ελέγχου τύπου

numberp : Ελέγχει αν το όρισμα είναι αριθμός:
pi
→ 3.14159265358979
(numberp pi) →
Τ
integerp : Ελέγχει αν το όρισμα είναι ακέραιος αριθμός:
(integerp ( / 3 4))
→ NIL
(integerp ( / 4 2)) → T
c
ratiop : Ελέγχει αν το όρισμα είναι τύπου
πηλίκου:
(ratiop (/ 4 3))
→ T

+
(ratiop (/ 4 2))
→ NIL
διότι το πηλίκο (/ 4 2) εκτελείται, επιστρέφοντας
z αποτέλεσμα τύπου integer, που είναι του ίδιου
επίπεδου με το ratio, πριν την εφαρμογή της ratiop .
rationalp : Ελέγχει αν το όρισμα είναι τύπου ρητού (rational). Υπενθυμίζεται οτι ρητός αριθμός
είναι ακέραιος ή πηλίκο ακεραίων:
(rationalp (/ 4 3)) → T
(rationalp (/ 4 2)) → T
symbolp

: Ελέγχει αν το όρισμα είναι σύμβολο:

(setq leonidas 'leon) → LEON
7
(symbolp
'leonidas) → T

Προσοχή: Σε compiler όπου τα "quoted" ονόματα - τιμές αναγνωρίζονται αυτόματα ως σύμβολα,
θα έχουμε:
(symbolp 'leon)
→ T

Αντίθετα, σε compiler όπου τα "quoted" ονόματα - τιμές δεν αναγνωρίζονται αυτόματα ως
σύμβολα (πρέπει προηγουμένως να οριστούν, πχ. με
x τηνSETQ), θα έχουμε:
lambda → NIL
(symbolp 'leon)
x



112

y
x



y



Κατηγορήματα συσχετισμού με συγκεκριμένο χαρακτηριστικό
zerop

: Ελέγχει αν το όρισμα είναι μηδέν:
(zerop
0)
→ Τ

+
(zerop 0.0)
→ Τ
(setq v13 (+ 4 -3.9 -0.1)) → 0.0
(zerop v13)
→ Τ
+

Προσοχή: η σύγκριση αφορά την εσωτερική υπολογιστική
αναπαράσταση του αριθμού, που
7 y
σχετίζεται με την αριθμητική της γλώσσας και του λειτουργικού συστήματος, πχ:
(zerop (expt 2 -100))
→ NIL
(zerop (expt 2 -100000))
→ Τ
plusp , minusp :
(plusp 0.03)
(minusp 0.03)

Ελέγχουν αν το όρισμαz είναι θετικός, αντίστ. αρνητικός αριθμός:
→ Τ
→ NIL

evenp , oddp : Ελέγχουν αν το όρισμα είναι άρτιος, αντίστ. περιττός αριθμός.
Προσοχή: το όρισμα οφείλει να είναι ακέραιος, αλλιώς επιστρέφουν σφάλμα.
(evenp 4)
→ Τ
(oddp 3)
→ Τ
(oddp 3.0) → Error…
listp

(listp
(setq
(listp
(listp

: Ελέγχει αν το όρισμα είναι λίστα:

'(1 (2 3) 3) )
→ Τ
mylist '(a b c d)) → (a b c d)
mylist)
→ Τ
'mylist)
→ NIL

Διότι mylist είναι σύμβολο∙ η τιμή του είναι λίστα, όχι το ίδιο το όνομα
null

: Ελέγχει αν το όρισμα είναι η κενή λίστα:
(null 3)
→ ΝΙL
(null ( ) ) → T
(null nil) → T

z

endp
: Ελέγχει αν το όρισμα, που οφείλει να είναι λίστα, είναι κενή, και αντιμετωπίζει ως
σφάλμα την περίπτωση ορίσματος που δεν είναι λίστα:
a
(endp nil)
→ T
(endp '(2) )→ ΝΙL
περιεχόμενο
(endp 2 )
→ Εrror: argument is not a list
Προσοχή: μερικοί compilers στην τελευταία περίπτωση επιστρέφουν T .

Ποσοδείκτες

2

every
: Εκφράζει τον ποσοδείκτη "γιa κάθε". Συνάρτηση δύο ορισμάτων, όπου το πρώτο είναι
κατηγόρημα ελέγχου ή τύπος και το δεύτερο φόρμα-ακολουθία (λίστα, string, array, vector…)
Ελέγχει αν όλοι οι όροι της φόρμας-ακολουθίας ικανοποιούν το κατηγόρημα.
(every #'plusp '(1 2 4 -12))
→ NIL
113
7

(every #'plusp '(1 2 4 (5)))
(every 'character '(#\a #\b))

→ Εrror…
→ T

notany : Αντίστοιχη με την EVERY , ελέγχει το "κανένα":
(notany #'zerop '(2 1)) → Τ
(κανένα δεν είναι μηδενικό)
(notany #'zerop '(0 1)) → NIL
(ψευδές το "κανένα μηδενικό")
notevery : Παρεμφερής με την NOTANY, ελέγχει το "όχι όλα":
(notevery #'zerop '(0 1)) → T
(δεν είναι όλα μηδενικά)
(notevery #'zerop '(2 1)) → T
(δεν είναι όλα μηδενικά)
(notevery #'zerop '(0 0 0)) → NIL
(ψευδές οτι δεν είναι όλα μηδενικά)
some

: Παρεμφερής με την EVERY, που ελέγχει το "μερικά":
→ T
(κάποιο είναι μηδενικό)
→ T
(κάποιο είναι μηδενικό)

(some #'zerop '(0 1))
(some #'zerop '(0 0))

5

Έλεγχος ισότητας
H ισότητα ελέγχεται από μια πλειάδα κατηγορημάτων ελέγχου, που συγκρίνουν την ισοδυναμία
οντοτήτων σε διάφορα επίπεδα. Είναι κατηγορήματα που δέχονται δύο ή περισσότερα ορίσματα,
και είναι τα ακόλουθα:
equalp
: Ελέγχει ισότητα αριθμών (σε επίπεδο number), χαρακτήρων ή strings, όπου τα
αλφαριθμητικά δεν διακρίνονται σε "κεφαλαία” ή “πεζά". Είναι η απλούστερη σύγκριση, που δεν
λαβαίνει υπ' όψη της παρά μόνο την εμφάνιση των ελεγχομένων οντοτήτων:
(equalp "Abc" "abc")

Τ

=

:
Ελέγχει ισότητα αριθμών σε επίπεδο τύπου number, ανεξάρτητα υποτύπου. Είναι το
ταχύτερο κατηγόρημα για αριθμητική σύγκριση:
(= 3 3.0 (+ 1 2) (/ 9 3) ) → Τ c
(= 3 3 4)
→ NIL<λ-έκφραση>
66
a

b

equal
: Ελέγχει επί πλέον του EQUALP αν συμπίπτουν τα συγκρινόμενα μέλη ως δομές.
Aπαιτεί ίδιο τύπο (και υποτύπο, αν διαθέτουν) των ελεγχομένων οντοτήτων. Χρησιμοποιείται
κυρίως στη σύγκριση λιστών.
(equal '(1 (2 3) 4) '(1 (2 3) 4) → T
(equal 3 3.0)
→ NIL
(equal 3 (+ 1 2))

Τ

. . . ορn
ορ2
eql : Ελέγχει πλήρη ταυτότητα των ελεγχομένωνορ2
οντοτήτων, από κάθε άποψη (τύπο, τιμή,
<λ-έκφραση>

ορ1

ιδιότητες, συνδέσεις, και σε όλο το βάθος της δομής τους το ίδιο).
Ίσες μέσω EQL οντότητες μπορούν να είναι και αντίγραφα της αυτής οντότητας. Είναι η πλήρως
σημασιολογική ισότητα αλλά είναι σκόπιμο να χρησιμοποιούνται τα προηγούμενα αν ζητείται
κάτι απλούστερο, για το λόγο οτι το EQL είναι περισσότερο χρονοβόρο.
(eql 3.0 3) → NIL
(eql 3 3)
→ T

114

77



eq
: Είναι ο αυστηρότερος από τους ελέγχους ισότητας. Ελέγχει, εκτός από τα παραπάνω,
και αν τα ορίσματά του είναι ή αντιπροσωπεύουν "το αυτό φυσικό αντικείμενο", ακόμα και από
την άποψη της θέσης στη μνήμη. Το χρησιμοποιούμε μόνον αν υπάρχει λόγος τέτοιας
ταυτότητας.
(eq 3 3)
→ NIL

διότι οι επαναλήψεις αριθμού είναι διαφορετικές συγκεκριμενοποιήσεις (στιγμιότυπα) του
αφηρημένου αντικειμένου (κλάση) “ο ακέραιος 3”, και όχι η αυτή οντότητα∙ όμως, αντίθετα,
έχουμε:
(setq a 5)
(eq 'a 'a)

→ T

διότι συγκρίνει το αυτό “υπαρκτό αντικείμενο"
(σύμβολο) με τον εαυτό του, ενώ:
33
(eq a a)
→ NIL
διότι συγκρίνει τιμές που είναι μεν ίσες αλλά δεν είναι το αυτό σύμβολο ή το αυτό φυσικό

αντικείμενο. Για τον ίδιο λόγο:
(eq '(1 2 3) '(1 2 3)) → NIL
Τα παραπάνω κατηγορήματα ισότητας είναι αναγραμμένα σε σειρά μειωνόμενης ταχύτητας
εκτέλεσης και αυξανομένων υποχρεώσεων που ελέγχονται. Υπάρχουν και ορισμένα ειδικά
κατηγορήματα “ταχείας σύγκρισης”, που δεν εξερευνούν τη δομή των αντικειμένων και
επιταχύνουν την εκτέλεση. Είναι τα ακόλουθα:

"case sensitive" λεξικογραφική
σύγκριση
3
string= , string/= : Ελέγχουν ταυτότητα, αντίστ. διαφορετικότητα από strings, εξαρτημένα
από το αν είναι κεφαλαία ή πεζά. 15

(string= “Giannis” “giannis”) progn
→ ΝΙL
string> , string>= , string< , string<=
: Ελέγχουν λεξικογραφική διάταξη από strings,
εξαρτημένα από το αν είναι κεφαλαία ή πεζά (ανάλογα με τον χρησιμοποιούμενο compiler, η
σύγκριση γίνεται είτε ως προς τον ASCII αριθμό των αντιστοίχων χαρακτήρων είτε ως προς τον

/
2
UNICODE αντίστοιχο αριθμό).
char= , char/= : Ελέγχουν ταυτότητα αντίστ. διαφορετικότητα από χαρακτήρες, παρόμοια με
τις STRING= , STRING/= , εξαρτημένα από τον τύπο κεφαλαίων ή πεζών.
(char= #\G #\g )
→ ΝΙL
char> , char>= , char< , char<=
: Παρόμοια, ελέγχουν λεξικογρα-φική διάταξη από
χαρακτήρες.

"case insensitive" λεξικογραφική σύγκριση
Αν επιθυμούμε λεξικογραφική σύγκριση ανεξάρτητη κεφαλαίων ή πεζών, χρησιμοποιούμε τα
παρακάτω. Κατά το πρότυπο της CL λειτουργούν μόνο για λατινικούς χαρακτήρες και είναι τα
ταχύτερα. Σε πιο σύγχρονους compilers λειτουργούν για το unicode set:

115

7

string-equal : Ελέγχει ταυτότητα από strings, ανεξάρτητα κεφαλαίων ή πεζών.
(string-equal “Giannis” “giannis”)
→ T
char-equal : Ελέγχει ταυτότητα από χαρακτήρες, ανεξάρτητα κεφαλαίων ή πεζών.
(char-equal #\G #\g )
→ T
string-lessp , string-greaterp , string-not-lessp , string-not-greaterp : Ελέγχουν τη διάταξη από
strings με λεξικογραφικό τρόπο, ανεξάρτητα από το αν είναι κεφαλαία ή πεζά.
char-lessp , char-greaterp , char-not-lessp , char-not-greaterp : Ελέγχουν λεξικογραφική
διάταξη από χαρακτήρες, ανεξάρτητα από το αν είναι κεφαλαία ή πεζά.

3.2 Λογικός έλεγχος και επιλογή
3.2.1 Διακλάδωση υπολογισμού υπό συνθήκες
Ο λογικός έλεγχος και η επιλογή στη Lisp χρησιμοποιείται για να καλύψει διάφορες ανάγκες
υπολογισμού: για επιλεκτική διακλάδωση υπολογισμού σε προκαθορισμένες περιπτώσεις,
προσδιορισμένη δηλωτικά ή επιτακτικά∙ για έλεγχο καταλληλότητας τιμών ή τύπο τιμών εισόδου
συνάρτησης∙ για έλεγχο της καθυστέρησης της εκτέλεσης∙ για προσαρμογή / εξειδίκευση
συμπεριφοράς ανάλογα με τις τιμές εισόδου ή τον τύπο τους∙ για έλεγχο της κατάστασης του
περιβάλλοντος.

Σε κάθε περίπτωση, η επιλογή αναφέρεται σε ένα προκαθορισμένο και πεπερασμένο σύνολο
δυνατών ανακατευθύνσεων, διαδοχικά ελεγχόμενο μέχρι να εντοπιστεί η κατάλληλη ειδική
περίπτωση (σε αντιδιαστολή με άλλα πλαίσια που θέτουμε εξειδίκευση, όπου δεν απαιτείται το
"προκαθορισμένο"). Ο σειριακός έλεγχος, όπως θα δούμε, μπορεί να αφήνει ίχνη ή όχι,
ανεξάρτητα του αν πετυχαίνει ή όχι, και αυτό είναι μια ουσιαστική διαφοροποίηση των
υπολογιστικών δυνατοτήτων της Lisp σε σχέση με άλλες γλώσσες.
Ο λογικός έλεγχος μπορεί να αφορά επαλήθευση κατηγορήματος ή λογικό συνδυασμό
κατηγορημάτων που να ελέγχουν, μεταξύ άλλων:
5
– τύπο τιμής μεταβλητής
ή λ-μεταβλητής, ή λογικό συνδυασμό τέτοιων τύπων
– τύπο αποτελέσματος εκτέλεσης
– επαλήθευση ορισμένης σχέσης (κατηγορήματος)
– κατάσταση ιδιότητας συμβόλου
Ο έλεγχος μπορεί να περιλαμβάνει, κατά τα συνήθη και σε άλλες γλώσσες, δενδροειδώς άλλους
ελέγχους, διασπώντας τον υπολογισμό σε τμήματα. Έτσι, μπορούμε να οδηγήσουμε τον
υπολογισμό σε μια δενδροειδώς διατεταγμένη πορεία επιλογών και υποχρεώσεων, με
προκαθοριζόμενη διαδοχή στη σάρωση του δένδρου.
Μπορούμε να δούμε την διακλάδωση του υπολογισμού σαν οδηγίες διαδικαστικής επιλογής ("αν
ισχύει Α τότε εκτέλεσε Β"). Μπορούμε όμως να τη δούμε σαν τρόπο κατηγοριοποίησης
καταστάσεων και προσδιορισμό των σχετικών με αυτές λειτουργιών, σε δηλωτικού τύπου
116

έκφραση ("αν ισχύει Α τότε η κατάσταση είναι Β"), ή ανάμεικτα.
Αν και οι θεμελιώδεις συναρτήσεις επιλογής εκτελούν "την πρώτη επιτυχία" και μόνον, θα δούμε
πως είναι δυνατό να προσδιορίσουμε όποιες εκτελέσεις επαληθεύουν κάποιες συνθήκες.

Ο λογικός έλεγχος ως φόρμα υπολογισμού
+

Στον ΣΠ ουσιαστικό ρόλο παίζει το γεγονός οτι το υποθετικό μέρος ελέγχου, ως λογικός
συνδυασμός που σχηματίζεται μέσω των λογικών συνδετικών, αποτελεί φόρμα υπολογισμού και
όχι απλώς έλεγχο κατάστασης. Ως φόρμα υπολογισμού μπορεί να κάνει οτιδήποτε, δηλωτικά ή
επιτακτικά, ανεξάρτητα του αποτελέσματος - λογικής τιμής που θα προκύψει, άρα ο λογικός
έλεγχος μπορεί να εμπεριέχει προσδιορισμό / μεταβολή κατάστασης ή/και δράση υπολογισμού
που ενδεχομένως επιδρά στους επόμενους ελέγχους ή εν γένει σε εφαρμογές που θα
ακολουθήσουν, ακόμα και αν η ελεγχόμενη περίπτωση αποτυχαίνει.
cond : Είναι η κύρια συνάρτηση επιλογής βάσει λογικού ελέγχου. Τα ορίσματά της είναι λίστες
(τουλάχιστον μία). Σε κάθε όρισμα – λίστα, πρώτος όρος είναι μια φόρμα που παίζει ρόλο
λογικού ελέγχου και επόμενοι η διαδοχή των εκτελέσεων που θα ακολουθήσει αν πετύχει ο
έλεγχος. Η σύνταξη είναι:
(cond
(έλεγχος1 σώμα1 )
(έλεγχος2 σώμα2 )
... ...
(έλεγχοςk σώμαk ) )
Τα σώματα είναι δυνατό να αποτελούνται από διαδοχικές φόρμες (από καμμία μέχρι οσεσδήποτε,
πεπερασμένου πλήθους) οπότε η παραπάνω μορφή θα σημαίνει:
(cond
(έλεγχος1 φόρμα11 φόρμα12 … φόρμα1a)
(έλεγχος2 φόρμα21 φόρμα22 … φόρμα2b )
... ...
(έλεγχοςk φόρμαk1 φόρμαk2 … φόρμαkm ) )

Έλεγχοι μπορούν να είναι οποιεσδήποτε υπολογίσιμες φόρμες, και όχι μόνο κατηγορήματα. Η
τιμή φόρμας νοείται λογικά "αληθές" αν δεν είναι NIL , και "ψευδές" αν είναι NIL . Αν προκύψει

σφάλμα από φόρμα ελέγχου, τότε η συνάρτηση
COND επιστρέφει σφάλμα και η εκτέλεση
διακόπτεται (δύο κατηγοριών σφάλματα: οριστικά, που δεν επιδέχονται διόρθωση, και
5
συνεχιζόμενα, που επιδέχονται διόρθωση).

1
Οι φόρμες στα παραπάνω σώματα, που σημειωτέον είναι
προαιρετικά, μπορούν να είναι είτε
σύνθετες φόρμες είτε μεταβλητές είτε σταθερές.

Τα ορίσματα της COND ελέγχονται διαδοχικά: πρώτα η υπόθεση, και αν επαληθευτεί, ακολουθεί
η εκτέλεση του σώματος και σταματά, αλλιώς προχωρά στην επόμενη υπόθεση.
Το καθένα όρισμα - λίστα της COND , δηλ.
(έλεγχος-x φόρμα-x1 φόρμα-x2… φόρμα-xm)
117

έχει το νόημα: "αν αληθεύει o έλεγχος-x , τότε εκτέλεσε διαδοχικά τις φόρμες που ακολουθούν,
σταμάτα τον υπολογισμό και επίστρεψε ως αποτέλεσμα της COND το αποτέλεσμα της τελευταίας
φόρμας"
Η διαδοχή " φόρμα-x1 φόρμα-x2… φόρμα-xm " είναι ένα εννοούμενο PROGN
Άρα, στην πρώτη λίστα - όρισμα όπου θα επαληθευτεί η υπόθεση, τότε, και μόνο τότε,
εκτελούνται οι επόμενοι όροι της φόρμας - όρισμα. Με το πέρας υπολογισμού αυτής της φόρμας ορίσματος η εκτέλεση σταματά, και η COND επιστρέφει το αποτέλεσμα του τελευταίου όρου.
Αν αυτό που θέλουμε είναι να επιστραφεί η τιμή κάποιας συγκεκριμένης μεταβλητής, λμεταβλητής ή συμβόλου, τότε αναγράφεται η μεταβλητή αυτή ως τελευταία φόρμα στο σώμα.
Προφανώς το ίδιο αν θέλουμε να επιστραφεί κάποια σταθερά, ή το όνομα συμβόλου (τότε
αναγράφεται "quoted").

Aν δεν επαληθευτεί καμμία υπόθεση, η COND επιστρέφει NIL . Επειδή το NIL είναι τύπου λίστας
και ενδέχεται να μην ταιριάζει ως τύπος στο ανώτερό του επίπεδο (δηλ. τη συνάρτηση προς την
οποία το COND επιστρέφει τιμή) συνήθως τίθεται ως τελευταίος έλεγχος μια παντού αληθής
τιμή (συνήθως κατ' ευθείαν Τ), ώστε αν φθάσει εκεί να εκτελεστεί το σώμα που ακολουθεί
επιστρέφοντας κατάλληλου τύπου αποτέλεσμα. Έτσι λύνεται και το πρόβλημα να μην έχουμε
προβλέψει κάθε δυνατή περίπτωση.
Η κατάσταση των μεταβλητών και των ορισμών συναρτήσεων που θέλουμε να ισχύσουν σε μια
περίπτωση της διακλάδωσης, μπορεί να δηλωθεί είτε στο συμπερασματικό μέρος είτε στο
υποθετικό. Διαφοροποιούνται ριζικά οι δύο τρόποι:
– αν θέλουμε να ισχύσει η κατάσταση μετά την επιτυχία του ελέγχου, τότε την αναγράφουμε ως
διαδοχή από φόρμες του σώματος, πχ. (setq…) ή (defun…) .

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

Παράδειγμα



(cond

; αν είναι 3<x≤300 επίστρεψε την τιμή του x
( (and (> x 3) (<= x 300) x )

; αλλιώς επίστρεψε την τιμή του x 2
(t

(* x x))

)

Εννοούμενη υπόθεση προς έλεγχο
Σε ένα conditional, στη θέση όπου αναμένεται υπόθεση προς λογικό έλεγχο, είναι δυνατό να
118



δοθεί οποιαδήποτε φόρμα, δεδομένου ότι τα πάντα νοούνται ότι έχουν λογική τιμή T ή NIL . Το
αποτέλεσμα υπολογισμού αυτής της φόρμας αποτελεί ταυτόχρονα την τιμή του λογικού ελέγχου.
Αν η τιμή δεν είναι NIL και δεν υπάρχει επόμενος όρος προς εκτέλεση, η τιμή αυτή είναι και
αποτέλεσμα του conditional. Το παραπάνω παράδειγμα μπορεί να γραφεί ως εξής:
(cond
((and (> x 3) (<= x 300) x)
>
((* x x)) )

► Η σύμπτωση υποθετικού και εκτελεστικού μέρους στη δεύτερη φόρμα - κλάδο, έχει ως
συνέπεια να περιέχει ο κλάδος μία μόνο φόρμα, και από αυτό προκύπτει η διπλή παρένθεση.
Η "έξω" παρένθεση καθορίζει τον όρο - όρισμα του COND και η μέσα" παρένθεση καθορίζει τη
φόρμα - σώμα που θα εκτελεστεί μέσα στον όρο αυτό

Γραφή με "if … then … else …"





b3

Δέχεται και τη συνήθη μορφή ελέγχου “ if… then… else…” , με σύνταξη:
+ )
(if έλεγχος then σώμα1 else σώμα2
όπου το else μέρος είναι προαιρετικό, και τα σώματα μπορούν
c3 να περικλείουν και άλλους

b1
ελέγχους.
+
2
Είναι πολύ χρήσιμη στην περίπτωση απλού διαδικαστικού ελέγχου, με ή χωρίς "else" μέρος, διότι
εκφραστικά είναι απλούστερη από την COND :
c1
(if έλεγχος then σώμα1 )
(if έλεγχος then σώμα1 else σώμα2)
b2


/
αλλά είναι περιοριστική για συνθετότερες εκφράσεις
όπου εμπλέκονται
δράσεις και δηλώσεις,
c2
...
όπως:

cond

(defun myfunc11
(cond

cond





 calc, αν είναι άρτιο:
; δώσε τιμή στο var την τιμή του

(( setq var (myevenp calc)))

; αλλιώς δώσε στο var τιμή την τιμή του calc αυξημένη κατά ένα:

(( setq var (1+ calc))) ) )

όπου η συνάρτηση MYEVENP ορίζεται έτσι ώστε<να επιστρέφει τον ίδιο τον αριθμό – όρισμά της
αν είναι άρτιος, αλλιώς να επιστρέφει NIL :
(defun myevenp (n) (cond ((evenp n) n)) )

Η χρήση του IF εξυπηρετεί σε δυο περιπτώσεις: i) εκφραστικά, όταν πρόκειται για απλή επιλογή
(if…then…else…), ιδίως αν ο χρήστης είναι συνηθισμένος σε τέτοια γραφή, και ii) στην
περίπτωση κιβωτισμένων ελέγχων, διότι η γραφή "if…then…else…" δέχεται νέο "if" στους
κλάδους, κοκ. (σε ορισμένους compilers το cond δεν δέχεται κιβωτισμό
επιπέδων ελέγχου).
12
Παράδειγμα

7
4
Επιπεδοποίηση λίστας με όρους (ενδεχομένως)
λίστες κοκ:
(defun flat (lis)
(if (atom lis)
3

119

2

1
(list lis))
(append (flat (car lis) (flat (cdr lis)) ) ) )

5

Η συνάρτηση FLAT σαρώνει αναδρομικά το δένδρο που δίνεται ως είσοδος "κατά βάθος" (depth
first search), συγκεντρώνοντας τα άτομα που βρίσκει, στη λίστα. που επιστρέφει (βλέπε
παρακάτω, στις λίστες, τον ορισμό της έννοιας της λίστας, του
3 δένδρου και των συναρτήσεων
LIST , APPEND , CAR , CDR).

► Σε compilers που δεν γίνεται δεκτός κιβωτισμός από COND , είτε "επιπεδοποιούμε" τις
διακλαδιζόμενες επιλογές, είτε περιγράφουμε τη διακλάδωση χρησιμοποιώντας την μορφή if…
then…else... .

Σύνοψη και παρατηρήσεις
α'.
Τo <enter> στη διαμόρφωση της σύνταξης του COND δεν παίζει ρόλο παρά μόνο στη
μορφοποίηση της εμφάνισης, όπως και σε κάθε χρήση τού <enter> στη Lisp (όπως και τα tabs
ή τα επαναλαμβανόμενα κενά). Το "τι είναι φόρμα" προσδιορίζεται από την αντιστοιχία
παρενθέσεων, και μόνον.

β'.
Η επιτυχία ή μη του conditional ενδέχεται να εξαρτάται από τις διαμορφωμένες συνθήκες
του περιβάλλοντος (σύμβολα, περιεχόμενό τους, ιδιότητές τους, λειτουργικότητά τους,
συνδέσεις τους, κλπ), οπότε είναι δυνατό να λειτουργήσει φυσιολογικά ή όχι ανάλογα με το τι
ισχύει στο πρόγραμμα. Πχ:
(+ (cond ((= a b) 0) ((> a b) 1) ) 1 )

θα λειτουργήσει φυσιολογικά εάν έχει προηγουμένως έχει δοθεί:
(setq a 2 b 1)

διότι η έξοδος του COND στην περίπτωση αυτή θα είναι αριθμητική τιμή, αλλά θα προκαλέσει
σφάλμα αν προηγουμένως έχει δοθεί:
(setq a 1 b 2)

διότι η έξοδος του COND θα είναι NIL , που δεν είναι δεκτό ως όρος αθροίσματος.
γ'.
Eίναι προφανές οτι μπορεί να χρησιμοποιηθεί ο έλεγχος για τη θέση κριτηρίου διακοπής
σε μια επανάληψη ή αναδρομή:
Παραδείγματα
1. Επαναλαμβανόμενος κύκλος με κριτήριο διακοπής:

(defun print-square-if-positive-number (x)
(cond
( (> x 0) (print (* x x)) (print-square-if-positive-number (– x 1))
( t (print "input not a positive number") ) ) )

2. Ορισμός παραγοντικού:

(defun factorial (n)
(cond
( (or (not (integerp n)) (< n 0))
(print "input must be a non negative integer") )
120

( ( equal n 0) 1 )
( t ( * n (factorial (– n 1)) ) )

) )

Εφαρμογή:

(factorial 4)

24
(factorial -2) →
“input must be a non negative integer”

3.2.2

Υπολογισμός στο υποθετικό μέρος λογικού ελέγχου

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

α'. Ενδεχόμενες λογικές συνέπειες της εκτέλεσης υπολογισμού στο υποθετικό μέρος
Ας θεωρήσουμε το ακόλουθο πρόγραμμα:

(setq a 1 b 2 c nil)
(defun g1 ( )
(cond
((and (setf c (cons a c)) nil) 'sucsess1)
((and (setf c (cons b c)) nil) 'sucsess2)
(t 'failed)))

Στο περιβάλλον έχει καθοριστεί οτι c είναι η κενή λίστα:
c
→ NIL
Ας δούμε τί θα συμβεί με κλήση της G :
(g1)
→ failed
διότι αποτυχαίνει η AND λόγω του NIL .
Αλλά ας δούμε τώρα το περιεχόμενο της c :
c
→ (2 1)
Βλέπουμε οτι πέτυχε (εκτελέστηκε) το πρώτο μέρος της υπόθεσης καί από τις δύο ελεγχόμενες
φόρμες, άσχετα από το οτι τελικά η υπόθεση απέτυχε καί στις δύο !!!
Αυτό συνέβηκε διότι πριν την αποτυχία των υποθέσεων και μέχρι τη διαπίστωση της
αποτυχίας, εκτελέστηκαν τα βήματα που ήταν δυνατό να εκτελεστούν (push…) σε κάθε
ελεγχόμενη φόρμα της COND , λόγω της διαδικαστικής μορφής που έχει η συνάρτηση G1 .
Έχουμε λοιπόν οτι, αν σε ένα conditional θέσουμε κάποια εκτέλεση στο μέρος του ελέγχου, αρκεί
να υπάρχει επεξηγηματική επιστροφή NIL της εκτέλεσης για να θεωρηθεί ως αποτυχία και "να
πάει στον επόμενο έλεγχο".
Αυτό δείχνει πως, εκτός από τη δυνατότητα χρήσης των λογικών συνδετικών με καθαρά λογικό
τρόπο, που θα επιστέφει πάντοτε το λογικά αναμενόμενο αποτέλεσμα, έχουμε και τη δυνατότητα
121

χρήσης τους και ως κοινές συναρτήσεις, με αποτέλεσμα διαδικαστικά προσδιοριζόμενο.
Από αυτό συνάγουμε οτι είναι απόλυτα αναγκαίο να κρίνουμε τη φόρμα που θα δώσουμε ως
έλεγχο, όχι μόνο από το τελικό της αποτέλεσμα, αλλά και από τις επί μέρους δράσεις που
βηματικά θα προκαλέσει μέχρι το πέρας του υπολογισμού της.
Επειδή τα πάντα είναι λογική τιμή στη Lisp, κάθε υπολογιστική λειτουργία είναι καί
διαδικαστική δράση καί λογικός υπολογισμός. Γι' αυτό, δεν είναι δυνατό να διακρίνουμε τυπικά
μια διαδικαστική δράση από έναν υπολογισμό που επιστρέφει λογική τιμή. Άρα, η διάκριση
πρέπει να γίνει σε σημασιολογικό επίπεδο (δηλ. ποιά έννοια έχει ο υπολογισμός για τον ευρύτερο
χώρο).
Προφανώς, στην περίπτωση αυστηρής χρήσης κατηγορημάτων ελέγχου (και σωστά
οργανωμένων) αποκλείεται να προκύψει ασυνεπής λογική κατάσταση.
Είναι λοιπόν αναγκαίο να λαβαίνουμε υπ' όψη μας αυτό το ενδεχόμενο όταν χρησιμοποιούμε τα
λογικά συνδετικά μέσα σε λογικό έλεγχο και ταυτόχρονα ως συναρτήσεις που παράγουν
διαδικαστικά κάποιο αποτέλεσμα. Αν είναι επιθυμητή αυτή η δράση, μπορούμε να
εκμεταλλευτούμε αυτή τη δυνατότητα, αλλά αν είναι ανεπιθύμητη πρέπει να την αποφύγουμε.
Από λογική άποψη, το ενδιαφέρον (και η προσοχή κατά τη σύνταξη προγράμματος) εντοπίζεται
στο οτι με τη χρήση αυτή των λογικών συνδετικών μπορούμε να προκαλέσουμε υπολογιστικά
παράβαση της λογικής Boole. Αυτό, από τυπική άποψη, οφείλεται στην φορμαλιστική ανάμειξη
λογικών και υπολογιστικών βημάτων στο μοντέλο του λ-λογισμού, ανάμειξη που τα Μαθηματικά
Λογικά μοντέλα (Λογική Boole, Προτασιακή Λογική, Κατηγορηματική Λογική) δεν επιτρέπουν,
διαφοροποιώντας ρητά τα επίπεδα αξιωμάτων και νόμων παραγωγής.

β'. Δρόμοι έκφρασης που ανοίγονται από την εκτέλεση υπολογισμού στο υποθετικό
μέρος
Το οτι μια λογική σύνθεση είναι συνάρτηση, επιτρέπει να ενσωματώσουμε σ' αυτήν ενεργοποίηση
διαδικασιών (δηλ. εκτέλεση καθυστερημένων εφαρμογών κάτω από συνθήκες ελεγχόμενες από
την ίδια τη λογική συνάρτηση. Έτσι, εκτός από απλό έλεγχο αν έχει συμβεί ή όχι κάποιο γεγονός,
μπορούμε να έχουμε και την ίδια την πρόκληση γεγονότος. Δεδομένου οτι η ενεργοποίηση
διαδικασίας μπορεί να προκαλεί ενεργοποίηση άλλης, μπορούμε να έχουμε γεγονότα που
προκαλούνται από άλλα γεγονότα, και αυτό να γίνεται μέσα στο υποθετικό μέρος (βλέπε στα
επόμενα "triggers" και "events").
Ακόμα περισσότερο, το υποθετικό μέρος μπορεί να είναι ή να περιέχει ένα βρόχο, επαναληπτικό
ή αναδρομικό, ο οποίος να περνά από έλεγχο ή/και πρόκληση γεγονότων, και να εξέρχεται με
έλεγχο γεγονότος ανωτέρου επιπέδου.

122

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

3.3.1 Οι βασικές μαθηματικές συναρτήσεις
Στην ενότητα αυτή θα περιγράψουμε τις βασικές μαθηματικές συναρτήσεις. Στο Κεφάλαιο 9 θα
δούμε τεχνικές συναρτησιακής σύνθεσης με τις οποίες μπορούμε να αξιοποιήσουμε βαθύτερα
αυτές τις βασικές συναρτήσεις.
abs : Επιστρέφει την απόλυτη τιμή του ορίσματος (αριθμού).
(abs -3.0)
→ 3.0
(abs 4)
→ 4
(abs #c(3 4))

5.0
max , min : Επιστρέφουν: το μεγαλύτερο, αντίστ. μικρότερο, από τα ορίσματα (που οφείλουν να
είναι τύπου real). Μεταξύ ίσων, επιστρέφει το αριστρότερο.
(max 3 2.99 -4.0 3.0) → 3

round : Συνάρτηση ενός πραγματικού ορίσματος και δύο εξόδων. Επιστρέφει τον πλησιέστερο
ακέραιο, εκτός αν είναι ακριβώς στη μέση (0.5) οπότε επιστρέφει τον πλησιέστερο άρτιο, και τη
διαφορά (συνάρτηση με διπλή έξοδο).
(round -1.3)
→ -1
(round 3.2)
(round 3.5)

-0.3
→ 3
0.2
→ 4
-0.5

exp
: Η συνάρτηση "exponential". Επιστρέφει τη βάση των νεπερείων λογαρίθμων
υψωμένη στη δύναμη του ορίσματος - αριθμού.
(exp 3)
→ 20.0855369231877
(exp #c(3 4)) → #c(-13.1287830814622 -15.200784463068)
(exp 1)
→ 2.71828182845905
(είναι ο αριθμός e )
sqrt

: Επιστρέφει την τετραγωνική ρίζα του ορίσματος, με τύπο εξόδου τον τύπο τής εισόδου.
(sqrt 4)
→ 2
(sqrt 4.0)
→ 2.0
(sqrt 3)
→ 1.73205080756888
123

e

(sqrt -1)



#c(0 1)

log
: Επιστρέφει το φυσικό λογάριθμο του ορίσματος (αριθμού). H συνάρτηση LOG με ένα
όρισμα αναφέρεται σε βάση e ενώ με δύο ορίσματα υπολογίζει το λογάριθμο του πρώτου
ορίσματος στη βάση που ορίζει το δεύτερο όρισμα.
(log 2)
→ 0.693147180559945
(log 2 2)
→ 1.0
(log 2 10) →
0.301029995663981
(log (exp 1))
→ 1.0
sin , asin , cos , acos, tan , atan : Οι γνωστές τριγωνομετρικές και αντίστροφες
τριγωνομετρικές, ενός ορίσματος. Μονάδα είναι το rad, αλλά μπορεί να μεταβληθεί από
παράμετρο - ειδική μεταβλητή του compiler.
(cos pi)
→ -1.0
(sin #c(2 3)) → #c(9.15449914691143 -4.16890695996656)
(acos 2)
→ #c(3.95798995482559E-16 1.31695789692482)
sinh , cosh , tanh : Οι γνωστές υπερβολικές συναρτήσεις, ενός ορίσματος.
ceiling : Δύο εξόδων, ενός πραγματικού ορίσματος. Επιστρέφει το μικρότερο ακέραιο που
είναι μεγαλύτερος του ορίσματος, μαζί και το σφάλμα που προκαλείται (όρισμα μείον
αποτέλεσμα).
(ceiling 3.7)
→ 4

-0.3

Με δύο πραγματικά ορίσματα, εκτελεί τη διαίρεση του πρώτου με το δεύτερο και επιστρέφει το
μικρότερο ακέραιο που είναι μεγαλύτερος του πηλίκου, και το σφάλμα που προκαλείται.
floor
: Ενός αριθμητικού ορίσματος και Δύο εξόδων. Με ένα επιστρέφει το μεγαλύτερο
ακέραιο που είναι μικρότερος του ορίσματος, μαζί και το σφάλμα που προκαλείται (όρισμα μείον
αποτέλεσμα).
(floor 3.7)
→ 3
0.7

Με δύο πραγματικά ορίσματα, εκτελεί τη διαίρεση του πρώτου με το δεύτερο και επιστρέφει το
μεγαλύτερο ακέραιο που είναι μικρότερος του πηλίκου, και το σφάλμα που προκαλείται:
(floor 13.1 3) → 4
1.1

mod : Παρόμοια της FLOOR, αλλά δεν επιστρέφει τη δεύτερη τιμή (το σφάλμα).
gcd , lcm : Δέχονται οποιοδήποτε πλήθος ακεραίων ορισμάτων . GCD επιστρέφει το μέγιστο
κοινό διαιρέτη, αντίστ. LCM επιστρέφει το ελάχιστο κοινό πολλαπλάσιο.
(gcd 12 8 20) → 4
(lcm 12 8 20)
→ 120
expt

: Δέχεται δύο αριθμητικά ορίσματα, βάση και εκθέτη:
(expt 0.5 2)
→ 0.25
124

(expt 2 3)
→ 8
(expt 2.0 3)
→ 8.0
(expt #c(2 3) #c(4 5)) →
→ #C(-0.75304583674856 -0.986428788647745)

rem : (από το remainder). Δέχεται δύο ακέραια αριθμητικά ορίσματα, διαιρετέο και διαιρέτη,
και επιστρέφει το υπόλοιπο της ακέραιης διαίρεσης.
(rem 14 3) → 2

rationalize , rational : Μετατρέπουν το όρισμα - αριθμό σε ανηγμένο πηλίκο ακεραίων
(μετατροπή σε τύπο ratio). Η πρώτη είναι αυστηρότερη, δίνοντας απόλυτη ακρίβεια, ενώ η
δεύτερη βασίζεται στην εσωτερική αριθμητική του υπολογιστή για να δώσει δίνει απλούστερο
αποτέλεσμα, ενδεχομένως σχετικής ακρίβειας.
(rationalize 0.2) → 1/5
numerator , denominator
: Ενός ορίσματος, τύπου πηλίκου. Επιστρέφουν αριθμητή, αντιστ.
παρονομαστή του πηλίκου.
random
: Ενός ορίσματος, τύπου αριθμού. Επιστρέφει αριθμό, ίδιου (υπο-) τύπου με την
είσοδο, μεγαλύτερο ή ίσο του μηδενός και μικρότερο του ορίσματος.
(random 3)
→ 2
(τυχαία τιμή, μεταξύ των 0 , 1 ή 2 )
(random 0.9) → 0.738782554864883
(τυχαία τιμή, στο διάστημα [0, 0.9) )

incf : Ειδική συνάρτηση macro, δύο ορισμάτων. Αυξάνει την τιμή του πρώτου ορίσματος κατά
το δεύτερο, μόνιμα, θεωρώντας το πρώτο όρισμα ως οντότητα, χρησιμοποιώντας την SETF για
τη μεταβολή της οντότητας. Επομένως το πρώτο όρισμα οφείλει να είναι θέση που να μπορεί η
SETF να την μεταβάλει (σύμβολο-μεταβλητή ή φυσική θέση). Αυτό σημαίνει πως δεν μπορεί να
είναι λ-μεταβλητή, τιμή δέσμευσης λ-μεταβλητής, ή σταθερά.
Αύξηση της τιμής του συμβόλου - μεταβλητής vv :
(setq vv 3)
(incf vv 4) → 7
(print vv)
→ 7

Αλλά:

→ Error…
(διότι ο αριθμός 3 δεν αποτελεί φυσική θέση)

(incf 3 4)

Αύξηση της τιμής φυσικής θέσης:
(setf (get 'cat 'legs) 4)
; για τη συνάρτηση GET βλέπε παρακάτω
(get 'cat 'legs) → 3
(incf (get 'cat 'legs) -1)
(get 'cat 'legs) → 3

decf

: αντίστοιχη της INCF, μειώνει την ποσότητα - πρώτο όρισμα, κατά το δεύτερο.

Μιγαδικοί αριθμοί
#c(real-part imaginary-part) : Συναντήσαμε ήδη τη γραφή αυτή μιγαδικού. Είναι ένα ειδικό
125

διάνυσμα (που χαρακτηρίζεται από το
c πρόθεμα #c), και αποτελεί υπο-τύπο του number, δύο

αριθμητικών συντεταγμένων.
a
realpart : Επιστέφει το πραγματικό μέρος μιγαδικού (έξοδος τύπου float).
(realpart #c(2 -5.3))
→ 2.0
imagpart : Επιστέφει το φανταστικό μέρος μιγαδικού (έξοδος τύπου float).
(imagpart #c(2 -5.3))
→ -5.3
phase
: Επιστέφει το όρισμα του μιγαδικού (δηλ. τη γωνία με τον άξονα των πραγματικών,
σε rad ∙ έξοδος τύπου float).
conjugate : Επιστέφει τον συζυγή μιγαδικού.
Πράξεις μιγαδικών:
Εκτελούνται ως συνήθεις αριθμητικές, σύμφωνα με τους κανόνες της Άλγεβρας:
(* #c(2 3) #c(4 5)) → #c(-7 22)
(/ 1 #c(1 1))
→ #c(1/2 -1/2)

3.3.2 Πίνακες και διανύσματα
Πίνακας (array) είναι ένας τύπος που έχει "αριθμημένες θέσεις", όπου καταχωρούνται στοιχεία,
b
και το καθένα προσδιορίζεται από ένα
δείκτες. Ο τύπος array είναι υποτύπος του
a ή περισσότερους
sequence, όπως και ο τύπος list.
4
3

Όταν οι όροι του πίνακα δεν είναι σύνθετες δομές, χρησιμοποιούμε τον υποτύπο simple-array του
array. Ένα simple-array μπορεί να αποτελείται από ίδιου τύπου τιμές ή διαφόρων τύπων.

Ένας πίνακας έχει διαστάσεις, από μία μέχρι επτά (σύμφωνα με το πρότυπο της CL), που
προσδιορίζονται από συντελεστή μετά το πρόθεμα # και ακολουθεί ο χαρακτήρας Α . Σε κάθε
διάσταση περιέχεται το αυτό πλήθος στοιχείων του ίδιου επιπέδου.
#2Α( ('a 'b) ('c 'd) ('e 'f) )
; πίνακας δύο διαστάσεων, 32
Διάνυσμα (vector) είναι υποτύπος του array, που είναι υποτύπος της ακολουθίας (sequence). Ένα
διάνυσμα σημειώνεται με το πρόθεμα # και είναι ένας μονοδιάστατος πίνακας:
#(1 3 5 6 9)
; διάνυσμα
5 αριθμητικών στοιχείων

cons
b
#('Νίκος 'Μαρία 'Θανάσης)
; διάνυσμα 3 στοιχείων, συμβόλων
Οι τύποι των στοιχείων (συντεταγμένων) ενός πίνακα ή ενός διανύσματος μπορούν εν γένει να
αναμειγνύονται :
#( 1 'a nil 3.4 #c(3 5) )

; φόρμα τύπου simple-array, υποτύπου vector

Είναι δυνατό να προσδιορίζεται ειδικά υποτύπος, με συγκεκριμένου τύπου στοιχεία.
Διάνυσμα (vector) είναι ένα μονοδιάστατος πίνακας. Ο υποτύπος bit-vector του vector
χαρακτηρίζει ένα διάνυσμα με τιμές τύπου bit (0 ή 1).
Ένας πίνακας διαφέρει από μια λίστα (εκτός του οτι είναι άλλος υποτύπος του sequence) κατά το
126

οτι ο πίνακας έχει αριθμημένες θέσεις, οι διαστάσεις του προσδιορίζουν ίδια τάξη στοιχείων σε
κάθε διάσταση, και υπάρχει τρόπος να "δείξουμε" κατ' ευθείαν μια θέση (σε λίστα, δείχνουμε ένα
στοιχείο μετρώντας θέσεις από την αρχή, εξ αιτίας του οποίου είναι χρονοβόρο σε μια μεγάλη
λίστα να "δείξουμε την προηγούμενη θέση").
Παράδειγμα πίνακα
1. Διδιάστατος πίνακας (54 , με αρίθμηση από το 1), όπου η πρώτη διάσταση δίνει το πλήθος
από ν-άδες (5) και η δεύτερη διάσταση δίνει το πλήθος των στοιχείων κάθε ν-άδας (4):
#2Α( (1 1 1 1) (2 2 2 2) (3 3 3 3) (4 4 4 4) (5 5 5 5) )

; ο πίνακας αυτός αποτελείται από πέντε τετράδες

2. Τριδιάστατος πίνακας (εδώ 234, με αρίθμηση από το 1)
#3Α( ( (1 1 1 1) (2 2 2 2) (3 3 3 3) )
( (4 4 4 4) (5 5 5 5) (6 6 6 6) ) )

; ο πίνακας αυτός αποτελείται από δύο τριάδες τετράδων

Δημιουργία πίνακα

Ένας πίνακας μπορεί να δημιουργηθεί είτε κατ' ευθείαν, με απλή αναγραφή του με πρόθεμα # αν
πρόκειται για διάνυσμα, και πρόθεμα #nA αν πρόκειται για πίνακα n διαστάσεων (1≤n≤7), είτε
μέσω της συνάρτησης MAKE-ARRAY .
make-array : συνάρτηση δημιουργίας πίνακα. Δέχεται ένα υποχρεωτικό όρισμα, ακέραιο
θετικό αριθμό αν είναι διάνυσμα ή "quoted" λίστα αν είναι περισσοτέρων διαστάσεων, η οποία
καθορίζει τις διαστάσεις. Η αρίθμηση είναι από το 1 :
(make-array '(3 4))
; διδιάστατος πίνακας, 34
→ #2A((NIL NIL NIL NIL) (NIL NIL NIL NIL) (NIL NIL NIL NIL))
Η συνάρτηση make-array δέχεται προαιρετικά κλειδιά καθορισμού των στοιχείων: μεταξύ άλλων,
το κλειδί :initial-element δηλώνει οτι το όρισμα που ακολουθεί, καθορίζει με ποιο στοιχείο θα
"γεμίσει αρχικά" ο πίνακας, ενώ το κλειδί :initial-contents δηλώνει οτι το όρισμα - πίνακας που
ακολουθεί, καθορίζει την αρχική κατάσταση ολόκληρου του πίνακα:
(make-array '(3 4) :initial-element 'a)
→ #2A((Α Α Α Α) (Α Α Α Α) (Α Α Α Α))

Ονομασία πίνακα

Αν θέλουμε να ονομάσουμε πίνακα, δεν έχουμε παρά να χρησιμοποιήσουμε την SETQ (προσοχή
στο "quote" που προηγείται του # : δηλώνει "το array που ακολουθεί, να μην υπολογιστεί",
αντίθετα από το συνδυασμένο πρόθεμα #' που δηλώνει "ακολουθεί όνομα συνάρτησης"):
(setq myarray1 '#(1 2 3 4))
; απ' ευθείας αναγραφή του πίνακα
(setq myarray2 ; δημιουργία του πίνακα μέσω της MAKE-ARRAY
(make-array '(3 4) :initial-contents
'( (a0 a1 a2 a3)
(b0 b1 b2 b3)
(c0 c1 c2 c3) ) )

127



#2A( (A0 A1 A2 A3)
(B0 B1 B2 B3)
(C0 C1 C2 C3) ) )

Η ακόλουθη καταχώρηση, με "quoted" το περιεχόμενο:
(setq myarray3 '(make-array '(2 3 4) :initial-element 'a) )

προφανώς δεν δημιουργεί τον πίνακα, διότι δεν εκτελείται η MAKE-ARRAY. Το σύμβολο
myarray2 έχει περιεχόμενο την ίδια την έκφραση δημιουργίας του πίνακα, και όχι τον πίνακα:
myarray3
→ (make-array '( 2 3 4) :initial-element 'a)
Με διπλό υπολογισμό του, θα δημιουργήσει τη στιγμή εκείνη τον πίνακα:
(eval myarray3 )
→ #3A(((A A A A) (A A A A) (A A A A))
((A A A A) (A A A A) (A A A A)))

Έτσι μπορούμε να καθυστερήσουμε τη δημιουργία του πίνακα, έχοντας προσδιορίσει τον
κανόνα δημιουργίας του.

Τυποποίηση στοιχείων πίνακα
Προκαθορισμός του τύπου των στοιχείων πίνακα γίνεται:
α'. έμμεσα με το αρχικό περιεχόμενο,
β'. έμμεσα με το αρχικό στοιχείο, και
γ'. απ' ευθείας με το κλειδί :element-type και τον τύπο των στοιχείων:
(setq arr24 (make-array '(3 2 4) :element-type 'bit)

Προσπέλαση ορισμένης θέσης πίνακα
Μια θέση πίνακα μπορεί να προσπελαστεί με τη συνάρτηση AREF :

aref : Συνάρτηση προσπέλασης στοιχείων πίνακα. Δέχεται δύο ορίσματα, πρώτο το όνομα
πίνακα και δεύτερο τις συντεταγμένες συγκεκριμένης θέσης. Επιστρέφει το περιεχόμενο της
θέσης (προσοχή: η αρίθμηση στις συντεταγμένες ξεκινάηςαπό το 0).
(aref myarray1 3) → 2
(περιεχόμενο της 4 θέσης)
(aref myarray2 2 3) → C3
(περιεχόμενο της θέσης (2 3), δηλ. συντ/νων (3,4) )

Καταχώρηση τιμής σε ορισμένη θέση πίνακα
Η τιμή καταχωρείται σε θέση που προσπελαύνεται με την AREF , με τη γνωστή συνάρτηση
καταχώρησης σε "φυσική οντότητα", την SETF :
(setq myarr (make-array 5 :initial-element 1))
→ #(1 1 1 1 1)
(setf (aref myarr 2) 1000))
; σημαίνει: καταχώρησε 1000 στην τρίτη θέση του myarr

Γράψαμε τη θέση ως υπ' αρ. 2 διότι η αρίθμηση αρχίζει από το 0 .
128

Τώρα το myarr έχει διαμορφωθεί ως εξής:
myarray1 → #(1 1 1000 1)
Αντίστοιχα, για πίνακα περισσοτέρων διαστάσεων:
(setq my2arr (make-array '(3 4) :initial-element 1))
→ #2A((1 1 1 1) (1 1 1 1) (1 1 1 1))

"καταχώρησε στην θέση (2 3) του my2arr ως τιμή το σύμβολο cat "
(setf (aref my2arr 2 3) 'cat) → CAT
my2arr → #2A((1 1 1 1) (1 1 1 1) (1 1 1 CAT))
βλέπουμε οτι η καταχώρηση έγινε στη θέση (3,4)
Στα επόμενα (βλέπε "τυποποίηση"), θα δούμε πώς μπορούμε να δημιουργήσουμε υπο-τύπους
πινάκων / διανυσμάτων ειδικής μορφής ή/και τύπων τιμών των θέσεων.
Παράδειγμα
Πρόκειται για την πρώτη κίνηση στο σκάκι (a-σπρα, μ-αυρα / πυργος, ιππος, -αξιωματικος1/2,
βασιλιας, βασιλισσα, στρατιωτης, κενη-θεση)
(setq σκάκι (make-array '(8 8) :initial-contents
'( (μ-π μ-ι μ-α1 μ-β μ-λ μ-α2 μ-ι μ-π)
(μ-σ μ-σ μ-σ μ-σ μ-σ μ-σ μ-σ μ-σ)

ε
ε
ε
ε
ε
ε
ε)

ε
ε
ε
ε
ε
ε
ε)

ε
ε
ε
ε
ε
ε
ε)

ε
ε
ε
ε
ε
ε
ε)

ε
ε
ε
ε
ε
ε
ε)
(α-σ α-σ α-σ α-σ α-σ α-σ α-σ α-σ )
(α-π α-ι α-α1 α-β α-λ α-α2 α-ι α-π) ) )
(defun κίνηση-από-στο
(από-θέση-x από-θέση-y σε-θέση-x σε-θέση-y)

; εντόπισε το πιόνι

(setq πιόνι (aref σκάκι '(από-θέση-x από-θέση-y ))

; άδειασε την παλιά θέση

(setf (aref σκάκι '(από-θέση-x από-θέση-y )) 'ε ) )

; τοποθέτησέ το στη νέα θέση

(setf (aref σκάκι '(σε-θέση-x σε-θέση-y)) πιόνι)
(defun πρώτη-κίνηση-μαύρων ( )

; αν τα άσπρα έχουν παίξει 2 θέσεις το στρατιώτη της βασίλισσας,
; τότε τα μαύρα να παίξουν το αντίστοιχο,
; αλλιώς να κινήσουν το στρατιώτη του βασιλιά 2 θέσεις:

(cond
((aref σκάκι '(5 4)) 'ΜΣ)
(κίνηση-από-στο 1 4 3 4))
(t (κίνηση-από-στο 1 3 3 3)) ))

Μεταβολή διαστάσεων πίνακα∙ δυναμικοί πίνακες

Με βάση τα παραπάνω, ένας πίνακας προσδιορίζεται ως σταθερών διαστάσεων (στατικό array).
129

Είναι όμως δυνατό να προσδιοριστεί ως προσαρμοζόμενων διαστάσεων (δυναμικό array), με το
κλειδί :adjustable που το ακολουθεί τιμή Τ (αντίστοιχα NIL για σταθερών, που είναι η default
τιμή). Η μεταβολή γίνεται με τη συνάρτηση ADJUST-ARRAY , η οποία δέχεται και προαιρετικές
παραμέτρους με τα ίδια κλειδιά της MAKE-ARRAY.

Προσθήκη στοιχείων στον πίνακα νέων διαστάσεων μπορεί να γίνει με την AREF , αλλά αν
θέλουμε απλή συσσωρευτική επισύναψη σε διάνυσμα, χρησιμοποιούμε την VECTOR-PUSH .
Αντίστοιχα, διαγραφή στοιχείου γίνεται με την VECTOR-POP .

Η λειτουργία των VECTOR-PUSH και VECTOR-POP μοιάζει με των PUSH και POP , που θα
δούμε παρακάτω, αλλά δεν είναι ίδιες: αναφέρονται στην τρέχουσα θέση του pointer και όχι
στην αρχή.
Προσθήκη στοιχείου γίνεται μόνο σε διανύσματα με εγκαταστημένο "pointer", που γίνεται με το
κλειδί :fill-pointer (και βέβαια να είναι δηλωμένα ως :adjustable Τ ).

Τιμές για το :fill-pointer είναι:
– Τ , για να δηλώσουμε "όλο το μήκος του διανύσματος" (ο pointer πάει στο τέλος)
– αριθμός, από 0 έως το μήκος του διανύσματος (ο pointer πάει στη θέση που δηλώνουμε,
αρχίζοντας από το 0)
Η τιμή αυτή καθορίζει το πού θα γίνει η προσθήκη νέου στοιχείου σε διάνυσμα, με την VECTORPUSH , και επιστρέφει την αρχική θέση του δείκτη, ο οποίος μεταβάλλεται +1 μετά από κάθε
προσθήκη. Αντίστοιχα, με την VECTOR-POP γίνεται διαγραφή του στοιχείου της θέσης του
δείκτη, και η θέση του δείκτη μεταβάλλεται κατά –1 μετά από κάθε αφαίρεση.
Παράδειγμα
Να δοθεί δυναμικό διάνυσμα 5 θέσεων με αρχικές τιμές και με τον δείκτη στη δεύτερη θέση∙ να
επεκταθεί το διάνυσμα σε 8 θέσεις, όπου οι νέες να πάρουν μια δοσμένη αρχική τιμή.
(setq vecta
(make-array 5 :adjustable t :fill-pointer 2
:initial-contents '(1 2 3 4 5) ) )
→ #(1 2)

(δηλ. επέστρεψε το διάνυσμα μέχρι τη θέση του δείκτη, που είναι στο δεύτερο στοιχείο)
Επέκταση του διανύσματος με αρχική τιμή, δεν επηρεάζει τις υπάρχουσες θέσεις και το
περιεχόμενό τους:
(adjust-array vecta 8 :initial-element 'a)

#(1 2)
το αποτέλεσμα είναι το ίδιο με το προηγούμενο διότι ο δείκτης δεν άλλαξε θέση∙
(vector-push 77 vecta)
→ 2
το στοιχείο που υπήρχε στην επόμενη θέση από τον δείκτη, αντικαταστάθηκε με 77, αλλά αυτό
που επιστράφηκε είναι το στοιχείο του δείκτη∙
(adjust-array vecta 8 :fill-pointer 8)
→ #(1 2 77 4 5 a a a)

τώρα ο pointer έχει μετακινηθεί στο όγδοο στοιχείο, και γι' αυτό η επιστροφή έδωσε και τα οκτώ
στοιχεία.
130

Συναρτήσεις που χρησιμοποιούν δείκτη σε πίνακα
fill-pointer

: Για να πάρουμε την τρέχουσα θέση ενός δείκτη, χρησιμοποιούμε τη συνάρτηση
FILL-POINTER :
(fill-pointer vecta)
→ 8

array-dimensions :
Αν χρειαστούμε επιστροφή των διαστάσεων πίνακα, μπορούμε να
χρησιμοποιήσουμε (μεταξύ άλλων) τη συνάρτηση ARRAY-DIMENSIONS :
(array-dimensions arr24) → (3 2 4)
(το arr24 ορίστηκε προηγουμένως)

► Οι συναρτήσεις VECTOR-PUSH και VECTOR-POP είναι καταστροφικές και μόνιμες,
όπως ακριβώς και οι PUSH και POP , που θα δούμε παρακάτω. Το νόημα "καταστροφικής" και
"μόνιμης" συνάρτησης θα αναπτυχθεί διεξοδικότερα στις συνδέσεις.

Σύνοψη και παρατηρήσεις

Οι πίνακες και τα διανύσματα έχουν δομή ανάλογη της λίστας. Ο τύπος array είναι "αδέλφι" του
list . Και οι δύο τύποι array και list είναι "παιδιά" του τύπου sequence. Κατά συνέπεια, μπορούμε
να εφαρμόσουμε όλες τις συναρτήσεις επεξεργασίας του γονικού τύπου sequence σε αντικείμενα
τύπου array ή vector, αλλά όχι συναρτήσεις λίστας. O τύπος vector είναι παιδί του array, κατά
συνέπεια μπορούμε να εφαρμόσουμε σε αντικείμενο τύπου vector όλες τις συναρτήσεις
επεξεργασίας array ή sequence .
Οι πίνακες αποτελούν διαφορετική διακλάδωση τύπου από τις λίστες:
– για λόγους ταχύτητας, διότι η κίνηση μέσα σε ένα πίνακα γίνεται μέσω δείκτη, και είναι πολύ
ταχύτερη από την κίνηση σε λίστα
– για λόγους συμβατότητας με δομές δεδομένων εκφρασμένων με πίνακες
– για να μπορούμε να αναφερόμαστε σε στοιχεία πίνακα με τις συντεταγμένες τους
– για να προκαθορίζουμε μια φόρμα ως ν-διάστατο πίνακα (1≤ν≤7) σταθερού πλήθους θέσεων
(δυνατό να μεταβάλλονται οι τιμές τους)
– για να προκαθορίζουμε τις διαστάσεις της δομής πίνακα, αλλά να είναι δυνατό να
προκαλέσουμε μεταβολή των διαστάσεων
– για να προκαθορίσουμε τον τύπο των στοιχείων και να προκαλέσουμε μεταβολές των τύπων
κατά τον υπολογισμό
– για να χρησιμοποιήσουμε μια συγκεκριμένη μορφή πίνακα ως γενικότερο τύπο, για να
δημιουργήσουμε ειδικότερους υποτύπους
Δεν θα επεκταθούμε εδώ περισσότερο στο λογισμό πινάκων. Απλώς αναφέρουμε οτι:
– Όπως και σε κάθε άλλη γλώσσα, οι πίνακες παίζουν σημαντικό ρόλο στον αριθμητικό
υπολογισμό, και αναπτύσσονται ειδικοί αλγόριθμοι (πράξεις πινάκων, επίλυση διανυσματικών
εξισώσεων, κλπ).
– Οι πίνακες μπορούν να χρησιμοποιηθούν για τον υπολογισμό ομοιόμορφα ταξινομημένων
στοιχείων, όπως σε βάσεις δεδομένων.
– Σε σύγκριση με τη δομή της λίστας, η δομή του πίνακα έχει το μειονέκτημα να μην είναι
131

τόσο ευέλικτη, αλλά έχει το πλεονέκτημα του pointer με τον οποίο μπορούμε να δείξουμε απ'
ευθείας κάποια θέση, και όχι αναζητώντας την, μετρώντας θέσεις από τον πρώτο όρο (αυτό είναι
κρίσιμο σε μια μεγάλη λίστα όπου πχ. θέλουμε "το προηγούμενο στοιχείο").

3.4 Επεξεργασία λίστας

Η επεξεργασία λίστας μπορεί να θεωρηθεί ως ο κορμός της Lisp (εξ ου και το όνομά της: LISt
Processing). Το τί είναι ακριβώς μια λίστα στη Lisp, θα το δούμε στον τομέα που αναφέρονται τα
ζεύγη με τελεία (dotted pairs).
Από συντακτική άποψη, λίστα είναι μια σύνθετη οντότητα - φόρμα που εκφράζεται ως μια
διατεταγμένη ν-άδα οντοτήτων - φορμών της Lisp, που αποτελούν τα στοιχεία - όρους της λίστας.
Όπως είδαμε, μια λίστα περικλείεται από παρενθέσεις και οι όροι χωρίζονται με ένα ή
περισσότερα κενά διαστήματα.
Μια λίστα έχει κεφαλή, που είναι ο πρώτος όρος της, και ουρά, που είναι η λίστα των όρων πλην
του πρώτου.
Οι συναρτήσεις επεξεργασίας λίστας κατηγοριοποιούνται ως εξής:
– συναρτήσεις που υπεισέρχονται στο εσωτερικό λίστας: εντοπισμός, προσθήκη ή διαγραφή
στοιχείου, χαρακτηρισμός στοιχείου - φόρμας ως προς λίστα, επιλογή όρων λίστας βάσει
κριτηρίου∙ βάσει αυτών ορίζονται και συναρτήσεις σάρωσης λίστας ή δένδρου, καθώς και
συναρτήσεις μαζικής επεξεργασίας στοιχείων λίστας
– συναρτήσεις που κατασκευάζουν / αναδιαμορφώνουν λίστα, καθώς και συναρτήσεις που
συνενώνουν ή συσχετίζουν λίστες
– συναρτήσεις μόνιμης επέμβασης σε οντότητα λίστα
– συναρτήσεις που αντιμετωπίζουν τις λίστες ως σύνολα.

3.4.1 Συναρτήσεις που εισέρχονται στο εσωτερικό λίστας

car : Συνάρτηση ενός ορίσματος τύπου λίστας. Επιστρέφει την κεφαλή, δηλ. τον πρώτο όρο
της λίστας. Ως όνομα, προέρχεται από την πρώτη έκδοση της Lisp, από τα αρχικά της "code
address register", που είχε να κάνει με την τεχνική υλοποίησης της δομής της λίστας.

Παρ' όλο που διατίθεται η –κατ' όνομα πιο πετυχημένη– ισοδύναμη συνάρτηση FIRST,
παραμένει σε χρήση λόγω της αναντικατάστατης ευκολίας που παρέχουν οι συνθέσεις ονομάτων
όπως CADADADAR (βλέπε παρακάτω):
(car '((1 2 3) 3 (4 5)) )



(1 2 3)

nth : Συνάρτηση δύο ορισμάτων: θετικός ακέραιος και λίστα. Επιστρέφει το ν-οστό (πρώτο
όρισμα) στοιχείο της λίστας (δεύτερο όρισμα). Η αρίθμηση αρχίζει από το μηδέν. Όρισμα κάτω
από το 0 δίνει τον πρώτο όρο, και από το μήκος της λίστας και πάνω, δίνει NIL
132

(nth 3 '(1 2 4 8 16) )
(defun nth1 (n lis)
(nth (1- n) lis))
(nth1 3 '(1 2 4 8 16) )



8



4

first , second , … , tenth : Επιστρέφουν το πρώτο, δεύτερο… δέκατο στοιχείο της λίστας
(ευκολότερες στη χρήση από την NTH).

cdr : Συνάρτηση ενός ορίσματος, τύπου λίστας. Επιστρέφει την ουρά της λίστας που έχει ως
όρισμα:
(cdr '((1 2) 3 (3 4)) )
→ (3 (3 4))
nthcdr : Επιστρέφει τη ν-οστή ουρά της λίστας (δηλ. ουρά της ουράς της ουράς της ουράς … ν
φορές).
(nthcdr 2 '( (1 2) 3 (3 4)) ) → ((3 4))
list-length : Επιστρέφει το μήκος της λίστας (δηλ. το πλήθος των στοιχείων πρώτου επιπέδου).
Αν η λίστα είναι κυκλική (βλέπε παρακάτω), επιστρέφει NIL
last

: Επιστρέφει το τελευταίο στοιχείο της λίστας.

butlast : Επιστρέφει τη λίστα χωρίς το τελευταίο στοιχείο της.
(butlast '((1 2) 3 (3 4)) ) → ((1 2) 3)
rest : Συνώνυμο του CDR

Cxx…xR , όπου x = A ή D, μέχρις 7 φορές (πχ. CADADDDAR). Οι συναρτήσεις αυτές
διευκολύνουν σε περιπτώσεις όπου υπεισέρχεται πολλαπλή χρήση των CAR , CDR σε
επεξεργασία λίστας πολλών επιπέδων. Η ανάγνωσή τους γίνεται εύκολα με “γενική κτητική” από
το δεύτερο όρο - γράμμα και μετά. Για παράδειγμα:
caar

: κεφαλή της κεφαλής,

caaar

: κεφαλή της κεφαλής της κεφαλής,

cddr

: ουρά της ουράς,

cαdr

: κεφαλή ης ουράς,

cdar

: ουρά της κεφαλής:
(cdar '( (a b c d) (b) (1 3) 'kostas ) )

→ (b c d)

Βάσει αυτών μπορούμε να συνθέσουμε οποιουσδήποτε άλλους συνδυασμούς, πχ:
(defun cadadadadadadadr (lis) (cadadadar (cdadadadr lis)))

3.4.2 Συναρτήσεις κατασκευής λίστας
list
: Mε ελεύθερο πλήθος ορισμάτων οποιουδήποτε τύπου, ακόμα και διαφορετικών τύπων.
Eπιστρέφει τη λίστα που σχηματίζεται από τα ορίσματα.
133

(list 1 2 4 'a (b c))



(1 2 4 'a (b c))

Η LIST μπορεί να οριστεί εύκολα μέσω αναδρομής και της CONS . Παρ' όλα αυτά, ορίζεται ως
primitive της γλώσσας διότι η συνένωση στοιχείων σε λίστα είναι θεμελιακή λειτουργία.
Παράδειγμα
Να οριστεί συνάρτηση που να διαβάζει τρεις όρους: όνομα μικρό, επίθετο, όνομα πατρός, και να
επιστρέφει τη λίστα:
(<όνομα> <επίθετο> του <ον.πατρός>)
(defun read-and-list ( ) (list (read) (read) 'του (read)) )
(read-and-list)
→ <κατάσταση εισόδου, δίνουμε τρεις όρους:>
Γιάννης <space> Ιωάννου <space> Γεωργίου <enter>
→ (Γιάννης Ιωάννου του Γεωργίου)

cons
: Δύο ορισμάτων: όρος και λίστα. Ενσωματώνει τον όρο ως πρώτο στοιχείο στη λίστα
(είτε υπάρχει ήδη είτε όχι). Επιστρέφει τη λίστα - αποτέλεσμα. Είναι η θεμελιακή δομική
συνάρτηση και θα επανέλθουμε.
(cons 5 '(1 2 4 6) )
→ (5 1 2 4 6)
(setq list1 '(a b c) )
(cons 'd list1)

list1 →
(a b c)

(d a b c)

(η δράση της cons δεν είναι μόνιμη)

append : Δύο ή περισσοτέρων ορισμάτων: όλα τύπου λίστας. Συνενώνει τις λίστες - ορίσματα
και επιστρέφει μία λίστα (η συνένωση γίνεται με απλή παράθεση των στοιχείων πρώτου
επιπέδου, χωρίς να επιπεδοποιεί το αποτέλεσμα).
(append '(1 3 5 7) '(2 4 6) )
→ (1 3 5 7 2 4 6)
(append '(1 (2 3) (4 5 7)) '(2 4 6) )
→ (1 (2 3) (4 5 7) 2 4 6)
(setq list2 '(e f g h))
(append list1 list2)

(a b c e f g h)
(setq list3 (append list1 list1 list2))

(a b c a b c e f g h)

adjoin : Δύο ορισμάτων, όπου το δεύτερο είναι λίστα. Λειτουργεί πανομοιότυπα με την CONS
αν το πρώτο όρισμα δεν ανήκει ήδη στη λίστα∙ αν ανήκει, επιστρέφει απλώς τη λίστα - δεύτερο
όρισμα.
(adjoin 3 '(1 2 4) ) → (3 1 2 4)
(adjoin 2 '(1 (2) 4) ) →
(2 1 (2) 4)
(adjoin 2 '(1 2 4) ) →
(1 2 4)
(setq term1 'w)
(adjoin term1 list1)

(w a b c)
list1

( a b c)
(η δράση της adjoin δεν είναι μόνιμη)

make-list : Ενός ορίσματος (φυσικός αριθμός). Κατασκευάζει φόρμα - λίστα ορισμένου
134

μήκους, όσο είναι η τιμή του ορίσματος. Στην κατασκευαζόμενη λίστα τα στοιχεία είναι όλα NIL
(makelist 5)
→ (nil nil nil nil nil)

Προαιρετικά δέχεται και προσδιορισμό του αρχικού περιεχομένου της λίστας, με ένα "στοιχείο”
που δίνεται ως δεύτερο όρισμα, μετά το χαρακτηρισμό-κλειδί :initial-element , και το οποίο θα
καταλάβει όλες τις θέσεις. Η περίπτωση αυτή είναι χρήσιμη για τον προκαθορισμό του τύπου των
στοιχείων, ώστε να γίνονται δεκτά από τη συνάρτηση που πρόκειται να τα επεξεργαστεί:
(make-list 4 :initial-element 0) → (0 0 0 0)
Χρησιμεύει για τη δημιουργία αρχικής κατάστασης σε μια ανακύκλωση η οποία εξελίσσει μια
λίστα σταθερού μήκους. Επίσης, για την επαναφορά οντότητας με τιμή λίστα, στην αρχική της
κατάσταση.
Παράδειγμα
Να γραφεί συνάρτηση που να αντικαθιστά μόνιμα ένα στοιχείο λίστας αριθμών με το γινόμενο της
τιμής που είχε, με ένα αριθμό που δίνεται.
Χρησιμοποιούμε την SETF :

(setq new-list (make-list 5 :initial-element 1)) → (1 1 1 1 1)
(defun replace-nth-element (n lis factor)
(setf (nth (1- n) lis) (* (nth (1- n) lis) factor)) lis)

Εφαρμογή:

(replace-nth-element 3 new-list 5)
(replace-nth-element 3 new-list 5)

→ (1 1 5 1 1)
→ (1 1 25 1 1)

copy-list : Ενός ορίσματος, τύπου λίστας. Κατασκευάζει λίστα - αντίγραφο του ορίσματος. Είναι
η απλούστερη συνάρτηση αντιγραφής λίστας, διότι αντιγράφει μόνο το λεγόμενο “άνω επίπεδο”
(top level) της λίστας, δηλ. αντιγράφει τους όρους της λίστας (σε όλο τους το βάθος, αν είναι και
αυτοί λίστες κοκ), χωρίς να καταχωρεί και τις (τυχόν) συνδέσεις τους προς άλλα στοιχεία.
Το αντίγραφο δεν παρακολουθεί αλλαγές του πρωτότυπου, και οι επεμβάσεις στο αντίγραφο δεν
επηρεάζουν το πρωτότυπο. Με άλλα λόγια, η αντιγραφή με την COPY-LIST δεν είναι διαρκής.
Χρησιμοποιείται ως ασφαλής τρόπος χρήσης λίστας η οποία δεν θέλουμε να καταστραφεί.
(setq listn1 '(2 4 6 8) )
→ (2 4 6 8)
(setq listn2 (copy-list listn1) ) → (2 4 6 8)
(setq listn2 '(0 0 0 0) )
→ (0 0 0 0)
listn1
→ (2 4 6 8)

Διευκρινίζουμε οτι διαφορετικής και πιο πολύπλοκης λειτουργικότητας είναι η COPY-ALIST που
θα δούμε στα επόμενα.
remove
: Δύο παραμέτρων: φόρμα και λίστα. Επιστέφει τη λίστα χωρίς το στοιχείο (για όλες
τις τυχόν επαναλαμβανόμενες εμφανίσεις, αλλά μόνο από το πρώτο επίπεδο των στοιχείων της
λίστας).
(remove 3 '(1 2 3 1 2 3))
→ (1 2 1 2)
reverse

: Μιας παραμέτρου, τύπου λίστας. Επιστρέφει τη λίστα ανεστραμμένη.
(reverse '(1 2 3 4 5)) → (5 4 3 2 1)
135

(setq list1 (a b c d))
(reverse list1)
list1

→ (a b c d)
→ (d c b a)
→ (a b c d)

O παραπάνω συναρτήσεις δρουν πάνω σε αντίγραφο της οντότητας - λίστας που δίνεται ως
είσοδος και η λειτουργικότητά τους σε καμμία περίπτωση δεν καταστρέφει το πρωτότυπο.

3.4.3 Συναρτήσεις μόνιμης αναδιαμόρφωσης λίστας
Οι συναρτήσεις αυτής της κατηγορίας έχουν την ιδιομορφία, επιπρόσθετα από τη λειτουργία τους
ως συναρτήσεις, να μεταβάλλουν μόνιμα μια η περισσότερες από τις οντότητες στις οποίες
εφαρμόζονται, και γι' αυτό αποκαλούνται καταστροφικές.

Η εφαρμογή τους χρειάζεται ιδιαίτερη προσοχή, διότι προκαλούν μεταβολή της υφιστάμενης
κατάστασης του προγράμματος. Τέτοια δράση μπορούν να προκαλέσουν η SETF και οι macro
συναρτήσεις, αλλά όχι οι κοινές συναρτήσεις.
Εκτός των αναφερομένων εδώ, η CL διαθέτει μια σειρά καταστροφικών συναρτήσεων.

pop : Ενός ορίσματος, τύπου λίστας. Αποκεφαλίζει τη λίστα και επιστρέφει το πρώτο στοιχείο,
όπως η CAR , αλλά η λίστα - είσοδος παίρνει πλέον την τιμή τής εξόδου. H δράση της POP
πάνω στη λίστα είναι μόνιμη, δηλ. μεταβάλλει οριστικά την είσοδο – λίστα, αντικαθιστώντας την
με το σώμα της.
(setq mylist10 '(a b c d)) → (a b c d)
(pop mylist10)
→ (b c d)
mylist10
→ (b c d)

push : Δύο ορισμάτων, όπου το πρώτο είναι οποιαδήποτε φόρμα και το δεύτερο είναι λίστα.
Επισυνάπτει το πρώτο όρισμα ως πρώτο στοιχείο στη λίστα (όπως η CONS) αλλά το δεύτερο
όρισμα (η λίστα) παίρνει οριστικά την τιμή της εξόδου.
H δράση της PUSH πάνω στη λίστα, εκτός από μόνιμη, είναι και διαρκής, δηλ. κάθε μετέπειτα
αλλαγή στο πρώτο όρισμα αφορά και το δεύτερο όρισμα – λίστα, που πλέον το περιέχει.
(setq lis '(1 2 4 6) )
(push 3 lis)
→ (3 1 2 4 6)
lis
→ (3 1 2 4 6)
(setq my ’(a b c))
(push my lis) → ((a b c) 3 1 2 4 6)
(setq my 'd)
lis
→ (d 3 1 2 4 6)

rplaca : (ονομασία από το “replace car”). Δύο ορισμάτων, όπου το πρώτο είναι λίστα και το
δεύτερο οποιοσδήποτε όρος. Αντικαθιστά την κεφαλή τής λίστας με το δεύτερο όρισμα. Η λίστα είσοδος παίρνει την τιμή του αποτελέσματος. Η δράση της RPLACA πάνω στη λίστα είναι
μόνιμη και διαρκής.
(setq listaa '(1 2 3 4) ) → (1 2 3 4)
(rplaca listaa 3)
→ (3 2 3 4)
136

listaa
(setq a 5)
(rplaca listaa a)
(setq a 7)
listaa

→ (3 2 3 4)
→ (5 2 3 4))
→ (7 2 3 4)

Η συνάρτηση RPLACA θέτει ένα δείκτη, από το δεύτερο όρισμα προς τη θέση της κεφαλής του
πρώτου. Αν συμβεί τα δύο ορίσματα να συνδέονται και αλλιώς, η αντιμετώπιση εξαρτάται από
τον compiler.
(setq a '(1 2 3))
(length a)
→ 3
(rplaca a a)
→ ((((# 2 3) 2 3) 2 3) 2 3)
a
→ ((((# 2 3) 2 3) 2 3) 2 3)

Η συμβολική αυτή μορφή εξόδου δίνεται από την Αllegro CL v.3 . Το μήκος της λίστας αυτής
παραμένει το ίδιο (αν και σχετικά με αυτό, η συμπεριφορά των compilers διαφέρει):
(length a)
→ 3
Προσοχή: Η συνάρτηση RPLACA "παίζει" με φυσικές θέσεις, όπως θα δούμε σε επόμενη
ενότητα, και είναι αρκετά επικίνδυνη όταν εφαρμόζεται σε σημασιολογικά επίπεδα.
rplacd : Δύο ορισμάτων, λίστες. Αντικαθιστά το σώμα τής πρώτης λίστας με τη δεύτερη
(“replace cdr”). Η λίστα - πρώτη είσοδος παίρνει την τιμή του αποτελέσματος. Η δράση της
RPLACD πάνω στη λίστα, είναι επίσης μόνιμη και διαρκής.
(setq listb '(5 6 7 8) ) → (5 6 7 8)
(setq listc '(1 2 3) )
→ (1 2 3)
(rplacd listb listc)
→ (5 1 2 3)
listb
→ (5 1 2 3)
(setq listc '(8 9) )
listb

→ (5 8 9)

Για την ακρίβεια, οι τρεις τελευταίες συναρτήσεις δημιουργούν μόνιμες συνδέσεις, μέσω της
CONS και της SETF, που μεταφέρουν αυτόματα στο αποδεχόμενο αντικείμενο κάθε αλλαγή που
επέρχεται στο συνδεόμενο αντικείμενο. Όπως και με την προηγούμενη συνάρτηση, και με την
RPLACD αν τα δύο ορίσματα συνδέονται, προκύπτουν ιδιάζουσες καταστάσεις (θα επανέλθουμε
στο θέμα αυτό):
(setq a '(1 2 3))
(length a)
→ 3
(rplacd a a)
→ (1 1 1 1 1 1 1 1 ...)
(length a)
→ Error …

(σφάλμα, μήνυμα οτι η λίστα έχει άπειρους όρους, ή μη τερματισμός)

Προσοχή: ισχύει ίδια παρατήρηση με την προηγούμενη.

replace : Δύο ορισμάτων, λίστες. Αντικαθιστά μόνιμα την πρώτη με τη δεύτερη. Δεν είναι
διαρκής (δηλ. μετέπειτα μεταβολή στο πρωτότυπο του δεύτερου ορίσματος δεν επηρεάζει το
πρωτότυπο του πρώτου ορίσματος) .
(setq a '(1 2 3) b '(4 5 6))

137

(replace 'a 'b)
→ (4 5 6)
a
→ (4 5 6)
(setq b '(7 8 9 0))
a
→ (4 5 6)

sort : Συνάρτηση δύο ορισμάτων. Το πρώτο είναι η λίστα που θα διαταχθεί, και το δεύτερο είναι
η συνάρτηση σύγκρισης. Δίνει έξοδο τη λίστα διατεταγμένη. Είναι καταστροφική με ένα
"περίεργο" τρόπο:
(setq sl '(3 1 5 32 5 ) )
(sort sl #'< ) → (1 3 5 5 32)
sl
→ (3 5 5 32)

Παρατηρούμε οτι το πρώτο στοιχείο χάθηκε !!! (η έξοδος είναι υπολογισμένη στην Allegro CL
v.3). Πρόκειται λοιπόν για μια στην κυριολεξία καταστροφική συνάρτηση, αφού η επέμβαση στο
πρώτο όρισμα δεν έχει κανένα νόημα σχετικό με την εκτελούμενη δράση.
Όπως ήταν αναμενόμενο, κατά την παραπάνω διάταξη, τα δύο ίσα στοιχεία τοποθετούνται σε
διαδοχικές θέσεις, όπου προφανώς είναι αδιάφορο "ποιο 5 από τα δύο" τοποθετήθηκε πρώτο. Το
ίδιο θα συμβεί για οποιαδήποτε στοιχεία που κρίνονται ίσα με την EQUALP. Όμως αν τα
συγκρινόμενα στοιχεία διαφοροποιούνται σε άλλο επίπεδο ισότητας και παίζει ρόλο η θέση τους,
αντί για την SORT χρησιμοποιούμε την ακόλουθη:
stable-sort : Παρόμοια με την προηγούμενη, αλλά κρατά την υφισταμένη διάταξη των ίσων
στοιχείων, ταυτοποιώντας "ποιο είναι ποιο" σε χαμηλότερο επίπεδο. Είναι πιο χρονοβόρα της
SORT .
nconc : Συνάρτηση δύο ή περισσοτέρων ορισμάτων τύπου λίστας. Προκαλεί συνένωση των
λιστών. Η τιμή από κάθε όρισμα - λίστα αντικαθίσταται μόνιμα αλλά όχι διαρκώς, από τη
συνένωση με τα επόμενα, ως λίστες. Tο αποτέλεσμα της NCONC είναι η τελική τιμή στην οποία
διαμορφώνεται το πρώτο όρισμα. Το τελευταίο όρισμα μένει αναλλοίωτο.
(setq l1 '(a
(nconc l1 l2
l1

l2

l3

(setq l2 '(1
l1


b c) l2 '(d e f) l3
l3)
→ (a b c d
(a b c d e f g h i
(d e f g h i j)
(g h i j)
2 3 4))
(a b c d e f g h i

(g h i j))
e f g h i j)
j)
(η δράση είναι μόνιμη)

»
»
(η δράση είναι μόνιμη)

j)

(η δράση δεν είναι διαρκής)

Καταστροφικές συναρτήσεις και δεσμεύσεις λ-μεταβλητών
Η "καταστροφική" δράση συναρτήσεων δεν μπορεί να περάσει μέσα από την εκτέλεση κοινών
συναρτήσεων οι οποίες καλούν τέτοιες συναρτήσεις μέσω των λ-μεταβλητών τους, διότι η
δέσμευση λ-μεταβλητής γίνεται πάνω σε αντίγραφο της οντότητας που προσδιορίζει το όρισμα.
Πχ:
(setq d-list1 nil d-list2 nil)
(push 3 d-list1) → (3)
(push 5 d-list1) → (5 3)
d-list1
→ (5 3)

138

Μέχρι εδώ, όλα είναι σύμφωνα με την αναμενόμενη (καταστροφική) δράση της PUSH. Αλλά,
όπως είδαμε στις macro συναρτήσεις, η δράση αυτή δεν "αντιγράφεται" μέσω ορισμού κοινής
συνάρτησης:
(defun another-push (elementx listy) (push elementx listy))
(setq d-list2 nil)
(another-push 3 d-list2)
→ '(3)
(another-push 5 d-list2)
→ '(5)
d-list2
→ NIL

3.4.4 Απόδοση της έννοιας "σύνολο" μέσω λίστας
Σύνολα, με τη μαθηματική τους έννοια, δεν έχουμε στη Lisp. Αλλά οι λίστες μπορούν να
αποδώσουν με αρκετή ευχέρεια το νόημα του συνόλου, αν εξασφαλιστεί οτι : i) η λίστα που
παριστά σύνολο δεν περιέχει πολλαπλά στοιχεία, ii) το σύνολο είναι πεπερασμένο, iii) δεν
συνδέουμε την υπόσταση του συνόλου με τη διάταξη των στοιχείων που δίνονται στη λίστα iv) τα
στοιχεία της λίστας δεν έχουν εσωτερικότερες συνδέσεις μεταξύ τους.
Η περιγραφή συνόλου μπορεί να αποδοθεί με λίστα είτε μέσω του "ανήκει στη λίστα" είτε μέσω
του συνόλου των αναδιατάξεων της λίστας. Οι συνολοθεωρητικές πράξεις, από τη Μαθηματική
τους υπόσταση είναι συναρτήσεις, άρα η έκφρασή τους με συναρτήσεις επεξεργασίας λίστας δεν
μεταβάλλει το νόημά τους (όπως συμβαίνει με τις λογικές σχέσεις).

Συνολοθεωρητικές πράξεις
Διατίθενται οι βασικές συναρτήσεις επεξεργασίας συνόλων, που θα μπορούσαν να οριστούν πολύ
εύκολα από το χρήστη:

union : Δέχεται δύο ορίσματα - λίστες, που αν συμβεί να παριστούν σύνολα επιστρέφει την
ένωσή τους, δηλ. επιστρέφει όλα τα στοιχεία τους μαζί σε μια λίστα.
Προσοχή: Τα τυχόν επαναλαμβανόμενα στοιχεία στην ίδια λίστα εκλαμβάνονται ως διαφορετικά
από τη Lisp, και επιστρέφονται επίσης επαναλαμβανόμενα στην ένωση. Κοινά στοιχεία μεταξύ
των δύο μελών επιστρέφονται στην ένωση μόνο μια φορά.

Η UNION διαφέρει από την APPEND σε δύο χαρακτηριστικά: i) η APPEND επιστρέφει τη
συνένωση των λιστών, αδιάφορα κοινών στοιχείων, και ii) η APPEND δίνει τα στοιχεία στην ίδια
διάταξη ενώ η UNION παίρνει πρώτα τη δεύτερη λίστα, την αναστρέφει, και μετά την ενώνει με
την πρώτη λίστα.
(setq a '(1 2 3) b '(4 5 6 )
(union a b)
→ (6 5 4
(append a b)
→ (1 2 3
(union a c)
→ (5 4 1
(append a c)
→ (1 2 3

c '(1 2 4 5) )
1 2 3)
4 5 6)
2 3)
1 2 4 5)

intersection : Δέχεται δύο ορίσματα - λίστες, και επιστρέφει τη λίστα από όλα τα κοινά
139

στοιχεία τους και μόνον αυτά. Αν συμβεί να είναι σύνολα, επιστρέφει την τομή τους. Iσχύει η ίδια
παρατήρηση με την UNION.
set-difference : Δέχεται δύο ορίσματα - λίστες, Επιστρέφει τη λίστα από όλα τα στοιχεία της
πρώτης λίστας (πρώτο όρισμα) τα οποία δεν είναι στη δεύτερη. Αν συμβεί οι είσοδοι να είναι
σύνολα, επιστρέφει τη συνολοδιαφορά τους. Iσχύει η ίδια παρατήρηση με την UNION.
subsetp : Δύο ορισμάτων – λίστες. Ελέγχει αν το πρώτο είναι υποσύνολο του δεύτερου (ως
σύνολα, δηλ. με οποιαδήποτε διάταξη, και όχι απλά ως διατεταγμένες ν-άδες)
(subsetp '(a b c) '(b a d a c)) → Τ
Ισότητα συνόλων μπορούμε να ορίσουμε μέσω της SUBSETP :
(defun equal-sets (set1 set2)
(and (subsetp set1 set2) (subsetp set2 set1)) )

Συναρτήσεις κατασκευής συνόλου
remove-duplicates : Συνάρτηση ενός ορίσματος - λίστας, που επιστρέφει τη λίστα χωρίς
επαναλαμβανόμενα στοιχεία. Δεν επιδρά στο πρωτότυπο του ορίσματος:
(remove-duplicates '(1 1 2 3 2)) → (1 2 3)
(setq a '(1 2 3 1 2 3))
(remove-duplicates a) → (1 2 3)
a → (1 2 3 1 2 3)

delete-duplicates : Παρόμοια με την REMOVE-DUPLICATES αλλά διαγράφει οριστικά τις
επαναλήψεις από τη λίστα – πηγή (δηλ. είναι καταστροφική συνάρτηση):
(setq lis '(a b c a b b))
(delete-duplicates lis)
→ (a b)
lis
→ (a b)

push-new : Δύο παραμέτρων, στοιχείο και λίστα. Παρόμοια με την PUSH αλλά εισάγει ως
κεφαλή το στοιχείο στη λίστα μόνον αν δεν ανήκει ήδη (καταστροφική και διαρκής).
(setq set1 '(1 2 3 4) set2 '(2
(push-new 3 set1) → (1 2 3
(push-new 3 set2) → (3 2 4
set2
→ (3 2 4

4 6) )
4)
6)
6)

3.5 Επεξεργασία συμβολοσειρών και γενικά ακολουθιών
Ο τύπος string (συμβολοσειρά) είναι υποτύπος του sequence (ακολουθία), άρα πάνω σε
συμβολοσειρές μπορούμε να εφαρμόσουμε τις συναρτήσεις επεξεργασίας ακολουθίας. Οι
συναρτήσεις αυτές προφανώς εφαρμόζονται και σε λίστες, διότι ο τύπος list επίσης είναι
υποτύπος του sequence. Πολλές συναρτήσεις είναι κοινές μεταξύ συμβολοσειρών και λιστών,
προφανώς διότι ουσιαστικά είναι συναρτήσεις επεξεργασίας ακολουθίας.
140

Συνένωση ακολουθιών
concatenate : Συνενώνει συμβολοσειρές, και γενικότερα φόρμες τύπου ακολουθίας (sequence).
Πρώτο όρισμα είναι ο τύπος της εξόδου (που οφείλει να ταιριάζει). Επόμενα είναι οποιοδήποτε
πλήθος από ακολουθίες.
Παραδείγματα
(concatenate 'string " το " "λεωφορείο " "είναι " "γεμάτο ")
→ " το λεωφορείο είναι γεμάτο "
(concatenate 'string '(#\η ) " " '(#\γ #\ά #\τ #\α) " πεινάει")
→ "η γάτα πεινάει"
(concatenate 'list '(a b c) '(d e f g))
→ (a b c d e f g)
(setf
(get 'person1 'name) 'Τσάμης
; για τη συνάρτηση GET βλέπε παρακάτω
(get 'person1 'sex) 'άρρην
(get 'person1 'occupation) 'διευθυντής
(get 'person1 'faml-sit) 'παντρεμένος )
(defun nominative (x)
(cond
( (eql (get x 'sex) 'άρρην) "ο κ. ")
( (and (eql (get x 'sex) 'θήλυ)
(eql (get x 'faml-sit) 'παντρεμένη)) "η κα ")
( (eql (get x 'sex) 'θήλυ) "η δις ")
Εφαρμογή:
(concatenate 'string
(nominative (get 'person1 'name) )
(get 'person1 'name) "είναι "
(get 'person1 'occupation) " και "
(get 'person1 'faml-sit) )
→ "o κ. Τσάμης είναι διευθυντής και παντρεμένος"

Μήκος, αντιστροφή, διάταξη ακολουθίας
Το μήκος δίνεται από τη γνωστή από τις λίστες LENGTH :
(length "το πράσινο μήλο είναι ξυνό")
→ 26
Η αντίστροφη κατά τη διάταξη ακολουθία, δίνεται από τη γνωστή από τις λίστες REVERSE:
(reverse "ΝΙΨΟΝΑΝΟΜΗΜΑΤΑΜΗΜΟΝΑΝΟΨΙΝ")
→ "ΝΙΨΟΝΑΝΟΜΗΜΑΤΑΜΗΜΟΝΑΝΟΨΙΝ"

Προκαλούμε διάταξη ακολουθίας με τις γνωστές από τις λίστες SORT και STABLE-SORT . Το
κριτήριο διάταξης παίζει ουσιαστικό ρόλο:

141

Διάταξη συμβολοσειράς:
(sort "απόψε τα μεσάνυχτα" #'char-lessp)
→ " άαααεεμνπσττυχψό"

Διάταξη λίστας συμβολοσειρών:
(sort '("Κώστας" "Μαρία" "Αννα") #'string-lessp)
→ ("Αννα" "Κώστας" "Μαρία")

Αναζήτηση και εντοπισμός ακολουθίας μέσα σε άλλη

search: Συνάρτηση δύο ορισμάτων, ακολουθιών. Ελέγχει αν το πρώτο είναι συνεχόμενο τμήμα
μέσα στο δεύτερο, και επιστρέφει τη θέση απ' όπου το πρώτο αρχίζει μέσα στο δεύτερο, με
μηδενική θέση το πρώτο στοιχείο (αν δεν βρεθεί, επιστρέφει NIL). Δέχεται και προαιρετική
παράμετρο, με κλειδί :test το οποίο ακολουθεί συνάρτηση - κριτήριο ισότητας:
(search "Γιάννης" "ο συνάδελφος ο Γιάννης έχει δύο παιδιά")
→ 15
(search "Συνάδελφος" "Ο συνάδελφος ο Γιάννης έχει δύο παιδιά")
→ NIL

Στο δεύτερο, το ψάξιμο απέτυχε λόγω του κεφαλαίου γράμματος. Αλλά μπορούμε να
προσδιορίσουμε το ψάξιμο με κριτήριο ισότητας τη συνάρτηση char-equal που δεν διαφοροποιεί
πεζά από κεφαλαία (case insensitive) (προσοχή: ενδέχεται να μη λειτουργεί άψογα με ελληνικούς
χαρακτήρες):
(search "Συνάδελφος"
"Ο συνάδελφος ο Γιάννης έχει δύο παιδιά" :test #string-equal)
→ T
(search "cat" "Dogs hate Cats" :test #'char-equal)
→ 10
(διότι το c είναι στην ενδέκατη θέση)

Στοιχεία ακολουθίας

Παίρνουμε το ν-οστό όρο ακολουθίας, με τη συνάρτηση ELT (από το "element"):
elt
: Συνάρτηση δύο ορισμάτων, ακολουθία και ακέραιο από 0 έως το μήκος της ακολουθίας.
Επιστρέφει τον αντίστοιχης τάξης όρο, με μηδενικής τάξης τον πρώτο όρο.
(elt "abc") 0) → #\a
(elt "abc") 2) → #\c
(elt "abc") 10)
→ Error …
(γενικά, η επιστροφή είναι εξαρτημένη από τον compiler)
(elt '(a b c) 1) → b
Προσοχή: H συνάρτηση ELT είναι χαμηλότερη της NTH . Αν εφαρμοστεί σε οντότητα που
προσδιορίζεται ως φυσική θέση, επιστρέφει αντικείμενο μέσα στη φυσική θέση, και μπορούμε να
το αλλάξουμε με την SETF .

142

Όνομα συμβόλου ως ακολουθία χαρακτήρων

Μια πολύ χρήσιμη εφαρμογή της ELT είναι στην περίπτωση που θέλουμε να κινηθούμε στους
χαρακτήρες συμβόλου ή στα ψηφία αριθμού. Επειδή η ELT βλέπει μόνο ακολουθίες, πρέπει να
μετατρέψουμε το όνομα του συμβόλου σε ακολουθία, και ένας τρόπος είναι με τη συνάρτηση
STRING . Έμμεσα, μπορούμε να μπούμε σε αριθμό, θέτοντάς του πρόθεμα "quote" (να
θυμόμαστε πως στα σύμβολα, πεζά και κεφαλαία ισοδυναμούν):
(setq my-symbol 'Parthenon)
(string 'my-symbol)

(string '1234)

(elt (string 'my-symbol) 0) →
(elt (string 'my-symbol) 1) →
(elt (string 'my-symbol) 2) →
(elt (string 'my-symbol) 20) →

"Parthenon"
"1234"
#\P
#\A
#\R
Error ...

(η ακριβής επιστροφή είναι εξαρτημένη από τον compiler)
(elt (string '1234) 3)
→ #\4

Η συνένωση χαρακτήρων σε string μπορεί να γίνει με την CONCATENATE εφαρμοσμένη πάνω
στη λίστα των χαρακτήρων:
(concatenate 'string
(list (elt (string (name 'kur)) 0) (elt (string (name 'lyp)) 1))

→ "ky"
Με εκμετάλλευση της συναρτησιακής σύνθεσης
δημιουργήσουμε σύμβολο από χαρακτήρες, όπως:

με

καθυστερήσεις,

μπορούμε

να

(eval
`(setq ,(print
(make-symbol (concatenate 'string
(list (elt (string (name 'kur)) 0) (elt (string (name 'lyp)) 1)))))
100))

→ ky
(όνομα του συμβόλου, εκτυπούμενο λόγω του print)
100
(τιμή του συμβόλου)

(για την ακρίβεια, το σύμβολο ky θα τυπωθεί ως #:ky , δηλ. οτι ανήκει στο package "#" των
"uninterned" συμβόλων).

3.6 Λίστα συνδεδεμένη με σύμβολο

Εκτός από την απόδοση λίστας ως τιμή σε σύμβολο, έχουμε και άλλους τρόπους σύνδεσης λίστας
με σύμβολο: ένας τρόπος είναι η συνδεδεμένη με το σύμβολο λίστα ιδιοτήτων (property list) και
ένας άλλος τρόπος (αποκλειστικά εναλλακτικός του πρώτου) είναι μέσω συνδεδεμένου πίνακα
κατακερματισμού (hash table).
Η χρήση τέτοιας σύνδεσης είναι ανεξάρτητη της απόδοσης τιμής στο σύμβολο, άρα μπορεί να
συνυπάρχει με την τιμή (περιεχόμενο) συμβόλου.

143

3.6.1 Λίστα ιδιοτήτων συμβόλου
Ένα σύμβολο, εκτός από περιεχόμενο - τιμή, μπορεί να έχει και “ιδιότητες”. Πιο συγκεκριμένα,
κάθε σύμβολο συνδέεται με μια λίστα, τη λεγόμενη λίστα ιδιοτήτων του, αρχικά κενή, της
οποίας:
– τα περιττής τάξης στοιχεία θεωρούνται ιδιότητες του συμβόλου, και
– τα άρτιας τάξης θεωρούνται ως η αντίστοιχη τιμή τής αμέσως αριστερά αναγραφόμενης
ιδιότητας,
δηλ η λίστα ιδιοτήτων συμβόλου έχει τη μορφή:
(ιδιότητα1 τιμή1 ιδιότητα2 τιμή2 ιδιότητα3 τιμή3 …)

► Ο όρος “τιμή ιδιότητας” που αναφέρεται εδώ, δεν σχετίζεται με τον όρο “τιμή συμβόλου”: οι
τιμές ιδιοτήτων δεν είναι τιμές των συμβόλων που παριστούν τις ιδιότητες. Αυτό σημαίνει: πως
κάθε όρος της λίστας ιδιοτήτων μπορεί να είναι σύμβολο, και ως σύμβολο να έχει τη δική του
τιμή, άσχετη από το τί αναφέρεται στη λίστα αυτή.
► Τα στοιχεία της λίστας ιδιοτήτων (ονόματα ιδιοτήτων ή τιμές ιδιοτήτων) αν είναι σύμβολα,
μπορούν να έχουν τη δική τους λίστα ιδιοτήτων, και αυτό δίνει τη δυνατότητα δημιουργίας από
πολυσύνθετες δομές "μέσα" στο σύμβολο.

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

Συναρτήσεις επεξεργασίας ιδιοτήτων συμβόλου
get : Συνάρτηση δύο ορισμάτων. Το πρώτο είναι σύμβολο ή φυσική οντότητα, και το δεύτερο
ιδιότητα του συμβόλου αυτού ή της φυσικής οντότητας. Επιστρέφει την τιμή της συγκεκριμένης
ιδιότητας. Αν δεν υπάρχει τέτοια ιδιότητα, επιστρέφει NIL.
Ας ορίσουμε το σύμβολο "γάτα" με περιεχόμενο τον εαυτό του:
(setq γάτα 'γάτα)

Έστω οτι έχουμε αποδώσει στο σύμβολο "γάτα" την ιδιότητα "πόδια" με τιμή 4 (το πώς, θα το
δούμε παρακάτω). Η φόρμα κλήσης της ιδιότητας και το αποτέλεσμα είναι:
(get 'γάτα 'πόδια) → 4
Αυτό που επιτελεί η συνάρτηση GET είναι: εντοπίζει τη λίστα ιδιοτήτων της οντότητας που
δίνεται ως πρώτο όρισμα, και μέσα σ' αυτήν εντοπίζει, ως όρο της λίστας, την ιδιότητα που
δίνεται ως δεύτερο όρισμα∙ επιστρέφει τον αμέσως επόμενο όρο της λίστας ιδιοτήτων, που
144

θεωρείται, σημασιολογικά, οτι είναι τιμή της ιδιότητας.

setf : Θεμελιακή συνάρτηση, παρεμφερής με την SETQ, διότι αποδίδει και αυτή περιεχόμενο σε
οντότητα. Διαφέρει όμως από αυτήν, διότι μπορεί να αποδώσει περιεχόμενο όχι μόνο σε σύμβολο
- μεταβλητή αλλά και σε κάθε "φυσική οντότητα", δηλαδή σε οτιδήποτε είναι δυνατό να
προσδιοριστεί στη Lisp μέσω υπολογισμού. Έτσι, μπορούμε να αποδώσουμε περιεχόμενο σε
οντότητες που δεν έχουν όνομα. αντίθετα από την SETQ που μόνο σε όνομα συμβόλου μεταβλητής μπορεί να εφαρμοστεί.
Μια τέτοια φυσική οντότητα είναι η «ιδιότητα "πόδια" του συμβόλου "γάτα"», και περιεχόμενο η
θεωρούμενη τιμή της ιδιότητας. H φόρμα που προσδιορίζει αυτή τη φυσική οντότητα είναι η:
(get 'γάτα 'πόδια)

στην οποία η SETF αποδίδει περιεχόμενο:
(setf (get 'γάτα 'πόδια) 4)

Με επαναχρησιμοποίηση της φόρμας απόδοσης ιδιότητας και αντίστοιχης τιμής για το ίδιο
σύμβολο και ιδιότητα, μπορούμε να μεταβάλλουμε την τιμή:
(setf (get 'γάτα 'πόδια) 2)
(get 'γάτα 'πόδια)
→ 2

Μια οντότητα μπορεί να έχει πολλές ιδιότητες (το μέγιστο πλήθος καθορίζεται από την τιμή
ειδικής μεταβλητής - παραμέτρου του compiler):
(setf (get 'γάτα 'μάτια) 2)
(setf (get 'γάτα 'γούνα) t)
(setf (get 'γάτα 'πόδια) 4)

Ένας σημασιολογικός χαρακτηρισμός μπορεί να δοθεί ως ιδιότητα με κάποια τιμή. Το όνομα
ιδιότητας, αν δεν είναι σύμβολο ήδη ορισμένο, δεν αποτελεί σύμβολο και δεν είναι "ορατό"
αυτόνομα. Η τιμή ιδιότητας μπορεί να είναι οποιαδήποτε φόρμα.
(setf (get 'γάτα 'συμπεριφορά) 'χαϊδεύεται)
(setf (get 'γάτα 'τρώει) '(ψάρι κρέας))
remprop : Aναιρεί ιδιότητα., μαζί με την τιμή της.
(remprop 'γάτα 'συμπεριφορά)
(get 'γάτα 'συμπεριφορά)
→ NIL

symbol-plist : Eπιστρέφει τη λίστα ιδιοτήτων συμβόλου:
(symbol-plist 'γάτα) →
→ (μάτια 2 γούνα t πόδια 4 τρώει (ψάρι κρέας) )
Η λίστα ιδιοτήτων συμβόλου είναι μια λίστα μόνιμα και διαρκώς συνδεδεμένη με το σύμβολο. Οι
περιττής τάξης όροι είναι οι ιδιότητες και οι άρτιας τάξης είναι οι αντίστοιχες τιμές (κάθε αμέσως
αριστερά αναφερόμενης ιδιότητας).
Μπορούμε να χρησιμοποιήσουμε απ' ευθείας την λίστα ιδιοτήτων συμβόλου, με συναρτήσεις
επεξεργασίας λίστας, και ακόμα να επέμβουμε σ' αυτήν, είτε αλλάζοντας κάποιο όρο:
(nsubst 'μάτια 'οφθαλμοί (symbol-plist 'γάτα))

είτε προσθέτοντας δυάδα όρων:
(push 'σουβλερά (symbol-plist 'γάτα))
145

(push 'νύχια (symbol-plist 'γάτα))

είτε αλλάζοντας ολόκληρη τη λίστα ιδιοτήτων:
(setf (symbol-plist 'γάτα)
'( κυνηγάει (πουλί ποντίκι) κυνηγιέται_από (σκύλος) ) )

οπότε παίρνουμε:
(get 'γάτα 'κυνηγάει)

→ (πουλί ποντίκι)

αρκεί σε κάθε περίπτωση να μη καταστρέψουμε το θεμελιακό χαρακτηριστικό οτι η λίστα
ιδιοτήτων έχει άρτιο πλήθος όρων.

Οργάνωση από συνθετότερες δομές, με χρήση ιδιοτήτων
Ένα σύμβολο μπορεί να έχει πολλές ιδιότητες, και κάθε ιδιότητα μπορεί να έχει τιμή οτιδήποτε,
όπως: σταθερά (αριθμό, χαρακτήρα, string), σύμβολο, λίστα, συναρτησιακή έκφραση (δοσμένη
ως "quoted" λ-έκφραση, όπως θα δούμε σε επόμενη ενότητα), φόρμα υπολογισμού που αν δοθεί
"quoted" καταχωρείται αυτούσια υπολογίζεται και καταχωρείται το αποτέλεσμα.
(setf (get 'γάτα 'πόδια)
'(εμπρός_δεξί εμπρός_αριστερό πίσω_δεξί πίσω_αριστερό ))
(setf (get 'γάτα 'μάτια)
'(δεξί αριστερό))

Μπορούμε να εκμεταλλευτούμε τον προσδιορισμό ιδιότητας φυσικής οντότητας συνδυάζοντας το
μηχανισμό απόδοσης ιδιοτήτων με μηχανισμούς επεξεργασίας λίστας. Έτσι μπορούμε να
δημιουργήσουμε δομές με μεγαλύτερο βάθος, όπως:
(setf (get (first (get 'γάτα 'μάτια)) 'χρώμα) 'μπλε)

; το αριστερό μάτι της γάτας έχει χρώμα μπλε

(setf (get (second (get 'γάτα 'μάτια)) 'χρώμα) 'πράσινο)

; το δεξί μάτι της γάτας έχει χρώμα πράσινο

Το μέγιστο βάθος καθορίζεται από το ελάχιστο των: μέγιστο μέγεθος λίστας, μέγιστο μέγεθος
λίστας ιδιοτήτων, μέγιστο πλήθος ορισμάτων συνάρτησης.

Χρησιμοποιούμε την τιμή Τ όταν απλώς θέλουμε να δηλώσουμε οτι η ιδιότητα είναι ένας αληθής
χαρακτηρισμός, αλλά αντίθετα, αποφεύγουμε να χρησιμοποιούμε την τιμή NIL για να δηλώσουμε
οτι η ιδιότητα αποτελεί ψευδή χαρακτηρισμό, διότι αυτό μπορεί να δημιουργήσει σύγχυση: τότε ο
υπολογισμός θα επιστρέφει NIL τόσο ως τιμή της ιδιότητας όσο και σε ανυπαρξία της
ιδιότητας.
Ο τρόπος απόδοσης ιδιοτήτων μπορεί να απεικονίζει ευθέως τον πραγματικό συσχετισμό των
εννοιών. Αυτό σημαίνει οτι καταγράφουμε με ένα συμβολικό τρόπο τη σχέση, αλλά όχι και την
εννοούμενη σημασία, που οφείλει ο χρήστης να έχει κατά νου:
(setq feline 'feline)
(setq mammal 'mammal)
(setq 'animal 'animal)
(setf (get cat 'kind) 'feline)
(setf (get feline 'kind) 'mammal)
(setf (get mammal 'kind) 'animal)

146

Η ευχέρεια σύνδεσης "σχεδόν οτιδήποτε με οτιδήποτε" και οπωσδήποτε με έναν εύχρηστο τρόπο
μέσω ιδιοτήτων, καθιστά δυνατή τη δημιουργία από πολύ πλούσιες δομές. Τέτοιες δομές μπορούν
να αξιοποιηθούν πολύ πλούσια με τον προσδιορισμό από κατάλληλες συναρτήσεις.

Παρατήρηση: Όπως είπαμε, για να καταχωρήσουμε κάποιο "αρνητικό χαρακτηρισμό" ως ιδιότητα
πρέπει να δώσουμε ως τιμή κάτι άλλο από NIL, πχ. NO. Μπορούμε να χρησιμοποιήσουμε τον όρο
NO με αρνητική σημασία, είτε απλώς νοητικά είτε υπολογιστικά:
(setq no nil) )
(defun is-true? (x) (eval x))
(defun has (object property) (get object property))
(setf (get 'octopus 'bones) 'no)

Χρήση:

(has 'octopus 'bones)

→ NO (προσοχή: λογικά είναι Τ)
(is-true? (has 'octopus 'bones)) →
→ NIL (ως τιμή της μεταβλητής NO)

Παράδειγμα
Θέλουμε να καταχωρήσουμε πως “η Μαρία είναι φύλου θηλυκού, με επάγγελμα εκπαιδευτικός, με
αριθμό τηλεφώνου 72918, με ηλικία 25 ετών, και με ενδιαφέροντα: κινηματογράφος, τηλεόραση,
εκδρομές, μαγείρεμα”.
Δημιουργούμε το σύμβολο - όνομα maria με τιμή 'maria :
(setq maria 'maria)

και αποδίδουμε τις ιδιότητες: 'sex με τιμή 'female , 'occupation με τιμή 'teacher και 'telephonenumber με τιμή "72918" (ως τύπου string):
(setf
(setf
(setf
(setf

Έχουμε

(get
(get
(get
(get

'maria
'maria
'maria
'maria

'sex ) 'female)
'profession ) 'teacher)
'telephone-number ) “72918” )
'interests ) '(cinema TV excursions cooking))

(get 'maria 'telephone-number) → "72918"
(remprop 'maria 'telephone-number)
(get 'maria 'telephone-number) → NIL
(symbol-plist 'maria) →
→ (sex female profession teacher interests (cinema TV
excursions cooking))

Χρήση συμβόλου ως τιμή ιδιότητας
Η τιμή ιδιότητας μπορεί να είναι σύμβολο, άρα να έχει τιμή.
Παράδειγμα
Να οριστεί συνάρτηση, έστω GET-VAL , που να επιστρέφει την τιμή της τιμής της ιδιότητας, αν
η τιμή είναι σύμβολο με τιμή, αλλιώς την τιμή αυτούσια:
(defun get_val (symbl prop)

147

(cond

; αν δεν υπάρχει η ιδιότητα, ; τότε επίστρεψε nil
( (equal (get symbl prop) nil)

nil)

; αν είναι σύμβολο με τιμή, η τιμή της ιδιότητας

( (and (symbolp (get symbl prop)) (boundp (get symbl prop)))

;; τότε υπολόγισε την τιμή της τιμής αυτής

(eval (get symbl prop)) )

;; αλλιώς δώσε την τιμή της ιδιότητας
(t (get symbl prop)) ) )
(setq αρθρωτά "δηλαδή έχουν σημεία κάμψης")
(setf (get 'σκύλος 'πόδια) 'αρθρωτά)
(setf (get 'σκύλος'χαρακτήρας) 'πιστός)

Εφαρμογή:

(get_val 'σκύλος 'χαρακτήρας) → πιστός
(get_val 'σκύλος πόδια)
→ αρθρωτά
(eval (get_val 'σκύλος πόδια)) → "δηλαδή έχουν σημεία κάμψης"

► Η χρήση από σύμβολο ως όνομα ιδιότητας, για ορισμένους compilers, πρέπει να γίνει μετά
τον ορισμό του συμβόλου.
Μπορούμε να αξιοποιήσουμε πολύ πλατειά τη χρήση συμβόλων ως ονόματα ιδιοτήτων ή τιμών
ιδιοτήτων, διότι ένα σύμβολο μπορεί να έχει δικό του περιεχόμενο, και δικές του ιδιότητες.
Η απόδοση τιμής σε σύμβολο μπορεί να συνοδεύεται από απόδοση ιδιοτήτων. Η διάκριση
"περιεχομένου" (ως τιμή) και "χαρακτηριστικών" (ως ιδιότητες) εξαρτάται από το "πώς εμείς
αντιλαμβανόμαστε συνδεδεμένα τα νοήματα".
Παράδειγμα
Πώς θα εκφράσουμε τη σχέση "το παράμεσο δάκτυλο του αριστερού χεριού του Γιάννη έχει ένα
κόκαλο σπασμένο ενώ τα άλλα είναι γερά";
Αντιμετωπίζουμε το θέμα μέσω λίστας ιδιοτήτων. Θα δούμε αργότερα μια καλύτερη
αντιμετώπιση, μέσω οντοτήτων τύπου structure.
Δηλώσεις:
; κατ' αρχήν δημιουργούμε το σύμβολο Γιάννης:
(setq Γιάννης 'Γιάννης)

; και σύμβολο - λίστα των δακτύλων χεριού:
(setq τα-δάκτυλα '(αντίχειρας δείκτης μέσος παράμεσος μικρό))
; προσδιορίζουμε τα χέρια του Γιάννη ως τιμή της ιδιότητάς του χέρια :
(setf (get Γιάννης 'χέρια) '(δεξί αριστερό))
; σε κάθε χέρι δίνουμε ιδιότητα δάκτυλα με τιμή το περιεχόμενο
; της λίστας τα-δάκτυλα
(setf (get (first (get Γιάννης 'χέρια)) 'δάκτυλα) τα-δάκτυλα)
(setf (get (second (get Γιάννης 'χέρια)) 'δάκτυλα) τα-δάκτυλα)

; τέλος, καθορίζουμε για κάθε δάκτυλο κάθε χεριού την "κατάστασή του":
(setf (get (second (get (second (get Γιάννης 'χέρια)) 'δάκτυλα))
148

'κατάσταση) 'γερό)
(setf (get (fourth (get (second (get Γιάννης 'χέρια)) 'δάκτυλα))
'κατάσταση) 'σπασμένο)

; και παρόμοια για τα υπόλοιπα δάκτυλα.

Απλούστερο θα ήταν να σχηματίσουμε μια αναδρομική συνάρτηση σάρωσης, που να
χαρακτηρίζει αρχικά όλα τα δάκτυλα "γερά" (επειδή τα "γερά" είναι τα περισσότερα) και μια μόνο
ειδική καταχώρηση να προσδιορίσει το "σπασμένο".
Προσδιορισμός οντοτήτων:
Αν θελήσουμε να "συμβολοποιήσουμε" τις φυσικές οντότητες - δάκτυλα των χεριών, θα
παρατηρήσουμε πως, αν και σημασιολογικά έχουν περισσότερο έννοια αντικειμένου παρά
συνάρτησης, εν τούτοις ουσιαστικά "ανήκουν σε κάποιον", άρα δεν είναι δυνατό να καθορίσουμε
μια μοναδική φυσική οντότητα για κάθε ένα από τα δάκτυλα του χεριού. Αποδίδοντας κάθε
"δάκτυλο" ως συνάρτηση του προσώπου στο οποίο ανήκει, καλύπτουμε αυτή την εξάρτηση:
(defun αριστερός-δείκτης (person)
(second (get (second (get person 'χέρια)) 'δάκτυλα)))
(defun αριστερός-παράμεσος (person)
(fourth (get (second (get person 'χέρια)) 'δάκτυλα)))

; κλπ.
Εφαρμογές:

(get (αριστερός-δείκτης Γιάννης) 'κατάσταση)
→ γερό
(get (αριστερός-παράμεσος Γιάννης) 'κατάσταση) → σπασμένο

; κλπ.
Θα δούμε σε επόμενη ενότητα, πως είναι πολύ απλούστερο και φυσικότερο να αποδοθούν οι
έννοιες αυτές ως κλάσεις.

Ομαδική καταχώρηση ιδιοτήτων
Συνήθως, αποδίδουμε σε ένα σύμβολο διάφορες ιδιότητες, και είναι ενοχλητική η επανάληψη του
συνδυασμού SETF - GET . Απλούστερο είναι, να ορίσουμε μια συνάρτηση ομαδικής
καταχώρησης ιδιοτήτων:
(defun put-props (person p-v-list)
(cond
( (not (endp p-v-list))
(setf person (get person (car p-v-list)) (cadr p-v-list))
(put-props (person (cddr p-v-list)))
(t t)))

Εφαρμογή:
(put-props 'Κώστας
'(είδος άνθρωπος γένους αρσενικού
αριθμός_ταυτότητας "Θ-345678" ηλικία 32
κατάσταση_υγείας καλή επάγγελμα βιολιστής
οικογ_κατάσταση άγαμος))
149

Οι ιδιότητες αυτές καταχωρούνται επιπρόσθετα στις ήδη υπάρχουσες, ενώ αποδίδεται η νέα τιμή
στις ήδη υφιστάμενες.

Μπορεί να έχει χρώμα η αλήθεια;
Αναφέραμε οτι οι λογικές τιμές Τ και NIL είναι επίσης σύμβολα με τιμή τον εαυτό τους. Ως
σύμβολα, είναι λογικό να περιμένει κανείς οτι δέχονται λίστα ιδιοτήτων. Πράγματι:
(setf
(setf
(get
(get

(get t 'color) 'red)
(get nil 'color) 'green)
t 'color)
→ red
nil 'color)
→ green

H λίστα ιδιοτήτων των T και NIL αναφέρεται είτε στο ίδιο το σύμβολο είτε στην τιμή τους
αδιάκριτα (δηλ. με χρήση "quote" ή όχι). Κάθε έμμεση παραγωγή των τιμών αυτών δίνει
ουσιαστικά το σύμβολό τους, άρα μπορούμε να αναφερθούμε στις ιδιότητές του:
(get (numberp pi) 'color)
→ red
(get (numberp 'Γιάννης) 'color) → green
(!!!)

3.6.2 Πίνακες κατακερματισμού

Μια εναλλακτική αντιμετώπιση της έννοιας "απόδοση ιδιοτήτων σε σύμβολο" είναι μέσω
πινάκων κατακερματισμού (hash tables). Και οι δύο τρόποι, χρησιμοποιούν εσωτερικά τις ίδιες
συνδέσεις, και γι' αυτό είναι ασύμβατες μεταξύ τους. Κατά συνέπεια, ο χρήστης πρέπει να
διαλέξει αν θα χρησιμοποιήσει τον ένα ή τον άλλο τρόπο και μόνον, για να συνδέσει
χαρακτηριστικά με κάποιο σύμβολο.
Επειδή το σύμβολο μπορεί να συμμετέχει σε ευρύτερες συνθέσεις, είναι σκόπιμο να ακολουθείται
η ίδια πορεία (λίστα ιδιοτήτων ή κατακερματισμού, αποκλειστικά) για το σύνολο των συμβόλων
που εμπλέκονται, ώστε να είναι δυνατή η ομοιόμορφη εφαρμογή αναδρομικών σχέσεων που
επεξεργάζονται τα σύμβολα αυτά και τις συνδέσεις τους.

Οι πίνακες κατακερματισμού παρουσιάζουν ορισμένα μειονεκτήματα απέναντι στις λίστες
ιδιοτήτων, και κάποια πλεονεκτήματα. Τα μειονεκτήματα είναι:
– Το σημασιολογικό αντίστοιχο των ιδιοτήτων, στους πίνακες κατακερματισμού (π.κ.), είναι οι
είσοδοι που είναι προσπελάσιμες άμεσα, και προσφέρουν μεγάλη ταχύτητα προσπέλασης
ακόμα και όταν το πλήθος των εισόδων είναι πολύ μεγάλο, σε αντίθεση με τη λίστα
ιδιοτήτων που πρέπει να σαρωθεί για να βρεθεί κάτι.
– Είναι έννοια γενικότερη από τη λίστα ιδιοτήτων: ένας π.κ. υφίσταται αυτόνομα και
συνδέεται, αν θέλουμε, επεξηγηματικά με οντότητα, ενδεχομένως και με περισσότερες της
μιας, όπου "οντότητα" μπορεί να είναι οποιαδήποτε θέση προσπελάσιμη από την setf, ενώ
αντίθετα η λίστα ιδιοτήτων είναι πάντα συνδεδεμένη με ένα και το αυτό σύμβολο (ή με ένα
φυσικό αντικείμενο).
Τα πλεονεκτήματα είναι:
150

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

Συναρτήσεις επεξεργασίας π.κ.
make-hash-table : Για δημιουργία ενός πίνακα κατακερματισμού (σημειώνουμε απλώς οτι
δέχεται διάφορα προαιρετικά κλειδιά, που εξειδικεύουν τη λειτουργικότητα):
(setq htbl (make-hash-table))
; δημιουργία π.κ. και ονομασία του
gethash : Για αναζήτηση τιμής εισόδου
(gethash 'color htbl)
→ red
Καταχωρούμε νέα είσοδο με συνδυασμό των SETF και GETHASH :
(setf (gethash 'input-name hash-symbol-name) 'input-value)
για παράδειγμα:
(setf (gethash 'color htbl) red)
(setf (gethash 'hight htbl) 100)

remhash

: Για διαγραφή εισόδου

(gethash 'color htbl)

clrhash
: Για σβήσιμο όλων των εισόδων
(clearhash hash-symbol-name)
hash-table-count : Δίνει το πλήθος των εισόδων του αναφερόμενου πίνακα
(hash-table-count hash-symbol-name) → <integer>
maphash : Συνάρτηση δύο ορισμάτων∙ το πρώτο πρέπει να είναι όνομα συνάρτησης ή λέκφραση και το δεύτερο πρέπει να είναι όνομα πίνακα κατακερματισμού (ή κατ' ευθείαν
προσδιοριζόμενος π.κ.).
(maphash #'όνομα-συνάρτησης όνομα-π.κ.)
(maphash #'λ-έκφραση όνομα-π.κ.)
Η λειτουργικότητα της MAPHASH είναι η εξής:

Εφαρμόζει τη συνάρτηση που δίνεται ως πρώτο όρισμα (αντίστ. τη λ-έκφραση) σε όλες τις
εισόδους και αντίστοιχες τιμές τού πίνακα που δίνεται ως δεύτερο όρισμα.
Η συνάρτηση που δίνεται ως πρώτο όρισμα (αντίστ. λ-έκφραση), οφείλει να δέχεται δύο
151

ορίσματα, και το πρώτο από αυτά τα ορίσματα ταιριάζει διαδοχικά σε κάθε είσοδο ενώ το
δεύτερο στην αντίστοιχη τιμή.
Ως συνάρτηση, η MAPHASH επιστρέφει πάντα NIL .
Παράδειγμα
Να γραφεί συνάρτηση που να αντικαθιστά σε π.κ. του οποίου οι τιμές που περιέχει είναι
αριθμητικές, όλες τις άρτιες με το μισό τους και όλες τις περιττές με το μισό τους συν ½
Έστω ο π.κ. my-hash :
(setq my-hash (make-hash-table))

με περιεχόμενο τρεις εισόδους p1, p2, p3 αντίστοιχης τιμής 4, 8, 5 :
(setf

(gethash 'p1 my-hash) 4
(gethash 'p2 my-hash) 8
(gethash 'p3 my-hash) 5 )

Ορίζουμε συνάρτηση SQU δύο ορισμάτων: είσοδο και αντίστοιχη τιμή, που ελέγχει αν η
είσοδος έχει τιμή άρτια ή περιττή και αντίστοιχα την μετατρέπει:
(defun squ (inpu valu)
(cond ( (evenp valu)
(setf (gethash inpu pk) (/ valu 2)))
( (oddp valu)
(setf (gethash inpu ht) (/ 2 (+ valu 1))))))
Η μεταβλητή pk είναι σύμβολο, και το ταυτοποιούμε με τον επιθυμητό π.κ. επεξηγηματικά:
(setq ht my-hash) ; για να προσαρμοστεί η squ στον πίνακα my-hash
δεν τη θέσαμε ως λ-μεταβλητή για να χρησιμοποιήσουμε την MAPHASH :
(maphash #'squ my-hash) ; για να εφαρμοστεί η squ στον πίνακα

Το αποτέλεσμα είναι το επιθυμητό.
Παρατηρούμε οτι ο τρόπος αυτός έφερε τα ορίσματα σε κατάλληλη μορφή για να εφαρμοστεί η
συνάρτηση MAPHASH , και δεν ορίσαμε νέα συνάρτηση που να επιτελεί το ζητούμενο.

3.6.3 Δένδρα και κληρονόμηση χαρακτηριστικών
Ένας τρόπος αναπαράστασης δένδρου είναι μέσω λίστας από λίστες από λίστες κλπ. Αυτό
μπορούμε να το αποδώσουμε με πολλούς τρόπους, όπως:
– με απ' ευθείας αναγραφή της πολυεπίπεδης λίστας
– με σύμβολα που έχουν περιεχόμενο λίστα από σύμβολα ή κενή λίστα, όπου τα σύμβολα έχουν
περιεχόμενο λίστα από σύμβολα ή κενή λίστα, μέχρις εξαντλήσεως των συμβόλων που
περιέχονται.
Η δεύτερη περίπτωση εξυπηρετεί πολύ περισσότερο, όπως θα δούμε αναλυτικά.
Σε μια δενδροειδή δομή όπου τα στοιχεία είναι σύμβολα, για ορισμένα χαρακτηριστικά τους, έχει
νόημα η κληρονόμησή τους από τους κόμβους - απογόνους. Έχουμε διάφορους τρόπους να
αντιμετωπίσουμε το ζήτημα αυτό, με κυριότερο και απλούστερο αυτόν που θα δούμε στις
152

κλάσεις. Εδώ θα κινηθούμε στα πλαίσια εφαρμογής "κοινών" συναρτήσεων σε λίστα, για να
δούμε διαφορετικές δυνατές προσεγγίσεις:
– τη διαδικαστική, όπου δημιουργούμε ένα "καταρράκτη ενημέρωσης" από πάνω προς τα κάτω,
και
– τη συναρτησιακή, όπου "δείχνουμε τον γονέα" κάθε κόμβου, και δημιουργούμε μια τεχνική
που δίνει συνέχεια στην αναζήτηση προς τα πάνω.
Είναι δυνατή η διάκριση "κληρονομούμενων" από "μη κληρονομούμενα" χαρακτηριστικά στη
δομή. Αρκεί γι' αυτό, να θέσουμε σε κάθε σύμβολο της δομής, μια ιδιότητα με όνομα έστω
κληρονομούμενα_χαρακτηριστικά και να της δώσουμε τιμή λίστα, η οποία περιέχει δυάδες:
πρώτο στοιχείο κάθε δυάδας να είναι όνομα χαρακτηριστικού, και δεύτερο η τιμή του. Τα
χαρακτηριστικά, αρκεί να τα αναγράψουμε μόνο στον ανώτερο κόμβο που τα έχει, και να
προκαλούμε την κληρονόμηση με σαρωτική εφαρμογή συνάρτησης η οποία θα βλέπει αυτή την
ιδιότητα.

Τα μη κληρονομούμενα χαρακτηριστικά μπορούμε να τα διακρίνουμε σε κατηγορίες που να
διαφοροποιούν "κόσμους": πχ, στο δένδρο των φυτών, την ιδιότητα ο-κόσμος-των-γνώσεων-τουΘανάση , με περιεχόμενό της, σε κάθε κόμβο "τί ξέρει ο Θανάσης για τον κόμβο αυτό".
Εδώ, για απλούστευση, θα θεωρήσουμε οτι όλα τα χαρακτηριστικά είναι κληρονομούμενα. και θα
τα αποδώσουμε ως ιδιότητες των οντοτήτων που είναι κόμβοι ή φύλλα του δένδρου (τις
προσδιορίζουμε ως σύμβολα). Θα αντιμετωπίσουμε το ζήτημα με δύο τρόπους:
i) Αποδίδοντας σε κάθε σύμβολο της δομής τα άμεσα χαρακτηριστικά του κόμβου ως ιδιότητες,
και προκαλώντας σαρωτικά την κληρονόμηση, ως απόδοση προσθέτων ιδιοτήτων σε κάθε
κόμβο. Στην περίπτωση αυτή, η σάρωση καταχώρησης πρέπει να προηγείται κάθε
αναζήτησης, εάν έχει προηγηθεί καταχώρηση.

ii) Πάλι αποδίδοντας σε κάθε σύμβολο τα άμεσα χαρακτηριστικά του ως ιδιότητες, αλλά τώρα
αντί για σάρωση καταχώρησης, εγκαθιστώντας μια σύνδεση κάθε οντότητας με το "γονέα"
της, μέσω της οποίας έχουμε πρόσβαση στα χαρακτηριστικά του γονέα, και από αυτόν
αναδρομικά όλων των προγόνων. Στην περίπτωση αυτή η σάρωση συμπίπτει με την
αναζήτηση.
Με τον τρόπο (i) εγκαθιστούμε την κληρονομικότητα "στατικά" αντιγράφοντας απλώς τις
ιδιότητες του γονέα σε κάθε παιδί. Πλεονέκτημα είναι, η αμεσότητα της πληροφορίας που
μπορούμε να λάβουμε εξετάζοντας απ' ευθείας κάθε οντότητα. Μειονεκτήματα είναι: η πολλαπλή
αναφορά των ίδιων ιδιοτήτων από τον αρχικό κόμβο που έχει την ιδιότητα και κάτω∙ ο χρόνος
που απαιτείται για την ενημέρωση του δένδρου∙ περιττές επαναλήψεις καταχωρήσεων ή
πολυπλοκότητα "σημαιών" που δηλώνουν αν έχει ολοκληρωθεί ή όχι κάποια κληρονομική
καταχώρηση, σε σχέση με νέο παιδί ή νέα ιδιότητα.
Με τον τρόπο (ii) εγκαθιστούμε "δυναμικά" την κληρονομικότητα, "ψάχνοντας" στη λίστα
ιδιοτήτων κάθε οντότητας και αν δεν βρεθεί εκεί η πληροφορία, πηγαίνουμε στη λίστα ιδιοτήτων
του γονέα της, και αυτό αναδρομικά μέχρι τη ρίζα. Πλεονεκτήματα είναι: η οικονομία χώρου και
χρόνου καταχώρησης∙ η δυναμική αυτής της προσέγγισης, διότι παρακολουθεί αυτόματα τις
μεταβολές χαρακτηριστικών των προγόνων. Μειονέκτημα είναι ο χρόνος αναζήτησης
153

πληροφορίας, λόγω της κίνησης προς τα πάνω μέχρι τη ρίζα. Πάντως, η κίνηση αυτή ακολουθεί
αλυσίδα και είναι ταχύτερη από τη σάρωση του δένδρου.
Θα δούμε τώρα την πορεία υλοποίησης κάθε τρόπου:

Μεθόδευση της κληρονόμησης σε μια ιεραρχία, με "από πάνω προς τα κάτω"
ενημέρωση
Κατά τη μεθόδευση αυτή, η απόδοση ενός χαρακτηριστικού σε κόμβο συνοδεύεται από απόδοση
του ίδιου χαρακτηριστικού σε όλους τους απογόνους του, αντιγράφοντάς το.
Προσδιορίζουμε το δένδρο της ιεραρχίας αποδίδοντας τον κάθε κόμβο με σύμβολο - μεταβλητή
και τους κλάδους του ως όρους της λίστας που θα δίνεται ως τιμή του συμβόλου, και αυτό μέχρι
φύλλων. Τα χαρακτηριστικά τα αποδίδουμε ως ιδιότητες των συμβόλων. Για απλούστευση, δεν
θα ασχοληθούμε με "σημαίες" προειδοποίησης αν χρειάζεται ή όχι μια καταχώρηση ή σάρωση.
Έστω η γνωστή ιεραρχία των ζώων:
(setq
(setq
(setq
(setq

ζώο '(θηλαστικό ιχθύς πτηνό))
; κόμβος
θηλαστικό '(κυνοειδές αιλουροειδές ανθρωποειδές)) ; κόμβος
αιλουροειδές '(γάτα τίγρη λιοντάρι))
; κόμβος
γάτα '(φιφίκος ρίκα))
; φύλλο του δένδρου

; κλπ. κλπ.
Χρειαζόμαστε συνάρτηση η οποία να επιτελεί τα εξής:
i) να αποδίδει κάποια ιδιότητα με ορισμένη τιμή σε κόμβο, αναφερόμενο με το όνομά του ως
σύμβολο, και
ii) να προκαλεί την απόδοση της ιδιότητας αυτής σε ολόκληρο τον κλάδο, για κάθε όνομα
συμβόλου που αποτελεί κατώτερο κόμβο του διδόμενου, μέχρι τα φύλλα.
Έστω οτι η ζητούμενη συνάρτηση είναι η PUT-NEW-PROPERTY . Αν την εφαρμόσουμε σε
σύμβολο, έστω το "ζώο" , για την ιδιότητα - χαρακτηριστικό "αποτελείται_από" με τιμή
"κύτταρα", θα πρέπει να προκαλέσει καταχώρηση τέτοια ώστε, αν ζητήσουμε την ιδιότητα αυτή
για το σύμβολο - φύλλο "ρίκα" (που είναι μια "γάτα", όπου "γάτα" είναι ένα "αιλουροειδές", όπου
"αιλουροειδές" είναι "ζώο" που έχει την ιδιότητα αυτή) να πάρουμε απάντηση "κύτταρα".
Η συνάρτηση καταχώρησης PUT-NEW-PROPERTY πρέπει να καλύπτει δύο απαιτήσεις: την
καταχώρηση στο σύμβολο - κόμβο απ' όπου ξεκινά το χαρακτηριστικό, και τη σάρωση της
λίστας για εντοπισμό των συμβόλων - παιδιών του κόμβου, όπου η λίστα είναι τιμή του
συμβόλου, και αυτό αναδρομικά ή επαναληπτικά μέχρι τα φύλλα.

Την πρώτη απαίτηση την πετυχαίνουμε με διαδικασία προσδιορισμένη στο σώμα της
συνάρτησης, και τη δεύτερη με κλήση άλλης συνάρτησης, έστω PPROPTREE , που θα
επιτελέσει τη σάρωση αναζήτησης των παιδιών, και σε κάθε παιδί θα καλεί ξανά την PUT-NEWPROPERTY

Θεωρούμε ως κεντρική συνάρτηση κλήσης από το χρήστη τη συνάρτηση καταχώρησης PUTNEW-PROPERTY που κάνει καταχώρηση σε σύμβολο και η οποία καλεί τη συνάρτηση
PPROPTREE που αναζητά την τιμή συμβόλου, τη σαρώνει ως λίστα και αναδρομικά σε όλο της
154

το βάθος, και για κάθε όρο εφαρμόζει ξανά την PUT-NEW-PROPERTY .
Με τον τρόπο αυτό δημιουργούμε διπλή αναδρομή, που δεν είναι πολύ αποτελεσματική, αλλά η
έννοια νέας καταχώρησης είναι οτι δεν πρόκειται για συχνά χρησιμοποιούμενη πράξη.
(defun put-new-property (property value tree-name)
(cond
; αν είναι σύμβολο το tree-name
((symbolp tree-name)

; καταχώρησε την ιδιότητα στο σύμβολο

(put-it property value tree-name))
(t nil))
; αλλιώς τίποτα
(cond
; αν έχει περιεχόμενο το σύμβολο (που θα είναι λίστα)
((boundp tree-name)

; καταχώρησε την ιδιότητα στα στοιχεία της λίστας

(pproptree property value (eval tree-name)))
(t nil)) )
; αλλιώς τίποτα
(defun pproptree (property value tree-list)
(cond
((and (listp tree-list) (not (null tree-list)))

;; καταχώρησε την ιδιότητα στο σύμβολο - κεφαλή της λίστας, αλλά
; και στο περιεχόμενό του, βάσει της προηγούμενης "ppropnam"
(put-new-property property value (car tree-list))

; καταχώρησε την ιδιότητα στη λίστα - σώμα που απομένει
(pproptree property value (cdr tree-list)))
(t nil)))
(defun put-it (property value object)
; βοηθητική συνάρτηση
(setf (get object property) value))

Καταχώρηση:
(put-new-property 'αποτελείται_από 'κύτταρα 'ζώο)

Αναζήτηση τιμής χαρακτηριστικού:
(get 'ρίκα 'αποτελείται_από) → 'κύτταρα
Αναζήτηση όλων των χαρακτηριστικών:
(symbol-plist 'ρίκα) → (αποτελείται_από κύτταρα <κλπ> <κλπ> )
Το μεγαλύτερο μειονέκτημα αυτής της προσέγγισης είναι οτι, για κάθε μεταβολή του δένδρου
οφείλουμε να καλέσουμε, για κάθε σύμβολο, παλαιό ή νέο, από τη ρίζα προς τα φύλλα, και για
κάθε ιδιότητα, τη συνάρτηση PUT-NEW-PROPERTY , προκαλώντας περιττές επαναλήψεις.

155

Μεθόδευση κληρονόμησης σε μια ιεραρχία μέσω σύνδεσης κάθε οντότητας με τον
γονέα της
Προτιμότερη από την προηγούμενη, είναι η μεθόδευση σύνδεσης κληρονομητέου
χαρακτηριστικού, συνδέοντας κάθε κόμβο με αυτόν που κληρονομεί. Με τη μεθόδευση αυτή δεν
αντιγράφεται το χαρακτηριστικό, αλλά προκαλούμε αναζήτηση που "γνωρίζει οτι οφείλει να
ψάξει και στον γονέα", και αυτό αναδρομικά μέχρι τη ρίζα.
Για να σταθεί δυνατή η αναζήτηση αυτή, πρέπει προφανώς να είναι ενήμερος ο κάθε κόμβος για
το "ποιος είναι ο γονέας του". Γι' αυτό καταχωρούμε μια πρόσθετη ιδιότητα σε κάθε οντότητα, με
όνομα "parent" και τιμή το όνομα - σύμβολο του γονέα:
(defun setqp (obj lis)
(setq obj lis)
; ορισμός νέου κόμβου με περιεχόμενο λίστα
(set-par obj lis))
; ορισμός ιδιότητας parent με τιμή τον γονέα
(defun set-par (obj lis)
; καταχώρηση γονέα σε κάθε όρο
(cond
( (not (endp lis))
(setf (get (car lis) 'parent) obj)
(set-par obj (cdr lis)) )
(t t) ))

Έστω μια ιεραρχική δομή, παρόμοια με την προηγούμενη, αλλά οργανωμένη μέσω της SETQP :
(setqp 'ζώα '(θηλαστικά ιχθείς πτηνά))
; κά.
(setf (get 'ζώα 'αποτελείται_από) 'κύτταρα))
; κά.
Η αναζήτηση ψάχνει πρώτα τις ιδιότητες του κόμβου, και αν βρει την ζητούμενη ιδιότητα μας
επιστρέφει την τιμή της, αλλιώς πηγαίνει στον κόμβο - γονέα, κοκ. μέχρι τη ρίζα:
(defun search-for-prop (obj prop)
(cond

; αν είναι ιδιότητα του κόμβου

((member prop (symbol-plist obj))

; επίστρεψε την τιμή της
(get obj prop))

; αλλιώς κάνε το ίδιο για τον γονέα
(t (search-for-prop (parent-obj obj) prop)) ))

Η συνάρτηση PARENT-OBJ αποτελεί μέθοδο ανεύρεσης του γονέα:
(defun parent-obj (obj)
(get obj parent))

Αναζήτηση του συνόλου των χαρακτηριστικών οντότητας, μπορεί να γίνει με συνένωση των
λιστών ιδιοτήτων όλων των προγόνων της οντότητας:
(defun characteristics (symb p)
(setq p (append
(symbol-plist symb)
(cond
156

((eql nil (get symb 'parent)) p)
(characteristics (get symb 'parent)) )))

Με τη μεθόδευση αυτή, μεταβολές του δένδρου είναι δεκτές, και η αναζήτηση χαρακτηριστικών
βρίσκει πάντα την "τελευταία ενημέρωση" χωρίς άλλη σάρωση.
Αν θέλουμε να οργανώσουμε παράλληλα, περισσότερα του ενός δένδρα, χρήσιμο είναι να
καταχωρήσουμε σε κάθε οντότητα το σε ποιο δένδρο ανήκει, πχ. σε ιδιότητα "root" να δώσουμε
τιμή το σύμβολο της ρίζας, και να διακρίνουμε τα δένδρα βάσει αυτού του χαρακτηριστικού.

Η "από κάτω προς τα πάνω" μεθόδευση για προσδιορισμό κληρονομικότητας σε ένα
σημασιολογικό δίκτυο
Ανάλογα με το προηγούμενο, μπορούμε να αντιμετωπίσουμε και τη γενικότερη περίπτωση όπου η
δομή δεν είναι απλή ιεραρχία: η ενημέρωση κόμβου για το ποιους κόμβους κληρονομεί, είναι
δυνατό να αφορά περισσότερους του ενός ανώτερους κόμβους (δηλ. ένας κόμβος μπορεί να έχει
περισσότερους του ενός γονείς). Τέτοιες δομές είναι κατευθυνόμενα γραφήματα χωρίς κύκλους.

Στην περίπτωση αυτή μπορούμε να εκφράσουμε την έννοια "γονείς" ως ιδιότητα με όνομα parent
και τιμή τη λίστα των ονομάτων των κόμβων - γονέων. Η αναζήτηση χαρακτηριστικού θα γίνει σε
κάθε γονέα και κάθε πρόγονο γονέα, μέχρι να βρεθεί το χαρακτηριστικό.
Σημαντικό είναι, ποια προτεραιότητα θα δώσουμε στη σάρωση, διότι είναι δυνατό περισσότεροι
του ενός γονείς να έχουν το αναζητούμενο χαρακτηριστικό, καταχωρημένο απ' ευθείας και με
διαφορετική τιμή: αν η σάρωση σταματά με τον πρώτο εντοπισμό, το παιδί θα πάρει την πρώτη
τιμή που θα βρεθεί. Αρα παίζει ρόλο η σειρά αναζήτησης (αν θα είναι πρώτα οριζόντια ή
κατακόρυφη∙ σε οριζόντια, αν θα είναι από αριστερά προς τα δεξιά ή αντίθετα∙ σε κατακόρυφη,
αν θα είναι από κάτω προς τα πάνω ή αντίθετα). Θεωρούμε εδώ οτι ψάχνουμε για το
χαρακτηριστικό πρώτα στον ίδιο τον κόμβο, μετά στον πρώτο γονέα του, μετά στον πρώτο γονέα
αυτού του γονέα, κοκ, και μετά στους επόμενους γονείς.
(defun search-for-prop (obj prop)
(cond
((member prop (symbol-plist obj)) ; αν είναι ιδιότητα του κόμβου
(get obj prop))
; επίστρεψε την τιμή της

; αλλιώς κάνε το ίδιο ψάχνοντας στους γονείς:

(t (search-list-for-prop (parent-obj obj) prop) ) ))

Η SEARCH-LIST-FOR-PROP θα κάνει το ψάξιμο σε όλους τους γονείς, μέχρι να βρεθεί το
χαρακτηριστικό:
(defun search-list-for-prop (lis)
(cond
( (and (not (endp lis))
(search-for-prop (car lis)))
( t
(search-list-for-prop (cdr lis)) )))

Ο κάθε γονέας ελέγχεται, και αν δεν βρεθεί η ιδιότητα, η αναζήτηση πάει στους προγόνους του.
157

Όταν η λίστα των γονέων τελειώσει, θα επιστέψει απλώς NIL .

3.7 Δημιουργία συνδέσεων μέσω ζευγών

Συνδέσεις είναι σύνθετες οντότητες (αποκαλούνται "conses") που δημιουργούνται από δύο
υφιστάμενες οντότητες (όχι απαραίτητα διάφορες). Ο όρος "conses" είναι ένας νεολογισμός που
έρχεται ως πληθυντικός του "cons", σύντμηση του "construction", όπου CONS είναι και το όνομα
της βασικής συνάρτησης κατασκευής τέτοιων δομών. Τέτοιες συνδέσεις αποκαλούνται "ζεύγη με
τελεία" (dotted pairs), ή απλά ζεύγη.
Η έννοια του ζεύγους στη Lisp αποτελεί το θεμελιακό λίθο για την κατασκευή λιστών, και από
αυτές εφαρμογών∙ δηλαδή, την αρχή από οποιοδήποτε υπολογισμό.
Ζεύγη κατασκευάζονται άμεσα με χρήση από ένα πρωτογενές σύμβολο, την τελεία, και έμμεσα
με χρήση της συνάρτησης CONS .
Τα ζεύγη έχουν δύο κεντρικά χαρακτηριστικά:
α'.
Είναι υπολογιστικές οντότητες, που μπορούμε να τις επεξεργαστούμε όπως οποιαδήποτε
άλλη μορφή οντότητας.
β'.

Παρακολουθούν την εξέλιξη των δύο μελών τους.

Θα δούμε αναλυτικά τον τρόπο δημιουργίας ζευγών, τις συναρτήσεις επεξεργασίας τους, και το
σημαντικό ρόλο που παίζουν στη συναρτησιακή σύνθεση.

3.7.1 Συνδέσεις σε μορφή "ζευγών με τελεία"
Ζεύγη με τελεία (dotted pairs) είναι θεμελιακές δομές που κατασκευάζονται από δύο υφιστάμενες
οντότητες. Εκφράζουν τον κατηγορηματικό συμπερασματικό νόμο:
"αν έχουμε το Α και έχουμε το Β, τότε έχουμε και το ζεύγος Α&Β"
Η σύνταξη του ζεύγους στη Lisp είναι (με κενά γύρω από την τελεία):
(a . b)

όπου a και b εννοούνται ως οντότητες της Lisp. Ένα ζεύγος αποτελεί φόρμα που είναι οντότητα
τύπου sequence, και προέρχεται από τη σύνδεση των δύο μελών του.
Μπορούμε να συνδέσουμε νέα οντότητα, έστω c , με το ζεύγος (a . b) ως οντότητα:
(c . (a . b))

κατασκευάζοντας έτσι μια τριάδα. Όμοια μπορούμε να κατασκευάσουμε τετράδα, πεντάδα κοκ.,
ν-άδα με πλήθος ν διαδοχικά συνδεομένων οντοτήτων:
(e . (d . (c . (a . b))))

Ένα ζεύγος (ή τριάδα, κοκ) μπορεί να δοθεί ως τιμή σε μεταβλητή:
(setq pair1 '(a . b))
(setq triplet1 '(c . (a . b)) )

158

Η δομή της λίστας ως σύνθεση διαδοχικών ζευγών
Αν το δεύτερο μέλος ζεύγους είναι ειδικά η οντότητα nil , δηλ. έχει τη μορφή:
(a . nil)

τότε το ζεύγος αποκαλείται λίστα με ένα στοιχείο, το a , που συντομογραφικά γράφεται κατά τα
γνωστά ως (a) . Αν ένα τέτοιο ζεύγος αποτελέσει δεύτερο μέλος νέου ζεύγους
(c . (a . nil)

τότε αποκαλείται λίστα δύο στοιχείων, που συντομογραφικά γράφεται (c a)
Γενικά, μια ν-άδα της μορφής:
(a . (b . (c . … (d . nil) …)))

αποκαλείται λίστα των n στοιχείων και γράφεται συντομογραφικά κατά τα γνωστά:
(a b c … d)

H οντότητα nil ορίζεται ως η κενή λίστα, που με τη συντομογραφική έκφραση λίστας γράφεται
ως ( ) .

Λίστες με τελεία (dotted lists)
Η συντομογραφική μορφή της λίστας μπορεί να συνδυαστεί με τη χρήση τελείας ή τελειών.
Ιδιαίτερο νόημα έχει η χρήση πριν το τελευταίο στοιχείο: Τα προ της τελείας αναφερόμενα
στοιχεία νοούνται δομημένα κατά τα συνήθη της λίστας, ενώ η τελευταία "ουρά" τέτοιας λίστας
δεν είναι το NIL αλλά το τελευταίο στοιχείο:
(a b c d . e)

Είναι απλώς συντομογραφία της πεντάδας:
(a . (b . (c . (d . e))))

3.7.2 Κατασκευή σύνδεσης μέσω εκτέλεσης συνάρτησης

Η γραφή λίστας σε μορφή διαδοχικής σύνδεσης ζευγών, αν και αναγνωρίζεται από τη Lisp, δεν
είναι εύχρηστη, διότι απαιτεί να γραφούν όλες οι θεωρούμενες συνδέσεις, σε όλο το βάθος και
πλάτος των δομών που κτίζουμε. Για το λόγο αυτό χρησιμοποιούμε την απλοποιημένη γραφή της
λίστας.
Η κατασκευή συνδέσεων μέσω υπολογισμού απαιτεί και κάποια συνάρτηση που να επιτελεί αυτό
το έργο, και αυτή είναι η CONS . Χρειαζόμαστε επίσης και συναρτήσεις που να εισχωρούν μέσα
σε ζεύγος, τόσο για αναγνώριση των μελών του όσο για μεταβολή τους. Τα ζεύγη, επειδή
προκύπτουν ως αποτελέσματα εφαρμογής της CONS, αποκαλούνται και "κελιά cons" (cons cells).
cons
: Συνάρτηση δύο ορισμάτων, που δημιουργεί και επιστρέφει το ζεύγος τους. Τα
ορίσματα μπορούν να είναι οποιεσδήποτε φόρμες.
– Αν το δεύτερο όρισμα είναι NIL επιστρέφει την αντίστοιχη συντομογραφική έκφραση, της
λίστας ενός στοιχείου.
– Αν το δεύτερο όρισμα είναι λίστα μη κενή, εξ ορισμού της λίστας το αποτέλεσμα θα είναι
159

λίστα, και στην περίπτωση αυτή πάλι επιστρέφει τη συντομογραφική έκφραση της λίστας∙
αυτό πρακτικά το βλέπουμε σαν να "επισυνάπτει στοιχείο σε λίστα".
– Αν και τα δύο ορίσματα είναι άτομα, επιστρέφει "ζεύγος με τελεία". Αν το πρώτο όρισμα είναι
λίστα και τοδεύτερο άτομο, επιστρέφει "λίστα με τελεία".
(cons 'a 'b)
→ (a . b)
(cons 'a nil)
→ (a)
( είναι το ζεύγος (a . nil) )
(cons 'a '(b))
→ (a b)
( είναι η τριάδα (a . (b . nil)) )
(cons 'a '(b c d)) → (a b c d)
( είναι η πεντάδα (a . (b . (c . (d . nil)))) )
(cons '(a b) '(c d)) → ((a b) c d)
( είναι η τριάδα ((a . (b . nil)) . (c . (d . nil))) )
(cons '(a b) nil)
→ ((a b))
( είναι το ζεύγος ((a . b) . nil) )
(cons nil '(a b))
→ (nil a b)
( είναι η τετράδα (nil . (a . (b . nil))) )
(setq pair2 (cons ('a 'b))) → (a . b)
( ονομασία του ζεύγους (a . b) ως pair2 )
Η επεξεργασία που αναφέρεται σε συνθέσεις ζευγών, απαιτεί και μεθόδους "αποσύνθεσής" τους.
Οι στοιχειώδεις συναρτήσεις αποσύνθεσης είναι οι γνωστές μας CAR και CDR :
car :
Συνάρτηση ενός ορίσματος - ζεύγους που επιστρέφει το πρώτο μέλος του
(car '(a . b))
→ a
(car '(a . (b . c))) → a
Αν πρόκειται για την ειδική περίπτωση της λίστας, το πρώτο μέλος του ζεύγους αποτελεί στη
συντομογραφική μορφή της λίστας το πρώτο στοιχείο της λίστας, και από αυτό προκύπτει η
γνωστή χρήση τής CAR :
(car '(a . (b . (c . nil)))) → a
(car '(a b c))
→ a
(η έκφραση είναι ταυτόσημη με την προηγούμενη)
Συμβατικά τίθεται να επιστρέφει η CAR τιμή NIL όταν το όρισμά της είναι NIL
(car nil)
→ nil
cdr :
Συνάρτηση ενός ορίσματος - ζεύγους που επιστρέφει το δεύτερο μέλος του
(cdr '(a . b))
→ b
(cdr '(a . (b . c))) → (b . c)
Αν πρόκειται για την ειδική περίπτωση της λίστας, το δεύτερο μέλος του αποτελεί στη
συντομογραφική μορφή της λίστας τη λίστα από το δεύτερο στοιχείο της και πέρα, δηλ. την ουρά,
και από αυτό προκύπτει η γνωστή χρήση τής CAR
(cdr '(a . (b . (c . nil)))) → (b . (c . nil))
(cdr '(a b c))
→ (b c)
(cdr '(a . nil))
→ NIL
160

→ NIL
Συμβατικά τίθεται, το CDR του NIL να επιστρέφει NIL :
(cdr nil)
→ NIL
(cdr '(a))

3.7.3 Σχηματική παράσταση ζευγών και συνδέσεων
Η δυνατότητα κατασκευής ζευγών προέρχεται από το γεγονός οτι οι οντότητες της Lisp έχουν
υποδοχή σύνδεσης "προς". Αυτή η μορφή τους, επιτρέπει την αλυσιδωτή σύνδεση περισσοτέρων
οντοτήτων. Η μορφή του ζεύγους επιτρέπει επίσης τη διακλάδωση, οπότε μπορούμε να
σχηματίσουμε δενδροειδείς δομές.
Ακολουθώντας την αναπαράσταση οντοτήτων ως τετραγωνίδια, η σχηματική παράσταση από ένα
ζεύγος με τελεία, είδαμε στην αρχή οτι μπορεί να δοθεί ως "ένα τετραγωνίδιο που περικλείει τα
δύο συνδεδεμένα τετραγωνίδια - οντότητες":

Η σύνδεση οντότητας a με ζεύγος (b . c) ώστε να αποτελέσει το ζεύγος (a . (b . c)) , δίνεται από
το ακόλουθο σχήμα (το βέλος σημαίνει οτι το περιεχόμενο "είναι αυτό που δείχνει το βέλος"):

Η αναπαράσταση "επίπεδης" λίστας, έστω της (a b c d) , με τον τρόπο αυτό, είναι:

Αντίστοιχα, η αναπαράσταση της λίστας (a (b c) d) είναι:

Η ονομασία σύνδεσης ή λίστας, γίνεται απλά με την SETQ ή την SETF . Θα σημειώνουμε το
όνομα της σύνδεσης "έξω" από το τετραγωνίδιο απ' όπου θεωρούμε πως αρχίζει η σύνδεση (το
εξωτερικότερο στοιχείο):

Ας θεωρήσουμε τώρα οτι η λίστα ((a b) c d) δίνεται ως τιμή στη μεταβλητή list1 :
(setq list1 '((a b) c d)

161

Η κεφαλή της list1 είναι η λίστα (a b) . Eφαρμόζοντας στη λίστα list1 την "καταστροφική"
συνάρτηση (rplaca 'list1 3) παίρνουμε:

Παρατηρούμε πως ένα τμήμα έμεινε τώρα ασύνδετο, το:

Αυτό το τμήμα περιττεύει πλέον, διότι καμμία σύνδεση δεν οδηγεί σ' αυτό, και καταναλώνει
άσκοπα θέση στη μνήμη∙ θα διαγραφεί με την πρώτη εκτέλεση του "garbage collector", που
γίνεται αυτόματα κατά χρονικά διαστήματα ή μόλις η κατανάλωση μνήμης ξεπεράσει ένα
ποσοστό, αλλά μπορεί να ζητηθεί και επεξηγηματικά με εκτέλεση συνάρτησης: (GC) .
Έστω τώρα η λίστα list2 :
(setq list2 '(a b))

Εφαρμόζοντας τη συνάρτηση RPLACA που έχουμε δει, με δύο ταυτοτικά (ίσα βάσει της EQL)
ορίσματα - λίστες, δημιουργούμε μια κυκλική λίστα:
(rplaca list2 )

Ας παρατηρήσουμε τη διαφορά ανάμεσα στο προηγούμενο (αν δεν το δίναμε) και το ακόλουθο:
(rplaca list2 'list2)

162

Ορισμένες συναρτήσεις όπως οι RPLACA και RPLACD χαρακτηρίζονται από το οτι το
αποτέλεσμά τους είναι μόνιμο και διαρκές.
– Μόνιμο σημαίνει οτι το αποτέλεσμα παίρνει οριστικά τη θέση της φόρμας που είναι υπό
επεξεργασία, αντικαθιστώντας την αρχική.
– Διαρκές σημαίνει οτι αν η οντότητα - αποτέλεσμα Α (εσωτερικά ή συνολικά) συνδέεται με
κάποια άλλη οντότητα Β, και αν η Β υποστεί μελλοντικά οποιαδήποτε αλλαγή (αντίστ. η Α
υποστεί μελλοντικά οποιαδήποτε αλλαγή) η Α αυτόματα εξαρτάται από τη νέα μορφή της Β
(αντίστ. η Β αυτόματα εξαρτάται από τη νέα μορφή της Α).
Γι' αυτό είναι ουσιαστική η διάκριση ανάμεσα στην απ' ευθείας σύνδεση και την αντιγραφή που
προκαλούν ορισμένες συναρτήσεις. Με ένα τέτοιο διάγραμμα μπορούμε να δούμε καθαρά τη
σύνδεση με "μοιραζόμενη οντότητα" και το ρόλο που παίζουν οι αλλαγές συνδεδεμένων μερών:
Παράδειγμα
(setq x (list 'a 'b))
(setq z1 (cons x x))
(setq z2 (cons (list 'a 'b) (list 'a 'b)))

Φαινομενικά, οι μεταβλητές z1 και z2 είναι πανομοιότυπες. Αλλά αν παρακολουθήσουμε τη
διαδοχή των βελών στα αντίστοιχα σχήματα, θα δούμε οτι :
– Στη z1, και τα δύο μέλη του ζεύγους είναι η λίστα x , άρα δείχνουν στην αρχή της λίστας x :


Η λίστα z2 έχει δύο μέλη τα οποία δημιουργούνται με τον ίδιο τρόπο, από την εκτέλεση της
έκφρασης (list 'a 'b) .

Και τα δύο μέλη της z2 δείχνουν στη x , όμως δεν πρόκειται για "την αυτή οντότητα" αλλά για
δύο διαφορετικούς υπολογισμούς (κλήσεις) της λίστας x . Θα προκληθούν δύο εκτελέσεις 163

κλήσεις τής (list 'a 'b) , οι οποίες είναι μεν πανομοιότυπες από πλευράς ορισμού αλλά
διαφορετικές πλέον ως φυσικές οντότητες∙ και οι δύο κλήσεις δείχνουν στα αυτά σύμβολα a , b
αλλά όχι ταυτόχρονα. Τίθεται το ερώτημα:
Παίζει ρόλο αυτή η διαφοροποίηση σε υπολογισμούς όπου υπεισέρχονται οι z1 και z2 ;
Ας δώσουμε μια συνάρτηση που να αντικαθιστά την κεφαλή λίστας με συγκεκριμένο σύμβολο,
και να επιστρέφει την αλλαγμένη λίστα:
(defun change-car (lit)

;; θέσε περιεχόμενο στην κεφαλή της κεφαλής της λίστας lit το
; σύμβολο this και επίστρεψε την τιμή της λίστας lit
(setf (caar lit) 'this) lit)

Οι z1 και z2 έχουν πανομοιότυπο περιεχόμενο (κρίνεται ίσο με την EQL):
z1
→ ((a b) a b)
z2
→ ((a b) a b)
Αν εφαρμόσουμε την CHANGE-CAR στις z1 και z2 παίρνουμε αντίστοιχα:
(change-car z1)
→ ((this b) this b)
(change-car z2)
→ ((this b) a b)
(!!!)

Αν και δυσνόητο μέσα από τα σύμβολα, παρακολουθώντας τα σχήματα, φαίνεται καθαρά το
γιατί: προφανώς στην πρώτη περίπτωση το πρώτο a ταυτοποιείται ως φυσική θέση με το δεύτερο,
ενώ στη δεύτερη περίπτωση αποτελούν αποτελέσματα διαφορετικών κλήσεων.
Το παραπάνω δείχνει οτι η φαινομενική ταύτιση, δηλ. αυτή που βλέπει ο χρήστης μελετώντας
φόρμες, δεν σημαίνει και πραγματική (φυσική). Ακριβώς για το σκοπό αυτό υπάρχουν τα
διαφορετικά "ίσον", όπου με EQL παίρνουμε απάντηση στο παραπάνω οτι z1 και z2 είναι ίσα,
ενώ με EQ οτι δεν είναι ίσα.

3.7.4 Λίστες συσχέτισης
Αποκαλούμε λίστα συσχέτισης (association list) μια λίστα που αποτελείται από ζεύγη, δηλαδή
έχει τη μορφή:
((a1 . a2) (b1 . b2) … (z1 . z2))

Χρησιμεύει για την απόδοση 1:1 αντιστοιχίας δύο ομάδων οντοτήτων, πχ. "άνθρωποι" και
"επάγγελμά τους". Διαθέτουμε ειδικές συναρτήσεις που κατασκευάζουν τέτοιες λίστες, και
συναρτήσεις που ανιχνεύουν στοιχεία από τέτοιες λίστες.

Οι λίστες συσχέτισης αντιγράφονται με την ειδική συνάρτηση COPY-ALIST . Η συνάρτηση
COPY-ALIST δημιουργεί αντίγραφο - λίστα συσχέτισης που παρακολουθεί το πρωτότυπο, δηλ.
τυχόν μεταβολές στο περιεχόμενο των συμβόλων - μελών του πρωτότυπου μεταφέρονται στο
αντίγραφο. Άρα πρόκειται για διαρκή αντιγραφή.

Κατασκευή λίστας συσχέτισης
Λίστα συσχέτισης μπορούμε να κατασκευάσουμε είτε με κατ' ευθείαν αναγραφή των ζευγών:
164

( setq my-assoc-list '( (a . 1) (b . 2) (c . 3) ) )

είτε με χρήση της συνάρτησης pairlis που κατασκευάζει λίστα συνδεδεμένων ζευγών από τις
λίστες των αντιστοίχων δύο μελών:

pairlis : Συνάρτηση δύο ορισμάτων - λιστών. Επιστρέφει τη λίστα συσχέτισης, δηλ. των
ζευγών που σχηματίζονται από τους όρους αντίστοιχης τάξης. Αυτή η λίστα ζευγών επιστρέφεται
κατά την ανάστροφο σειρά από τη σειρά των μελών της. Αν οι αρχικές λίστες δεν είναι ίσου
μήκους, σταματά όταν τελειώσει η μικρότερη, αλλά το ταίριαγμα αρχίζει από την αρχή
(αριστερά) των δύο λιστών, άρα περισσεύουν τα στοιχεία από το τέλος της μεγαλύτερης:
(setq ls1 '(Γιάννης Κώστας Μαρία Θανάσης))
(setq ls2 '(Μαθηματικός Φυσικός Χημικός))
(setq κατάλογος (pairlis ls1 ls2)) →
→ ( (Μαρία . Χημικός)
(Κώστας . Φυσικός)
(Γιάννης . Μαθηματικός) )

Αναζήτηση στοιχείων σε λίστας συσχέτισης
Η αναζήτηση στοιχείων που αναφέρονται μέσα σε λίστα ζευγών μπορεί κατ' αρχήν να γίνει με
συναρτήσεις επεξεργασίας κοινής λίστας, όπως:
(defun επάγγελμα (πρόσωπο λίστα)
(cond ((eql πρόσωπο (caar λίστα)) (cdar λίστα))
(t (επάγγελμα πρόσωπο (cdr λίστα)))))

Κλήση (σε σχέση με την λίστα "κατάλογος" που ορίστηκε προηγουμένως):
(επάγγελμα 'Κώστας κατάλογος) → Φυσικός
αλλά είναι πιο άμεσο να γίνει με τις ειδικές συναρτήσεις επεξεργασίας λίστας ζευγών ASSOC
και RASSOC :

assoc
: Συνάρτηση δύο ορισμάτων. Πρώτο όρισμα είναι κάποιο πρώτο μέλος ζεύγους που
ανήκει σε λίστα συσχέτισης, και δεύτερο όρισμα είναι η λίστα συσχέτισης στην οποία θα γίνει η
αναζήτηση. Επιστρέφει το αντίστοιχο δεύτερο μέλος:
(assoc 'Μαρία κατάλογος) → Χημικός

rassoc : Συνάρτηση δύο ορισμάτων. Πρώτο όρισμα είναι κάποιο δεύτερο μέλος ζεύγους, που
υποτίθεται οτι ανήκει σε λίστα συσχέτισης, και δεύτερο όρισμα είναι η λίστα συσχέτισης στην
οποία θα γίνει η αναζήτηση. Επιστρέφει το αντίστοιχο πρώτο μέλος:
(rassoc 'Χημικός κατάλογος) → Μαρία
► Αν υπάρχουν στοιχεία που ταιριάζουν με περισσότερους του ενός δυνατούς τρόπους, οι
ASSOC και RASSOC επιστρέφουν μόνο το πρώτο ταίριαγμα, στη σειρά που τα περιλαμβάνει η
λίστα ζευγών (η οποία, πρέπει να θυμόμαστε οτι είναι ανάποδη από τις λίστες από τις οποίες
κατασκευάστηκε).

165

Μεταβολή στοιχείου μέσα σε λίστα συσχέτισης
Γίνεται με εντοπισμό της θέσης του στοιχείου, και τοποθέτηση τού νέου περιεχομένου τής θέσης
με την SETF :
(setf (cdr (second κατάλογος)) 'Αστροφυσικός)

Τώρα έχουμε:
(assoc 'Κώστας κατάλογος)

→ Αστροφυσικός

Προσθήκη ζεύγους σε λίστα συσχέτισης
Μπορούμε να επισυνάψουμε ένα νέο ζεύγος ως πρώτο σε λίστα ζευγών:
acons : Συνάρτηση τριών ορισμάτων: τα δύο πρώτα θα σχηματίσουν το νέο ζεύγος, και τρίτο
είναι η λίστα ζευγών στην οποία θα καταχωρηθεί το νέο ζεύγος, το οποίο θα καταχωρηθεί πρώτο,
δεδομένου οτι η λίστα αυτή κατασκευάζεται προς τα αριστερά:
(acons 'Νίκη 'Γιατρός κατάλογος)

→ ( (Νίκη . Γιατρός)
(Μαρία . Χημικός)
(Κώστας . Αστροφυσικός)
(Γιάννης . Μαθηματικός) )

3.8 Ασκήσεις
1. α) Να δώσετε συνάρτηση COUNT-DEPTH ενός ορίσματος, που να μετρά το βάθος (επίπεδα)
της εισόδου της, προσδιορίζοντας ως μηδενικό βάθος την περίπτωση όπου η είσοδος είναι
άτομο, βάθος 1 όταν είναι (μη εκτελέσιμη) λίστα που αποτελείται από άτομα, βάθος 2 αν
κάποιο(-α) στοιχείο(-α) της λίστας είναι λίστα ατόμων κοκ. Δηλ, βάθος να είναι το
μεγαλύτερο πλήθος από "παρενθέσεις που έχουν ανοίξει διαδοχικά χωρίς να έχουν κλείσει".
β) Να δώσετε συνάρτηση COUNT-ATOMS , ενός ορίσματος - λίστας, που να μετρά το
πλήθος των ατόμων που υπάρχουν σε οποιοδήποτε βάθος μέσα στη λίστα.
γ) Να δώσετε συνάρτηση COUNT-EVERYTHING , ενός ορίσματος - λίστας, που να μετρά
τους όρους - στοιχεία όλων των μορφών, που υπάρχουν σε όλα τα επίπεδα της λίστας.
2. α) Να δώσετε συνάρτηση REVERSE-ALL, ενός ορίσματος – δένδρου σε μορφή λίστας
λιστών κοκ, που να αντιστρέφει τη διάταξη όλων των λιστών του δένδρου σε όλο το βάθος
του δένδρου.
β) Να δώσετε συνάρτηση REPLACE-ALL , δύο ορισμάτων, όπου το πρώτο είναι λίστα από
ζεύγη και το δεύτερο λίστα, που να αντικαθιστά στη δεύτερη λίστα κάθε στοιχείο που
συμβαίνει να είναι πρώτο στοιχείο ζεύγους της πρώτης λίστας, με το αντίστοιχο δεύτερο του
ζεύγους.
3. Να δώσετε συνάρτηση RANDOM-IN-INTERVAL που να δέχεται δύο ορίσματα - αριθμούς και
να επιστρέφει τυχαίο αριθμό αυστηρά μέσα στο ανοικτό διάστημά τους, και (υπο)τύπου του
166

ισχυρότερου των άκρων.
4.

Να δώσετε συνάρτηση ALL-COND παρεμφερή με την COND αλλά τέτοια που να εκτελεί
όλες τις “γραμμές” των οποίων η υπόθεση.

5. Να δώσετε συνάρτηση FIND-ALL-PROPS που να δέχεται όρισμα λίστα και να επιστρέφει για
κάθε στοιχείο της λίστας: α) αν είναι σύμβολο, το σύνολο των ιδιοτήτων του και αντιστοίχων
τιμών, γραμμένα κατά ζεύγη∙ β) αν είναι λίστα, αντίστοιχα να κάνει το ίδιο για κάθε στοιχείο
της και να επιστρέφει συνολικά σε λίστα τις ιδιότητες και τιμές όλων∙ γ) να επαναλαμβάνει
το ίδιο σε κάθε βάθος.
6. Να γράψετε συνάρτηση TRANSLATE τριών ορισμάτων που να μεταφράζει λέξεις, από τρεις
φυσικές γλώσσες, με πρώτο όρισμα τη λέξη, δεύτερο τη γλώσσα όπου δίνεται η λέξη και τρίτο
τη γλώσσα όπου ζητείται η απάντηση. Για το σκοπό αυτό να προσδιορίσετε ένα κατάλληλο
"λεξικό" αντιστοιχίας λέξεων.

7. Να δημιουργήσετε μια δενδροειδή δομή και να προσδιορίσετε συνάρτηση κληροδότησης
ιδιοτήτων από κόμβο σε κλάδους. Να προσδιορίσετε συνάρτηση find που να ψάχνει μέσα στο
παραπάνω δένδρο και να επιλέγει τα σύμβολα που έχουν συγκεκριμένη ιδιότητα, δίνοντας
μόνο τον ανώτερο κόμβο που έχει την ιδιότητα.
8.

Να ορίσετε δομή “τραπέζι”, με ιδιότητες "σχήμα", "αριθμός από πόδια", "διάσταση" και
αντίστοιχες τιμές (κατάλληλου τύπου) και δημιουργηθεί ένα σύμβολο με δομή που να
αποδίδει το νόημα "στρογγυλό τραπέζι διαμέτρου 1.20 μ".
Με δεδομένο οτι σε ένα στρογγυλό τραπέζι κάθεται κανείς άνετα αν διαθέτει χώρο πλάτους
άνω των 80 εκ., περιορισμένα σε λιγότερο και το ελάχιστο μέχρι 60 εκ., να σχηματιστούν
συναρτήσεις που να απαντούν στο “αν είναι δυνατό να φάνε n άτομα σε συγκεκριμένο
στρογγυλό τραπέζι”, για παράδειγμα: (is-possible-to-eat-on 'table3 6) , και επίσης να
απαντά στο "πόσα άτομα κάθονται άνετα στο τραπέζι αυτό".

9. α) Να οριστεί συνάρτηση F τριών αριθμητικών ορισμάτων, Α , Β , C , η οποία να επιλύει την
εξίσωση Αx + B = C (απαντώντας: x = … ).
β) Να οριστεί διαδικασία G η οποία να επιλύει την εξίσωση:
Αx + B = C , όπου A , B , C είναι καθολικές μεταβλητές.
γ) Να οριστεί συνάρτηση S ορισμάτων x και y, που να χρησιμοποιεί τις καθολικές
αριθμητικές μεταβλητές A1 , B1 , C1 , A2 , B2 , C2 και να επιλύει το σύστημα:
(

Α1x + B1y = C1

,

Α2x + B2y = C2

)

δ) Να οριστεί συνάρτηση που να επιτελεί τη διαδικασία επίλυσης της δευτεροβάθμιας
εξίσωσης (χρησιμοποιείστε όποια προσέγγιση θέλετε).
10. α) Να οριστεί συνάρτηση που να δέχεται είσοδο λίστα από τριάδες ακεραίων αριθμών ή
αριθμών – πηλίκων και να απαντά ποιες από αυτές (αναφέροντάς 'τες) είναι πλευρές τριγώνου,
και από αυτές, ποιες είναι πλευρές ορθογωνίου τριγώνου.
β) Να οριστεί συνάρτηση που να δέχεται είσοδο λίστα από τριάδες δεκαδικών αριθμών και
να απαντά ποιες από αυτές (αναφέροντάς 'τες) είναι πλευρές ορθογωνίου τριγώνου, όπου το
167

κριτήριο ισότητας αριθμών να αναφέρεται στα δύο πρώτα δεκαδικά τους, και μόνον (όχι
στρογγυλευμένα).

11. α) Να οριστεί συνάρτηση που να δέχεται ως όρισμα μια λίστα ακεραίων αριθμών, και να
επιστρέφει λίστα με πρώτο όρο τον ίδιο με της λίστας εισόδου και επόμενους τα γινόμενα των
όρων της λίστας εισόδου μέχρι την ίδια τάξη. Πχ. για είσοδο (1 2 3 4) να επιστρέψει
(1 2 6 24) .

β)Να οριστεί συνάρτηση που δέχεται ως όρισμα μια λίστα ακεραίων αριθμών, και
επιστρέφει τη λίστα όρων που αποτελούν το άθροισμα των τετραγώνων τους μέχρι την ίδια
τάξη. Πχ. για είσοδο (1 2 3 4) να επιστρέψει (1 5 14 30)
12. Να γραφεί συνάρτηση Ρ δύο ορισμάτων - λιστών, που: να μετατρέπει τις λίστες εισόδου L1,
L2 σε σύνολα S1, S2, να τυπώνει: τα σύνολα αυτά, την ένωσή τους, την τομή τους και τη
διαφορά τους, και ως συνάρτηση να επιστρέφει την ένωση των συνόλων. Σχηματίστε τις
λίστες a και b 100 όρων, με στοιχεία που παράγει η (RANDOM 100) , και εφαρμόστε την Ρ σ'
αυτές.

13. Κατασκευάστε συναρτήσεις παρόμοιες των ASSOC και RASSOC που να επιστρέφουν το
σύνολο των δυνατών ταιριαγμάτων ζευγών (και όχι μόνο το πρώτο).
14. Να γράψετε πρόγραμμα που να επιτρέπει την αναζήτηση α) του συνόλου των καταχωρήσεων
σε ένα πίνακα κατακερματισμού που ορίζεται μέσα στο πρόγραμμα αυτό, και β) του συνόλου
των εισόδων του.

168

4 Επικοινωνία, Διαχείριση Χώρου
Στο κεφάλαιο αυτό θα δούμε σε συντομία διάφορα θέματα που αφορούν τη σχέση προγράμματος
με το περιβάλλον του: περιβάλλον συγγραφής, ευρύτερο περιβάλλον χρήσης, επικοινωνία με το
χρήστη, ανταλλαγή δεδομένων, κά.
Θα δούμε τη δυνατότητα "πακεταρίσματος" συμβόλων κατά κατηγορίες και αρχεία, τεχνική που
αξιοποιείται σημαντικά τόσο σε επίπεδο προγραμματισμού (φόρτωμα του κατάλληλου
προγραμματιστικού περιβάλλοντος, ως χώρου χρήσιμων επαναχρησιμοποιήσιμων συναρτήσεων,
και ως χώρου υποστήριξης του προγραμματιστή) όσο και σε επίπεδο εκτέλεσης (μη επιβάρυνση
της μνήμης με περιττά σύμβολα, σε κάθε φάση προγραμματισμού ή/και εκτέλεσης).

4.1 Το πρόγραμμα και το περιβάλλον του
Εδώ θα αναλύσουμε ορισμένα ζητήματα που αφορούν την επικοινωνία προγράμματος με τον
"εξωτερικό κόσμο". Η έννοια του εξωτερικού κόσμου περιλαμβάνει τον χρήστη και τα αρχεία.

4.1.1 Προσαρμογή της λειτουργικότητας του προγράμματος

Συχνά ορίζουμε συναρτήσεις που έχουν σκοπό να εξυπηρετήσουν τον τελικό χρήστη να καλέσει
κάποιες συναρτήσεις ή γενικότερα φόρμες, που από τον ορισμό τους, είτε έχουν πολύπλοκη
σύνταξη και η χρήση τους γίνεται ευκολότερη αν δοθεί μια πιο εύχρηστη μορφή (όπως σε
προηγούμενο παράδειγμα, με το "σπασμένο δάκτυλο", το σύμβολο "αριστερός-δείκτης" ως
συνάρτηση του προσώπου στο οποίο ανήκει), είτε έχουν βοηθητικές παραμέτρους που είναι από
πριν γνωστό ποιά τιμή πρέπει να δώσει ο χρήστης όταν θα τις καλέσει (όπως οι αρχικές τιμές
στην επαναληπτική FIBONACCI), είτε είναι συναρτήσεις της Lisp με όνομα που δεν είναι
σημασιολογικά συνδεδεμένο με το context χρήσης και είναι σκόπιμο να αλλαχτεί με άλλο που να
έχει πιο άμεσο νόημα (για το λόγο αυτό το CAR έγινε FIRST).
Τέτοιες συναρτήσεις είναι των εξής κατηγοριών:
α'. Διεπαφής με το χρήστη (user interface) όταν δίνουν την κατάλληλη πληροφορία στον χρήστη
(τί διατίθεται, πώς χρησιμοποιείται) και προσαρμόζουν τη λειτουργικότητα ανάλογα με τις
επιλογές του χρήστη, προκαλώντας εσωτερικά την αναγκαία σύνθεση συναρτήσεων και την
εφαρμογή της.
β'. Διευκόλυνσης, που κάνουν την χρήση κάποιων συναρτήσεων ευκολότερη∙ είναι ειδική
περίπτωση της προηγούμενης. Έχουν διάφορους στόχους, όπως: i) τη νοηματική προσαρμογή των
ονομάτων συμβόλων, ii) τη συγκεκριμενοποίηση γενικότερης συνάρτησης περιορίζοντας τις
169

διαθέσιμες κλήσεις (με την κατάλληλη κατά περίπτωση δέσμευση κάποιων λ-μεταβλητών), iii)
την οργάνωση ομαδικής επεξεργασίας συνόλου ομοειδών οντοτήτων μέσω μιας μόνο κλήσης.
γ'. Προσαρμογής στο context, που εντάσσουν και εξειδικεύουν τη συμπεριφορά των
χρησιμοποιουμένων συναρτήσεων στα δεδομένα και τις απαιτήσεις του περιβάλλοντος χρήσης.
Τεχνικά ανάγονται στις περιπτώσεις α' και β', αλλά έχουν την έννοια προσαρμογής ολόκληρης
ομάδας συμβόλων στο context αναφοράς.
δ'. Εξειδίκευσης λειτουργικότητας, που προκαλούν εσωτερικά μερική εφαρμογή γενικότερων
συναρτήσεων ώστε να προσαρμοστούν στα δεδομένα, όπως τον τύπο ή το πλήθος των
αντικειμένων εφαρμογής.
ε'. Διεύρυνσης λειτουργικότητας, που με βάση τη μορφή μιας διαθέσιμης συνάρτησης κτίζεται
συνάρτηση που καλύπτει ευρύτερες περιπτώσεις.
Οι παραπάνω κατηγορίες αποτελούν περισσότερο τρόπο που εμείς βλέπουμε τέτοιες συναρτήσεις
παρά τυπικά διαφοροποιούμενες κατηγορίες συναρτήσεων.
Ακόμα, δεδομένου οτι:
– ο τελικός χρήστης πρόκειται να χρησιμοποιήσει συνθετικά κάποιες συναρτήσεις του
προγράμματος, και σε μερικές περιπτώσεις μπορεί να επέμβει προγραμματιστικά στον κώδικα
του προγράμματος,
– αλλά οπωσδήποτε θα επέμβει με ένα τρόπο τεχνικά απλούστερο από αυτόν του αρχικού
προγραμματιστή, ο οποίος έχει πολύ βαθύτερη γνώση των λειτουργιών του προγράμματος που
σχεδίασε και συνέταξε,
– και βέβαια οι παρεμβάσεις θα γίνονται μόνο σε συγκεκριμένα σημεία του κώδικα, όπου οι
μεταβολές έχουν σημασία για τη χρήση,
είναι σκόπιμο να προσφέρουμε στον τελικό χρήστη ένα σύνολο συναρτήσεων που να
διευκολύνουν τις παρεμβάσεις όπου κρίνεται οτι θα φανεί χρήσιμο. Δεν είναι αναγκαίο να
προβλεφθούν τα πάντα, διότι είναι δυνατό να οργανωθούν και εκ των υστέρων τέτοιες
συναρτήσεις.
Οι συναρτήσεις αυτές δεν διαφέρουν από τις κοινές παρά μόνο ως προς τη σημασία που τους
αποδίδουμε για την χρήση του προγράμματος. Οπωσδήποτε πρέπει να λαμβάνεται υπ' όψη οτι η
ύπαρξη τέτοιων συναρτήσεων στον κώδικα κατά κανόνα μειώνει την αποτελεσματικότητα και
συνεπώς πρέπει να συνεκτιμώνται οφέλη και κόστος.
Σε συνθέσεις προγραμμάτων ψηλής αλληλεπίδρασης (έμπειρα συστήματα, εκπαιδευτικά
περιβάλλοντα...) ο ορισμός συναρτήσεων διεπαφής είναι δυνατό να στοχεύει στο να διευκολύνει
τον χρήστη να συνενώνει τη λειτουργικότητα του προγράμματος με γνώσεις που προβλέπεται να
έχει. Τέτοιες συναρτήσεις κατά κανόνα δέχονται είσοδο ψηλής νοηματικής συνθετότητας, που
οδηγεί σε πολύπλοκη κατασκευαστική συνθετότητα.
Στις συνηθέστερες περιπτώσεις, η διευκόλυνση αφορά την απλούστευση του περιβάλλοντος, πχ.
στην περίπτωση χρήσης από παιδιά χρειάζεται η τάδε παρουσίαση της πληροφορίας (πχ. η γλώσσα
LOGO).
170

Στην κατηγορία διεπαφής ανήκουν και συνθετότερες δομές συναρτήσεων που, με απλή χρήση
γραφικού περιβάλλοντος, εικονιδίων και ποντικιού, βοηθούν να κατασκευάζουμε
αλληλοσυσχετισμένα παράθυρα εξειδικευμένης λειτουργικότητας.
Παραδείγματα
1. Διευκόλυνση του χρήστη στην καταχώρηση ιδιοτήτων σε σύμβολο:
Να γραφεί συνάρτηση που να καταχωρεί σε δοσμένο σύμβολο, ιδιότητα με αντίστοιχη τιμή.
(defun put-property (object property value)
(cond ( (symbolp object)
(setf (get object property) value) )
( (setq object 'initialized)
(setf (get object property) value) )

) )

Χρήσιμο είναι να δίνεται και κάποιου είδους υποστήριξη στη χρήση της, όπως:
(setq str “put-property: συνάρτηση τριών ορισμάτων: αντικείμενο,
ιδιότητα, τιμή”)
(defun help-me-on-properties ( ) (print str))

Χρήση:
help-me-on-properties

“put-property: συνάρτηση τριών ορισμάτων: αντικείμενο, ιδιότητα, τιμή”

Αυτό οδηγεί πιό εύκολα το χρήστη, πχ. να δώσει:
(put-property 'Κώστας 'επάγγελμα 'υδραυλικός)

2. Μετονομασία συναρτήσεων:
Μετονομασία συνάρτησης σε όνομα που αντιλαμβάνεται άμεσα ο τελικός χρήστης
Είναι δυνατό να αλλάξει κανείς τα χρησιμοποιούμενα ονόματα συναρτήσεων της Lisp, και έτσι
να σχηματίσει μια "δική του" γλώσσα, πιο κατανοητή (κοινών συναρτήσεων με DEFUN,
macros με DEFMACRO) :
(defun head (lista) (car lista) )
(defmacro πρόσθεσε-νέο-στοιχείο-σε-λίστα
(στοιχείο όνομα-λίστας)
`(push στοιχείο ,όνομα-λίστας) )

3. Προσαρμογή της συμπεριφοράς συναρτήσεων:
Προσαρμογή της συμπεριφοράς συναρτήσεων πιο κοντά στον τρόπο που ο τελικός χρήστης
αντιλαμβάνεται τον υπολογισμό που υποδηλώνει το όνομα της συνάρτησης:
Η "κεφαλή", για τον τελικό χρήστη, είναι έννοια που θα μπορούσε να χαρακτηρίζει το πρώτο
στοιχείο από ομάδα όμοιων, όπου αν δεν υφίσταται ομάδα αλλά ένα και μοναδικό στοιχείο, η
κεφαλή να είναι το στοιχείο αυτό.
(defun head-like (object)
171

(cond
((listp object) (car object))
((string object) (char object))
((char object) (char object))
((symbolp object) object) ) )

Kλήση:
(head-like
(head-like
(head-like
(head-like

'(3 4 5))
'Ντίνος)
"Ντίνος")
#\Ν)






3
Ντίνος
#\Ν
#\Ν

4.1.2 Αυτόματη γέννηση συμβόλων

Συχνά, και ιδιαίτερα κατά τη λειτουργία (εκτέλεση) προγράμματος που αυτοεξελίσσεται,
χρειάζεται να δημιουργηθούν με αυτόματο τρόπο σύμβολα – μεταβλητές. Η CL διαθέτει δύο
συναρτήσεις που γεννούν σύμβολα, την GENSYM και την GENTEMP :
gensym : Συνάρτηση που επιστρέφει ένα νέο σύμβολο:
– χωρίς όρισμα, δίνει όνομα με πρόθεμα G όπου ακολουθεί ένας αριθμός (αύξων στην ίδια
χρήση του προγράμματος)
– με όρισμα ακέραιο αριθμό, έστω 14 , γεννά το σύμβολο G14 (αν ήδη υπάρχει, δεν δημιουργεί
νέο, ούτε το καταστρέφει)
– με όρισμα string, θέτει ως πρόθεμα στον αύξοντα αριθμό το string αυτό αντί του G
Ο αύξων αριθμός των συμβόλων που γεννώνται είναι η τιμή της ειδικής μεταβλητής GENSYMCOUNTER , που αυξάνεται κατά ένα με κάθε νέο σύμβολο που γεννά η GENSYM ή η
GENTEMP κατά την ίδια περίοδο χρήσης του προγράμματος.
(gensym)
→ G513
(gensym "my") → my514

Προσοχή: Το δημιουργούμενο σύμβολο δεν εντάσσεται σε πακέτο ("uninterned") και σε
ορισμένους compilers δεν είναι χρησιμοποιήσιμο χωρίς να γίνει μια επεξηγηματική καταχώρηση
στο χρησιμοποιούμενο πακέτο. Αυτό γίνεται με απλή χρήση (πρόθεση) της συνάρτησης IMPORT
η οποία αν κληθεί με ένα όρισμα - όνομα συμβόλου, το εισάγει στο τρέχον πακέτο (βλέπε
παράδειγμα παρακάτω). Μερικοί compilers θέτουν το παραγόμενο όνομα μέσα σε "μπάρες", που
είναι χαρακτήρες του ονόματος (πχ. |G513| ).
gentemp : Συνάρτηση που επιστρέφει ένα νέο σύμβολο, όπως και η προηγούμενη, με τη
διαφορά οτι το τοποθετεί αυτόματα μέσα στο τρέχον πακέτο, ή στο πακέτο που θα προσδιοριστεί.
– χωρίς όρισμα, γεννά σύμβολο με αρχικό χαρακτήρα Τ που τον ακολουθεί ένας αύξων
αριθμός.
– με ένα όρισμα τύπου string, γεννά σύμβολο με αρχικό μέρος το string αυτό.
172

– αν έχει και δεύτερο όρισμα, αυτό πρέπει να είναι όνομα πακέτου, και είναι αυτό όπου θα
τοποθετηθεί το σύμβολο.
(gentemp)
→ T14
(gentemp "my-symbol-") → my-symbol-15
(eval `(setq ,(print (gentemp) ) 12)) →
T4 (εκτύπωση λόγω της PRINT)
12 (επιστροφή τιμής του υπολογισμού)
Ποιά είναι η τιμή του Τ4 ;
Τ4
→ 12

H GENTEMP είναι πιο ευέλικτη από την GENSYM , λόγω της αυτόματης σύνδεσης με το τρέχον
πακέτο. Μια διαφορά μεταξύ των GENSYM και GENTEMP είναι οτι, αν η χρήση του
δημιουργούμενου συμβόλου είναι τοπική, με το πέρας της χρήσης του ο αύξων αριθμός
συμβόλων μειώνεται κατά μια μονάδα.
Παραδείγματα
1. Πολλαπλές δράσεις πάνω σε νεοδημιουργούμενο σύμβολο:
Να οριστεί συνάρτηση που να δημιουργεί σύμβολο - μεταβλητή με πρόθεμα δοσμένο string, να
τυπώνει το όνομά του, και να καταχωρεί σ' αυτό μια τιμή που θα διαβάζει από την είσοδο.

Αν χρησιμοποιήσουμε την GENSYM , χρειαζόμαστε και την εισαγωγή του συμβόλου στο
τρέχον πακέτο (package) συμβόλων, άρα χρήση της IMPORT . Επειδή χρειαζόμαστε πολλαπλές
λειτουργίες που αφορούν το δημιουργούμενο σύμβολο, πρέπει οι διαδοχικά εφαρμοζόμενες
συναρτήσεις σ' αυτό να δίνουν έξοδο το ίδιο το σύμβολο. Λόγω του οτι η IMPORT δίνει έξοδο
Τ όταν πετύχει, ορίζουμε μια δική μας "import", έστω IMPORT-ELEMENT , που να επιστρέφει
την είσοδό της (θα δούμε παρακάτω την τεχνική αυτή, ως βάση της διασωλήνωσης). Η ίδια
συνάρτηση μπορεί να αναλάβει και την εκτύπωση:
(defun import-element (s)
(import s)
; να γίνει εισαγωγή του ορίσματος στο πακέτο,
(print s)
; να τυπωθεί το όνομά του,
s)
; και να επιστραφεί το σύμβολο αυτούσιο.

Η απόδοση τιμής σε σύμβολο προϋποθέτει οτι το όνομα του συμβόλου είναι δοσμένο
(θεωρείται "quoted") και δεν είναι δυνατό να είναι αποτέλεσμα υπολογισμού.
Επειδή είναι απαραίτητο να κληθεί πρώτα η GENSYM , πάνω στο αποτέλεσμα της οποίας να
εφαρμοστεί η SETF ή η SETQ , χρησιμοποιούμε την "backquote" για καθυστέρηση του
υπολογισμού, σε συνδυασμό με την "comma" για να δώσουμε προτεραιότητα υπολογισμού
στην GENSYM , και ενεργοποιούμε τον καθυστερημένο υπολογισμό με την EVAL . Έστω οτι
το string είναι "aa" και θέλουμε να καταχωρηθεί στο σύμβολο η τιμή 8:
(eval
`(setf ,(import-element (gensym "aa")) 8) )
→ |aa36|
(εκτύπωση λόγω της IMPORT-ELEMENT)
→ 8
(επιστρεφόμενη τιμή από τον υπολογισμό της SETF)

Επομένως, η ζητούμενη συνάρτηση είναι:
173

(defun create-print-and-assign (strin val)
(eval `(setf ,(import-element (gensym strin)) val) ))

Είναι αξιοπαρατήρητο με πόσο μικρό κώδικα περιγράφουμε τον υπολογισμό αυτό.
2. Μαζική παραγωγή νέων συμβόλων και τοποθέτησή τους σε λίστα:
Να γραφεί συνάρτηση που να επιστρέφει λίστα με ν νέα σύμβολα
Η παρακάτω συνάρτηση ADD-SYMBOLS δέχεται τρία ορίσματα: το πρώτο νοείται οτι θα είναι
ένας φυσικός αριθμός, το δεύτερο ένα string και το τρίτο μια λίστα. Επιστρέφει τη λίστα
εισόδου με επί πλέον n νέα σύμβολα:

(defun add-symbols (n str lis)
(cond
( (= n 0) (import-list lis))
( t (push (gensym str) lis) (add-symbols str lis))))
Η IMPORT-LIST πρέπει να οριστεί, για τον ίδιο λόγο με την παραπάνω IMPOR : Δεν
μπορούμε να χρησιμοποιήσουμε τη συνάρτηση IMPORT διότι εισάγει μεν λίστα συμβόλων
αλλά επιστρέφει Τ :
(import-list (lis) (import lis) lis)

Εφαρμογή:
(add-symbols 5 "we" '( ) )
→ ( |we535| |we534| |we533| |we532| |we531| )

(μορφή απάντησης στην Allegro CL)
3. Δημιουργία νέας συνάρτησης:
Να γραφεί συνάρτηση ενός ορίσματος τύπου string, που να δημιουργεί ένα νέο σύμβολο συνάρτηση μίας μεταβλητής, με όνομα που να έχει αρχικό μέρος το string εισόδου της, και να
αποδίδει στο σύμβολο αυτό συναρτησιακό περιεχόμενο (σώμα), αυτό που θα διαβάσει από την
είσοδο. Πρέπει να επιστρέφει το όνομα του συμβόλου.
(defun create-new-function (str)
(eval `(defun ,(gentemp str) (x) ,(read)))

Εφαρμογή:
((create-new-function "my-function-")

;; δημιουργεί το νέο σύμβολο - συνάρτηση και φέρνει σε κατάσταση
; εισόδου, για να διαβάσει το σώμα της συνάρτησης∙ δίνουμε:
(+ x 3)

; επιστρέφει το όνομα της νέας συνάρτησης:
→ |my-function-15|
Αν καλέσουμε τη συνάρτηση αυτή:
(|my-function-15| 4) → 7

174

4.1.3 Διασωλήνωση
Διασωλήνωση είναι η τεχνική όπου διαδοχικοί υπολογισμοί χρησιμοποιούν την αυτή τιμή
εισόδου, η οποία "περνά" μέσα από κάθε υπολογισμό για να δοθεί στον επόμενο.

Η συναρτησιακή σύνθεση προσφέρεται για την οργάνωση τέτοιας λειτουργικότητας, διότι οι
υπολογιστικές συναρτήσεις μπορούν να έχουν ταυτόχρονα όψη ταυτοτικής συνάρτησης μιας
μεταβλητής, δηλ. που επιστρέφει την τιμή εισόδου της, και αλγόριθμου, που επιτελεί ένα
προκαθορισμένο έργο, όπου η τιμή εισόδου είναι δυνατό να είναι οποιαδήποτε φόρμα.

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

Η σημασία της διασωλήνωσης είναι κάπως διαφορετική από τη συνήθη σύνθεση συναρτήσεων
διότι: i) δεν ταυτίζουμε τη διαδοχή εκτέλεσης των διεργασιών με τη διαδοχή σύνθεσης των
συναρτήσεων και ii) ενδέχεται να χρειαζόμαστε μεταφορά από αυτούσιο το αντικείμενο
δέσμευσης ως προς το οποίο νοείται η διασωλήνωση.

Διασωλήνωση με μεταβίβαση αντιγράφου της τιμής εισόδου σε κάθε βήμα
Μια απλή τεχνική με την οποία πετυχαίνουμε τη διασωλήνωση, είναι να προκαλέσουμε ως έξοδο
κάθε βήματος την είσοδό του. Αν τα συναρτησιακά βήματα συμπεριλαμβάνουν περισσότερες
διεργασίες, σε σειρά ή παράλληλες, μπορούμε με χρήση καθυστερήσεων να καθορίσουμε τη
διαδοχή των εκτελέσεων, χωρίς υποχρεωτική υπακοή στη διαδοχή των συνθέσεων: αρκεί γι' αυτό
να εντάξουμε τη συνολική διασωλήνωση σε μια συνάρτηση - περίβλημα που θα καθορίζει τη
σύνθεση των συναρτήσεων και θα διαχειρίζεται την προτεραιότητα των εκτελέσεων των
διεργασιών.

175

Το παραπάνω ζήτημα είναι ευρύτατο και ανήκει στο χώρο των διερευνήσεων του τί μπορεί
να πετύχει ο ΣΠ με τη Lisp.
Παράδειγμα
Προσδιορισμός των συναρτήσεων με τρόπο που να επιστρέφουν την τιμή εισόδου τους:
; αρχική κατάσταση λίστας εργαζομένων και εργασίας τους
(setq εργαζόμενοι nil)

; αρχική κατάσταση λίστας εστιαζόμενων
(setq θέση_στο_εστιατόριο nil)

; αρχική κατάσταση κρατημένων θέσεων
(setq place 0)
(defun ενημέρωση_εργαζομένων (name job)

; ενημέρωση λίστας εργαζομένων για το νέο εργαζόμενο
(push (print (list name job)) εργαζόμενοι)

; επιστροφή του ονόματος του εργαζόμενου

name)
(defun κράτηση_θέσης_στο_εστιατόριο (name)

; ενημέρωση λίστας εστιατορίου
(push name restaurant-list)

; κράτηση ακόμα μιας θέσης
(incf place)

; επιστροφή του ονόματος του εργαζόμενου
name)

Εφαρμογή:
(κράτηση_θέσης_στο_εστιατόριο
( ενημέρωση_εργαζομένων
("Γιάννης" "ζωγράφος")))
→ "Γιάννης"

Διασωλήνωση με αναφορά των διαδοχικών βημάτων σε αυτούσια την φυσική τιμή
εισόδου

Στο παραπάνω, η αναγραφή της λ-μεταβλητής ως τελευταίου υπολογισμού, εξασφαλίζει τη
μεταφορά της τιμής εισόδου προς το επόμενο επίπεδο, αλλά ως αντίγραφο, διότι η ίδια η
δέσμευση λ-μεταβλητής γίνεται πάνω σε αντίγραφο της οντότητας - τιμής εισόδου. Εάν
απαιτείται μεταφορά κάποιας φυσικής οντότητας αυτούσιας, πρέπει να γίνει εφαρμογή της
σειράς όλων των συναρτησιακών εκφράσεων πάνω στο αρχικό φυσικό αντικείμενο, με τη διαδοχή
που καθορίζει η προτεραιότητα των εκτελέσεων.
Συναντάμε την περίπτωση αυτή όταν πρόκειται για φυσικό αντικείμενο που δημιουργείται "επί
τόπου" από εκτέλεση εφαρμογής στα πλαίσια μιας γενικότερης εφαρμογής, όπως ένα στιγμιότυπο
176

ή ένα νέο όνομα συμβόλου: αν καλέσουμε ξανά το μηχανισμό που το δημιούργησε, θα λάβουμε
ένα άλλο αντικείμενο.
Εδώ η θέση καθυστερήσεων παίζει πρωταρχικό ρόλο, διότι επιτρέπει την αποσύνδεση της
διαδοχής εκτέλεσης, την οποία προσδιορίζει η σύνθεση των συναρτήσεων, από τη διαδοχή
εκτέλεσης που απαιτεί η ευρύτερη εφαρμογή. Αμεσότερος τρόπος είναι να χρησιμοποιήσουμε
συνδυασμούς από "backquote" και "comma", όπως στo παρακάτω. Επίσης, με χρήση macros
μπορούμε να πετύχουμε πολύ εύκολα τη μεταβίβαση αυτή.
Παράδειγμα
Να δοθεί ο εξής αλγόριθμος: «φτιάξε (τουλάχιστον) δύο ονόματα συμβόλων με πρόθεμα "name-"
και αύξοντα αριθμό, και δώσε τους κοινή τιμή, το όνομα συμβόλου που θα προκύψει από το
πρόθεμα "value-" και αύξοντα αριθμό» .
Διαδικαστική αντιμετώπιση:
Απομονώνουμε την παραγωγή της τιμής από την παραγωγή των συμβόλων - μεταβλητών και
κάνουμε την ανάθεση τιμής στη συνέχεια.
(let (val var1 var2)
(setq val (gentemp "value-"))
(setq var1 (gentemp "name-"))
(setq var2 (gentemp "name-"))
(setq var1 val var2 val))

Μεικτή αντιμετώπιση:
; στα πλαίσια τοπικής μεταβλητής local
(let (local)

; προσδιόρισε μια τιμή για την local, που να είναι νέα μεταβλητή,
; με αρχικό συνθετικό value(setq local (gentemp "value-")))

; και δώσε της τιμή μια άλλη μεταβλητή, για την οποία κάνε το ίδιο
(eval
`(setq ,(gentemp "name-") local ,(gentemp "name-") local)))

Παρατηρούμε πως, με τον τρόπο αυτό, δώσαμε μεν διαφορετική διαδοχή στην εκτέλεση από αυτή
που θα καθόριζε η σύνθεση αν δεν είχαν τεθεί καθυστερήσεις, αλλά δεν το αξιοποιήσαμε. Αυτό
θα κάνουμε στο επόμενο:
Συναρτησιακή αντιμετώπιση με διασωλήνωση:
Προκαλούμε τη δημιουργία της τιμής με τη συνάρτηση VAL (χωρίς λ-μεταβλητές) και τη δίνουμε
ως έξοδό της.
Με τη συνάρτηση VAR (μιας λ-μεταβλητής) δημιουργούμε σύμβολο και του αποδίδουμε τιμή,
ενώ η συνάρτηση δίνει έξοδό της την είσοδό της.
(defun val ( ) (gentemp "value-"))
(defun var (name)
(eval `(setq ,(gentemp "name-") name)) name)
177

Με κατάλληλη κλήση σύνθεσης, πετυχαίνουμε το ζητούμενο:
(var (var (val)))
; παραγωγή δύο νέων συμβόλων
(var (var (var (var (val)))))
; παραγωγή τεσσάρων νέων συμβόλων
Βλέπουμε οτι τώρα διαθέτουμε ένα μηχανισμό παραγωγής απλά προσδιοριζόμενο και πολύ πιο
ευέλικτο στην κλήση του.
Το ζήτημα της παραγωγής συμβόλων είναι σημαντικό, με ιδιαίτερη αξιοποίηση στα στιγμιότυπα
(θα τα μελετήσουμε στα επόμενα).

4.2 Επικοινωνία εφαρμογής με το εξωτερικό περιβάλλον

Ένα από τα σημαντικότερα πλεονεκτήματα της Lisp είναι η δυνατότητα ανάγνωσης συνθέτων
φορμών από την είσοδο και η δυνατότητα συσχετισμού τους με τη λειτουργικότητα του
προγράμματος. Για παράδειγμα, ανάγνωση της συναρτησιακής έκφρασης που θα χρησιμοποιηθεί
στα πλαίσια του ίδιου υπολογισμού.
Εδώ θα δούμε τις δυνατότητες επικοινωνίας του προγράμματος με το περιβάλλον του, χρήστη ή
αρχεία. Ειδικότερα θέματα που αφορούν την επικοινωνία είναι σε μεγάλο βαθμό εξαρτημένες από
το υπολογιστικό σύστημα και είναι προτιμότερο να συνδυαστούν με τη μελέτη των δυνατοτήτων
του compiler που θα χρησιμοποιηθεί.

4.2.1 Ρεύμα εισόδου / εξόδου

Ως αρχική κατάσταση, ρεύμα εισόδου είναι το πληκτρολόγιο και εξόδου η οθόνη. Η κατάσταση
αυτή καθορίζεται από τις ειδικές μεταβλητές *STANDARD-INPUT* και *STANDARD-OUTPUT* .
Μπορούμε να προκαλέσουμε προσωρινή αλλαγή των ρευμάτων εισόδου – εξόδου τοπικά, με
κατάλληλη παράμετρο των READ και PRINT (βλέπε παρακάτω). Μπορούμε να προκαλέσουμε
μόνιμη, καθολική αλλαγή των ρευμάτων εισόδου – εξόδου, μεταβάλλοντας την τιμή των
*STANDARD-INPUΤ* και *STANDARD-OUTPUT* , με χρήση της DEFPARAMETER , προς ένα
όνομα ρεύματος που παριστά ήδη ανοιγμένο αρχείο.

Ανάγνωση από καθοριζόμενη είσοδο / εκτύπωση σε καθοριζόμενη έξοδο

Βασική συνάρτηση ανάγνωσης στοιχείων εισόδου είναι η READ, η οποία διαβάζει ολόκληρη
φόρμα (άτομο, ή σύνθεση φόρμα με ταίριαγμα παρενθέσεων) από το ρεύμα εισόδου.
Αν η συνάρτηση που εκτελείται έχει περισσότερα του ενός "read", οι αντίστοιχες είσοδοι
αναμένονται ως διαδοχικές φόρμες από την κυρία είσοδο, με διαχωριστικό το κενό διάστημα ή το
"enter" (στο toploop, τελευταίο πρέπει να είναι "enter", για να εκτελεστεί η συνάρτηση).

Ο τύπος του στοιχείου εισόδου καθορίζεται από την εκτελούμενη συνάρτηση. Εκτέλεση της
READ σημαίνει "ανάγνωση φόρμας και επιστροφής της, ως τιμής της συνάρτησης", δηλ. στη θέση
178

της φόρμας (READ) τοποθετείται η φόρμα που διάβασε. Προσοχή: η φόρμα που διαβάζει η READ
δεν υπολογίζεται.

Αν δούμε έναν υπολογισμό ως διαδικασία, η είσοδος τιμών στον υπολογισμό μέσω της READ
αποτελεί τη συνήθη σε διαδικαστικές γλώσσες ανάγνωση τιμών κατά τη διάρκεια της εκτέλεσης,
και η δέσμευση των λ-μεταβλητών καθορίζει τις αρχικές συνθήκες της εκτέλεσης. Όμως σε ένα
συναρτησιακό πρόγραμμα αυτές οι έννοιες μπορούν να είναι πολύ βαθύτερες, διότι τόσο η
δέσμευση λ-μεταβλητών όσο και η ανάγνωση μπορούν να παίρνουν τιμή οποιαδήποτε φόρμα,
οσοδήποτε σύνθετη και αν είναι, ακόμα και τέτοια που να περιλαμβάνει ορισμό υπολογισμού,
(ακόμα και επανορισμό της συνάρτησης που τρέχει) εκτέλεση υπολογισμού, υπό καθυστέρηση
εκτέλεση, και γενικά οτιδήποτε είναι δυνατό να περιλαμβάνει μια φόρμα, με μοναδική
προϋπόθεση να προβλέπεται στο σώμα τής συνάρτησης ο τύπος της φόρμας δέσμευσης / της
φόρμας ανάγνωσης (αυτό συχνά το θέτουν οι συγγραφείς εγχειριδίων Lisp ως πρόκληση προς
τους προγραμματιστές της C++ !!!).

Η διαφοροποίηση ανάμεσα σε τιμές στις οποίες δεσμεύονται λ-μεταβλητές και σε τιμές που
διαβάζει η READ , βλέποντάς το διαδικαστικά, έγκειται στο "πότε" δίνονται οι τιμές: των λμεταβλητών πρέπει να δοθούν κατά την κλήση της συνάρτησης, ενώ οι τιμές που διαβάζονται με
read δίνονται "την στιγμή όπου ζητούνται" (πανομοιότυπα με κάθε διαδικαστική γλώσσα). Άρα οι
πρώτες παίζουν ρόλο προσδιορισμού του πλαίσιου της εκτέλεσης του αλγόριθμου, απέναντι στις
δεύτερες που παίζουν ρόλο προσδιορισμού της τρέχουσας κατάστασης. Είναι ιδιαίτερα
σημαντική, αν πρόκειται για ανάγνωση μέσα σε ένα βρόχο του οποίου κάθε εκτέλεση αποτελεί
μια φάση της λειτουργίας.

Από τη συναρτησιακή άποψη, η διαφοροποίηση είναι βαθύτερη: οι λ-μεταβλητές παίζουν το ρόλο
"σκαλοπατιών" για τη μετάβαση από τη μια εφαρμογή στην επόμενη, κατά επίπεδα δέσμευσης,
ρόλος που δεν μπορεί να υποκατασταθεί άμεσα από τη χρήση της READ . Επί πλέον, οι λμεταβλητές "έχουν όνομα", ενώ η φόρμα (READ) δεν είναι αναγκαίο να δώσει τιμή σε
μεταβλητή: μπορεί να δώσει είσοδο σε υπολογισμό κατ' ευθείαν.
Η εκτύπωση σε προκαθοριζόμενη έξοδο, τυπώνει κατά τα γνωστά ολόκληρη φόρμα. Όπως
έχουμε δει, διαφοροποιούμε εκτύπωση που αποσκοπεί σε ανάγνωση κειμένου από τον χρήστη,
από την εκτύπωση που αποσκοπεί σε ανάγνωση κώδικα από τον compiler.

Ανάγνωση οντότητας από καθοριζόμενο ρεύμα εισόδου
read : Συνάρτηση, που έχει δύο τρόπους χρήσης:
α'. Αν κληθεί χωρίς ορίσματα:
(read)

επιστρέφει τον τρέχοντα όρο (φόρμα) που παίρνει από το κύριο ρεύμα εισόδου. Αν πρόκειται για
είσοδο από το πληκτρολόγιο, διαβάζει την πλήρη φόρμα (άτομο ή από παρένθεση που ανοίγει σε
αντίστοιχη παρένθεση που κλείνει) και συνεχίζει την εκτέλεση μετά από εισαγωγή <enter> (το
<enter> πριν τη συμπλήρωση της φόρμας παίζει ρόλο κενού διαστήματος).
Η συνάρτηση READ δεν εκτελεί τη φόρμα εισόδου, δηλ. αυτό που διαβάζει παίρνει τη θέση του
179

(READ) αυτούσιο. Ως φόρμα εισόδου μπορούμε να δώσουμε οτιδήποτε, αρκεί να ταιριάζει ο
τύπος του στη θέση του (READ) .
(defun fr ( ) (+ (read) (read)))
(fr)
→ <περιμένει από την είσοδο δύο αριθμούς∙ δίνουμε:
3 <space> 7 <enter>

.

→ 10
Αν παρ' όλα αυτά θέλουμε να υπολογιστεί η είσοδος δεν έχουμε παρά να δώσουμε αντί για την
έκφραση (READ) , την έκφραση (EVAL (READ)) .
β'. Αν δοθεί με ένα όρισμα, αυτό αφορά το ρεύμα εισόδου:
(read i-stream-name )
Το ρεύμα εισόδου i-stream είναι ένα σύμβολο - όνομα μεταβλητής, που καθορίζει το αρχείο από
το οποίο θα γίνει η ανάγνωση. Η τιμή της μεταβλητής i-stream πρέπει να είναι string (όνομα σε
εισαγωγικά) που να προσδιορίζει το μονοπάτι (path) του αρχείου (ή μόνο το όνομα, για το τρέχον
μονοπάτι).

Όνομα συμβόλου που καθορίζει ρεύμα εισόδου προσδιορίζεται είτε με τη συνάρτηση OPEN , είτε
με τη συνάρτηση WITH-OPEN-FILE (θα τις δούμε παρακάτω). Μπορούμε να έχουμε
περισσότερα του ενός διαθέσιμα ρεύματα εισόδου (το μέγιστο πλήθος εξαρτάται από τον
compiler, συνήθως τέσσερα).

Εκτύπωση οντότητας σε καθοριζόμενο ρεύμα εισόδου
print , prin1, princ : Είδαμε τις συναρτήσεις αυτές με ένα όρισμα.
Η PRINT μπορεί να κληθεί και με δεύτερο όρισμα, οπότε αυτό αφορά το ρεύμα εξόδου, και
συντάσσεται παρόμοια με την READ . Το όνομα ρεύματος εξόδου καθορίζεται επίσης μέσω της
OPEN ή της WITH-OPEN-FILE

(print o-stream-name)
format : Συνάρτηση που καθορίζει τη μορφή εκτύπωσης. Παρέχει πολλές δυνατότητες, όπως
ανακατεύθυνσης σφάλματος, καθορισμού της αλληλεπίδρασης με το χρήστη σε περίπτωση
σφάλματος.
prin1-to-string : Παρόμοια με την PRIN1 , με τη διαφορά πως η έξοδος μετατρέπεται σε τύπου
string.
princ-to-string : Αντίστοιχα, για εκτύπωση χωρίς εισαγωγικά ή άλλους συμβολικούς
χαρακτήρες
write
: Παρόμοια με την PRINT , αλλά δέχεται προαιρετικά κλειδιά που ελέγχουν τον
εκτυπωτή (όλοι οι Lisp compilers υποστηρίζουν κάποια σχετικά κλειδιά που παρουσιάζουν
διαφοροποιήσεις μεταξύ τους).
write-to-string
: Αντίστοιχη της PRIN1-TO-STRING , με τις δυνατότητες της WRITE για
έλεγχο του εκτυπωτή.
Οι δύο τελευταίες, επειδή αφορούν κυρίως τη χρήση του εκτυπωτή και παίρνουν παραμέτρους
180

εξαρτημένες από τον τύπο του εκτυπωτή, για να μη προκληθεί σφάλμα, ενεργοποιούνται
επεξηγηματικά από ειδικές μεταβλητές (όταν πάρουν τιμή Τ) .
Παραδείγματα
1. Ανάγνωση ατόμων:
i) Εντολή στο toploop που θα διαβάσει ένα άτομο και θα το τυπώσει.
(print (read)) → <αναμένει στοιχείο εισόδου∙ πληκτρολογούμε:>
2 <enter>



(εκτύπωση)
(η τιμή που επιστρέφεται από τη συνάρτηση PRINT)
ii) Συνάρτηση που να διαβάζει τρεις αριθμούς και να επιστρέφει το άθροισμά τους
2
2

(defun sum-of-three-numbers ( )
(+ (read) (read) (read) ) )

Εφαρμογή:
(sum-of-three-numbers)

→ <αναμένει στοιχείο εισόδου∙ πληκτρολογούμε:>

4 <space> 6 <space> 8 <enter>
→ 18

2. Ανάγνωση φόρμας εφαρμογής και διαχείριση της εκτέλεσής της:
i) Εντολή στο toploop που θα διαβάσει μια φόρμα και θα την τυπώσει.
Είναι ακριβώς η ίδια με την εντολή που διαβάζει άτομο:
(print (read))

→ <αναμένει στοιχείο εισόδου∙ πληκτρολογούμε:>
(square 3)

(square 3) (εκτύπωση)
(square 3)
(τιμή που επιστρέφεται από τη συνάρτηση PRINT)

Παρατηρούμε οτι η τιμή που επιστρέφεται είναι η φόρμα αυτούσια και όχι το αποτέλεσμα του
υπολογισμού της.
ii) Συνάρτηση που να διαβάζει μια φόρμα, να την τυπώνει αυτούσια, να την εκτελεί και να
επιστρέφει το αποτέλεσμα.
Λόγω του οτι αυτό που διαβάζει η READ δεν εκτελείται, χρειάζεται να εξαναγκάσουμε την
εκτέλεση:
(defun read-and-execute ( )
(eval (print (read))) )

Καλούμε:

<αναμένει στοιχείο εισόδου∙ πληκτρολογούμε:>
(square 3)

(square 3)
(εκτύπωση)
9
(επιστροφή τιμής

(read-and-execute)

181

iii) Εντολή στο toploop που θα διαβάσει μια φόρμα, θα την εκτελέσει, θα τυπώσει και θα
επιστρέψει το αποτέλεσμα.
Τώρα η εκτέλεση πρέπει να προηγηθεί της εκτύπωσης:
(print (eval (read)))

→ <αναμένει στοιχείο εισόδου∙ πληκτρολογούμε:>

(square 3)

9

9

(εκτύπωση)
(επιστροφή)

iv) Συνάρτηση που να διαβάζει μια φόρμα εφαρμογής, να τυπώνει το αποτέλεσμά της αλλά να
επιστρέφει τη φόρμα αυτούσια.
(defun read-and-not-evaluate ( )
(print (eval
; τύπωσε το αποτέλεσμα της φόρμας:
(setq v (read)))) ; δώσε τιμή στο v αυτό που θα διαβάσεις
v) )
; επίστρεψε την τιμή της v

Εκτέλεση:
(read-and-not-evaluate)

→ <αναμένει στοιχείο εισόδου∙ πληκτρολογούμε:>

(square 3)

9
→ (square 3)

(εκτύπωση)
(επιστροφή)

3. Ανάγνωση συναρτησιακής έκφρασης

i) Συνάρτηση που να διαβάζει όνομα συνάρτησης μιας μεταβλητής και να την εφαρμόσει σε
όρισμα που να διαβάζει επίσης.
Το πρώτο που θα διαβάσει θα είναι όνομα συνάρτησης, άρα για να εκτελεστεί πρέπει να δοθεί
ως όρισμα της FUNCALL (βλέπε αναλυτικά για τη χρήση της FUNCALL στο επόμενο
κεφάλαιο). Το δεύτερο που θα διαβάσει, θα είναι το όρισμα της συνάρτησης αυτής, άρα πρέπει
να γραφεί ως δεύτερο όρισμα της FUNCALL .
(defun execute-function ( )
(funcall (read) (read)))

Εκτέλεση:


<αναμένει στοιχείο εισόδου∙ πληκτρολογούμε:>

(execute-function)
square <enter>

(προσοχή: δώσαμε αυτούσιο το όνομα της συνάρτησης)
4 <enter>
(δώσαμε την τιμή εφαρμογής της συνάρτησης)
→ 16
ii) Συνάρτηση που να διαβάζει όνομα συνάρτησης τριών μεταβλητών και να την εφαρμόσει σε
αντίστοιχα ορίσματα που να διαβάζει επίσης.
Πανομοιότυπα με το προηγούμενο, αλλά προβλέποντας τρία ορίσματα για την καλούμενη
182

συνάρτηση:
(defun execute-function-3 ( )
(funcall (read) (read) (read) (read)))

Εκτέλεση:

(execute-function-3) →

<αναμένει στοιχεία εισόδου∙ πληκτρολογούμε:>
+ <space> 3 <space> 5 <space> 12 <enter>
→ 20

iii) Συνάρτηση που να διαβάζει φόρμα που αποτελεί συναρτησιακή έκφραση τριών μεταβλητών
και να την εφαρμόσει σε αντίστοιχα ορίσματα που να δαβάζει επίσης.
Η READ μπορεί να διαβάσει λ-έκφραση (για χρήση λ-εκφράσεων βλέπε επόμενα κεφάλαια).
Χρησιμοποιούμε την συνάρτηση EXECUTE-FUNCTION-3 :
(execute-function-3) →
<γυρίζει σε κατάσταση εισόδου∙ πληκτρολογούμε:>
( lambda (x y) (* x y z) )

<enter>

;; ορίσαμε τη συναρτησιακή έκφραση μιας λ-μεταβλητής∙που
; ουσιαστικά πρόκειται για ανώνυμη συνάρτηση
;; το ταίριαγμα της αρχικής παρένθεσης καθορίζει το τέλος
; του προσδιορισμού τής συναρτησιακής έκφρασης,
; και αυτή δίνεται ως τιμή στο πρώτο read
;; τώρα περιμένει είσοδο τιμής για τα επόμενα τρία read ∙ δίνουμε :

4 <space> 5 <space> 6 <enter>
→ 120

Ανάγνωση χαρακτήρων
read-char : Συνάρτηση ανάγνωσης ενός χαρακτήρα. Χωρίς ορίσματα, διαβάζει από την κυρία
είσοδο ένα χαρακτήρα και τοποθετεί ένα δείκτη στον επόμενο. Αν δεν υπάρχει χαρακτήρας στην
είσοδο, περιμένει μέχρι να δοθεί χαρακτήρας.

Με πρώτο όρισμα ένα ρεύμα εισόδου, ανακατευθύνει την ανάγνωση στο ρεύμα αυτό. Αν δοθούν
και άλλα δύο ορίσματα, χαρακτηρίζουν "τί να επιστρέψει αν συναντήσει κάτι ειδικό". Στην
παρακάτω φόρμα, διαβάζει χαρακτήρα από το ρεύμα char-stream1 που υποτίθεται οτι παριστά
αρχείο, και αν διαβάσει "end-of-file" (που δηλώνεται με το σύμβολο ΕΟF) να επιστρέψει τιμή ΝΙL
:
(read-char char-stream1 nil 'eof)

read-char-no-hang : Παρόμοια με την READ-CHAR αλλά δεν περιμένει είσοδο. Αν υπάρχει
ήδη ένας χαρακτήρας, τον διαβάζει, αλλιώς επιστρέφει αμέσως NIL .

183

4.2.2 Επικοινωνία με αρχεία, διαχείριση αρχείων
Άνοιγμα αρχείων για ανάγνωση ή εγγραφή
Κεντρική συνάρτηση διαχείρισης αρχείων είναι η OPEN , αν και στην πράξη χρησιμοποιούμε
περισσότερο την "τοπική" WITH-OPEN-FILE . Η λειτουργικότητα της OPEN συνδέεται στενά με
το λειτουργικό σύστημα, και ο χρήστης θα πρέπει να αναζητήσει σχετικές λεπτομέρειες στο
σχετικό "reference manual" του compiler που χρησιμοποιεί. Συνοπτικά έχουμε:

open : Συνάρτηση που ανοίγει αρχείο και καθορίζει το αντίστοιχο ρεύμα. Υποχρεωτικό όρισμα
το όνομα του αρχείου, που αν δεν προσδιορίζεται αλλιώς πρόκειται για είσοδο. Προαιρετικά
ορίσματα τα ακόλουθα, σε μορφή από κλειδιά που ακολουθούνται από άλλα κλειδιά
εξειδίκευσης:
– κλειδί

:direction , με δεκτές τιμές τα ακόλουθα κλειδιά:

:input , :output , :io ,

:probe ("= πρόβα", για test εξακρίβωσης λειτουργίας της επικοινωνίας με το αναφερόμενο

μέσον)
– κλειδί :element-type με δυνατές τιμές κάποιο τύπο φόρμας, κατά τα γνωστά

– κλειδί :if-exists , που αναφέρεται στην ύπαρξη του αρχείου, και δέχεται ως τιμή διάφορα
κλειδιά προσδιορισμού της δράσης "τί να κάνει αν υπάρχει το αρχείο"∙ κυριότερα: :append
, :overwrite , :rename

– κλειδί :if-does-not-exist , που αναφέρεται στην ύπαρξη του αρχείου, με δεκτές τιμές
διάφορα κλειδιά προσδιορισμού της δράσης "τί να κάνει αν δεν υπάρχει το αρχείο"∙ βασικά
είναι τα :create και :error .
Δημιουργία - διαγραφή αρχείου:
i) Δημιουργούμε νέο αρχείο χρησιμοποιώντας απλώς την OPEN με το κλειδί :create
ii)Διαγράφουμε αρχείο με την DELETE-FILE . H σύνταξη μονοπατιού είναι εξαρτημένη από το
λειτουργικό σύστημα. Για DOS ή Windows είναι:
(delete-file "c:\\lisp\\myfiles\\myfile1.lsp")

; σύνταξη για Windowsπροσοχή στο διπλό \\
είτε:
(delete-file "c:/lisp/myfiles/myfile1.lsp")

; σύνταξη για Windows και για UNIX
Προσδιορισμός ρεύματος:
Kαθορίζουμε το όνομα του ρεύματος απλά με την SETQ . Πχ, για προσδιορισμό ρεύματος σε
περιβάλλον PC :
(setq in-stream1
(open "c:\\lisp\lisp-files\\charfile.txt"
:direction :input
:if-does-not-exist :error))
184

Χρήση:
(read-char in-stream1 nil 'eof)

Παράδειγμα
Χρήση της READ για συνένωση λιστών - τιμών εισόδου από αρχείο και πληκτρολόγιο:
(defun append-lists-from-file-and-keyboard (file-name)
(let ((li nil) (strm " "))
; η συνάρτηση let περιγράφεται στους τόπους
(setq strm

; άνοιξε το αρχείο για διάβασμα

(open file-name :direction :input))
(setq li
(append
;; συνένωσε τις λίστες,
(read strm)
; διαβάζοντας την πρώτη λίστα από το αρχείο
(read)))
; και τη δεύτερη από το πληκτρολόγιο
(close file-name)
; κλείσε το αρχείο
li) )
; επίστρεψε τη λίστα – αποτέλεσμα

Άλλες χρήσιμες συναρτήσεις διαχείρισης αρχείων / εισόδου - εξόδου

peak-char
: Διαβάζει τον πρώτο χαρακτήρα του ρεύματος εισόδου όπως η READ-CHAR ,
αλλά χωρίς να μετακινήσει τον δείκτη. Χρησιμεύει για ανίχνευση "τί είναι παρακάτω".

file-position : Με μια παράμετρο, το όνομα του ρεύματος, δίνει την τρέχουσα θέση του δείκτη
(pointer) στο αρχείο.
Με δεύτερη παράμετρο, αριθμό, τοποθετεί το δείκτη στη θέση αυτή.
write-char : Γράφει χαρακτήρα σε ρεύμα εξόδου.
(setq out-stream1
(open "charfile.txt"
:direction :ouput
:if-does-not-exist :create))

Χρήση:
(write-char #\a out-stream1)

write-string : Παρόμοιας σύνταξης με την write-char , αλλά γράφει ολόκληρο το string που
δίνεται:
(write-string "όλα καλά" out-stream1)

file-length : Με όρισμα ρεύμα - αρχείο, δίνει το μέγεθος τού αρχείου.
close : Με όρισμα ρεύμα - αρχείο, κλείνει το (ανοιγμένο) αρχείο.
load: Με είσοδο string ονόματος ή μονοπατιού αρχείου, φορτώνει το πρόγραμμα που περιέχει το
αρχείο.
with-open-file : Πιο ευέλικτη από την ΟΡΕΝ για "μικρές δουλειές". Ανοίγει το αρχείο
προσωρινά, εκτελεί την αναφερόμενη εργασία και το κλείνει. Καθορίζει το όνομα του ρεύματος
επί τόπου.
185

– Το πρώτο όρισμα - λίστα περιέχει τα συνήθη ορίσματα της ΟΡΕΝ .
– Το δεύτερο όρισμα είναι η συνάρτηση που θα εκτελεστεί.
(with-open-file
(<όνομα ρεύματος> "<μονοπάτι αρχείου>" <κλειδιά>)
(<συνάρτηση> <ορίσματα>) )
► Η CL δεν διαθέτει κάποιο primitive SAVE , διότι η λειτουργία του εξαρτάται από το
λειτουργικό σύστημα, αλλά ορισμένες υλοποιήσεις το διαθέτουν. Με βάση τα παραπάνω,
μπορούμε να δημιουργήσουμε τη δική μας συνάρτηση SAVE .

4.3 Διαχείριση του χώρου των συμβόλων
Αναφέρουμε εδώ συνοπτικά τις βασικές δυνατότητες διαχείρισης του χώρου των συμβόλων μέσα
από την Lisp. Επειδή οι δυνατότητες αυτές έχουν κάποια τεχνική συνάφεια με το λειτουργικό
σύστημα (φυσική θέση αρχείων και γενικά εισόδου - εξόδου), είναι προτιμότερο να αναζητήσει ο
χρήστης την πλήρη λειτουργικότητα των σχετικών συναρτήσεων στο manual του compiler που
χρησιμοποιεί.

4.3.1 "Πακετάρισμα" συμβόλων
Το "πακετάρισμα" είναι μια τεχνική που επιτρέπει διαμέριση του συνόλου των συμβόλων σε
τμήματα ανεξάρτητα ή επικοινωνούντα, για πιο ευέλικτη διαχείριση μεγάλων χώρων συμβόλων.
Χρησιμεύει για ομαδοποίηση συναρτήσεων προς δημιουργία βιβλιοθηκών ή "κόσμων".

Η ίδια η Lisp χρησιμοποιεί ένα αριθμό από "πακέτα" (packages), με ονόματα ενδεικτικά του
περιεχομένου τους, όπως: common-lisp (είναι τα σύμβολα της CL), common-lisp-user (είναι τα
ειδικά σύμβολα που καθορίζουν το περιβάλλον επικοινωνίας με το χρήστη και χρησιμοποιεί το
προηγούμενο πακέτο), keyword (είναι το σύνολο των keywords, της Lisp και του χρήστη), system
ή sys (είναι το σύνολο των συμβόλων του προγράμματος), common-graphics , κά. (τα ονόματα
αυτά είναι standard της ANSI-CL)
Έχουμε δύο επίπεδα πακεταρίσματος, με διαφορετικό νόημα: του "χώρου" (module), και του
"πακέτου" (package). Πρακτικά, ένα module περιέχει ένα ή περισσότερα packages. Τα modules
αξιοποιούνται περισσότερο στους χώρους αντικειμένων (δηλ. που περιέχουν κλάσεις, μεθόδους,
αρχέτυπες συναρτήσεις, στιγμιότυπα). Τα packages αξιοποιούνται περισσότερο στις "κλασικές"
συναρτήσεις.

Η χρήση ενός module έχει περισσότερο νόημα φυσικού διαχωρισμού του χώρου των συμβόλων,
συνήθως σε ανεξάρτητα αρχεία, ενώ του package έχει περισσότερο νόημα εννοιολογικού
διαχωρισμού του χώρου των συμβόλων, ανάλογα με το σκοπό που εξυπηρετούν. Γι' αυτό
προσδιορίζουμε ένα module για να περικλείσουμε οντότητες με τεχνική αλληλεξάρτηση, και ένα
package για να περικλείσουμε οντότητες με νοηματική αλληλεξάρτηση.
186

Σκόπιμο είναι, χωρίς να επιβάλλεται, ένα module να περιέχει ακριβώς ένα package και να
περιέχεται ακριβώς σε ένα αρχείο. Το όνομα του αρχείου είναι string, το οποίο μπορεί να
συμπίπτει με το όνομα του συμβόλου του πακέτου, και αυτό με του module.
Κάθε πακέτο καθιστά "αόρατα" και μη χρησιμοποιήσιμα τα σύμβολα που περιέχει, μέχρι να
ζητηθεί το πακέτο, και έτσι δεν απασχολείται άσκοπα μνήμη. Τα σύμβολα γίνονται ορατά και
χρησιμοποιήσιμα είτε με την κλήση του πακέτου, είτε με την "εξαγωγή" (export) συγκεκριμένων
συμβόλων από το πακέτο προς το γενικό χώρο χρήσης ή ειδικά προς άλλο πακέτο. Η εξαγωγή
διευκολύνει πολύ στον περιορισμό χρήσης της μνήμης "στα απαραίτητα". Επίσης, ένα πακέτο
μπορεί να καλέσει (import) συγκεκριμένα σύμβολα από άλλο πακέτο χωρίς να χρειάζεται να
φορτωθεί το τελευταίο ολόκληρο.

Οι βασικές συναρτήσεις διαχείρισης module είναι οι PROVIDE (δημιουργία) και REQUIRE
(φόρτωμα). Το φόρτωμα ενός module φέρνει τη δυνατότητα χρήσης του συνόλου των συμβόλων
των πακέτων που περιέχει, αλλά η κλήση τέτοιου συμβόλου γίνεται με πρόθεμα το όνομα του
πακέτου ακολουθούμενο από διπλή πάνω-κάτω τελεία :: , εκτός αν είναι σύμβολο του τρέχοντος
πακέτου.

Η διαχείριση πακέτων γίνεται με τις συναρτήσεις DEFPACKAGE (δημιουργία), IN-PACKAGE
(έναρξη καταγραφής νέων συμβόλων σε πακέτο, μέχρι να αλλάξει ή μέχρι τέλους του αρχείου,
και έμμεσα φορτώνει το πακέτο), USE-PACKAGE (θέση πακέτου ως τρέχοντος). Οι συναρτήσεις
INPORT και EXPORT επιτρέπουν ειδική χρήση συναρτήσεων από ένα πακέτο προς άλλο, χωρίς
σαν χρειάζεται φόρτωμα ολόκληρου του πακέτου. Πακέτα μπορούν να μεταφέρονται σε πακέτα
χρήσης, ολόκληρα. Η συνάρτηση Shadow επιτρέπει ειδική ένταξη συμβόλων σε ένα πακέτο,
καθιστώντας αόρατο ότι δεν χρειάζεται να βλέπει ο χρήστης.
Δεν θα επεκταθούμε στο θέμα του πακεταρίσματος, διότι είναι προτιμότερο να το μελετήσει ο
αναγνώστης σε σχέση με τον χρησιμοποιούμενο compiler και με το λειτουργικό σύστημα.

4.3.2 Χρήση μερικής ή ολικής μεταγλώττισης
Στους πιο εξελιγμένους compilers της Lisp, συναντάμε τη δυνατότητα διαχείρισης της
μεταγλώττισης, που ελέγχεται από τις παρακάτω συναρτήσεις:
compile-file : Η συνάρτηση COMPILE-FILE μεταγλωττίζει ένα αρχείο με πηγαίο (source) κώδικα
Lisp, με ή χωρίς ανακατεύθυνση εξόδου της μεταγλώττισης.
Παράδειγμα
Το path εννοείται σε φόρμα PC. Εννοείται οτι το αρχείο myfile.txt έχει γραφεί σε κάποιον text
editor.
;; compilation του source κώδικα και φόρτωση στη μνήμη:
(compile-file “c:/source/myfile.txt”) ; το πηγαίο αρχείο
;; compilation του source κώδικα και σώσιμο του compiled σε αρχείο:
(compile-file “c:/source/myfile.txt” “c:/compiled/myfile.lsp”)
187

;; ανεξάρτητη φόρτωση του compiled αρχείου:
(load “c:/compiled/myfile.lsp”)

compile : Η συνάρτηση COMPILE μεταγλωττίζει κάτι συγκεκριμένο (φόρμα). Καλείται με το
όνομα του “αντικειμένου” που θα μεταγλωττιστεί, το οποίο έχει οριστεί προηγουμένως
(προϋποθέτει απενεργοποίηση της αυτόματης μεταγλώττισης).
(defun a (x) (* x x))
(compile 'a)

Σε φτωχότερους compilers, η μεταγλώττιση γίνεται αυτόματα μόλις δοθεί η φόρμα. Σε
πλουσιότερους, το αυτόματο ή όχι καθορίζεται από παράμετρο της κλήσης του compiler ή
interpreter της Lisp.
Μπορεί να κληθεί η COMPILE και με δεύτερο όρισμα, όπου πρώτο είναι το όνομα του συμβόλου
– συνάρτησης και δεύτερο είναι η ίδια η περιγραφή του σώματος (που πρέπει να δοθεί ως quoted
λ-έκφραση):
(compile 'a '(lambda (x) (* x x))) → Α
(a 2)

4
H παραπάνω έκφραση (compile 'a ...) όταν η μεταγλώττιση γίνεται αυτόματα (που είναι η default
κατάσταση), ισοδυναμεί με την:
(defun a (x) (* x x))

4.3.3 Yποστήριξη συγγραφής, παρακολούθηση εκτέλεσης
Διατίθεται στην CL μια σειρά συναρτήσεων που διευκολύνουν τον χρήστη της γλώσσας στo να
αναγνωρίσει το ρόλο και τη μορφή συμβόλων, είτε built-in της Lisp είτε του προγράμματος.
Αυτές είναι οι APROPOS , DESCRIBE , INSPECT , DOCUMENTATION .

Ορισμένες υλοποιήσεις διαθέτουν ένα "συναρτησιακό editor" (συνάρτηση ED), ο οποίος παρέχει
ευκολίες κίνησης σε επίπεδα και συνδέεται αυτόματα με τον compiler.
Η συνάρτηση TRACE διευκολύνει τον εντοπισμό λαθών ή ακόμα και την παρακολούθηση του
τρόπου λειτουργίας συναρτήσεων.
Ακόμα, με συναρτήσεις χρόνου μπορούμε να χρονομετρήσουμε τη διάρκεια υπολογισμού
συγκεκριμένων τμημάτων, ή, βαθύτερα, να εξαρτήσουμε την εκτέλεση από τον πραγματικό ή
σχετικό χρόνο.

Αναζήτηση πληροφορίας σχετικά με κάποιο σύμβολο
apropos
: Συνάρτηση ενός ορίσματος - string που δίνει όλη την πληροφορία σχετικά με το
αναφερόμενο όνομα (τύπου string ή symbol), είτε αυτό αποτελεί όνομα συμβόλου είτε (ως string)
μέρος ονόματος, είτε ακόμα και έμμεσα αναφερόμενο string στις πληροφορίες που υπάρχουν για
κάποιο σύμβολο. Η έξοδος που δίνει, εξαρτάται από τον compiler.
(apropos 'open)

188

apropos-list
: Παρόμοια με την APROPOS, αλλά δίνει μόνο τη λίστα των συμβόλων μέρος
των οποίων είναι το string, ή συμπίπτει με κάποιο.
describe : Συνάρτηση ενός ορίσματος που δίνει μια γενική περιγραφή του συμβόλου ορίσματός της, όπως αν είναι δεσμευμένο ή όχι, σε ποιο package περιέχεται, κλπ. Η
συγκεκριμένη έξοδος που δίνει, εξαρτάται από τον compiler.
documentation : Είναι συνάρτηση ενός ορίσματος και επιστρέφει τον ορισμό (περιεχόμενο) του
συμβόλου - ορίσματος. Δίνει επίσης και άλλες πληροφορίες σχετικές με το σύμβολο (οι
πληροφορίες αυτές εξαρτώνται από τον compiler).
inspect : Συνάρτηση που δίνει, όπως και η DESCRIBE, πληροφορίες για το σύμβολο - όρισμά
της, αλλά με αλληλεπιδρώντα τρόπο: ο χρήστης μπορεί να κινηθεί "ψάχνοντας" τη δομή του, και
ακόμα και να επέμβει σ' αυτό.
ed : H συνάρτηση ED , χωρίς ορίσματα, απλώς καλεί έναν editor (εξαρτημένο από τον
compiler). Με ένα όρισμα (τύπου string) ανοίγει αρχείο:
(ed "c:/lisp/myfiles/lib1.lsp")
trace :
H συνάρτηση TRACE ενεργοποιεί την κατάσταση παρακολούθησης του υπολογισμού

της συνάρτησης που δίνεται ως όρισμα. Εφαρμογή:
(trace όνομα-συνάρτησης)
untrace : Αναιρεί την ή τις ενεργοποιήσεις παρακολούθησης εκτέλεσης που είχαν τεθεί
προηγουμένως. Χωρίς παραμέτρους, αναιρεί γενικά κάθε TRACE που έχει δοθεί.
(untrace)

Με παράμετρο - όνομα συνάρτησης αναιρεί την παρακολούθηση ειδικά αυτής της συνάρτησης:
(untrace όνομα-συνάρτησης)
step : Συνάρτηση που προκαλεί κατά βήμα εκτέλεση της φόρμας - ορίσματός της και επιστρέφει
εισόδους και έξοδο κάθε βήματος (η συνάρτηση fast-fibonacci ορίζεται στα επόμενα):
(step (fast-fibonacci 64))

break : H συνάρτηση BREAK διακόπτει την εκτέλεση και φέρνει στο toploop, σε κατάσταση
επιλογής για: συνέχεια της εκτέλεσης, γύρισμα στο προηγούμενο επίπεδο, εγκατάλειψη της
εκτέλεσης και γύρισμα στο top level, γύρισμα στον editor, κλπ. Οι επιλογές εξαρτώνται από τον
compiler. Εφαρμογή:
(break)
(break "…message…")

room

:

Δίνει την κατανάλωση μνήμης, διαθέσιμο υπόλοιπο, κλπ.

time : H συνάρτηση TIME , ενός ορίσματος, δίνει το χρόνο υπολογισμού / εκτέλεσης του
ορίσματος (ή τους χρόνους, αν στο σύστημα έχουν νόημα διαφόρων ειδών μετρήσεις χρόνου).
Τυπώνει το χρόνο υπολογισμού της φόρμας - ορίσματός της, καθώς και άλλες πληροφορίες, όπως
για χρήση του garbage collector, και επιστρέφει κανονικά το αποτέλεσμα της συνάρτησης ορίσματος. Για το λόγο αυτό μπορεί να παρεμβληθεί στον κώδικα για μέτρηση του χρόνου
εκτέλεσης ενός μέρους του υπολογισμού ή ολόκληρου, χωρίς να παρενοχλήσει την εκτέλεση. Πχ:
(* 2 (time (fast-fibonacci 1000)) )
189

;; επιστρέφει το διπλό της τιμής της εξόδου τής συνάρτησης
; fast-fibonacci και τυπώνει τις πληροφορίες χρόνου υπολογισμού
; τής συνάρτησης fast-fibonacci
dribble : Συνάρτηση με όρισμα string, που είναι ένα όνομα αρχείου ή μονοπάτι. Ανοίγει το
αρχείο και αντιγράφει σ' αυτό κάθε είσοδο - έξοδο που εμφανίζεται στο toploop. Χωρίς όρισμα,
σταματά την αντιγραφή και κλείνει το αρχείο. Το αρχείο αυτό μπορούμε να το μελετήσουμε από
οποιονδήποτε text editor.
(dribble "c:/my/spy.txt")
; άνοιγμα του αρχείου, εγκαινίαση εγγραφής
... ...
; εγγράφεται στο αρχείο spy ότι γίνεται στο toploop
(dribble)
; τέλος εγγραφής και κλείσιμο του αρχείου
Είναι ιδιαίτερα χρήσιμη συνάρτηση:
– για παρακολούθηση των ενεργειών του τελικού χρήστη (πχ. μαθητευόμενο)
– για πειραματισμούς όπου συνθέτει κανείς "τοπικά" ιδέες χωρίς να είναι απαραίτητα
ολοκληρωμένες ή σωστές (ώστε να σωθούν ως κώδικας)
– για επιλογή χρησίμων συμβόλων που σταδιακά προέκυψαν μετά από μια αλληλεπιδραστική
χρήση
– για εντοπισμό φάσεων κατά την αλληλεπίδραση, που εκ των υστέρων εκτιμήθηκε οτι είχαν
ενδιαφέρον
– για αναζήτηση προηγουμένων ορισμών, που δόθηκαν και αργότερα μεταβλήθηκαν

Συναρτήσεις σύνδεσης με τον πραγματικό χρόνο
get-universal-time : Συνάρτηση χωρίς ορίσματα. Επιστρέφει την ένδειξη του πραγματικού
ρολογιού, ως ακέραιο αριθμό που μετρά τα δευτερόλεπτα (με ακρίβεια εκατοστού) από κάποια
σταθερή αρχή χρόνου (1 Jan 1900 GMT , 86400 sec/day).
(get-universal-time)

Μπορεί να χρησιμοποιηθεί ως έχει, για μέτρηση διαφορών χρόνου από αρχή μέχρι τέλος μιας
εκτέλεσης ή από μια φάση της εκτέλεσης σε άλλη, ή για συγχρονισμό με την πραγματικότητα σε
προγράμματα πραγματικού χρόνου. Με κατάλληλη μετατροπή, παίρνουμε την έξοδο σε μορφή
συνήθων μονάδων μέτρησης χρόνου, αλλά είναι συχνό να διαθέτει ο compiler κάποια συνάρτηση
που να επιτελεί αυτό το έργο.

get-internal-real-time : Διαφέρει από την προηγούμενη κατά το οτι αρχή του χρόνου είναι η
αρχή εκτέλεσης (φορτώματος) του προγράμματος.
(get-internal-real-time)

get-internal-run-time : Διαφέρει από την προηγούμενη κατά το οτι μετρά το χρόνο που διέθεσε
η μηχανή για την τρέχουσα χρήση της Lisp (αν λειτουργεί σε "time sharing").
(get-internal-run-time)

get-decoded-time : δίνει την τρέχουσα ώρα, σε συνήθεις μονάδες μέτρησης (sec, min, hour,
day, month, year, name-of-day, daylight-saving-time-p, time-zone)

decode-universal-time : με ένα όρισμα - αριθμό που είναι έξοδος της GET-UNIVERSAL-TIME ,
190

τον μετατρέπει σε συνήθεις μονάδες. Δέχεται και δεύτερο όρισμα, που είναι η ωριαία ζώνη της
περιοχής.

encode-universal-time : με ορίσματα sec, min, hour, day, month, year δίνει έξοδο στη μονάδα
(δευτερόλεπτα από κάποια αρχή) αντίστοιχα με την GET-UNIVERSAL-TIME . Χρησιμεύει για
πράξεις χρόνου. Δέχεται και δεύτερο όρισμα, που είναι η ωριαία ζώνη της περιοχής.
sleep
: συνάρτηση ενός ορίσματος. Σταματά την εκτέλεση τόσα δευτερόλεπτα όση είναι η
τιμή του ορίσματος:
(defun print-and-wait (x n) (print x)
(cond
((= n 0) (beep))
(t (sleep 0.5) (print-and-wait (* 2 x) (1- n))) ))

Συλλογή απορριμμάτων
Απορρίμματα, είναι καταχωρημένες στη μνήμη πληροφορίες, που δεν συνδέονται με κάποιο
σύμβολο, λόγω μεταβολών που έχουν επέλθει, ή έχουν δημιουργηθεί από κάποιον υπολογισμό
που έχει περατωθεί.
gc
: Είναι η συνάρτηση - συλλογέας απορριμμάτων (garbage collector). Είναι συνάρτηση,
που αν κληθεί χωρίς ορίσματα, προκαλεί διαγραφή όλων των περιττών στοιχείων από τη μνήμη
(δηλ. ότι έχει καταχωρηθεί και δεν είναι πλέον συνδεδεμένο με κάποιο από τα στοιχεία του
προγράμματος). Η ακριβής λειτουργικότητα είναι εξαρτημένη από το σύστημα και τον compiler.
Τρέχει αυτόματα από τη Lisp όταν η κατανάλωση μνήμης υπερβεί κάποια όρια (αρχικά
προσδιορισμένα, αλλά που μπορεί να επαναπροσδιορίσει ο χρήστης).
Αν και η εκτέλεση της GC είναι αυτόματη, επειδή προκαλεί κάποια καθυστέρηση (της τάξης
δεκάτων του δευτερολέπτου) σε εκτελέσεις συναρτήσεων πραγματικού χρόνου είναι σκόπιμο να
ζητείται σε φάσεις όπου δεν έχει σημασία αυτή η καθυστέρηση. Μια καλή θέση να ζητήσουμε
(GC) είναι πριν την ανάγνωση τιμής εισόδου.

4.3.4 Μεταβλητές του συστήματος
Το σύμβολο του πολλαπλασιασμού ("αστεράκι": * ) καλούμενο σε θέση μεταβλητής, δεν είναι
ένας οποιοσδήποτε χαρακτήρας, ούτε η συνάρτηση του πολλαπλασιασμού: παριστά μια
συγκεκριμένη μεταβλητή, που παίζει ειδικό ρόλο. Οι μεταβλητές αυτές είναι δύο ειδών:
– οι ειδικές του συστήματος (πολλές εκατοντάδες για κάθε πακέτο)
– οι του πρώτου επιπέδου (toploop) που είναι ελάχιστες, κοινές όλων των πακέτων.

Ειδικές μεταβλητές του συστήματος
Ως ειδικές μεταβλητές του συστήματος χαρακτηρίζονται αυτές που αποτελούν τις παραμέτρους
της λειτουργίας της Lisp, και των οποίων οι (αρχικές ή διαμορφωμένες) τιμές καθορίζουν τις
191

συνθήκες λειτουργίας του (κάθε) προγράμματος. Είναι τύπου symbol , υποτύπου special .
Οι πρωτογενείς ειδικές μεταβλητές του συστήματος έχουν συνήθως όνομα ανάμεσα σε
αστερίσκους, όπως: *ERROR-OUTPUT* που καθορίζει το ρεύμα εξόδου για τα μηνύματα
σφάλματος, *STANDARD-OUTPUT* που καθορίζει το κύριο ρεύμα εξόδου, *STANDARDINPUT* που καθορίζει το κύριο ρεύμα εισόδου, κά.
Είδαμε οτι μπορούμε να προσδιορίσουμε και νέες ειδικές μεταβλητές μέσω της συνάρτησης
DEFVAR (όμοια σύνταξη με την SETQ). Η σκοπιμότητα χρήσης ειδικών μεταβλητών είναι:
– να διαμορφώσουμε τη λειτουργικότητα του compiler
– να προσδιορίσουμε μεταβλητές ειδικής μεταχείρισης από το πρόγραμμα, ή διαχείρισης του
χώρου του προγράμματος
Μπορούμε να χρησιμοποιήσουμε τις ειδικές μεταβλητές ως κοινά σύμβολα, αλλά επέμβαση στην
τιμή τους γίνεται μόνο με τη συνάρτηση DEFPARAMETER .
Μεταξύ άλλων, πολύ χρήσιμες ειδικές μεταβλητές είναι οι εξής:
*PRINT-CASE* : Διαμορφώνει το στυλ εκτύπωσης (πεζά - κεφαλαία). Δεκτές τιμές είναι οι
εξής:
:upcase (για εκτύπωση ονομάτων συμβόλων με κεφαλαία),
:downcase (για εκτύπωση ονομάτων συμβόλων με μικρά),
:capitalize (για εκτύπωση ονομάτων συμβόλων με αρχικό κεφαλαίο).
*PRINT-LENGTH* : Ορίζει το μέγιστο πλήθος στοιχείων εκτύπωσης λίστας. Δεκτές τιμές:
φυσικός αριθμός, ή NIL για απεριόριστη εκτύπωση.
– μικρό πλήθος διευκολύνει πολύ τις δοκιμές, αλλά είναι περιοριστικό για τον υπολογισμό
– μεγάλο πλήθος είναι ενοχλητικό κατά την εκτύπωση αν δεν είναι ακριβώς αυτό που ζητάμε,
και το κυριότερο, είναι δυνατό να προκαλέσει υπερχείλιση και να σταματήσει η εκτέλεση
– απεριόριστο πλήθος χρησιμοποιούμε μόνο όταν βεβαιωθούμε πως δεν υπάρχει κίνδυνος, και
χρειαζόμαστε το ακριβές πλήθος.
Σε κάθε περίπτωση, δεν επιδρά στον εσωτερικό υπολογισμό.
*PRINT-BASE* : Διαμορφώνει τη βάση τού αριθμητικού συστήματος (δεκαδικό, δυαδικό,
δεκαεξαδικό…) με το οποίο τυπώνονται τα αποτελέσματα (αρχική τιμή 10).
*READ-BASE* : Προσδιορίζει τη βάση αρίθμησης για στοιχεία εισόδου.
Σημείωση: Τα αστεράκια των ειδικών μεταβλητών είναι χαρακτήρες του ονόματος. Δεν είναι
αναγκαία.
Παράδειγμα
(defun change-base (n m)
(defparameter read-base n) (setq v (read))
(defparameter print-base m) (print v) )

Εφαρμογή:
(change-base 8 16)

→ <κατάσταση εισόδου∙ δίνουμε οκταδικό:>
192

1234567

<παίρνουμε έξοδο, τον αριθμό στο δεκαεξαδικό:>
→ 12D687

Μεταβλητές του "toploop"
Ως μεταβλητές του πρώτου επιπέδου ανακύκλωσης (toploop variables) χαρακτηρίζεται ένα
σύνολο ειδικών μεταβλητών που "κρατούν μια μικρή ιστορία" του τί έχει συμβεί στα τελευταία
βήματα εκτέλεσης.
Οι μεταβλητές: + , ++ , +++ παίρνουν αντίστοιχα τιμή την: τελευταία, προτελευταία, προπροτελευταία, φόρμα που υπολογίστηκε.
Οι μεταβλητές: * , ** , *** παίρνουν τιμή το: τελευταίο, προτελευταίο, προ-προτελευταίο
στοιχείο που δόθηκε (αποτέλεσμα που υπολογίστηκε, φόρμα που δόθηκε για υπολογισμό). Αν η
φόρμα που εκτελείται, δίνει περισσότερα του ενός αποτελέσματα, κρατούν το τελευταίο.
Οι μεταβλητές: / , // , /// κρατούν αντίστοιχα το σύνολο (λίστα) των αποτελεσμάτων που
έδωσε η αντίστοιχη φόρμα (που κλήθηκε τελευταία, προτελευταία, προ-προτελευταία).
Οι ίδιες οι μεταβλητές αυτές αποτελούν φόρμες, και η διαδοχική κλήση τους μεταβάλλει την
ιστορία. Επομένως, για να επανέλθουμε σε προγενέστερα βήματα, πρέπει να λάβουμε υπ' όψη και
τα βήματα που προκαλούν οι ίδιες, αν πρόκειται να χρησιμοποιηθούν επανειλημμένα.

193

4.4 Ασκήσεις
1. Να γράψετε συνάρτηση που να διαβάζει αριθμούς μέχρι να δώσουμε είσοδο τον αριθμό 1000
και να προσδιορίζει το μέσο όρο τους, α) τυπώνοντας το αποτέλεσμα σε κάθε είσοδο, β) στο
τέλος μόνο.

2. Να γράψετε συνάρτηση F που να διαβάζει όνομα συνάρτησης ενός ορίσματος (η οποία να
είναι ήδη ορισμένη) και μια τιμή κατάλληλου τύπου για είσοδο της συνάρτησης που διάβασε,
και να την εφαρμόζει στο όρισμα αυτό.

3. Να γράψετε συνάρτηση R που να διαβάζει τον ορισμό συνάρτησης G τριών ορισμάτων, και
τρεις τιμές κατάλληλου τύπου για να δοθούν ως ορίσματα στην G, και να εφαρμόζει την G σ'
αυτά.
4. Να γράψετε συνάρτηση που να διαβάζει από την είσοδο τον ορισμό νέας συνάρτησης χωρίς
ορίσματα, όπου η νέα συνάρτηση να εκτελείται, και αυτή να διαβάζει από την είσοδο τον
ορισμό νέας συνάρτησης ενός ορίσματος, να διαβάζει το όρισμα αυτό από την είσοδο και να
δίνει τον τελικό υπολογισμό.

5. α) Να γράψετε συνάρτηση MERGE1 που να παίρνει είσοδο από δυο διαφορετικά ρεύματα
αντίστοιχα την τιμή από δυο λίστες αριθμών, να διατάσσει κατ' αύξουσα σειρά την καθεμία,
και στη συνέχεια να τις συνενώνει σε λίστα την οποία να διατάσσει.
β)
Να γράψετε συνάρτηση MERGE2 που να έχει το ίδιο αποτέλεσμα με την MERGE1
αλλά να διαβάζει ήδη διατεταγμένες λίστες και να κάνει απ' ευθείας διατεταγμένη συνένωση,
εφαρμόζοντας αλγόριθμο "merge".

6. Να δώσετε τρόπο με τον οποίο που μια συνάρτηση S να διαβάζει τον ορισμό συνάρτησης H
και τιμές κατάλληλου τύπου και πλήθους για να δοθούν ως ορίσματα στην H και να εφαρμόζει
την Η σ' αυτά.
7. Να γράψετε συνάρτηση που να διαβάζει από αρχείο κειμένου και να μετρά τους χαρακτήρες,
τις λέξεις (με κριτήριο το κενό διάστημα) και τις φράσεις (με κριτήριο την τελεία).

8. Να γράψετε συνάρτηση R που να διαβάζει από αρχείο κειμένου, να εντοπίζει τη λέξη
"τηλέφωνο" και να επιστρέφει το πόσες φορές εμφανίζεται. Επίσης, συνάρτηση Q που να
επιστρέφει τη λίστα των θέσεων χαρακτήρων στο αρχείο, όπου ευρίσκεται η αρχή κάθε
εμφάνισής της. Τέλος, συνάρτηση Ρ που να επιστρέφει τη λίστα των θέσεων λέξεων, όπου
ευρίσκεται κάθε εμφάνισή της.

9. Θέλουμε να δημιουργήσουμε μια βάση δεδομένων, όπου να καταχωρούνται στοιχεία για
πρόσωπα, και να δημιουργείται αυτόματα σύμβολο (στη μορφή personΧΧ) που να
αντιπροσωπεύει κάθε νέο πρόσωπο, και να επισυνάπτονται σ' αυτό τα εξής χαρακτηριστικά:
Επίθετο, Ονομα, Ημερ.γέννησης (λίστα: (έτος μήνας ημέρα) ), Αρ.ταυτότητας,
Δ/ση_κατοικίας (λίστα (πόλη συνοικία δρόμος αριθμός) ), τηλ.κατοικίας, Ειδικότητα,
194

Επάγγελμα, Δ/ση_εργασίας (λίστα) τηλ.εργασίας, Οικογ.κατάσταση (ένα από τα: έγγαμος,
άγαμος, διαζευγμένος), όνομα συζύγου (σύμβολο ή η λέξη 'κενό) ονόματα-παιδιών (λίστα
από σύμβολα).
Για το σκοπό αυτό χρειαζόμαστε:
α) μια συνάρτηση εισαγωγής δεδομένων, πχ:
(add-person 'Κωστής 'Ίων (1950 7 14) "Ζ-345678" …)

όπου σε κάθε εισαγωγή να δημιουργείται αυτόματα το επόμενο (πχ. person34) και να
καταχωρούνται συσχετισμένα με αυτό τα δεδομένα
β) τις αναγκαίες συναρτήσεις εξερεύνησης της βάσης, όπως:
(personal-info 'Κωστής)
→ <όλα τα προσωπικά του στοιχεία>
(professional-info 'Κωστής) → <όλα τα επαγγελματικά του στοιχεία>
γ) συναρτήσεις μαζικής αναζήτησης, πχ. (who-is 'profession) , ή ψάξιμο σε χαρακτηριστικά –
λίστες, πχ.
(who-lives-in 'Athens) , (who-is-born-before 1984)
δ) συναρτήσεις διαμόρφωσης στοιχείων, πχ. για εισαγωγή νέας πληροφορίας
(add-new-info 'Κωστής 'πίστωση 'όχι)

ή αντίστοιχα, για αλλαγή ήδη καταχωρημένης πληροφορίας.

10. Σχηματίστε λίστα με όνομα g-list, 100 συμβόλων, ονόματος |first-level-element-Χ| όπου ως
Χ να τοποθετείται αυτόματα κατάλληλος αύξων αριθμός, και όπου το καθένα από τα στοιχεία
αυτά να είναι λίστα με περιεχόμενο 100 σύμβολα, ονόματος: |second-level-element-Υ| , με Υ
ομοίως, και το καθένα από τα τελευταία να έχει τιμή-σύμβολο με όνομα |value-Ζ|, όπου Ζ
αριθμός που να συμπίπτει με το αντίστοιχο Υ .

11. Να ορίσετε συνάρτηση που να δέχεται δύο ορίσματα, μια λίστα αριθμών και μια συνάρτηση
- διμελή αριθμητική πράξη, και να επιστρέφει λίστα που έχει πρώτο όρο τον ίδιο με τη λίστα
εισόδου και επόμενους όρους τους αριθμούς που προκύπτουν διαδοχικά από την εφαρμογή της
πράξης αυτής πάνω στον τελευταίο όρο που υπολογίστηκε για τη λίστα εξόδου και τον
επόμενο μη χρησιμοποιημένο όρο της λίστας εισόδου. Πχ. για πρώτο όρο τη λίστα (1 2 3 4)
και δεύτερο τη συνάρτηση F που ορίζεται βάσει της μαθηματικής σχέσης F(x,y) = 5∙x–y , να
επιστρέφει τη λίστα (1 3 12 56) .

12. Να ορίσετε συνάρτηση που να δέχεται δύο ορίσματα, μια λίστα αριθμών και μια συνάρτηση
- μονομελή αριθμητική πράξη, και να επιστρέφει τη λίστα που έχει όρους τους αριθμούς που
προκύπτουν διαδοχικά από την εφαρμογή της πράξης αυτής πάνω στον επόμενο μη
χρησιμοποιημένο όρο της λίστας εισόδου. Πχ. για πρώτο όρο τη λίστα (1 2 3 4) και
δεύτερο τη συνάρτηση G που ορίζεται βάσει της μαθηματικής σχέσης G(x) = 2∙x 2 , να
επιστρέφει τη λίστα (2 8 18 32) .

195

5 Συναρτήσεις Ανώτερης Τάξης, Τόποι, Τύποι
Στο κεφάλαιο αυτό θα δούμε τον τρόπο δημιουργίας συναρτήσεων ανώτερης τάξης και χρήσεις
τους, και τη δυνατότητα χρήσης ονομάτων συναρτήσεων ως τιμών λ-μεταβλητών. Επίσης, τον
τρόπο ορισμού συναρτήσεων με προαιρετικά ή βοηθητικά ορίσματα.
Στη συνέχεια θα δούμε θέματα που αφορούν τόπους μεταβλητών και συναρτήσεων. Τέλος θα
μελετήσουμε τις τεχνικές δημιουργίας υποτύπων και εφαρμογές τους.

5.1 Συναρτήσεις ανώτερης τάξης
Χαρακτηρίζουμε ως συναρτήσεις ανώτερης τάξης (higher order functions) αυτές που δέχονται ως
ορίσματά τους συναρτήσεις (δηλ. ονόματα συναρτήσεων και όχι απλώς συναρτησιακές φόρμες
εφαρμογής). Κεντρικό χαρακτηριστικό των συναρτήσεων ανωτέρας τάξης, είναι οτι η τάξη
μπορεί να θεωρηθεί είτε ως πεπερασμένη (πρώτης τάξης οι συναρτήσεις που εφαρμόζονται σε
σταθερές, δεύτερης οι συναρτήσεις που εφαρμόζονται σε συναρτήσεις που εφαρμόζονται
σταθερές, κοκ.) είτε ως απεριόριστη (συναρτήσεις που είναι δυνατό να εφαρμοστούν σε
οποιασδήποτε τάξης συναρτήσεις). Η διάκριση δεν είναι ριζική παρά μόνο μεταξύ των
συναρτήσεων πρώτης και ανωτέρας τάξης, διότι, όπως θα δούμε, η τάξη στις συναρτήσεις
ανωτέρας τάξης εξαρτάται περισσότερο από τον τρόπο χρήσης παρά από τον ορισμό της
συνάρτησης.
Διακρίνουμε τριών ειδών συναρτήσεις ανώτερης τάξης:

i) αυτές που προκαλούν εφαρμογή συνάρτησης, με το νόημα οτι δέχονται συνάρτηση ως
όρισμα και την εφαρμόζουν πάνω σε κάτι∙ αυτό επιτρέπει τη δέσμευση μεταβλητής σε
συνάρτηση
ii) αυτές που προσδιορίζουν τελεστές, δηλ. συναρτήσεις που εφαρμόζονται σε συναρτήσεις και
παράγουν συναρτήσεις
iii) αυτές που εξυπηρετούν την οργάνωση πολυεπίπεδης λειτουργίας του προγράμματος,
παρέχοντας δυνατότητες διαδοχικών εφαρμογών (εφαρμογή συνάρτησης, με αποτέλεσμα
συνάρτηση, εφαρμογή του αποτελέσματος ως συνάρτηση, με αποτέλεσμα συνάρτηση, κοκ).
Εδώ θα αναφερθούμε στα δύο πρώτα είδη (που διαφοροποιούνται μόνον σημασιολογικά) ενώ το
τρίτο θα το μελετήσουμε διεξοδικά σε επόμενα κεφάλαια, διότι χρειάζεται πρώτα να δούμε σε
βάθος το νόημα της λ-έκφρασης.

196

5.1.1 Δήλωση έκφρασης ως συναρτησιακής
Όπως είδαμε, η κλήση συγκεκριμένης συνάρτησης για εφαρμογή γίνεται πάντα με γραφή του
ονόματός της ως πρώτο στοιχείο σε μια φόρμα – λίστα όπου επόμενα στοιχεία είναι οι τιμές των
ορισμάτων της, οι οποίες μπορούν επίσης να είναι φόρμες υπολογισμού. Είδαμε ακόμα οτι ο
χρήστης μπορεί να διαμορφώσει ελεύθερα τη φόρμα εφαρμογής μιας συνάρτησης.
Στις περιπτώσεις που μελετήσαμε, όπου αναφέρθηκε κλήση συνάρτησης σε χαμηλότερο επίπεδο
(δηλ. η συνάρτηση που καλούμε, να καλεί εσωτερικά άλλες συναρτήσεις προς εφαρμογή),
θεωρήσαμε αυτονόητο οτι κάθε καλούμενη συνάρτηση είναι συγκεκριμένη: είτε προκαθορισμένη
μέσα στη φόρμα που εκτελείται, είτε οριζόμενη "επί τόπου" κατά την κλήση (ως όρισμα), είτε
δίνοντάς την ως είσοδο στη READ.
Διαθέτουμε όμως μια ευρύτερη δυνατότητα: να αποδώσουμε σε λ-μεταβλητή την έννοια
συνάρτησης, ώστε κατά την εφαρμογή να δεσμεύσουμε τη μεταβλητή αυτή σε συγκεκριμένη
συνάρτηση, επιλεγόμενη στη φάση εφαρμογής. Ο σκοπός αυτός εξυπηρετείται από ορισμένες
πρωτογενείς συναρτήσεις που διαθέτει η CL και ρόλος τους είναι να καλέσουν συνάρτηση για
εφαρμογή. Επειδή η καλούμενη συνάρτηση έχει θέση ορίσματος σ' αυτές, και το όρισμα δεν είναι
τίποτα άλλο από την τιμή δέσμευσης της αντίστοιχης λ-μεταβλητής, οι συναρτήσεις αυτές μας
επιτρέπουν να δούμε τις συναρτήσεις ως αντικείμενα δέσμευσης κατά τον υπολογισμό. Μέσω
αυτών μπορούμε να ορίσουμε συναρτήσεις με ορίσματα συναρτήσεις.
Διευκρινίζουμε οτι οι συναρτήσεις που καλούν συνάρτηση για εφαρμογή, αν πρόκειται να
χρησιμοποιηθούν για κλήση εκ των προτέρων καθορισμένης συνάρτησης, δεν κάνουν τίποτα
περισσότερο από αυτό που κάνουμε με τη σύνταξη κάποιας φόρμας υπολογισμού, και η χρήση
τους προφανώς αποτελεί περιττολογία. Αλλά αν πρόκειται να χρησιμοποιήσουμε μεταβλητή που
παριστά όνομα συνάρτησης (και όχι φόρμα εφαρμογής, όπως στις συνήθεις περιπτώσεις) παίζουν
έναν ουσιαστικό ρόλο.
Διαθέτοντας μια τέτοια δυνατότητα, μπορούμε να μεθοδεύσουμε την κλήση συνάρτησης με πολύ
πιο ευέλικτους τρόπους, και να αποδώσουμε σύνθετα νοήματα, όπως:
– "εφάρμοσε τη συνάρτηση F πάνω στα εξής ορίσματα …"
– "εφάρμοσε τη συνάρτηση F πάνω στα ορίσματα που θα δοθούν από την είσοδο"
– "εφάρμοσε τη συνάρτηση που θα δοθεί από την είσοδο, πάνω στα εξής ορίσματα"
– "εφάρμοσε τη συνάρτηση που θα δοθεί από την είσοδο, πάνω στα ορίσματα που θα δοθούν
επίσης από την είσοδο"
– "εφάρμοσε τη συνάρτηση που θα προκύψει από την εκτέλεση της διαδικασίας Α, πάνω στα
ορίσματα Β"
– "εφάρμοσε τη συνάρτηση που θα προκύψει από την εκτέλεση της διαδικασίας Α, πάνω στα
ορίσματα που θα δοθούν από την είσοδο"
– "εφάρμοσε τη συνάρτηση που θα προκύψει από την εκτέλεση της διαδικασίας Α, πάνω στα
ορίσματα που θα προκύψουν από την εκτέλεση της διαδικασίας Β"
197

και αυτά σε επανειλημμένες διαδοχικές εφαρμογές.

Συνάρτηση που δηλώνει οτι "το όρισμά της είναι συναρτησιακή έκφραση"

Το όνομα καλούμενης συνάρτησης πρέπει να είναι το πρώτο στοιχείο της εκτελούμενης φόρμας,
και αυτό δεν μπορεί να είναι μεταβλητή. Για να "κατεβάσουμε" ένα όνομα συνάρτησης σε
επίπεδο μεταβλητής, είναι απαραίτητη πρώτα απ' όλα μια βοηθητική συνάρτηση η οποία να
δέχεται ως όρισμά της είτε: i) ένα όνομα συνάρτησης και να το επιστρέφει ii) μια συναρτησιακή
έκφραση και να την επιστρέφει, iii) μια έκφραση υπολογισμού που να επιστρέφει όνομα
συνάρτησης ή συναρτησιακή έκφραση, το οποίο να επιστρέφει επίσης. Αυτή είναι η θεμελιακή
συνάρτηση FUNCTION :
function :

<symbol-function> → <symbol-function>

Η συνάρτηση FUNCTION δέχεται ένα όρισμα το οποίο, μετά τον υπολογισμό του, πρέπει να
παριστά όνομα συνάρτησης ή συναρτησιακή έκφραση, και το επιστρέφει. Αυτό που ουσιαστικά
κάνει, είναι να "λέει στη Lisp οτι αυτό το όνομα ή η έκφραση είναι συνάρτηση", οπότε η Lisp το
επεξεργάζεται ως συνάρτηση, και εξ αιτίας από το γεγονός αυτό, περιμένει να ακολουθήσουν τα
ορίσματα της συνάρτησης.
Η απλούστερη μορφή σύνταξης, αν δεν θέλουμε να υπολογιστεί το όρισμα, είναι:
(function '<όνομα-συνάρτησης>)
Επιστρέφει το όνομα της συνάρτησης. Πχ, για να δηλώσουμε οτι το σύμβολο square είναι όνομα
συνάρτησης:
(function 'square) → square
Αυτό βέβαια δεν έχει ιδιαίτερο νόημα στην περίπτωση συγκεκριμένων ονομάτων συναρτήσεων,
διότι είναι περιττολογία. Στη θέση του ορίσματος μπορούμε να δώσουμε φόρμα της οποίας ο
υπολογισμός επιστρέφει όνομα συνάρτησης:
(function <φόρμα που επιστρέφει όνομα συνάρτησης> )
πχ, για να δηλώσουμε οτι το όρισμα που ακολουθεί είναι ορισμός της CUBE :
(function (defun cube (x) (* x x x)) )

Γενικότερα, τη θέση του ορίσματος μπορεί να καταλάβει οποιοσδήποτε υπολογισμός που έχει ως
αποτέλεσμα μια συναρτησιακή έκφραση:
(function <φόρμα που επιστρέφει συναρτησιακή έκφραση> )
Συνεπώς, τη θέση του ορίσματος μπορεί να καταλάβει είτε οποιαδήποτε "quoted" συναρτησιακή
έκφραση:
(function '<συναρτησιακή έκφραση>)
είτε φόρμα της οποίας ο υπολογισμός επιστρέφει συναρτησιακή έκφραση:

Σε κάθε περίπτωση η εφαρμογή της FUNCTION επιστρέφει έκφραση που αναγνωρίζεται από τον
compiler ως συνάρτηση. Με άλλα λόγια, μπορούμε να κατεβάσουμε σε επίπεδο εφαρμογής τον
προσδιορισμό συναρτησιακής έκφρασης.
Τεχνικά, ο ρόλος της FUNCTION είναι να "ειδοποιήσει" τον compiler οτι ακολουθεί
198

συναρτησιακή έκφραση (ώστε να την μεταγλωττίσει, αν είναι άγνωστη, και γενικά να συνεχίσει
το κτίσιμο της στοίβας του υπολογισμού)

Συντομογραφία της FUNCTION : Επειδή η χρήση της function είναι συχνή, έχει ως
συντομογραφική έκφραση το # που τίθεται ως πρόθεμα στην αντίστοιχη έκφραση.
Συντομογραφία

της

σύνθεσης FUNCTION - QUOTE : Επειδή η χρήση της σύνθεσης
(function (QUOTE …)) είναι πολύ συχνή, έχει μια συντομογραφική έκφραση, το #' που τίθεται
ως πρόθεμα στην αντίστοιχη έκφραση.
Έτσι οι παραπάνω φόρμες εφαρμογής της FUNCTION απλοποιούνται στις αντίστοιχες:
#'<όνομα συνάρτησης>
#<έκφραση υπολογισμού που επιστρέφει όνομα-συνάρτησης>
#'<σύνθετη συναρτησιακή έκφραση>
#<έκφραση υπολογισμού που επιστρέφει συναρτησιακή έκφραση>
Προσοχή: Αν και θεωρητικά, βάσει των κανόνων του λ-λογισμού, η έκφραση
((function 'square) 3) , είναι ισοδύναμη της (square 3) , δεν είναι αποδεκτό στην CL να δώσουμε
την πρώτη έκφραση, διότι ο πρώτος όρος φόρμας υπολογισμού δεν μπορεί να είναι φόρμα
υπολογισμού∙ η απαγόρευση αυτή παρακάμπτεται εύκολα με τη μεθόδευση που θα δούμε
παρακάτω.

5.1.2 Έμμεση εφαρμογή δηλωμένης συναρτησιακής έκφρασης
Η FUNCTION ως συνάρτηση επιστρέφει την τιμή του ορίσματός της, δηλώνοντας στον compiler
οτι πρόκειται για συναρτησιακή έκφραση. Όμως δεν μπορεί να χρησιμοποιηθεί αυτή η έξοδος για
εφαρμογή, δεδομένου οτι για να προσδιορίσουμε μια εφαρμογή, πρέπει να αναγράψουμε ως
πρώτο όρο στη φόρμα της εφαρμογής, το όνομα της συνάρτησης. Άρα κάποια άλλη συνάρτηση
πρέπει να προκαλέσει την εφαρμογή.

Κλήση συναρτησιακής έκφρασης προς εφαρμογή: α' περίπτωση
apply : Συνάρτηση που προκαλεί εφαρμογή συναρτησιακής έκφρασης σε δοσμένα ορίσματα.
Επιστρέφει το αποτέλεσμα της εφαρμογής της συναρτησιακής έκφρασης.
α'. Η απλούστερη μορφή χρήσης:
Συνάρτηση: apply: #'<functional expression>  <list> → <term>
Είναι συνάρτηση ειδικής μορφής, με δύο ορίσματα, όπου:
– το πρώτο όρισμα είναι "quoted" συναρτησιακή έκφραση, δηλωμένη ως τέτοια μέσω της
FUNCTION (σε προθεματική γραφή, δίνουμε το όνομα της συνάρτησης με πρόθεμα #')
– το δεύτερο όρισμα είναι μια λίστα με όρους τις τιμές των ορισμάτων στα οποία θα
εφαρμοστεί το πρώτο όρισμα.
Η φόρμα εφαρμογής της APPLY είναι:
(apply
199

#’<συναρτησιακή έκφραση>
'(όρισμα_1 όρισμα_2 … όρισμα_n ) )
δηλαδή, οι εξής τρείς περιπτώσεις:
(apply #’<όνομα συνάρτησης> '(όρισμα_1 όρισμα_2 … όρισμα_n ) )
(apply #'<λ-έκφραση> '(όρισμα_1 όρισμα_2 … όρισμα_n ) )
(apply
#<έκφραση υπολογισμού που δίνει συναρτησιακή έκφραση>
'(όρισμα_1 όρισμα_2 … όρισμα_n ) )

Το "quote" στο δεύτερο όρισμα αποτρέπει τη Lisp από το να υπολογίσει τα στοιχεία τής λίστας,
που θα οδηγούσε στο να αναζητήσει ρόλο συνάρτησης στη φόρμα-1, αλλά βέβαια, αν θέλουμε να
γίνει τέτοιος υπολογισμός θα δώσουμε το δεύτερο όρισμα - λίστα σε μορφή φόρμας υπολογισμού
χωρίς "quote" .
Προφανώς, αν η καλούμενη προς εφαρμογή έκφραση δεν δέχεται ορίσματα, το δεύτερο όρισμα λίστα θα είναι η κενή λίστα.
Παραδείγματα

(apply #'square '(2)) → 4
(apply #'square '(2 3 5)) → 4

(διότι η square δέχεται ένα όρισμα, και παίρνει το πρώτο από τη λίστα)
(apply #'/ '(3 4))
→ ¾
(float (apply #'/ '(3 4)) ) → 0.75
(apply #'+ '(5 10 15))
→ 30
(apply #'square '((+ 2 3)) ) → Error…
(λόγω του quote δεν υπολογίζεται το άθροισμα αλλά η SQUARE
περιμένει είσοδο αριθμό )
(apply #'square ((+ 2 3)) ) → Error…
(διότι η κεφαλή της λίστας ((+ 2 3)) δεν είναι συνάρτηση)
(apply #'square (eval (+ 2 3)) ) → 25
(apply #'print '(( )))
→ NIL
(apply #'print '((+ 2 3)))
→ (+ 2 3)
(εκτέλεση της print)
(+ 2 3)
(επιστροφή τιμής)
(apply #'identity '((+ 2 3))) → (+ 2 3)
β'. Η γενικότερη μορφή χρήσης:
apply: #'<functional expression>  <term> n  <list> → <term>
Όταν η καλούμενη συναρτησιακή έκφραση δέχεται περισσότερα του ενός ορίσματα, μπορούν να
αναγραφούν είτε όλα μέσα στη λίστα των ορισμάτων είτε κάποια (ένα, περισσότερα ή όλα) έξω
από τη λίστα αυτή, και συγκεκριμένα, πριν τη λίστα. Η γενική φόρμα είναι:
(apply #’<συναρτησιακή έκφραση>
όρισμα_1 … όρισμα_k '(όρισμα_k+1 … όρισμα_n ) )
► Τονίζουμε οτι η τελική λίστα μπορεί να είναι κενή αλλά δεν μπορεί να λείπει.
► Όταν η καλούμενη συνάρτηση δέχεται ορίσματα λίστες, είναι απλούστερο να τα θέσουμε ως
ορίσματα - "quoted" λίστες σε διαδοχή, με τελευταίο την κενή λίστα. Αν η καλούμενη συνάρτηση
200

δέχεται ως ορίσματα άτομα, είναι απλούστερο να τα θέσουμε όλα μέσα στην τελική λίστα.
Οι ακόλουθες εκφράσεις είναι ισοδύναμες:
(apply #’<συνάρτηση> '( var.1 … var.k var.k+1 … var.n ) )
(apply #’<συνάρτηση> var.1 … var.k '(var.k+1 … var.n ) )
(apply #’<συνάρτηση> var.1 … var.k next-vars )

όπου η μεταβλητή next-vars θα πρέπει, κατά την εφαρμογή, να πάρει τιμή τη λίστα των
υπολοίπων ορισμάτων, δηλ. την (var.k+1 …var.n )
Παραδείγματα
(apply
(apply
(apply
(setq
(apply
(apply
(apply
(apply

#’+ '(1 2 3 4 5))
→ 15
#’+ 1 2 3 4 '(5)) → 15
#’+ 1 2 3 '(4 5)) → 15
a '( 2 4))
#'* 10 a)
→ 80
#'list '(1 2 3))
→ (1 2 3)
#'list 1 2 3 nil)
→ (1 2 3)
#'list 1 2 3 '(4 5 6)) → (1 2 3 4 5 6)

Με το να χρησιμοποιήσουμε μεταβλητή η οποία παριστά την τελική λίστα, αποκτάμε τη
δυνατότητα να δεσμεύσουμε αυτή τη μεταβλητή οποτεδήποτε, πριν τον υπολογισμό εφαρμογής
της καλούμενης συνάρτησης).Αυτό προφανώς παίζει ρόλο στην περίπτωση συνάρτησης που
δέχεται μεταβαλλόμενου πλήθους ορίσματα, ακριβώς επειδή η λίστα μπορεί να εκφράσει το
νόημα του μεταβλητού πλήθους όρων. Έτσι, μπορούμε να προσδιορίσουμε την εφαρμογή
ακόμα και αν αγνοούμε πόσα ορίσματα θα δοθούν τελικά Θα δούμε τέτοιες χρήσεις.
Συναντάμε συχνά χρήσεις της APPLY σε περιπτώσεις κλήσης συνάρτησης η οποία δέχεται
ορίσματα λίστες, ακολουθίες ή strings, διότι συνδυάζει την ευχέρεια μεταβαλλόμενου πλήθους
ορισμάτων με την ευχέρεια μεταβαλλόμενου πλήθους στοιχείων μέσα στα ορίσματα - λίστες.
Παραδείγματα
1. Εφαρμογή της APPLY σε συνάρτηση με ορίσματα-λίστες ελεύθερου πλήθους:
(apply #’append '(a b c) '(d e ) '((f g h)))
→ (a b c d e f g . h)

► Η τελεία μέσα στη λίστα δηλώνει οτι αυτή η λίστα δεν έχει τελική ουρά το nil αλλά το h
(βλέπε στα επόμενα: "dotted lists"). Για να μη προκληθεί αυτό, πρέπει να δώσουμε:
(apply #’append ’(a b c) ’(d e ) ’(f g h) nil)

→ (a b c d e f g h)
2. Αξιοποίηση στη σύνθεση φράσης σε φυσική γλώσσα:
(setq person '( "Μαρία " "Νίκος " "Γιάννης " "τραπέζι " ))
(setq naming '( "o " "η " "το " ))
(setq relation '( "είναι " "έχει " ))
201

(setq
(setq
(setq
(setq

negation '( "δεν " "ούτε "))
connectives '( "ή " "είτε " "και " "αλλά " ))
qualification '( "δικηγόρος " "γιατρός " ))
object '( "σπίτι " "ποδήλατο " "βάρκα " ))

Κλήση για παραμετρική δημιουργία φράσης (ας θυμηθούμε οτι η σύνταξη της
CONCATENATE χρειάζεται πρώτο όρισμα τον τύπο του αποτελέσματος και επόμενα ένα
ελεύθερο πλήθος όρων που επιδέχονται συνένωση στον τύπο αυτό):
(apply #'concatenate 'string
(first naming) (second person) (first relation)
(second qualification) (third connectives) (second relation)
(third object) nil)

"o Νίκος είναι γιατρός και έχει βάρκα

Παρόμοια, έχουμε:
(apply #'concatenate 'string
(first naming) (third person) (first negation) (first relation)
(first qualification) (third connectives) (second negation)
(second relation) (second object) nil)
→ "o Γιάννης δεν είναι δικηγόρος ούτε έχει ποδήλατο "

Κλήση συναρτησιακής έκφρασης προς εφαρμογή: β' περίπτωση

Δεύτερος τρόπος που εκφράζει το "εφάρμοσε την συνάρτηση Α στα ορίσματα που ακολουθούν",
είναι μέσω της συνάρτησης FUNCALL .

Η FUNCALL διαφέρει από την APPLY κατά το οτι, ενώ η τελευταία στοχεύει στην εφαρμογή τής
καλούμενης συνάρτησης πάνω σε λίστα ορισμάτων, όπου αυτή η λίστα μπορεί συνολικά να είναι
αποτέλεσμα άλλης διεργασίας, και να καθορίζεται εκ των υστέρων το ποια θα είναι τα
ορίσματα, η FUNCALL στοχεύει στην εφαρμογή τής καλούμενης συνάρτησης απ' ευθείας πάνω
στα δοσμένα (ή υπολογιζόμενα) ορίσματα, όπου καθένα αυτοτελώς μπορεί μεν να είναι
αποτέλεσμα άλλης διεργασίας αλλά πρέπει να είναι προκαθορισμένα ως οντότητες.
funcall

: #'<symbol-function>  <arg>n → <term>

Είναι συνάρτηση παρεμφερής με την APPLY, αλλά τα ορίσματα της καλούμενης συνάρτησης
είναι δοσμένα στο ίδιο επίπεδο (δηλ. δεν τοποθετούνται μέσα σε λίστα). Η μορφή της είναι:
(funcall #'<συναρτησιακή έκφραση> όρισμα-1 όρισμα-2 … όρισμα-n )
δηλαδή οι περιπτώσεις:
(funcall #’<όνομα συνάρτησης> φόρμα-1 φόρμα-2 … φόρμα-n )
(funcall #’<συναρτησιακή έκφραση> φόρμα-1 φόρμα-2 … φόρμα-n )
(funcall #<έκφραση υπολογισμού με αποτέλεσμα συνάρτηση>
φόρμα-1 φόρμα-2 … φόρμα-n )
(funcall
202

#<έκφραση υπολογισμού με αποτέλεσμα συναρτησιακή έκφραση>
φόρμα-1 φόρμα-2 … φόρμα-n )
Μια περίπτωση εφαρμογής, είναι για ταυτόχρονο καθολικό ορισμό και κλήση εφαρμογής
συνάρτησης:
(funcall
(defun
<όνομα συνάρτησης>
(v1 v2 …vk)
<σώμα>)
<όρισμα1> <όρισμα2> …<όρισμαk>)

Είναι προφανές οτι αν γνωρίζουμε εκ των προτέρων τη συνάρτηση που καλεί η FUNCALL , δεν
χρειάζεται να γραφεί η κλήση στη μορφή αυτή, διότι τότε η έκφραση:
(funcall
#' <όνομα συνάρτησης>
<όρισμα1> <όρισμα2> …<όρισμαk> )
ισοδυναμεί πλήρως με την:
(<όνομα συνάρτησης> <όρισμα1> <όρισμα2> …<όρισμαk> )

Άρα έχει ουσιαστικό νόημα η χρήση της FUNCALL μόνο όταν πρόκειται για περιπτώσεις όπου
δεν μπορούμε να αναγράψουμε συγκεκριμένο όνομα συνάρτησης, όπως όταν η καλούμενη
συναρτησιακή έκφραση:
– δίνεται ως μεταβλητή, που θα δεσμευτεί αργότερα σε συνάρτηση
– πρόκειται για φόρμα υπολογισμού, που το αποτέλεσμά της είναι συνάρτηση (όπως η (defun
…) που είδαμε)
– πρόκειται για λ-έκφραση, που αυτή καθ' εαυτή παίζει ρόλο συναρτησιακής έκφρασης/
Μπορεί να εφαρμοστεί σε ορίσματα, αλλά δεν είναι δυνατό να εφαρμοστεί απ' ευθείας, όπως
θα δούμε σε επόμενο κεφάλαιο.
Παραδείγματα
1. Προσδιορισμός οτι μια λ-μεταβλητή θα δεσμευτεί σε συνάρτηση:
(defun a (x y) (* y (sin x)))
(defun b (x y) (* (+ x 12) (– y 7)))
(defun fun (f x y) (square (funcall f x y)))

Εφαρμογές:
→ 7
→ 0.31863767
→ 2025
2. Άμεση κλήση συνάρτησης προς εφαρμογή:
(funcall #'+ 1 2 3 4)
→ 10
(defun square (x) (* x x)) → square
(fun #'+ 3 4)
(fun #'a 3 4)
(fun #'b 3 4)

203

(defun cube (x) (* x x x))
→ cube
(+ (funcall #'square 3) (funcall #'cube 2))
(funcall #'pythagorio 3 4)
→ 5
(η pythagorio έχει οριστεί προηγουμένως)

→ 17

Η κλήση συγκεκριμένων συναρτήσεων μέσω της funcall όπως στα παραπάνω, είναι
περιττολογία: θα μπορούσαν πολύ καλά να κληθούν απ' ευθείας, όπως:
(+ (square 3) (cube 2)) → 17
(pythagorio 3 4)
→ 5
3. Κλήση έμμεσα προσδιοριζόμενης συνάρτησης:
Η FUNCALL είναι εξαιρετικά χρήσιμη σε δύο περιπτώσεις:
i) Όταν η συνάρτηση στην οποία εφαρμόζεται, είναι σε θέση όπου αυτό δεν είναι δυνατό
συντακτικά, όπως:
(funcall (defun plus2 (x) (+ 2 x)) 5) → 7
Η DEFUN επιστρέφει το σύμβολο – συνάρτηση PLUS2 , και η FUNCALL το εφαρμόζει στο 5
(δεν θέτουμε πρόθεμα #' στην φόρμα της DEFUN διότι η DEFUN επιστρέφει όνομα συνάρτησης
αναγνωρίσιμο από τον compiler).
ii) Όταν η συνάρτηση στην οποία εφαρμόζεται, είναι η τιμή μιας μεταβλητής, πχ:
(setq a #'square)
(funcall a 3) → 9

5.1.3 Συναρτήσεις – τελεστές
Ένα από τα σημαντικότερα πλεονεκτήματα της Lisp είναι η ευχέρεια προσδιορισμού
συναρτήσεων που λειτουργούν ως τελεστές, και αυτό το πετυχαίνουμε με χρήση κυρίως της
FUNCALL . Στην περίπτωση που μια συνάρτηση δέχεται ως όρισμα μια συνάρτηση και
επιστρέφει συνάρτηση, η πρώτη λειτουργεί ως τελεστής πάνω στη δεύτερη (που αποκαλείται
τελεστέος).

Σκοπός προσδιορισμού και χρήσης ενός τελεστή είναι ο μετασχηματισμός της τελεστέας
συνάρτησης, η οποία ανήκει σε ένα σύνολο συναρτήσεων (αυτών που γίνονται δεκτές ως όρισμα
του τελεστή). Μπορούμε να δούμε ένα τελεστή ως ένα εν δυνάμει αντιπρόσωπο των
αποτελεσμάτων - συναρτήσεων που είναι δυνατό να ληφθούν από εφαρμογές του, δηλαδή ως
αφαιρετική έκφραση.

Η εφαρμογή τελεστή σε συνάρτηση είναι μια σύνθεση συναρτήσεων. Πρέπει όμως να
αντιδιαστείλουμε τη σύνθεση αυτή, που είναι σύνθεση συναρτησιακών εκφράσεων, από τη
σύνθεση που πετυχαίνουμε με "από μέσα προς τα έξω" διαδοχικές εφαρμογές σταθερού
αποτελέσματος, που είναι σύνθεση εφαρμογών: Στην παρακάτω έκφραση, το αποτέλεσμα
προκύπτει με εφαρμογή πρώτα της CUBE στο 3 , με έξοδο 27 , και έπειτα της SQUARE στο 27 ,
με έξοδο 729 ∙ όμως σε κανένα βήμα δεν δημιουργείται η αφηρημένη συναρτησιακή έκφραση "ο
204

κύβος του τετραγώνου":
(square (cube 3))→

(square 27)

→ 729

Πετυχαίνουμε τη δημιουργία αφηρημένων συναρτησιακών εκφράσεων με προσδιορισμό
τελεστών, με την εφαρμογή τελεστή σε συνάρτηση, και με την εφαρμογή τελεστή σε τελεστή.

Τελεστέος χωρίς λ-μεταβλητές
Η απλούστερη περίπτωση είναι τελεστής που εφαρμόζεται σε συνάρτηση που δεν έχει
μεταβλητές. Τέτοιες συναρτήσεις εκτελούνται με την κλήση τους, και αποτελούν συνήθως την
αλγοριθμική περιγραφή κάποιας λειτουργίας.
Ας θεωρήσουμε τη συνάρτηση HELLO-FUNCTION χωρίς λ-μεταβλητές:
(defun hello-function ( ) (princ "hello") )

και την ακόλουθη συνάρτηση REPEAT-PROC , δύο λ-μεταβλητών, φυσικό αριθμό και
συνάρτηση χωρίς μεταβλητές:
(defun repeat-proc (n f)
(cond
( (= n 0) t )
( t (funcall f) (repeat-proc (1- n) f) ) ))

Η REPEAT-PROC προκαλεί επανάληψη του υπολογισμού της συνάρτησης που έχει ως δεύτερο
όρισμά της τόσες φορές όση είναι η τιμή τού πρώτου ορίσματος. Εδώ, η συναρτησιακή σύνθεση
είναι ορισμένη αφαιρετικά, πριν κληθεί προς εφαρμογή.
► Στο σώμα τής REPEAT-PROC η F είναι λ-μεταβλητή. Το να δηλωθεί οτι παριστά όνομα
συνάρτησης, θα απαιτηθεί με την κλήση της FUNCALL προς εκτέλεση, άρα μετά τη δέσμευση
της λ-μεταβλητής. Άρα, είναι στο όνομα της συνάρτησης πάνω στην οποία θα εφαρμοστεί, που
θα χρειαστεί να τεθεί το πρόθεμα #' .
Εφαρμογή:

(repeat-proc 3 #'hello-function)
→ hello hello hello
(εκτύπωση)
Τ
(επιστροφή τιμής)

Τελεστέος με ακριβώς ένα όρισμα, το οποίο ορίζεται ως λ-μεταβλητή του τελεστή

Θεωρούμε την παρακάτω τελεστική συνάρτηση ADD1-A, με δύο λ-μεταβλητές, έστω f και x ,
όπου θέλουμε η πρώτη να δεσμευτεί σε συνάρτηση ενός ορίσματος, και η δεύτερη στο όρισμα
του τελεστέου.
Είναι αρκετό να οριστεί στο σώμα της ADD1-A η κλήση της F μέσω της FUNCALL, και να
ακολουθεί, σύμφωνα με τη σύνταξη της FUNCALL, το όρισμά της:
(defun add1-a (f x)
(+ a (funcall f x)))

Έστω και το εξής περιβάλλον συμβόλων:
(defun trinomial (x) (+ (* a x x) (* b x) c) )
205

(defun formula (x) (log (+ (exp x) 1)) )
(setq a 2 b 3 c 4)
Εφαρμόζοντας την ADD1-A με πρώτο όρισμα μια συνάρτηση μιας λ-μεταβλητής και δεύτερο την

τιμή που θέλουμε για τη μεταβλητή αυτή, παίρνουμε το τελικό αποτέλεσμα:
(add1-a #'formula 3) → 5.04858735157374
(add1-a #'trinomial 3)→ 33

Τελεστέος με προκαθορισμένου πλήθους ορίσματα που προσδιορίζονται ως λμεταβλητές του τελεστή
Με τον ίδιο τρόπο μπορούμε να προσδιορίσουμε τελεστή που εφαρμόζεται σε συνάρτηση με
περισσότερες μεταβλητές: Αρκεί να αναφερθούν στη λίστα των λ-μεταβλητών του τελεστή και να
επισυναφθούν ως παράμετροι εφαρμογής της τελεστέας συνάρτησης.
Παράδειγμα
Έστω η συνάρτηση – τελεστής με τελεστέο συνάρτηση τεσσάρων ορισμάτων:
(defun mult-a-b-c (f x y z w)
(* a b c (funcall f x y z w)))

και η συνάρτηση τεσσάρων ορισμάτων:
(defun four-var-f ( x y z w)
(+ (* w (cos z)) (* x (sin y))) )

Εφαρμογή του τελεστή:
(mult-a-b-c #' four-var-f 5 6 7 8)



111.21937305

Tελεστής που καλεί σύνθετα τoν τελεστέo

Έστω η δι-γραμμική σύνθεση (az+b)/(cz+d) (συνάρτηση μιγαδικής μεταβλητής και μιγαδικής
συντελεστών, γνωστή ως μετασχηματισμός Moebius). Ως συνάρτηση μιας λ-μεταβλητής z και
τεσσάρων παραμέτρων - συμβόλων a, b, c, d, ορίζεται ως:
(defun moebius (z)
(/ (+ (* a z) b) (+ (* c z) d) ) )

Για να ορίσουμε συνάρτηση, έστω με όνομα MOEBIUS-F , με νόημα τελεστή όπου το τελεστέο
μέρος θα είναι συνάρτηση μιας λ-μεταβλητής z και a, b, c, d σύμβολα, κατ' αναλογία με τα
παραπάνω αρκεί:
1ο : να αντικαταστήσουμε στη λίστα των λ-μεταβλητών της MOEBIUS το z με δύο λμεταβλητές, μία (έστω f ) που θα δεσμευτεί στον τελεστέο, και μία (έστω x) που θα
δεσμευτεί στο όρισμα του τελεστέου, και:
ο
2 : να αντικαταστήσουμε στο σώμα της MOEBIUS κάθε εμφάνιση του z με (funcall f x)
Άρα, ο τελεστής MOEBIUS-F που ζητήσαμε, θα είναι:
(defun moebius-f (f x)
(/ (+ (* a (funcall f x)) b) (+ (* c (funcall f x)) d)) )
206

Χρήση εξωτερικών μεταβλητών ή σταθερών ως ορίσματα του τελεστέου

Ας θεωρήσουμε την παρακάτω τελεστική συνάρτηση MULT, με μια λ-μεταβλητή f που θα
δεσμευτεί στον τελεστέο, ο οποίος δέχεται δύο ορίσματα αλλά αυτά δεν αποτελούν λ-μεταβλητές
του τελεστή: το πρώτο όρισμα του τελεστέου δίνεται από την τιμή της εξωτερικής μεταβλητής
vara και το δεύτερο είναι η σταθερά 5 .

Η κλήση της F ορίζεται στο σώμα της MULT μέσω της FUNCALL κατά τα γνωστά, και
ακολουθούν, σύμφωνα με τη σύνταξη της FUNCALL, τα ορίσματα της F, που θα είναι vara και 5 .
Κατά την κλήση του τελεστή, προφανώς πρέπει να δοθεί μόνο ένα όρισμά του, το όνομα της
τελεστέας συνάρτησης:
(setq vara 4)
(defun mult (f)
(* 3 (funcall f vara 5)))
Στο σώμα της mult καθορίστηκε οτι: i) η f θα έχει δύο ορίσματα, και ii) οτι τα ορίσματα αυτά

δεσμεύονται στις τιμές 4 και 5

(defun a-two-arguments-function (x y)
(+ (* x y) (* 2 x)))

Κλήση:

(mult #'a-two-arguments-function )

→ 84

Τελεστής που έχει και λ-μεταβλητές που δεν σχετίζονται με τον τελεστέο
Η χρήση -μεταβλητών που αφορούν τον τελεστέο δεν απαγορεύει να υπάρχουν και άλλες λμεταβλητές του τελεστή:
(defun new-add (f x n)
(+ n (funcall f x)))

Κλήση:
(new-add #'formula 3 2)
→ 5.04858735157374
Στη συνάρτηση NEW-ADD η πρώτη λ-μεταβλητή παριστά την τελεστέα συνάρτηση, η δεύτερη
το όρισμα της τελεστέας συνάρτησης, και η τρίτη χρησιμοποιείται στο σώμα της NEW-ADD .

Τελεστές που εφαρμόζονται σε k το πλήθος τελεστέους

Παρόμοια ορίζουμε τελεστές που προκαλούν κάποια σύνθεση, με μεταβλητή κλήση k
διαφορετικών συναρτήσεων. Αρκεί να αναφερθούν συνολικά οι αντίστοιχες λ-μεταβλητές στη
λίστα των λ-μεταβλητών του τελεστή, και κάθε αναφορά συνάρτησης, έστω της func-k, στο
σώμα του τελεστή να γίνεται με φόρμα κλήσης συνάρτησης:
(defun myfunct (… func-k … func-n …) …
…(funcall func-k …) … (funcall func-n …)… )

Κλήση μεταβλητής συνάρτησης μέσα σε μεταβλητή συνάρτηση
Συνθετότερη περίπτωση είναι αυτή όπου καλούμε μεταβλητή συνάρτηση ως όρισμα συνάρτησης
207

που και αυτή είναι μεταβλητή. Με τον τρόπο αυτό έχουμε εφαρμογή τελεστή σε μεταβλητό
τελεστή.

Έστω οτι η fun1 είναι λ-μεταβλητή που τίθεται στη συνάρτηση-τελεστή TELEST με σκοπό να
παραστήσει μεταβλητό όνομα συνάρτησης η οποία δέχεται τέσσερα ορίσματα. Αντίστοιχα για την
fun2 , η οποία δέχεται τρία ορίσματα :
(defun telest (fun1 fun2 x1 x2 x3 y1 y2 y3 …)
… (funcall fun1 x1 (funcall fun2 y1 y2 y3) x2 x3)
… (funcall fun1
(funcall fun2 x2 y1 a) x1 y1 (funcall fun2 x1 y1 y1) )
… )

Η FUN1 , στο σώμα της TELEST την πρώτη φορά καλείται με πρώτο όρισμα την τιμή δέσμευσης
(συντομ.: τ.δ.) της x1, δεύτερο το αποτέλεσμα της κλήσης της FUN2 με ορίσματα τις τ.δ. των y1,
y2, y3 , τρίτο την τ.δ. της x2 και τέταρτο την τ.δ. της x3 . Η fun1 τη δεύτερη φορά καλείται με
πρώτο όρισμα το αποτέλεσμα της κλήσης της fun2 με ορίσματα τις τ.δ. των x2 και y1 και την
τιμή της a, όπου a καθολική μεταβλητή, δεύτερο και τρίτο όρισμα τις τ.δ. των x1 και y1, και
τέταρτο το αποτέλεσμα της κλήσης της fun2 με ορίσματα τις τ.δ. των x1, y1, y1 .
Οι λ-μεταβλητές που αναφέρονται στο σώμα, είναι όλες λ-μεταβλητές της συνάρτησης-τελεστή
TEL, ανεξάρτητα από τον ειδικό ρόλο που παίζουν μέσα στο σώμα της.

Σύνοψη και συμπεράσματα
Οι περιπτώσεις δεν εξαντλούνται στις παραπάνω: Μπορούμε να έχουμε τελεστή ή/και τελεστέο
με προαιρετικά ορίσματα, με τα κλειδιά και τις τεχνικές χρήσης τους που αναφέρονται παρακάτω.
Επίσης, τελεστή με αναδρομή ή επανάληψη.

Συνοψίζοντας τα παραπάνω, έχουμε οτι ένας τελεστής ορίζεται ως εξής:
– Οι τελεστέοι αποτελούν αντικείμενα δέσμευσης λ-μεταβλητών του τελεστή.
– Οι λ-μεταβλητές του τελεστή που πρόκειται να δεσμευτούν σε συναρτήσεις, αποτελούν, στο
σώμα του τελεστή, το πρώτο όρισμα μιας συνάρτησης που έχει τη δυνατότητα να καλέσει
συνάρτηση ως όρισμά της, όπως είναι οι FUNCALL και APPLY .
– Κατά την κλήση εφαρμογής τελεστή που καλεί τον τελεστέο με την FUNCALL ή την APPLY ,
το όνομα του τελεστέου πρέπει να έχει το πρόθεμα #' .
– Εάν οι τιμές δέσμευσης κάποιων λ-μεταβλητών του τελεστή πρόκειται να αποτελέσουν
ορίσματα των τελεστέων, απλώς τοποθετούνται στο σώμα του τελεστή στην αντίστοιχη θέση
των ορισμάτων των τελεστέων.
Στα επόμενα θα δούμε και μεθόδευση προσδιορισμού τελεστών μέσω λ-εκφράσεων.

208

5.2 Συναρτήσεις με προαιρετικά ορίσματα
Είναι δυνατό να ορίσουμε συναρτήσεις με μεταβαλλόμενο πλήθος ορισμάτων, είτε ελεύθερο
όπως είναι η πρόσθεση και ο πολλαπλασιασμός, είτε προκαθορισμένο, συγκεκριμένου τύπου ή
θέσης το καθένα, αλλά με προαιρετική χρήση τους κατά την κλήση. Για το σκοπό αυτό
χρησιμοποιούμε τα λεγόμενα λ-κλειδιά.

5.2.1 Συναρτήσεις με ελεύθερο πλήθος ορισμάτων

Είδαμε τις συναρτήσεις πρόσθεσης και πολλαπλασιασμού, που δέχονται ελεύθερο πλήθος
ορισμάτων. Μπορούμε να ορίσουμε τέτοιες συναρτήσεις, χρησιμοποιώντας ένα ειδικό "κλειδί", το
&rest :
&rest : Είναι ένα λ-κλειδί, που λέγεται έτσι διότι αναγράφεται μέσα στη λίστα των λμεταβλητών συνάρτησης. Δηλώνει οτι η οριζόμενη συνάρτηση δέχεται, από την εμφάνιση του
κλειδιού και πέρα, απεριόριστο (αλλά πεπερασμένο) πλήθος ορισμάτων, δυνατόν και κανένα.

Μετά το κλειδί αυτό ακολουθεί μια και μοναδική λ-μεταβλητή, έστω x . Η x αποτελεί τη λίστα
όπου συσσωρεύονται τα ορίσματα - τιμές (ή φόρμες που θα υπολογιστούν για να δώσουν τιμές)
που θα δοθούν
στη συνάρτηση κατά την κλήση της, από τη θέση τού &rest και πέρα. Το σώμα
σύμβολο
πρέπει να ορίζεται
με τρόπο
που ναπεριεχόμενο
επεξεργάζεται
αυτή τη υποδοχή
λίστα xσύνδεσης
των δοσμένων τιμών,
εσωτερικό
εξωτερικό
συναρτησιακό περιεχόμενο
οσεσδήποτε δοθούν.
όνομα
όνομα
μεταβλητής
Ας δούμε λεπτομερέστερα τις περιπτώσεις χρήσης τού &rest :

Συνάρτηση με προαιρετικά όλα τα ορίσματα
Στην περίπτωση όπου θέλουμε να προσδιορίσουμε συνάρτηση που δεν έχει υποχρεωτικά
ορίσματα αλλά όσα δώσουμε να γίνουν δεκτά, το κλειδί &rest αναγράφεται ως πρώτος όρος της
λίστας λ-μεταβλητών, και ακολουθεί μια και μοναδική λ-μεταβλητή:
(defun όνομα-συνάρτησης (&rest x) σώμα )
Η μεταβλητή x παριστά τη λίστα των τιμών που θα δοθούν ως ορίσματα κατά την κλήση της
συνάρτησης (από κανένα μέχρι n).
Παραδείγματα
1. Αλλαγή ονόματος συνάρτησης που δέχεται προαιρετικά ορίσματα:
Να αλλαχθεί το όνομα του "+" σε "sum-of-values".

Χρησιμοποιούμε την APPLY και όχι την FUNCALL, διότι χρειαζόμαστε τη συσσώρευση από
όσα ορίσματα δοθούν κατά την κλήση σε λίστα, άρα ταιριάζει καλύτερα στην περίπτωση αυτή η
λίστα - όρισμα της APPLY :
(defun sum-of-values (&rest x) (apply #'+ x) )

Εφαρμογή:
209

(sum-of-values 2 3 4) → 9
(sum-of-values)
→ 0

2. Μαζική επεξεργασία των όρων που θα δοθούν κατά την κλήση:
i) Το τετράγωνο του αθροίσματος όσων αριθμών δοθούν κατά την κλήση.
(defun square-of-sum (&rest x)
(setq v (apply #'+ x)) (* v v) )

Εφαρμογή:

(square-of-sum)
→ 0
(square-of-sum 5)
→ 25
(square-of-sum 2 3 4) → 81

ii) Το άθροισμα των τετραγώνων όσων αριθμών δοθούν κατά την κλήση.
(defun sum-of-squares (&rest x)
(apply #'+ (mapcar #'square x)))

(θα δούμε τη συνάρτηση MAPCAR στο επόμενο κεφάλαιο)
Εφαρμογή:
(sum-of-squares 3 4 5) → 50

Συνάρτηση με κάποια ορίσματα υποχρεωτικά και άλλα προαιρετικά
Ενδέχεται να θάλουμε συνάρτηση με κάποια ορίσματα υποχρεωτικά και άλλα προαιρετικά. Στην
παρακάτω έκφραση οι λ-μεταβλητές x, y, … z αντιστοιχούν στα υποχρεωτικά ορίσματα, που
πρέπει να δοθούν πρώτα κατά την κλήση, και η λ-μεταβλητή w θα δεσμευτεί στη λίστα των
επιπλέον, προαιρετικών ορισμάτων που τυχόν θα δοθούν κατά την κλήση:
(defun όνομα-συνάρτησης (x y … z &rest w) σώμα )
Παραδείγματα
1. Η περίπτωση όπου λειτουργικά το πρώτο όρισμα παίζει διαφορετικό ρόλο από τα επόμενα:
Να δοθεί συνάρτηση που να αφαιρεί από το πρώτο όρισμα (αριθμό, που θα δίνεται
υποχρεωτικά) όσους αριθμούς ακολουθήσουν (που θα δίνονται προαιρετικά).
(defun subtract-from-first (x &rest y)
(– x (apply #'+ y)) )

Εφαρμογή:
(subtract-from-first 10 )
→ 10
(subtract-from-first 10 1 2 3) → 4

2. Χρήση προαιρετικών μεταβλητών σε συνάρτηση διευκόλυνσης του χρήστη:
Συνάρτηση που προκαλεί διαδοχική ύψωση σε δυνάμεις.
Ας ορίσουμε την παρακάτω συνάρτηση δύο ορισμάτων, αριθμό και λίστα αριθμών:
(defun expts (res lis)
210

(cond
( (endp lis) res)
( t (expts (expt res (car lis))
(cdr lis)) ) ) )
Κατά τον υπολογισμό εφαρμογής τής EXPTS το πρώτο όρισμα υψώνεται σε δύναμη, που είναι

το πρώτο στοιχείο της λίστας η οποία δίνεται ως δεύτερο όρισμα∙ το αποτέλεσμα υψώνεται στη
δύναμη που είναι το δεύτερο στοιχείο της λίστας, κοκ. Η χρήση όμως της EXPTS είναι κάπως
"αφύσικη" συντακτικά, διότι χρειάζεται την αρχική βάση χωριστά και τους διαδοχικούς εκθέτες
σε λίστα:
(expts 3 ( ) ) → 3
(λόγω της οριακής συνθήκης της expts)
(expts 3 '(0) )
→ 1
(expts 3 '(2) )
→ 9
(expts 2 '(3 4 5)) → 1152921504606846976
Πιο φυσικό για το χρήστη, είναι να έχει μια συνάρτηση που να δέχεται πρώτο όρισμα τη βάση και
επόμενα, όσα δοθούν, τις διαδοχικές δυνάμεις:
(defun exponents (x &rest y)
(expts x y))

Εφαρμογές:
(exponents 2 3 4)

4096
(exponents 1.7 2.3 0.8 2) → 7.04777499812913
(exponents 2 3 4 5 6 ) →
234854258277383322788948059678933702737568254890831987070729097153220902511
4608443463698998384768703031934976

5.2.2 Συναρτήσεις με μεταβλητό πλήθος ορισμάτων
Το λ-κλειδί &rest που είδαμε, προσδιορίζει οτι κατά την κλήση της συνάρτησης γίνονται δεκτά
όσα ορίσματα δώσουμε, αλλά του ίδιου (γενικού ή ειδικότερου) τύπου και παρόμοιου ρόλου στον
υπολογισμό, ώστε να εφαρμόζεται πάνω σ' αυτά, επαναλαμβανόμενα, κάποια βασική συνάρτηση
(όπως στις συναρτήσεις + , * , LIST , APPEND).

Υπάρχει όμως και η περίπτωση να θέλουμε να θέσουμε κατά την κλήση συγκεκριμένα ορίσματα,
όπου το καθένα να έχει το δικό του ρόλο ή/και τύπο, χωρίς να είναι υποχρεωτικό να
χρησιμοποιήσουμε όλες τις προκαθορισμένες κατά τον ορισμό λ-μεταβλητές. Ακόμα, ενδέχεται
να θέλουμε να χρησιμοποιήσουμε κατά τον υπολογισμό, λ-μεταβλητές που χρησιμοποιούνται
εσωτερικά στο σώμα αλλά παίρνουν τιμή μέσα στο σώμα και δεν χρειάζεται να δοθούν
αντίστοιχα ορίσματα. Γι' αυτό χρειάζεται να χαρακτηριστούν αντίστοιχα οι προαιρετικές
μεταβλητές∙ διατίθενται τα λ-κλειδιά &optional , &key , &aux :

211

Προκαθορισμένα ορίσματα, αλλά προαιρετικά από ένα σημείο και πέρα

&optional : Είναι λ-κλειδί, που δηλώνει οτι οι μεταβλητές που ακολουθούν, αποτελούν μεν
συγκεκριμένα ορίσματα της συνάρτησης, όπως και οι κοινές λ-μεταβλητές, αλλά είναι
προαιρετικές για την κλήση, και συγκεκριμένα, μπορούν να μην δοθούν ορίσματα από κάπου και
πέρα για τις &optional λ-μεταβλητές. Προσοχή: για να δοθεί όρισμα για κάποια συγκεκριμένη λμεταβλητή, πρέπει να αναφερθούν και όλα τα προηγούμενά του.

Αυτό σημαίνει οτι η είσοδος τιμών ως ορίσματα μπορεί αυθαίρετα να κοπεί από κάπου και πέρα,
άρα τα ορίσματα πρέπει να είναι γραμμένα κατά σειρά φθίνουσας σπουδαιότητας, ώστε
φθάνοντας σε κάποιο όρισμα τάξης k, να έχουν δοθεί προηγουμένως τα σημαντικότερα∙ και
αντίθετα, αν είναι αναγκαίο το τάξης k να μην πλεονάζουν τα προηγούμενά του.

Πριν από το κλειδί &optional μπορούν να αναφέρονται και υποχρεωτικές λ-μεταβλητές,
αντίστοιχα με την χρήση του &rest . Η γενική φόρμα είναι:
(defun όνομα-συνάρτησης
(x1 x2 … xk &optional w1 w2 … wn) σώμα )
Είναι υποχρεωτικό να δοθούν τιμές ως ορίσματα της συνάρτησης κατά την κλήση, για τις λμεταβλητές που προηγούνται του &optional , δηλ εδώ τις x1 x2 … xk , ενώ είναι προαιρετικό για
τις μεταβλητές που έπονται του &optional, δηλ. εδώ τις w1 w2 … wn, "κόβοντας" από το τέλος
τους. Στο σώμα, ο υπολογισμός που αναφέρεται στις wk είναι αναγκαίο να θεωρεί οτι αν δεν
δοθούν, έχουν τιμή NIL.
Παράδειγμα
Μια απλή βάση πληροφοριών:
Να δημιουργηθεί μια βάση δεδομένων, με συνάρτηση που να καταχωρεί σε οντότητες "πρόσωπα" ορισμένα χαρακτηριστικά τους, κατά δεδομένη σειρά σπουδαιότητας, όπου αν δεν
γνωρίζουμε κάποιο, τα επόμενα δεν έχουν πλέον σημασία. Να δοθεί τρόπος αναζήτησης της
περιεχόμενης πληροφορίας.
(defun personal-information
(person surnam nam genr numbr-id
&optional dat-of-birthday family-sit addr tel-nr nicknam)
(setf
(get person 'surname) surnam)
(get person 'name) nam)
(get person 'genre) genr)
(get person 'number-id) numbr-id)
(get person 'date-of-birthday) dat-of-birthday )
(get person 'family-situation) family-sit)
(get person 'address) addr)
(get person 'tel) tel-nr)
(get person 'nickname) nicknam) )
(setq person1 'person1)

Καταχώρηση:
212

( personal-information person1
"Αλεξόπουλος" "Αλέξης" "άρρην" "N-345678"
'(14 Αυγούστου 1950) "ύπανδρος" '(Αθήνα (Πατησίων 847)) )

Λήψη πληροφορίας:
(get person1 'date-of-birthday) → (14 Αυγούστου 1950)
(get person1 'address)
→ (Αθήνα (Πατησίων 847))
(get person1 'nickname)
→ NIL

Προαιρετικά ορίσματα που προσδιορίζονται με "το όνομά τους"

&key : λ-κλειδί, που χρησιμοποιείται σε περιπτώσεις όπου έχουμε προαιρετικές μεταβλητές
συγκεκριμένου ρόλου, όπως με το &optional, αλλά που θέλουμε να χρησιμοποιήσουμε αδιάκριτα
υποχρέωσης.

Αποτελεί ένα τρόπο "ταύτισης" των δεδομένων εισόδου με τις μεταβλητές στις οποίες
αναφέρονται, χωρίς να υπάρχει ανάγκη να είναι δτην ίδια διάταξη. To λ-κλειδί &key προσδιορίζει
ακριβώς οτι ακολουθούν λ-μεταβλητές με ειδικά κλειδιά αναφοράς τους, που είναι ονόματα που
χαρακτηρίζουν τις μεταβλητές, και κάθε λ-μεταβλητή από το &key και μετά, εκτός από το όνομά
της, δυνητικά έχει και ένα όνομα αναφοράς - κλειδί, που αναγράφεται υποχρεωτικά με πρόθεμα
":".
Είναι δυνατό να αποδώσουμε αντίστοιχες των λ-μεταβλητών αρχικές τιμές, ώστε αν δεν δοθούν
αντίστοιχα ορίσματα, να χρησιμοποιηθούν οι αρχικές τιμές. Για λ-μεταβλητή που δεν δίνεται
αρχική τιμή, εννοείται ως αρχική τιμή η NIL . Το κλειδί αναφοράς μεταβλητής και η αρχική τιμή
είναι προαιρετικά, τόσο στον ορισμό όσο και στην κλήση. Η σύνταξη της λίστας των λμεταβλητών με &key, είναι η ακόλουθη (διάφορες δυνατές περιπτώσεις):
(defun <όνομα συνάρτησης>
(… &key
((:κλειδί1 μεταβλ1) αρχ-τιμή1) ; κλειδί, λ-μεταβλητή, αρχική τιμή
((:κλειδί2 μεταβλ2) αρχ-τιμή2) ;
»
»
»
((:κλειδί3 μεταβλ3)) ; κλειδί, λ-μεταβλητή, αρχ.τιμή NIL
(μεταβλ4 αρχ-τιμή4) ; λ-μεταβλητή, αρχ.τιμή, χωρίς κλειδί
μεταβλ5
; λ-μεταβλητή χωρίς κλειδί, με αρχ.τιμή NIL
…)
σώμα )

Κατά την κλήση της συνάρτησης μπορούμε να δώσουμε επιλεκτικά όρισμα σε οποιεσδήποτε
από τις μεταβλητές που ακολουθούν το &key , απλώς δίνοντας προηγουμένως το κλειδί χαρακτηριστικό που τις ταυτοποιεί. Για όποια μεταβλητή δεν δοθεί όρισμα, παίρνει στη θέση του
την αρχική τιμή.

Για τυχόν αναφερόμενες λ-μεταβλητές στον ορισμό μετά το &key αλλά χωρίς ειδικό κλειδί
αναφοράς τους, το ποιο όρισμα αντιστοιχεί στην καθεμία, προσδιορίζεται από τη θέση τους
μεταξύ των ορισμάτων, όπως και στις κοινές λ-μεταβλητές.
213

Παρατηρούμε οτι η χρήση "μονής - διπλής ή καθόλου" παρένθεσης καθορίζει τί αναμένεται να
δοθεί και πώς στη φόρμα κλήσης.
Παράδειγμα
Να δημιουργηθεί μια βάση δεδομένων προσωπικών στοιχείων, σε μορφή λίστας από λίστες, με
χρήση κλειδιών στα αντίστοιχα πεδία.
(setq persons nil)
(defun mybase
(&key
((:surname surnam))
((:name nam))
((:nr-of-children nr) 0)

; θέσε τα νέα στοιχεία σε λίστα και πρόσθεσέ την στη
; λίστα persons :
(push (list surnam nam nr) persons)

; επίστρεψε τη λίστα των νέων στοιχείων:

(list surnam nam nr) )

Η push έχει μόνιμα αποτελέσματα, διότι τροποποιεί απ' ευθείας το σύμβολο – μεταβλητή
"persons" και όχι την τιμή δέσμευσης λ-μεταβλητής.
Χρήση:
(mybase :surname 'Klinton :name 'Billy)
→ (Klinton Billy 0)
(mybase :name 'John :nr-of-children 4 :surname 'Lenon )
→ (Lenon John 4)
(mybase :surname 'Conan)
→ (Conan Nil 0)
(mybase :name 'Maria :nr-of-children 7)
→ (Nil Maria 7)
persons →
→ ((NIL Maria 7) (Conan NIL 0) (Lenton John 4) (Klinton Billy 0))

Μπορούμε να ορίσουμε συναρτήσεις διευκόλυνσης στην αναζήτηση στοιχείων μέσα στη βάση.

Συνάρτηση με λ-μεταβλητές που χρησιμοποιούνται μόνον εσωτερικά στον ορισμό
Εάν θέλουμε να χρησιμοποιήσουμε στο σώμα κάποιες λ-μεταβλητές που απαιτούνται εσωτερικά
αλλά δεν έχει νόημα η δέσμευσή τους σε ορίσματα κατά την κλήση της συνάρτησης,
χρησιμοποιούμε το λ-κλειδί &aux :
&aux : λ-κλειδί που καθορίζει οτι οι λ-μεταβλητές που ακολουθούν δεν αντιστοιχούν σε
ορίσματα της συνάρτησης κατά την εφαρμογή, αλλά δεσμεύονται από τις πράξεις στο σώμα. Δηλ.
δεν προβλέπεται να δοθεί είσοδος γι' αυτές τις μεταβλητές, παρ' όλο που αναφέρονται σε
υπολογισμούς του σώματος, και παίρνουν τιμή δέσμευσης μέσα από τον υπολογισμό, κατά την
214

εφαρμογή.
Χρησιμοποιούνται κυρίως όταν η οριζόμενη συνάρτηση χρησιμοποιεί στο σώμα της άλλη
συνάρτηση με περισσότερες λ-μεταβλητές, που δεσμεύονται απ' ευθείας από τον αλγόριθμο και
όχι από την κλήση.
Οι λ-μεταβλητές του &aux δέχονται (προαιρετικά) αρχική τιμή, αλλιώς ως αρχική τιμή τους
νοείται το ΝΙL .
Παράδειγμα
Να συγκεντρωθούν σε λίστα η οποία θα επιστραφεί, όλοι οι ακέραιοι θετικοί αριθμοί από
δοσμένη τιμή Ν (όρισμα) κατεβαίνοντας μέχρι το 1 .
(defun enter-elem (x &aux z)
(cons-all (* x x) z))
(defun cons-all (x lis)
(cond
( (= 0 x) lis)
(t (push x lis) (cons-all (1- x) lis))))

Εφαρμογές:
(enter-elem
(enter-elem
(enter-elem
(enter-elem

2)
3)
4)
2)






(1 2 3 4)
(1 2 3 4 5 6 7 8 9)
(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16)
(1 2 3 4)

Η εκτέλεση της ENTER-elem δεν αφήνει μόνιμα αποτελέσματα, παρ' όλη τη χρήση της
καταστροφικής PUSH, διότι εδώ η PUSH τροποποιεί λίστα που είναι τιμή λ-μεταβλητής, άρα
χρησιμοποιεί προσωρινό αντίγραφο του συμβόλου lis, και δεν επηρεάζει την αρχική τιμή του.
Όμως όσο εκτελείται η αναδρομική κλήση, παραμένει η συσσωρευτική δράση.

5.2.3 Εξαρτήσεις της συμπεριφοράς συνάρτησης
Ένα πολύ σημαντικό ζήτημα είναι να ανακατευθύνουμε τον υπολογισμό ανάλογα με το τί δίνεται.
Τότε η συνάρτηση παίρνει για τον χρήστη την έννοια πεπλεγμένης σχέσης, διότι η κλήση της
κατάλληλα επιλυμένης φόρμας είναι εσωτερική στο σώμα της συνάρτησης. Αυτό το
πετυχαίνουμε με κατάλληλη χρήση των κλειδιών. Συγκεκριμένα:
Το κλειδί &key , που επιτρέπει τη δέσμευση συγκεκριμένης λ-μεταβλητής σε συγκεκριμένο
στοιχείο εισόδου μέσω ονόματος, μας επιτρέπει ταυτόχρονα να θεωρήσουμε το αντίστοιχο όνομα
- κλειδί ως είδος (κατηγορία, μέγεθος) για τις τιμές δέσμευσης, και να εξαρτήσουμε το τί θα
γίνει από το τί δίνεται.
Παράδειγμα
Θέλουμε να υλοποιήσουμε το νόμο του Νεύτωνα F=m*γ .
215

Είναι μια σχέση που συνδέει τρία μεγέθη, και είναι καταληκτικά υπολογίσιμη όταν δίνονται τα
δύο ή και τα τρία.
(defun newton
(&key ((:force f)) ((:mass m)) ((:acceleration a)))
(cond
( (and (eql f nil) (not (eql m nil)) (not (eql a nil)))
(terpri) (princ "η δύναμη είναι: ") (princ (* m a)) )
( (and
(eql m nil) (not (eql f nil)) (not (eql a nil)) (not (eql a 0)))
(terpri) (princ "η μάζα είναι: ") (princ (/ f a)) )
( (and (eql m nil) (eql f 0) (eql a 0))
(terpri) (princ "η μάζα είναι: ") (princ (/ f a)) )
( (and
(eql a nil) (not (eql f nil)) (not (eql m nil)) (not (eql m 0)))
(terpri) (princ "η επιτάχυνση είναι: ") (princ (/ f m)) )
( (and
(not (or (eql f nil) (eql m nil) (eql a nil))) (eql f (* m a)))
(terpri) (princ "τα δεδομένα είναι συμβατά") )
(t "λάθος έκφραση") ))

Χρήση:
(newton :force 15 :mass 4)
→ η επιτάχυνση είναι: 15/4
(newton :acceleration 7.0 :force 14.3) →
→ η μάζα είναι: 2.0428572
(newton :acceleration 9.81 )
→ "λάθος έκφραση"

Η τελική επιλογή (t "λάθος έκφραση") μπορεί να γίνει λεπτομερέστερη. Ακόμα η σχέση είναι
προσδιορίσιμη ως συνάρτηση όταν ένα, δύο ή τρία από τα μεγέθη είναι σύμβολα και τα υπόλοιπα
δίνονται ως λ-μεταβλητές (συνολικά 10 περιπτώσεις).

5.3 Τόποι συμβόλων
Στην ενότητα αυτή θα εξετάσουμε την έννοια του τόπου καθώς και τις δυνατότητες αξιοποίησης
τόπων.

5.3.1 Τόποι και χρησιμότητά τους
Θα εξετάσουμε την αναγκαιότητα χρήσης τόπων από τη σημασιολογική άποψη, σε αντιδιαστολή
από την τεχνική άποψη: Όπως ήδη αναφέραμε, και θα δούμε πιο συγκεκριμένα στο Κεφ.9, η
216

θεώρηση τόπων, αν και αναγκαία, δεν είναι τίποτα άλλο από εκμετάλλευση του γεγονότος οτι
όλες οι λ-εκφράσεις θέτουν ένα "φράγμα υπόστασης" στις οντότητες που παριστούν οι λμεταβλητές τους.

Χρήση τόπων από τη σημασιολογική άποψη

Τα σύμβολα συνθέτουν ένα χώρο "ορατό από παντού". Συχνά όμως, τα σύμβολα που
χρησιμοποιούμε σε κάποιο υπολογισμό παύουν να χρειάζονται μετά το πέρας του, ή ακόμα πιο
λεπτά, τα σύμβολα που καλούνται σε κάποια φάση ενός υπολογισμού να παύουν να χρειάζονται
στην επόμενη φάση. Η σπατάλη πόρων του συστήματος αλλά και "λέξεων" που παριστούν
σύμβολα, είναι προφανής.
Τα προβλήματα τέτοιας σπατάλης, κοινά σε κάθε είδος προγραμματισμού, στον ΣΠ γίνονται
ακόμα εντονότερα, για πολλούς λόγους όπως:
– Ένα συναρτησιακό πρόγραμμα έχει μορφή δάσους, όπου αν δεν διαθέταμε τοπικότητα, κατά
τη χρήση θα συσσωρεύονταν εκατοντάδες, ακόμα και χιλιάδες σύμβολα, τα περισσότερα των
οποίων χρειάζονται μόνον εσωτερικά σε υπολογισμούς. Η διαρκής παρουσία τους, θα
επιβάρυνε άσκοπα τη μνήμη και θα προκαλούσε δυσχέρειες χρήσης.
– Η παρεχόμενη στον τελικό χρήστη δυνατότητα να χρησιμοποιεί σύμβολα με συνθετικό τρόπο
θα γινόταν γίνεται εξαιρετικά πολύπλοκη, λόγω διόγκωσης του χώρου των συμβόλων.
Η χρήση τοπικών συμβόλων (μεταβλητών ή συναρτήσεων) μας απαλλάσσει από τέτοια
προβλήματα, και μας δίνει ένα τρόπο να κρατάμε προς συνθετική αξιοποίηση μόνο το "πάνω
επίπεδο" του καθενός χώρου, όπως μια ομάδα από σύμβολα που έχει έννοια η χρήση τους από τον
τελικό χρήστη σε συγκεκριμένη φάση αξιοποίησης του προγράμματος,
– "κρύβοντας" τα κατώτερα επίπεδα από τον χρήστη, που συνήθως χρειάζεται να γνωρίζει το "τί
κάνει" μια διεργασία αλλά όχι και το "πώς το πετυχαίνει",
– μειώνοντας το φόρτο της μηχανής, διότι μόνον όταν κληθεί για εφαρμογή ένας τόπος θα
δημιουργήσει τον "κόσμο που κρέμεται από κάτω",
– και αυτά, σε πολλαπλά επίπεδα, αν θέλουμε.
Με την κατά "τόπους" διαχείριση των συμβόλων, όπως θα δούμε, αποκτάμε πολλούς βαθμούς
ευελιξίας στην συναρτησιακή οργάνωση:
– διαμόρφωση τού τί βλέπει ο τελικός χρήστης, χωρίς να είναι υποχρεωτικά στατικό αυτό,
– προσδιορισμός τού ποιος είναι ο χώρος που βλέπει η χρησιμοποιούμενη συνάρτηση ή
μεταβλητή
– προσδιορισμός τού από πού είναι ορατό ένα σύμβολο (δηλ. τί μπορεί να επιδράσει σ' αυτό ή
να πάρει την πληροφορία που περιέχει)
– καθορισμός των χρονικών πλαισίων υπόστασης ενός συμβόλου.
Καθορίζονται έτσι, για κάθε σύμβολο αλλά και για κάθε ενέργεια του τελικού χρήστη, σαφώς
περιχαρακωμένα όρια, και πετυχαίνουμε ουσιαστική οικονομία πόρων αλλά και καλύτερη
217

εννοιολογική οργάνωση του προγράμματος.

5.3.2 Η έννοια της τοπικότητας συμβόλου
Συναντήσαμε στα προηγούμενα, δυο περιπτώσεις όπου η αναφερόμενη λειτουργία στηρίζεται σε
μεταβλητές που δεν έχουν υπόσταση έξω από κάποια όρια:
– Στην περιγραφή της συνάρτησης PROG, γίνεται χρήση κάποιων μεταβλητών που υφίστανται
μόνο στα πλαίσια υπολογισμού της PROG.

– Στον ορισμό συνάρτησης μέσω της DEFUN, χρησιμοποιώντας την SETQ πάνω σε λμεταβλητές, προσδιορίζουμε μια τοπική δράση, που αρχίζει με την εκτέλεση της συνάρτησης
και παύει να έχει οποιαδήποτε συνέχεια μετά το πέρας της εκτέλεσης.

Στην πρώτη περίπτωση, έχουμε προσδιορισμό και χρήση μεταβλητών οι οποίες αρχίζουν να
υφίστανται ως οντότητες – σύμβολα με την έναρξη της εκτέλεσης, και χρησιμοποιούνται
εσωτερικά στον υπολογισμό με τον ίδιο τρόπο που χρησιμοποιούνται τα σύμβολα, και παύουν να
υφίστανται με τον τερματισμό του υπολογισμού τής PROG . Τέτοιες μεταβλητές ονομάζονται
τοπικές.
Στη δεύτερη περίπτωση, η καταχώρηση που προκαλεί η SETQ υφίσταται όσο εκτελείται η
εφαρμογή της συνάρτησης, διότι μόνο στα πλαίσια αυτά υφίστανται οι λ-μεταβλητές ως
σύμβολα, και μπορούμε να χρησιμοποιήσουμε τις καταχωρημένες λ-μεταβλητές πανομοιότυπα με
τις καθολικές μεταβλητές∙ η καταχώρηση παύει να υφίσταται μετά το πέρας του υπολογισμού
της εφαρμογής.

Μια συνάρτηση, έστω F, που ορίζεται εγκλεισμένη μέσα στο σώμα άλλης συνάρτησης, έστω G,
"βλέπει" τις λ-μεταβλητές της G με τον ίδιο τρόπο που βλέπει και τα σύμβολα του προγράμματος.
Δηλαδή, οι λ-μεταβλητές συνάρτησης, κατά την εφαρμογή της, αποτελούν σύμβολα για τα
κατώτερα επίπεδα υπολογισμού, αλλά αυτό έχει νόημα μόνο στα χρονικά και λειτουργικά
πλαίσια της εκτέλεσης της εφαρμογής. Έχουμε έτσι ένα τρόπο προσδιορισμού χρονικών ορίων
"υπόστασης αυτών των συμβόλων".
Ο προσδιορισμός τέτοιων ορίων μπορεί να εξυπηρετήσει διαφορετικούς στόχους (και γι' αυτό
οργανώνεται αντίστοιχα με διαφορετικό τρόπο) όπως:
α'. Εξοικονόμηση πόρων του συστήματος, με το να μη καταλαμβάνουν χώρο στη μνήμη όσο
δεν χρησιμοποιούνται.
Δεδομένου οτι ένα συναρτησιακό πρόγραμμα διατηρεί φορτωμένα στη μνήμη:
– τα σύμβολα του τρέχοντος πακέτου
– τα σύμβολα του προγράμματος, σχετικά ή άσχετα προς την τρέχουσα εφαρμογή
– τα σύμβολα που δημιουργεί ο χρήστης
– τα σύμβολα που δημιουργούν οι εκτελέσεις
συμπεραίνουμε ότι: καμμία ποσότητα μνήμης δεν θα μπορούσε να θεωρηθεί επαρκής, ακόμα και
για πολύ απλά προγράμματα, αν δεν γίνει ορθολογιστική χρήση, ανάλογα με τις τρέχουσες
218

ανάγκες.
β'. Χρήση των ιδίων ονομάτων σε διαφορετικές υπολογιστικές περιπτώσεις οι οποίες έχουν
εννοιολογική ομοιότητα, χωρίς να επηρεάζει η μια περίπτωση την άλλη.
Αυτό προσφέρει ευελιξία για ομοιόμορφη ανάπτυξη κώδικα, διότι παρεμφερείς ειδικότερες
καταστάσεις μπορούν να αποδίδονται με τα ίδια συμβολικά ονόματα.

γ'. Οι μεταβλητές να είναι ορατές μόνον από συγκεκριμένα και προκαθορισμένα μέρη του
προγράμματος (άλλα σύμβολα) ή κάτω από συγκεκριμένους κανόνες (που στον ΣΠ σημαίνει, από
εκτέλεση εφαρμογής).
Βλέποντάς το από την άποψη των "δικαιωμάτων" που έχει η εκτελούμενη συνάρτηση, ο
περιορισμός αυτός εξασφαλίζει τη συνεπή χρήση, ιδίως κάτω από δυνατότητες συνθετικής
χρήσης συναρτήσεων.
δ'. Να υφίστανται οι μεταβλητές μόνο στα χρονικά πλαίσια που έχει νόημα η ύπαρξή τους.
Αυτό, εκτός από εξοικονόμηση πόρων, προσφέρει και πολύ μεγάλη ευελιξία στην οργάνωση
εξελισσόμενων μερών: μετά από την εκτέλεση των δράσεων που καθορίζουν την ολοκλήρωση
μιας λειτουργικής φάσης, είναι λογικό να απατήσουμε να παραμείνει ο χώρος που περιέχει μόνο
τα σύμβολα που είναι απαραίτητα για την επόμενη φάση εξέλιξής του.
ε'.
Με τον εγκλεισμό των τοπικών συμβόλων σε μη ορατά από το χρήστη μέρη, ο χώρος
των συμβόλων γίνεται σαφέστερος και η άμεση κλήση ή συνθετική χρήση συμβόλων γίνεται
ευχερέστερη.
Το χαρακτηριστικό του "εγκλεισμού", διευρύνεται σημαντικά από τη δυνατότητα που έχουμε να
ανοίγουμε "παράθυρα" ή "φίλτρα" από και προς ένα τόπο.
Ένας τόπος μπορεί να προσδιοριστεί στο σώμα συνάρτησης, και κατά συνέπεια να δημιουργηθεί
με την εφαρμογή της, ή να τον δημιουργήσουμε απ' ευθείας στο ανώτερο επίπεδο, και κατά
συνέπεια να αποτελέσει μια τοπική εκτέλεση (πολύ χρήσιμο στην περίπτωση που θέλουμε να
χρησιμοποιούμε υφιστάμενα μέρη του προγράμματος, που προκαλούν μεταβολές, χωρίς να
καταστρέφουμε υφιστάμενες καταστάσεις).
Ένας τόπος μπορεί να περιέχει ορισμό καθολικών συμβόλων, που συνεχίζουν να έχουν υπόσταση
και μετά το πέρας της εκτέλεσης που προσδιόρισε τον τόπο. Γενικά, με εγκλεισμένες συνθέσεις
συναρτήσεων και δημιουργίας τόπων, μπορούμε να εκφράσουμε πολυσύνθετες καταστάσεις όπου
τόποι εμπεριέχονται σε τόπους και καθορίζουμε τα δικαιώματα των μερών.

219

5.3.3 Συναρτήσεις προσδιορισμού τόπου
Έχουμε δύο κατευθύνσεις προσδιορισμού τοπικών μεταβλητών: i) ανεξαρτήτων μεταξύ τους, και
ii) εξαρτημένων, κατά τη διαδοχή αναφοράς τους.

Προσδιορισμός τόπου μεταβλητών ανεξαρτήτων μεταξύ τους
Μπορούμε να προσδιορίσουμε τοπικές μεταβλητές ανεξάρτητες μεταξύ τους, κατά κύριο λόγο
χρησιμοποιώντας μια από τις συναρτήσεις: DO , PROG , LET .
Ο πιο συνήθης τρόπος –που μπορεί να αξιοποιηθεί τόσο διαδικαστικά όσο και δηλωτικά– είναι με
χρήση της συνάρτησης LET :
let :

((<symbol>  <term>) n)  <term> m → <term>

Είναι ειδικής μορφής συνάρτηση. Είναι η κατ’ εξοχήν «αρμόδια» συνάρτηση για τον
προσδιορισμό τοπικών συμβόλων (μεταβλητών και συναρτήσεων).
Ο τόπος των συμβόλων δεν καθορίζεται μόνο από την τυπική μορφή σύνταξης της κλήσης της
LET , αλλά και από τον τρόπο χρήσης της, όπως θα δούμε στα παραδείγματα. Η σύνταξή της
είναι:
(let
( (var-1 value-1) (var-2 value-2) . . . (var-n value-n) )

< προαιρετικά δηλώσεις που αφορούν τις μεταβλητές>
<σώμα> )
όπου var-k , για κάθε k, είναι τοπική μεταβλητή και value-k η αντίστοιχη αρχική τιμή.
Ως συνάρτηση, η LET επιστρέφει το αποτέλεσμα υπολογισμού της τελευταίας φόρμας του
σώματος.
► Πρέπει να διευκρινιστεί οτι η LET δεν "ορίζει κάτι", απλώς εκτελείται όταν κληθεί (η
εκτέλεσή της όμως μπορεί να περιλαμβάνει ορισμούς). Ο τόπος παίρνει υπόσταση κατά την
εκτέλεση της LET , και παύει να υφίσταται με το πέρας της. Την χρησιμοποιούμε για να
προσδιορίσουμε ένα τόπο στα πλαίσια κάποιας εκτέλεσης, αλλά μπορούμε να "ονομάσουμε" την
κλήση της, είτε με όνομα συμβόλου - μεταβλητής είτε με όνομα συνάρτησης, και ανάλογα να
καλούμε τον τόπο:
(setq locus-1 '(let … ) )

► Είναι δυνατό να δοθούν μεταβλητές χωρίς αρχική τιμή. Τότε πρέπει υποχρεωτικά να
ακολουθεί σχετική με τις μεταβλητές αυτές δήλωση, η οποία να προηγείται της πρώτης κλήσης
τους στο σώμα, και βέβαια η δήλωση να καθορίζει αυτά που απαιτεί η κλήση τους (τύπο ή αρχική
τιμή). Τέτοιες είναι οι a και b στο ακόλουθο:
Παράδειγμα
Να προσδιοριστεί τόπος με τρεις τοπικές μεταβλητές , a b c , όπου η πρώτη να προσδιορίζεται
εσωτερικά στον τόπο με ανάγνωση εισόδου, η δεύτερη βάσει της πρώτης, και η τρίτη με αρχική
220

τιμή.

Ο προσδιορισμός τοπικών μεταβλητών μέσω της LET είναι παράλληλος, άρα η (τυχόν)
εξάρτηση μεταξύ τους πρέπει να προσδιοριστεί στο σώμα:
(let
(a b (c 3))
(setq a (read))
; δήλωση
(cond
; δήλωση
((numberp a)(setq b (* a a)))
( t (setq b 0)) )
(print (+ b c)) )
; σώμα
→ <περιμένει είσοδο, λόγω του READ. Δίνουμε: >
2 <enter>
→ 7

Η αναφορά – ορισμός των τοπικών μεταβλητών σε LET είναι ανεξάρτητος της σειράς εγγραφής
τους. Αν η τιμή τοπικής μεταβλητής δίνεται με υπολογισμό, δεν είναι δυνατή η χρήση άλλης
τοπικής (του ιδίου επιπέδου) μεταβλητής μέσα στον υπολογισμό αυτό. Για το λόγο αυτό η
συνάρτηση LET χαρακτηρίζεται ως παράλληλη.

Συσχετισμοί τόπων
Περισσότερες της μιας συναρτήσεις προσδιορισμού τόπου μπορούν να χρησιμοποιηθούν
κιβωτισμένα, δηλ εγκλεισμένες διαδοχικά, σε αλυσίδα ή δενδροειδώς. Ο εγκλεισμός τόπων
μπορεί να γίνει με χρήση από LET ή οποιεσδήποτε άλλες συναρτήσεις προσδιορισμού τόπου.
Η χρήση τόπου ως σώμα συνάρτησης έχει νόημα όταν περικλείει τοπικούς υπολογισμούς μέσα
στο σώμα, με δικές τους λ-μεταβλητές: οι λ-μεταβλητές της συνάρτησης έτσι κι αλλιώς
λειτουργούν τοπικά για τον υπολογισμό στο σώμα, άρα για να χρειαστούμε επιπρόσθετα και
τοπικές μεταβλητές, θα πρέπει να αφορούν κάποιους εσωτερικότερα κλεισμένους υπολογισμούς,
για τους οποίους οι λ-μεταβλητές της συνάρτησης θα παίζουν ρόλο συμβόλων, στο σχήμα:

(defun world1 (x1 x2 …)
(let ((t1 v1) (t2 v2) …) <εσωτερικότεροι υπολογισμοί> ))
Οι αρχικές τιμές v1, v2 των τοπικών μεταβλητών t1, t2 , επειδή "έρχονται απ' έξω", παίζουν ρόλο

προσαρμογής των υπολογισμών στις συνθήκες του περιβάλλοντος.
Η περίπτωση όπου ο τόπος προσδιορίζεται στο σώμα συνάρτησης, εξυπηρετεί είτε την απλή
ονομασία του τόπου, είτε την "οριοθέτηση" κάποιας ή κάποιων λειτουργιών, ως προς το χώρο που
αυτές βλέπουν και το χώρο που τις βλέπει, για ευέλικτη διαχείριση του χρόνου όπου εκτελούνται.
Η χρήση τόπου ως τιμή συμβόλου μπορεί να χρησιμοποιηθεί για κλήση της φόρμας (LET…) ως
εκτελέσιμης εφαρμογής:
(setq world2 '(let…) )
(funcall world2)
→ …
(εκτελείται η φόρμα (let …) που προσδιορίζει τον τόπο)

και ακόμα πλατύτερα, για προσδιορισμό μιας ομάδας τέτοιων εφαρμογών, εξαρτώντας την
221

επιλογή από "κάτι άλλο":
(setq ομάδα -τόπων
'( (let …)
(let …)
(let …) ) )

; πρώτος τόπος
; δεύτερος τόπος
; τρίτος τόπος κλπ.
(third ομάδα -τόπων) → …
(κλήση του τρίτου τόπου)
Η κλήση τόπου δεν είναι τίποτα άλλο από εκτέλεση εφαρμογής, με αρχή και τέλος, η οποία όπως
κάθε εφαρμογή μπορεί να αφήνει "ίχνη", με την έννοια προσδιορισμού ή μεταβολής συμβόλων.
Δεδομένου οτι οι αρχικές τιμές τοπικών μεταβλητών μπορούν να εξαρτώνται από σύμβολα,
συμπεραίνουμε οτι οι δράσεις δημιουργίας τόπων μπορούν να αλληλοεπηρεάζονται, με την
έννοια οτι η δράση δημιουργίας ενός τόπου είναι δυνατό να λειτουργεί με τρόπο που να
διαμορφώνει του περιβάλλον όπου θα δημιουργηθεί ο "επόμενος τόπος".

Τόπος που περιβάλλει συναρτήσεις
Μια συνάρτηση προσδιορισμού τόπου μπορεί να περιλαμβάνει ένα σύνολο συναρτήσεων που
αυτές και μόνον αυτές μοιράζονται ένα σύνολο από μεταβλητές. Έχουμε δύο κατευθύνσεις:
α'. οι συναρτήσεις αυτές να υφίστανται αυτόνομα, δηλ. να μπορούν να κληθούν ανεξαρτήτως
της συνάρτησης ορισμού του τόπου, και
β'. οι συναρτήσεις αυτές να υφίστανται μόνο στα πλαίσια ύπαρξης του τόπου, δηλ. να μπορούν
να κληθούν μόνο από την εκτέλεση της συνάρτησης ορισμού του τόπου.

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

Κατά τον εγκλεισμό του ορισμού μιας συνάρτησης που κάνει χρήση τοπικών μεταβλητών, έστω
F, μέσα στο LET, ο ορισμός αυτός αν γίνεται με συνάρτηση καθολικού ορισμού, ( DEFUN,
defmacro, DEFMΕTHOD…) εξακολουθεί να είναι καθολικός και μόνιμος, παρ' όλο που έγινε
μέσα στα πλαίσια του τόπου.

Σημαντικό είναι οτι, τα τοπικά σύμβολα που προσδιορίζει το LET αποκτούν υπόσταση με την
κλήση της F ακόμα και μετά το πέρας του υπολογισμού του LET. Δηλαδή, επαναδημιουργείται
"τοπικά" ο τόπος με κάθε κλήση της F, αλλά μόνο σε ότι αφορά την F (αν η LET που ορίζει τον
τόπο επιτελεί και κάποια άλλη δράση, ανεξάρτητη της F, η δράση αυτή δεν θα επανεκτελεστεί με
την εκτέλεση της F).

Θα δούμε σχετικά παραδείγματα, όπου βλέπουμε τις τοπικές μεταβλητές "απ' έξω από τον τόπο",
και αυτό γίνεται με χρήση καθολικών συναρτήσεων ορισμένων μέσα στον τόπο.
Παράδειγμα
Να οριστεί τόπος μέσα στον οποίο να ορίζονται καθολικές συναρτήσεις, οι οποίες να
222

χρησιμοποιούν τις τοπικές μεταβλητές αυτού του τόπου.
(let
((initial 1))
(defun one (x)
(cond
((<= x 3) initial)
(t (setq initial (* initial 2)) (one (1- x)))) )
; (συνεχίζεται)
(defun two (x)
(cond
((< x 2) initial)
(t (setq initial (/ initial 2)) (two (– x 2)))) ) )
→ TWO
(γιατί;)

Κλήση εφαρμογής των συναρτήσεων έξω από τον τόπο των μεταβλητών τις οποίες
χρησιμοποιούν:
(one 100) → 158456325028528675187087900672
(two 1000) → 1 / 132073632783916311588084946…

Σειριακή έναντι παράλληλης αναφοράς τοπικών μεταβλητών
Στη LET , όπως είδαμε, κάθε οριζόμενη μεταβλητή είναι ανεξάρτητη των άλλων, δηλ. ο ορισμός
τοπικών μεταβλητών είναι παράλληλος. Πχ:
(setq a 3)
(let ((a 2) (b a))
(princ "a = ") (princ a) (princ " , b = ") (princ b))

a=2 , b=3

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

let* :
((<symbol>  <term>) n)  <term> m → <term>
Παρόμοιας σύνταξης και λειτουργίας με τη LET , η LET* διαφέρει στο οτι ο προσδιορισμός των
τοπικών μεταβλητών είναι σειριακός , δηλ. κάθε οριζόμενη μεταβλητή μπορεί να κληθεί σε
επόμενο ορισμό μεταβλητής και επομένως παίζει ρόλο η σειρά αναφοράς τους.
(setq a 3)
(let* ((a 2) (b a))
(princ "a = ") (princ a) (princ " , b = ") (princ b))
→ a=2 , b=2
223

Η αναφορά του a στην παραπάνω φόρμα (b a) ορισμού της τοπικής μεταβλητής b , καλεί την τιμή
της τοπικής μεταβλητής a, δηλ. 2 , και όχι της καθολικής a, δηλ. 3 , διότι έγινε στα πλαίσια της
LET* .

Ορισμός και τοπική εφαρμογή συναρτήσεων

Είδαμε οτι ο ορισμός συναρτήσεων μέσα σε ένα τόπο (πχ. με DEFUN μέσα στο LET) μπορεί να
κάνει χρήση των τοπικών μεταβλητών, και να κληθεί έξω από τον τόπο, μεταφέροντας έτσι
πληροφορία που αφορά τις τοπικές μεταβλητές "έξω από τον τόπο".

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

Προφανώς η πρόσβαση αυτή δεν είναι ελεύθερη: εξαρτάται από τη λειτουργικότητα της
συνάρτησης. Επομένως, ένας ρόλος τέτοιων συναρτήσεων είναι να φιλτράρουν κατάλληλα την
πληροφορία που "εξάγουν" σχετικά με τις συνθήκες των τοπικών μεταβλητών, ή αντίστοιχα την
πληροφορία που "εισάγουν" απ' έξω προς τον τόπο (πχ. η εκτέλεση της συνάρτησης να
αναδιαμορφώνει τις συνθήκες αυτές). Άρα είναι ένα ισχυρό εργαλείο καθορισμού τού "τί βλέπει
κανείς απ' έξω από τον τόπο, κοιτάζοντας μέσα" και "σε τί μπορεί κανείς να επιδράσει μέσα στον
τόπο, κοιτάζοντας απ' έξω". Δεν καθορίζουν το "ποιος βλέπει", διότι είναι καθολικά σύμβολα και
είναι ορατά από παντού.
Αν όμως τέτοιες συναρτήσεις δεν είναι καθολικά σύμβολα αλλά τοπικά ενός ευρύτερου τόπου
που περιβάλλει τον εν λόγω τόπο, τότε αυτά είναι ορατά (στο ίδιο μέτρο καθορισμού της
ορατότητας) από κάποια πλαίσια και αόρατα από άλλα. Έτσι, έμμεσα καθορίζουμε το "ποιος
βλέπει μέσα στον τόπο".

Για να έχουμε συναρτήσεις ορισμένες τοπικά και κατά συνέπεια δυνατό να κληθούν μόνο στα
πλαίσια του τόπου) πρέπει να χρησιμοποιήσουμε, αντί της DEFUN , μια συνάρτηση ορισμού και
εφαρμογής τοπικών συναρτήσεων, που είναι η FLET .
flet : Συνάρτηση ειδικής σύνταξης, που χρησιμεύει για τον τοπικό ορισμό και εφαρμογή
συνάρτησης:
(let …
(flet (
(<όνομα-1> <λ-λίστα> <σώμα>)

; ορισμός της συνάρτησης
... ...
; άλλοι ορισμοί / κλήσεις
(<όνομα-1> <όρισμα1> … <όρισμαk> )
; εφαρμογή της συνάρτησης
))

…)

Προσοχή στη συντακτική και εννοιολογική διαφορά της FLET από την DEFUN: η FLET καί
224

ορίζει καί εφαρμόζει συνάρτηση, "επί τόπου".

Όνομα συνάρτησης ορισμένο μέσα σε ένα τόπο (πχ. ορισμένο με LET) με χρήση της FLET αντί
της DEFUN , δεν είναι ορατό έξω από τον τόπο. Τέτοιο όνομα μπορεί να συμπίπτει με καθολικό
όνομα συνάρτησης, και ο τοπικός ορισμός έχει προτεραιότητα μέσα στον τόπο. Με τον τρόπο
αυτό μπορούμε να επαναπροσδιορίσουμε τοπικά τη συμπεριφορά συνάρτησης.

5.3.4 Εμβέλεια, κάλυψη και ζωή συμβόλων
Από αυτά που είδαμε παραπάνω, συμπεραίνουμε οτι η ισχύς "τοπικού συμβόλου" έχει τις εξής
όψεις:

α'. "Μέχρι πού μπορεί να λειτουργήσει" το τοπικό σύμβολο, δηλ. ποια είναι η εμβέλειά του
(scope): είναι δυνατό να οριστεί μια συνάρτηση μέσα σε ένα τόπο με τρόπο που να
χρησιμοποιηθεί μόνο μέσα στον τόπο αυτό και σε εσωτερικότερους τόπους, όπως είναι δυνατό
να οριστεί με τρόπο που να είναι ορατή και "απ' έξω".
Ακόμα, είναι δυνατό να ελεγχθεί το "πόσο έξω", με εγκλεισμό τόπων ή με διαδικαστικά
προσδιορισμένη δημιουργία και διαγραφή συνάρτησης.

β'. "Ποιός μπορεί να δει" το τοπικό σύμβολο και "σε ποιο βαθμό μπορεί να επιδράσει σ' αυτό";
δηλ. πώς καλύπτεται, και αυτό σε διάφορα επίπεδα:
i)
είναι ορατό από παντού,
ii)
είναι "κρυμμένο" από τον έξω χώρο, δηλ. από τα όρια του τόπου,
iii)
είναι ορατό από συγκεκριμένο άλλο τόπο, και
iv)
εκτός από ορατό, είναι δυνατό να επέμβει σ' αυτό η οντότητα που το βλέπει.
γ'. Πόσο υφίσταται χρονικά (extent) το τοπικό σύμβολο; δηλ.
i)
αν υφίσταται "για πάντα", ανεξάρτητα του τόπου μέσα στον οποίο δημιουργήθηκε,
ii)
αν υφίσταται μόνο στα πλαίσια του χρόνου που εκτελείται ένας υπολογισμός,
iii)
αν υφίσταται σε κάποιο ευρύτερο λειτουργικό πλαίσιο, που εκτείνεται πέρα από το χρόνο
εκτέλεσης της συνάρτησης που προσδιορίζει τον τόπο και τι όρια έχει αυτό το πλαίσιο,
iv)
αν είναι εξαρτημένο από την υπόσταση άλλων συμβόλων.

Αλληλοαναφορά στοιχείων από διαφορετικούς τόπους
Ας δούμε ένα ερώτημα που γεννάται από την αλληλοαναφορά συμβόλων που ορίζονται σε
διαφορετικούς τόπους, μη κιβωτισμένους:
Τί θα γίνει αν οριστούν τοπικά , μέσα σε δύο διακεκριμένα LET , δύο διαφορετικές
συναρτήσεις, με το ίδιο όνομα, και κληθεί συνάρτηση με το όνομα αυτό εξωτερικά του LET ;
Η απάντηση είναι:
Από τα LET που στο σώμα τους ορίζονται συναρτήσεις με ίδια ονόματα και διαφορετικούς
ορισμούς, το LET που θα τρέξει τελευταίο, καθορίζει τη συνάρτηση την οποία θα βλέπει
καθολικά όλο το πρόγραμμα.
225

Σ' αυτό, χρειάζεται να θυμηθούμε οτι το DEFUN δεν προκαλεί τρέξιμο του σώματος της
συνάρτησης που ορίζει, άρα η διαδοχή των ορισμών δεν παίζει ρόλο στον καθορισμό
προτεραιοτήτων. Ρόλο παίζει η σειρά κλήσης των συναρτήσεων και μόνο (βλέπε παραδείγματα 5
και 6).
Διασταύρωση από LETs μπορεί να επιτευχθεί, μέσω συναρτήσεων που ορίζονται εσωτερικά τού
ενός και καλούνται μέσα από το άλλο, αλλά αυτό θέλει πολλή προσοχή διότι μπορεί να
προκαλέσει ανεπιθύμητα αποτελέσματα
Παραδείγματα
1. Τοπικός προσδιορισμός μεταβλητών και χρήση τους:
(setq a 1 b 2)
; καθολικές μεταβλητές
(defun g (x) x)
; καθολική συνάρτηση (είναι η ταυτοτική)

(let
((a 3) (b 4))
; τοπικές μεταβλητές
(defun f (x) (+ a b (* x 5)))
; συνάρτηση καθολικά ορατή
(defun h (x) x )
;
»
»
; τοπική κλήση της f , με χρήση της τοπικής μεταβλητής a
(f a) )
→ 20
Η τιμή αυτού του LET είναι η τιμή της F στο a . Ο υπολογισμός της F στο a γίνεται βάσει των
αντιστοίχων τιμών των τοπικών μεταβλητών a και b .
Αν καλέσουμε την F για εφαρμογή, πχ. στο 2 , εξωτερικά του τόπου, ο υπολογισμός του (f 2)
"βλέπει" τις τοπικές μεταβλητές a και b , παρόλο που η εκτέλεση της LET έχει ολοκληρωθεί.:
(f 2)
→ 17

Αλλά:
(f a) → 12
(g a)
→ 1
(h a)
→ 1

διότι κατά την εξωτερική κλήση, το όρισμα a είναι η καθολική μεταβλητή
2. Συνάρτηση που ορίζεται μέσα σε ένα τόπο και χρησιμοποιείται εκτός του τόπου:

Προφανώς η προηγούμενη συνάρτηση F μπορεί να χρησιμοποιηθεί από άλλη συνάρτηση, έστω
Ρ , η οποία με τον τρόπο αυτό κάνει έμμεσα χρήση των τοπικών μεταβλητών a και b του
προηγούμενου τόπου αλλά δεν μπορεί να παρέμβει σ' αυτές, όπως:
(defun p (x) (cos (f x)))

Εφαρμογή:
→ 0.283662185463225
Η συνάρτηση Ρ χρησιμοποιεί τις τοπικές μεταβλητές a b , με τις τιμές που είχε δώσει η
εκτέλεση του προηγουμένου LET, μέσω της F .
(p (* 2 pi))

Βλέπουμε οτι, συναρτήσεις οριζόμενες μέσα στον τόπο τέτοιες που να κάνουν χρήση των
226

τοπικών μεταβλητών, παίζουν παράλληλα περισσότερους ρόλους:
i) καθιστούν τον τόπο μια διαρκώς υπαρκτή έννοια, ανεξάρτητα του οτι έχει κλείσει ο
υπολογισμός του LET ,
ii) φιλτράρουν κατάλληλα την πληροφορία, πχ. καθιστώντας ορατές της τοπικές μεταβλητές
αλλά απαγορεύοντας να πειραχτούν.
Αντίστοιχο ρόλο μπορούν να παίξουν και καθολικές μεταβλητές οριζόμενες μέσα στον τόπο, με
χρήση των τοπικών μεταβλητών στην τιμή τους, και καλούμενες εξωτερικά
3. "Κρύψιμο" συνάρτησης, ώστε να είναι ορατή μόνο μέσα στον τόπο:
Άμεσα μπορούμε κρύψουμε συνάρτηση μέσα σε τόπο, ορίζοντάς την κα καλώντας την μέσω της
FLET αντί της DEFUN : τότε η συνάρτηση δεν είναι ορατή έξω από τον τόπο. Αλλά και έμμεσα
μπορούμε να "κρύψουμε" υπολογισμό μέσα σε ένα τόπο:
– Να κρατήσουμε "ανώνυμη" τη συναρτησιακή έκφραση, είτε δίνοντάς την ως λ-έκφραση που
εφαρμόζεται "επί τόπου", είτε δίνοντάς την ως τιμή σε τοπική μεταβλητή, και να κληθεί για
υπολογισμό μέσα στον τόπο όπου χρειάζεται, πχ:
(let ((a '(lambda (x) (1+ x)) …)

(funcall #'a (read)) …)

– Να ορίσουμε συνάρτηση με DEFUN και να τη διαγράψουμε ακριβώς πριν το πέρας του LET :
(let …
(defun fn …)

<χρήση της fn>

(delete-function 'fn))

4. "Πέρασμα" τοπικών μεταβλητών έξω από τον τόπο:
Όπως είδαμε προηγουμένως στο παράδειγμα1, χώροι έξω από τον τόπο δεν μπορούν να δουν
άμεσα τις τοπικές μεταβλητές, αλλά είναι δυνατό να δοθεί μέσα στον τόπο συνάρτηση που να τις
"δείχνει". Εδώ δίνουμε ένα άλλο παράδειγμα (η συνάρτηση CASE προσδιορίζεται στα επόμενα):
(let ( (a 3) (b 2))
(defun f (x) (+ a b (* x 5)))
(defun show (x)
(case x
(a a) ; αν κληθεί η (show 'a) επίστρεψε την τιμή του a
(b b)
; αν κληθεί η (show 'b) επίστρεψε την τιμή του b
(t nil) ) ))
; αλλιώς, επίστρεψε ΝΙL

Εφαρμογή:

(show 'a) → 3

; είναι η τοπική μεταβλητή a του τόπου όπου ορίζεται η SHOW
(f 10)
→ 55
227

; αναφέρεται στις τοπικές μεταβλητές a και b
5. Διαμόρφωση τοπικών συνθηκών:
Οι τοπικές συνθήκες μπορούν να διαμορφωθούν και απ' έξω από τον τόπο (και να καθορίζουν
το πλαίσιο της επόμενης κλήσης του τόπου), μέσω καθολικής συνάρτησης που ορίζεται
εσωτερικά στον τόπο και διαμορφώνει τις αρχικές συνθήκες για τις τοπικές μεταβλητές, όπως:
(let ((x 2) (y 4))
(defun my-f (z) (print (* x z)) (setq x (+ x 3)) ) )

Εξωτερική κλήση:
(my-f 3)
→ 6
5
(my-f 3)
→ 15
→ 8

(εκτύπωση την πρώτη φορά)
(επιστροφή την πρώτη φορά)
(εκτύπωση τη δεύτερη φορά)
(επιστροφή τη δεύτερη φορά)

6. Ανάμεικτη χρήση τοπικών και καθολικών μεταβλητών με το ίδιο όνομα:
Η ανάμειξη τοπικών και καθολικών μεταβλητών με το ίδιο όνομα σε έναν υπολογισμό "εκτός
του τόπου" εξαρτάται απόλυτα από τον ορισμό και τη χρήση των καλουμένων συναρτήσεων :
(setq a 12 b 23)
; ορισμός καθολικών μεταβλητών
(print a)
→ 12
(αναφέρεται στην καθολική μεταβλητή a)
(print 'a)
→ a
»
»
»
(+ 2 (show 'a)) → 5
(αναφέρεται στην τοπική μεταβλητή a)
Αλλά προσοχή:
(+ a (show 'a)) → 15
διότι το πρώτο a είναι το καθολικό, τιμής 12 , ενώ το δεύτερο a είναι το τοπικό στο οποίο
αναφέρεται η SHOW , τιμής 3 (εξ αιτίας του ορισμού της, που δίνεται στο προηγούμενο
παράδειγμα).
7. Ορισμός συνάρτησης με ίδιο όνομα σε διαφορετικούς τόπους:

Έστω οτι ορίζουμε ένα δεύτερο τόπο, που επίσης ορίζει συνάρτηση SHOW με πανομοιότυπο
σώμα, αλλά με διαφορετικές τιμές των τοπικών μεταβλητών:
(let ((a 30) (b 20))
(defun show (x) (case x (a a) (b b)) ) )

Ο υπολογισμός αυτού του LET μετά το προηγούμενο, καθορίζει οτι κλήση τής SHOW θα
αναφέρεται στις νέες τοπικές μεταβλητές:
(show 'b)

→ 20

228

8. Τόπος που ορίζεται μέσα στο σώμα συνάρτησης:
Ορισμός συνάρτησης με χρήση του LET μέσα στο σώμα της, δεν ενεργοποιεί τον τόπο∙ όμως τον
ενεργοποιεί η κλήση της οριζόμενης συνάρτησης:
(defun ff ( )
(let ((a 7) (b 11))
(defun show (x) (case x (a a) (b b)) ) )

Εφαρμογές:
Από την SHOW που προσδιόρισε το προηγούμενο LET, παίρνουμε:
(show 'b) → 20
Με κλήση της FF, επαναπροσδιορίζουμε την SHOW :
(ff )
(show 'b) → 11

9. Διασταύρωση από τόπους :
Είναι δυνατό να ορίζεται συνάρτηση μέσα σε τόπο και να καλεί στο σώμα της συνάρτηση που
ορίζεται σε άλλο τόπο. Η δεύτερη μπορεί επίσης να καλεί την πρώτη:
(defun a1 ( ) (let ((x 1)) (defun act1 ( ) (princ x) (act2 ))))
(defun a2 ( ) (let ((x 3)) (defun act2 ( ) (princ x) (act1 ))))

Η κλήση της a1 ενεργοποιεί το αντίστοιχο LET, που ορίζει την ACT1 που καλεί την ACT2 :
(a1) → act1
Η κλήση της a2 ενεργοποιεί το αντίστοιχο LET, που ορίζει την ACT2 που καλεί την ACT1 :
(a2) → act2
Κλήσεις:
(act1)
→ 1313131313131313131313131313131313…
(act2) → 3131313131313131313131313131313131…
Τα αποτελέσματα δεν είναι αριθμοί αλλά ατέρμονες διαδοχικές εκτυπώσεις των αριθμών - τιμών
1 και 3 (προσοχή, δεν τερματίζεται η εκτέλεση).

5.3.5 Χρονικά πλαίσια "ζωής" συμβόλων
Σχετικά με τη ζωή οντοτήτων, έχουμε τα εξής:
– ένα σύμβολο καθολικής εμβέλειας "ζει" από τη δημιουργία του μέχρι την καταστροφή του
από δράση εκτέλεσης.
– ένα τοπικό σύμβολο "ζει" κατά τη διάρκεια της κλήσης του τόπου, που είναι εκτέλεση
εφαρμογής.
– μια λ-μεταβλητή συνάρτησης αποτελεί σύμβολο για τον υπολογισμό, το οποίο "ζει" κατά τη
διάρκεια εφαρμογής της συνάρτησης.
229

Ο εγκλεισμός τόπων καθορίζει "πλαίσια ζωής" μέσα σε "πλαίσια ζωής",
αλληλεξάρτηση.

σε δενδροειδή

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

Συμπεράσματα
Οι τοπικές μεταβλητές υφίστανται από την έναρξη εκτέλεσης της συνάρτησης που ορίζει τον
τόπο τους (LET, LET*, PROG, PROG*, DO, DO*…) και μέχρι το πέρας της εκτέλεσης.
Παραβαίνει τον κανόνα αυτό η δυνατότητα μετέπειτα "φιλτραρισμένης" χρήσης τους από
συναρτήσεις, και γενικότερα, καθολικά σύμβολα, εσωτερικά ορισμένα του τόπου και εξωτερικά
καλούμενα, που κατά κάποιο τρόπο "ανοίγουν ένα παράθυρο προς τον τόπο".
Αυτό το "άνοιγμα" προς ένα τόπο αποτελεί μια επέκταση της έννοιας "τόπος" που μπορεί να φανεί
πολύ χρήσιμη όταν θέλουμε ένα τρόπο έμμεσης αναφοράς σε ένα τόπο, δηλαδή χωρίς να είναι
απαραίτητο να τον δημιουργούμε επεξηγηματικά κάθε φορά που αναφερόμαστε σε στοιχεία του.
Διευκρινίζουμε ξανά τα εξής:
– Τα αναφερόμενα μέσα στο σώμα συνάρτησης σύμβολα τοπικών μεταβλητών έχουν
προτεραιότητα απέναντι σε σύμβολα με ίδιο όνομα τα οποία είναι καθολικές μεταβλητές, και
το ίδιο για τα ορίσματα σε εσωτερική του τόπου κλήση προς υπολογισμό. Αντίθετα, σε
εξωτερική του τόπου κλήση προς υπολογισμό, τα αναφερόμενα στα ορίσματα σύμβολα
αναφέρονται στα αντίστοιχα καθολικά.

– Ίδια ονόματα συμβόλων που προσδιορίζονται μέσα σε περισσότερους του ενός τόπους και
κάνουν χρήση τοπικών μεταβλητών, αναφέρονται στην τελευταία κλήση τόπου. Άρα
οφείλουμε να τηρούμε την ιστορία των κλήσεων των τόπων για να γνωρίζουμε την "τρέχουσα
κατάσταση" του συμβόλου.
– Διασταύρωση τόπων είναι δυνατή, και αυτό συσχετίζει με φαινομενικά πεπλεγμένο τρόπο τις
αναφορές σε τοπικές μεταβλητές και συναρτήσεις. Ουσιαστικά, όλα εξαρτώνται από τη
διαδοχή των εκτελέσεων που δημιούργησαν τους τόπους.

230

5.4 Τυποποίηση
Με τον όρο τυποποίηση ή υποτυποποίηση (subtyping) εννοούμε τη δημιουργία και αντίστοιχη
χρήση νέων τύπων με σκοπό την εξειδίκευση της συμπεριφοράς συνάρτησης (δεν
διαφοροποιούνται στην CL οι δύο όροι, διότι το δένδρο των τύπων έχει μία και μοναδική ρίζα,
την Τ , άρα είναι δυνατή η δημιουργία μόνο υποτύπων κάποιου πρωτογενούς τύπου).

Πρωτογενή σύμβολα που καθορίζουν κάποιο βασικό τύπο στην Common Lisp είναι, αλφαβητικά,
τα ακόλουθα (εντάσσονται στην δενδροειδή δομή των τύπων). Αυτοί είναι οι λεγόμενοι "βασικοί
τύποι" (standard types) και επιστρέφονται από την ΤΥΡΕ-ΟF . Αν έχει οριστεί άλλος τύπος,
ειδικότερος αυτών μέσω της DEFTYPE , ελέγχεται με την TYPEP αλλά δεν επιστρέφεται από
την TYPE-OF (τα παρακάτω ενωτικά είναι χαρακτήρες των τύπων).
array atom base-character bignum bit bit-vector character compiled-function
complex cons double-float extended-character fixnum float function hash-table
integer keyword list long-float nil null number package pathname random-state
ratio rational readtable real sequence short-float signed-byte simple-array simplebit-vector simple-string single-float standard-char stream structure strucrture-class
string symbol t unsigned-byte vector

Βασικοί τύποι της Common Lisp

Σε μερικούς compilers διακρίνεται και ο τύπος lambda, ενώ σε άλλους οι λ-εκφράσεις
εντάσσονται στον τύπο cons, κατά το πρότυπο της CL.
Όπου οι τύποι είναι ιεραρχικά εξαρτημένοι, η ΤΥΡΕ-ΟF επιστρέφει τον ειδικότερο (πάντα στα
πλαίσια του πίνακα αυτού).

Το δένδρο των τύπων αποτελεί ένα τρόπο ταξινόμησης των οντοτήτων της Lisp κατά κλάσεις
(υποκλάσεις, κλπ.), των οποίων οι οντότητες του τύπου τους, αποτελούν αντικείμενα.

Ένα πολύ ισχυρό μέσον για τη δημιουργία τύπων και ταυτόχρονα να είναι κλάσεις που
αντιπροσωπεύουν αντικείμενα, είναι το κτίσιμο δομών του τύπου structure ή του ειδικότερου
τύπου strucrture-class που θα δούμε αναλυτικά.
Πολύ σημαντική διευκρίνιση: Στους βασικούς τύπους, με το νόημα οτι είναι ορατοί από την
TYPE-OF , εντάσσονται δυναμικά και οι οντότητες που δημιουργούνται με τις συναρτήσεις
DEFSTRUCT και DEFCLASS , που θα δούμε στα επόμενα.

231

5.4.1 Έλεγχος τύπου

Ο τύπος μεταβλητής ελέγχεται από δύο γενικές συναρτήσεις, τις TYPE-OF και TYPEP , και μια
σειρά συναρτήσεων ειδικού ελέγχου για κάθε τύπο. Σχετική είναι και η SUBTYPEP που ελέγχει
σχέσεις κλάσης - υποκλάσης τύπων:
type-of :
<atom> → <type>
Είναι συνάρτηση ενός ορίσματος το οποίο μπορεί να είναι οτιδήποτε. Eπιστρέφει τον ειδικότερο
τύπο του, από τον πίνακα βασικών τύπων. Προφανώς, αν το όρισμα δοθεί quoted, επιστρέφει τον
τύπο αυτού καθ' εαυτού του ορίσματος, αλλιώς τον τύπο της τιμής του (και προσοχή, είναι
διαφορετικές έννοιες).
(type-of 4 )
→ integer

typep
:
<atom>  <type> → <Boolean>
Συνάρτηση δύο ορισμάτων, όπου πρώτο είναι η ελεγχόμενη οντότητα και δεύτερο τύπος, στον
οποίο ελέγχεται αν ανήκει. Επιστρέφει Τ ή NIL .
(typep 4 'number)
→ T
Διαθέτουμε συναρτήσεις ελέγχου κάθε βασικού τύπου: το σύμβολό τους σχηματίζεται κατά
κανόνα από τα παραπάνω σύμβολα τύπων, με κατάληξη τον χαρακτήρα "p" για όσα είναι απλές
λέξεις, και "-p" για όσα είναι σύνθετες λέξεις (προσοχή στο ενωτικό).
Εξαίρεση είναι οι τύποι: NULL και ATOM , που χρησιμοποιούνται για έλεγχο ως έχουν (χωρίς
"p"), και οι Τ και NIL , που είναι οριακοί τύποι ("άκρα" του κατευθυνόμενου γραφήματος των
τύπων) και είναι τετριμμένο να χρησιμοποιούνται για έλεγχο τύπου.
(arrayp 3)
→ ΝΙL
(arrayp #(1 2 3)
→ T
(ratiop (/ 3 4))
→ T
(ratiop (/ 4 2))
→ NIL
(rationalp 2)
→ T
(atom 'a)
→ T
(atom '(1 2 3))
→ NIL
(simple-vector-p #(3 4 5)) → Τ

Οι λογικές τιμές Τ και NIL θεωρούνται (και) τύποι. Επισημαίνουμε ξανά οτι δεν υπάρχει
πρωτογενής τύπος "logical" ή "boolean" στην CL, και αυτό οφείλεται στον ειδικό ρόλο που
παίζουν οι λογικές τιμές ως τύποι: Τ είναι ο γενικότερος τύπος όλων, και ΝΙL είναι ο ειδικότερος
τύπος όλων.
subtypep :

<type>  <type> → <Boolean>

Συνάρτηση δύο ορισμάτων, που πρέπει να είναι τύποι. Ελέγχει αν το πρώτο είναι υποτύπος του
δεύτερου:
(subtypep 'integer 'number)
→ T
► Η TYPEP εφαρμόζεται για έλεγχο κάθε τύπου ή υποτύπου, προκαθορισμένου ή ορισμένου
232

πρόσθετα, ενώ η TYPE-OF εφαρμόζεται για εντοπισμό του τύπου της οντότητας μόνο στα
πλαίσια των βασικών τύπων και των τύπων που ορίστηκαν ως κλάσεις (βλέπε στα επόμενα
DEFSTRUCT, DEFCLASS). Αν έχουμε ορίσει εμείς νέους υποτύπους με την DEFTYPE, δεν
επιστρέφονται από τη συνάρτηση αυτή.

Μετατροπή τύπου
Πολλές φορές θέλουμε να χρησιμοποιήσουμε μια συνάρτηση που δίνει αποτέλεσμα ορισμένου
τύπου, για να δώσουμε είσοδο σε συνάρτηση που απαιτεί όρισμα άλλου τύπου, αλλά να μας
εμποδίζει η τυπική ασυμβατότητα των τύπων χωρίς να υπάρχει ουσιαστικός λόγος. Μια λύση
είναι, να μετατρέψουμε τον τύπο της οντότητας που δίνεται ως έξοδος από τη μία συνάρτηση,
στον κατάλληλο τύπο που απαιτεί ως είσοδο η άλλη συνάρτηση.
Αυτό προϋποθέτει βέβαια οτι είναι συμβατοί οι τύποι μεταξύ τους:
– Όταν ο τύπος εισόδου καλύπτει ιεραρχικά τον τύπο εξόδου, δεν χρειάζεται μετατροπή (πχ. να
δίνεται integer όπου απαιτείται number).
– Αν αντίθετα, ο πρώτος είναι ειδική περίπτωση του δεύτερου, και η οντότητα εξόδου έχει τα
απαιτούμενα χαρακτηριστικά ώστε με την εξειδίκευση να ταιριάξει ως είσοδος, τότε η
μετατροπή λύνει το πρόβλημα. Η συνάρτηση COERCE αναλαμβάνει αυτή τη μετατροπή:
coerce

: <atom>  <type> → <atom>

Είναι συνάρτηση μετατροπής τύπου. Δύο ορισμάτων, όπου το πρώτο είναι η οντότητα - άτομο
που θέλουμε να μετατρέψουμε τον τύπο της, και δεύτερο ο νέος τύπος. Έξοδος είναι η ίδια η
οντότητα, αλλά ανήκει στο νέο τύπο. Δεν είναι μόνιμη. Εάν αποτύχει η μετατροπή επιστρέφει
σφάλμα.
(coerce 3 'float)
→ 3.0
(defun fn (x)
(typecase x

; σε σχέση με τον τύπο του x :
; αν x είναι τύπου integer, επίστρεψε το x ως έχει
(integer x))

; αν x είναι ratio, επίστρεψε το x έχοντας εκτελέσει τη διαίρεση:

(ratio (coerce x float))

; αλλιώς, δώσε μήνυμα και επίστρεψε 0 :

(t (print "ακατάλληλος τύπος") 0)))

► Έχουμε δει τη χρήση της FLOAT ως συνάρτηση μετατροπής αποτελέσματος σε τύπου float.
Εδώ τη βλέπουμε το σύμβολο FLOAT ως τύπο. Αυτό συμβαίνει διότι το σύμβολο "float" είναι
σύμβολο που είναι ταυτόχρονα συνάρτηση και τύπος.

233

5.4.2 Δημιουργία υποτύπων
Ορίζονται μέσω τής συνάρτησης DEFTYPE , ειδικής σύνταξης, με κάθε λογικό συνδυασμό
τύπων ή σχέσεων που αναγνωρίζονται ως τύποι.
Ο συνδυασμός αυτός γίνεται με χρήση των συναρτήσεων:
AND , OR , NOT , SATISFIES , ΜEMBER

και υπαρχόντων τύπων. Η σύνταξη των AND, OR, NOT είναι η γνωστή, αλλά γίνεται
συγκεκριμένη χρήση, του προσδιορισμού συνθέτου τύπου βάσει άλλων τύπων ή εν γένει
κατηγορημάτων:
; έλεγξε αν ο τύπος του "anbv" δεν είναι λίστα και δεν είναι αριθμός:
(typep "anbv" '(and (not list) (not number))) → T
Η συνάρτηση satisfies μετατρέπει κατηγόρημα σε τύπο. Γράφουμε:
(typep 3 '(satisfies numberp))

αντί για το ισοδύναμο:
(typep 3 'number)

► Στο παραπάνω, για την ιδιότητα - κατηγόρημα "numberp" υπάρχει αντίστοιχος τύπος
"number", οπότε η satisfies δεν προσφέρει κάτι νέο. Ενδέχεται όμως να χρησιμοποιήσουμε
κατηγόρημα στο οποίο δεν αντιστοιχεί κάποιος πρωτογενής ή προσδιορισμένος τύπος:
(defun big (x) (> x 32700)) → BIG
(typep 3 '(satisfies big))
→ NIL
(typep 300000 '(satisfies big)) → T

Απ' ευθείας χρήση της SATISFIES μπορεί να γίνει σε έλεγχο όπως στον παραπάνω "big". Πιο
ουσιαστικά χρησιμεύει για να συνθέσουμε νέο τύπο βάσει συγκεκριμένων ιδιοτήτων που
θέλουμε να πληρούν τα "αντικείμενά" του. Για το σκοπό αυτό χρησιμοποιούμε την DEFTYPE :
deftype : <type>  <lambda-list>  '<declarations list> → <type>
<type>  <lambda-list>  <body list> → <type>
<type> <lambda-list>  '<declarations list>  <body list> → <type>
Είναι συνάρτηση macro , που προσδιορίζει νέο τύπο. Επιστρέφει τον νέο τύπο. Έχει κάποιες
ιδιομορφίες σύνταξης που πρέπει να τηρηθούν:
(deftype νέος-τύπος
(<λίστα λ-μεταβλητών)
'(<προαιρετική λίστα δηλώσεων σχετικά με τον τύπο>)
(<προαιρετικό σώμα σχετικό με τις λ-μεταβλητές>) )
Προσοχή στο οτι η λίστα των δηλώσεων πρέπει να είναι "quoted".
Ας δούμε τώρα πώς και πότε χρησιμοποιείται η DEFTYPE :

1) Τύπος που αντιπροσωπεύει δεδομένη ομάδα οντοτήτων (λίστα ονομάτων)
Στην απλούστερη μορφή χρήσης της, η συνάρτηση DEFTYPE δεν έχει λ-μεταβλητές. Με χρήση
234

του MEMBER μπορούμε να προσδιορίσουμε ένα σύνολο "αντικειμένων" που αντιπροσωπεύονται,
αυτά και μόνον αυτά, από τον τύπο:
(deftype όνομα-υποτύπου ( )
'(member
ειδική-περίπτωση1 ειδική-περίπτωση2 … ειδική-περίπτωσηk) )
► Η κενή λ-λίστα αναγράφεται για λόγους σύνταξης. Το "quote" τίθεται διότι ο αναφερόμενος
όρος '(member…) αποτελεί τη δήλωση στο DEFTYPE . Δεν αναγράφεται σώμα προς εκτέλεση.
Παράδειγμα
Ορισμός τύπου science ως υποτύπου του symbol , σε μορφή λίστας συμβόλων που ανήκουν στον
τύπο αυτό.
(deftype science ( )
'(member
mathematics physics chemistry biology geology
astronomy informatics))

Χρήση:
(typep 'informatics 'science) → Τ
(typep 'geology 'science)
→ Τ
(typep 'medicine 'science) → NIL

(μερικοί compilers επιστρέφουν, αντί για Τ , την υπολίστα περιπτώσεων τύπων από τον
ελεγχόμενο όρο και πέρα, που λογικά είναι ισοδύναμο με Τ).
► Προσοχή στην ιδιόμορφη σύνταξη της δήλωσης '(member…) .

2) Τύπος που ορίζεται από λογικό συνδυασμό τύπων

Ορίζεται με οποιοδήποτε λογικό συνδυασμό αποδεκτών τύπων στο σώμα της DEFTYPE.
Αποδεκτοί τύποι είναι είτε αυτοί από τον παραπάνω πίνακα, είτε αυτοί που δίνονται από
εκφράσεις της μορφής "(satisfies κατηγόρημα)" , και λογικός συνδυασμός είναι οποιαδήποτε
σύνθεση με AND , OR , NOT .
Παράδειγμα
Ορισμός τύπου number-or-alpha , που δηλώνει οτι η ελεγχόμενη οντότητα είναι είτε τύπου
number είτε τύπου "αλφαριθμητικός χαρακτήρας".
(deftype number-or-alpha ( )
'(or (satisfies numberp) (satisfies alphanumericp) ))

(το κατηγόρημα alphanumericp είναι πρωτογενές και χαρακτηρίζει τους βασικούς χαρακτήρες,
δηλ. a-z, A-Z, 0-9).
Εφαρμογή:
(typep 100 'number-or-alpha)
→ T
(typep #\b 'number-or-alpha) → T
235

→ NIL
Παρατηρούμε πως τώρα έχουμε δημιουργήσει ένα τύπο - παιδί δύο τύπων, άρα έχουμε
προκαλέσει συνένωση κλάδων στο αρχικό δένδρο.
(typep #\@ 'number-or-alpha)

3) Τύπος που ορίζεται από σχέση περιορισμού πάνω σε προϋπάρχοντα τύπο:

Στα προηγούμενα χρησιμοποιήσαμε την DEFTYPE χωρίς λ-μεταβλητές. Το αντικείμενο με το
οποίο συσχετίζεται ο τύπος, προσδιορίστηκε ως όρισμα της TYPEP. Όμως χωρίς χρήση λμεταβλητής δεν μπορούμε να εκφράσουμε νοήματα με σχέσεις που αναφέρονται στο ίδιο το
καλούμενο αντικείμενο, όπως: "να ισχύει για το αντικείμενο, έστω x, η σχέση (x+5) 2>100". Αυτό
το πετυχαίνουμε με το να θέσουμε λ-μεταβλητή της DEFTYPE που θα δεσμευτεί στο αντικείμενο
που θα χαρακτηρίζεται με τον δημιουργούμενο τύπο (βλέπε παρακάτω παράδειγμα):
(deftype <όνομα τύπου> (x)
(<σχέση που αναφέρεται στο x>) )

► Εδώ δεν θέτουμε quote διότι πρόκειται για το σώμα που υπολογίζεται (στη φόρμα αυτή του
DEFTYPE λείπουν οι δηλώσεις).
Η χρήση είναι κάπως ιδιόμορφη, σε σχέση με την προηγούμενη μορφή, διότι το όνομα-τύπου
τώρα έχει όρισμα, και αυτό το όρισμα είναι το ελεγχόμενο αντικείμενο:
(typep * '( <όνομα τύπου> <ελεγχόμενη τιμή> ))
Το σύμβολο * τίθεται εδώ με το νόημα του "οτιδήποτε" για τον τυπικό λόγο οτι η TYPEP
χρειάζεται να υπάρχει στη θέση αυτή ένα όρισμα (σταθερά ή μεταβλητή), χωρίς όμως να το
χρησιμοποιεί στον υπολογισμό, ο οποίος γίνεται μέσα στο δεύτερο όρισμα (ακριβέστερα, το * σε
θέση ορίσματος παριστάνει μεταβλητή). Το δεύτερο όρισμα της TYPEP κάνει καί τον
προσδιορισμό του τύπου καί τον έλεγχο.
Χρειάζεται προσοχή η χρήση της DEFTYPE διότι είναι macro που ορίζει macro.

4) Δημιουργία υποτύπων πίνακα
Οι οντότητες τύπου "πίνακα" (array), ή "διάνυσμα" (vector) εκτός από αυτό καθ' εαυτό το όνομα
του τύπου, είναι δυνατό να χαρακτηριστούν και ως προς τον τύπο των στοιχείων τους, και ακόμα,
ως προς το μέγεθος. Μια ουσιαστική ευκολία για την εξειδίκευση αυτή, προσφέρουν οι λεγόμενες
λίστες προσδιορισμού τύπου (type specifier lists). Οι λίστες αυτές χρησιμοποιούνται
πανομοιότυπα με τους τύπους.
Πρώτος όρος σε μια τέτοια λίστα είναι το όνομα του τύπου, και ακολουθούν ένας ή περισσότεροι
όροι που προσδιορίζουν κατά σειρά κάποια χαρακτηριστικά:
α'.
Σε οντότητα τύπου vector ο δεύτερος όρος της λίστας προσδιορισμού τύπου δηλώνει τον
τύπο των στοιχείων και ο τρίτος όρος το πλήθος τους. Για παράδειγμα, εκτός από τον γενικό τύπο
'vector , έχουμε και τον ειδικότερο:
'(vector integer)

που είναι ο τύπος "διάνυσμα ακεραίων συντεταγμένων", ή τον ακόμα ειδικότερο
'(vector integer 100)
236

που είναι ο τύπος "διάνυσμα, 100 ακεραίων συντεταγμένων".
β'.
Αντίστοιχα,
'(array integer 3 4)

είναι ο τύπος "πεδίο, διαστάσεων 4x5 , τιμής ακεραίας για κάθε θέση" (η μέτρηση στις
παραμέτρους που καθορίζουν τις διαστάσεις αρχίζει από το 0).
Ο προσδιορισμός τύπου της κατηγορίας αυτής, γίνεται με την DEFTYPE :
(deftype integer-array ( ) '(array integer 3 4))

Αν τώρα σχηματίσουμε ένα πίνακα που να ικανοποιεί τη συνθήκη αυτή, ο έλεγχος τύπου με την
TYPEP θα δώσει Τ .
Παρατήρηση: Δεν μπορούμε να δημιουργήσουμε αντικείμενο αυτού του τύπου, με απλή αναφορά
στον τύπο, όπως αντίστοιχα μπορούμε με τις κλάσεις, που θα δούμε στα επόμενα.
Παραδείγματα
1. Ορισμός υποτύπου:
Να οριστεί τύπος big-integer, ως υποτύπος του integer, με επιπρόσθετο κριτήριο να είναι
μεγαλύτερος του 1000000 :
(deftype very-big-integer (x)
(and (integer x) (> x 1000000)))

Χρήση:

(typep * '(very-big-integer 100000000000)→ T
(typep 100000000000 'very-big-integer )
→ T
(typep * '(very-big-integer 10)
→ NIL

Αλλά:
(type-of 100000000000)

→ BIGNUM

2. Ορισμός τύπου βάσει λογικής σύνθεσης κριτηρίων:
Να οριστεί τύπος in-interval-min-max στον οποίο να ανήκουν οι πραγματικοί αριθμοί που
ανήκουν στο ανοικτό διάστημα (min , max) :
i) Με άκρα min και max ως εξωτερικές μεταβλητές:
(deftype in-interval-min-max (val)
(and (realp val) (< min val max)))

Χρήση:
(setq min 3 max 5)
(typep * '(in-interval-min-max 3.2))

→ T
Το σύνολο των στοιχείων αυτού του τύπου είναι μεταβαλλόμενο, διότι εξαρτάται από τις τιμές
των min και max .
ii) Με άκρα min και max ως λ-μεταβλητές του ορισμού του τύπου:
(deftype in-interval (min val max)
(and (realp val) (realp min) (realp max) (< min val max)))
237

Χρήση:
(setq r 4)
(typep * '(in-interval 2 r 7))

→ T

Η α' περίπτωση ορίζει συγκεκριμένο διάστημα, ενώ η δεύτερη μεταβλητό. Η δεύτερη είναι
χρήσιμη σε περίπτωση όπου ο υπολογισμός έχει περισσότερα επίπεδα, όπου το διάστημα
ορίζεται σε ανώτερο επίπεδο και χρησιμοποιείται σε κατώτερο.

3. Προσδιορισμός τύπου για τιμές με συγκεκριμένο χαρακτηριστικό
Ο παραπάνω χαρακτηρισμός της σχέσης "ανήκει σε σύνολο" ως τύπου, μέσω της MEMBER,
αναφέρεται σε στατικά προσδιορισμένο σύνολο τιμών, και κάθε μεταβολή του συνόλου πρέπει
να περάσει από επαναπροσδιορισμό του τύπου, που είναι μια ριζική ενέργεια, αρκετά επικίνδυνη
αν γίνεται κατά τη διάρκεια ευρύτερης εκτέλεσης που κάνει χρήση του τύπου.
Για να καλύψουμε και αυτή την περίπτωση, δεν έχουμε παρά να προσδιορίσουμε τύπο με βάση
όχι αυτή καθ' εαυτή τη λίστα τιμών αλλά το συμβολικό όνομά της (οπότε το περιεχόμενο το
αλλάζουμε κατά βούληση).
Πρώτα απ' όλα χρειαζόμαστε μια σχέση "ανήκει" με έξοδο καθαρά Τ ή NIL :
(defun mem (x lis)
(cond
((member x lis) t)
(t nil)))

Με βάση αυτή τη σχέση, δίνουμε τον ορισμό τύπων:
(deftype όνομα-ανθρώπου (x) (mem x ονόματα-ανθρώπων))
(setq ονόματα-ανθρώπων nil )

Χρήση:
(typep * '(όνομα-ανθρώπου Γιάννης))
(push 'Γιάννης ονόματα-ανθρώπων)
(typep * '(όνομα-ανθρώπου Γιάννης))

(προσοχή στο αστεράκι)

→ NIL
→ T

Δήλωση τύπου
Όπως προαναφέραμε, η Lisp μπορεί να κρίνει αυτόματα την καταλληλότητα του τύπου εισόδου
συνάρτησης, βάσει των εξής κριτηρίων:
i) "αν ο απαιτούμενος τύπος είναι ψηλότερα από τον τύπο της οντότητας που δίνεται, τότε η
οντότητα ταιριάζει", και
ii) "αν η συνάρτηση εξειδικεύεται βάσει τύπων, τότε εντοπίζεται ο χαμηλότερη δυνατή εξειδίκευση
που ταιριάζει στον τύπο".
Αυτό προσδίδει εξαιρετική ευελιξία και δυναμικότητα στη χρήση συναρτήσεων, αλλά αν δεν τη
χρειαζόμαστε, υποχρεώνουμε τον compiler σε υπολογισμούς, περιττούς μερικές φορές. Ένας
238

άμεσος τρόπος αποφυγής εκτενών ελέγχων καταλληλότητας εισόδου είναι να προσδιοριστεί ρητά
ο τύπος οντότητας, τρόπος που πάντα υπακούει στον κανόνα "αν η οντότητα ανήκει σε ένα τύπο
α, ανήκει και σε κάθε υπερτύπο του".

Η συνάρτηση που δηλώνει τον απαιτούμενο τύπο της οντότητας που δίνεται, είναι η DECLARE.
Η συνάρτηση που δηλώνει τον τύπο αποτελέσματος έκφρασης υπολογισμού είναι η THE . Οι
συναρτήσεις αυτές αποτρέπουν τον έλεγχο, για αύξηση της ταχύτητας.

the :
Συνάρτηση με δύο ορίσματα, τύπο (εννοείται quoted) και υπολογισμό (που
υπολογίζεται), και δηλώνει οτι ο υπολογισμός έχει αποτέλεσμα αυτού του τύπου.

declare : Συνάρτηση δήλωσης τύπου. Τη χρησιμοποιούμε για να δηλώσουμε τον απαιτούμενο
τύπο λ-μεταβλητής κατά τον ορισμό συνάρτησης, ή ονόματος συμβόλου μέσα σε ένα
υπολογισμό. Η συμπεριφορά της συνάρτησης που περιέχει την DECLARE, σε περίπτωση
σφάλματος, είναι εξαρτημένη από τον compiler που χρησιμοποιούμε. Αυτό που προσδιορίζει η
DECLARE έχει τοπική εμβέλεια μέσα στα πλαίσια του υπολογισμού που εκτελείται.
Η σύνταξη της DECLARE, μεταξύ άλλων δυνατοτήτων που δεν θα αναφέρουμε εδώ, γίνεται με
χρήση της συνάρτησης ΤΥΡΕ:
(declare (type <τύπος> <στοιχείο> ))
Αναγράφεται πάντοτε ως πρώτος όρος στο σώμα:
(defun aa (x)
(declare (type integer x))
(remainder x 2))

Μια χρήσιμη περίπτωση χρήσης της DECLARE είναι για να δηλώσουμε αγνόηση ορισμάτων που
δεν πρόκειται να χρησιμοποιηθούν, σε συνδυασμό με τη βοηθητική συνάρτηση IGNORE:
(defun foo (& rest args)
(declare (ignore args))
(print "foo εκτελέστηκε"))

Παράδειγμα
Άθροισμα των όρων διανύσματος 100 1000 δεκαδικών, τύπου single-float :
(setq array1 (make-array '(100 1000)
:element-type 'single-float
; δεκαδικός απλής ακρίβειας
:initial-element 1.0))
(defun sum-of-elements (arr)
(declare (type (simple-array single-float (100 1000)) arr))
(let ((s 0.0))
(declare (type single-float s))
(dotimes (row 100)
; η dotimes περιγράφεται στα επόμενα
(dotimes (column 1000)
(incf s (aref arr row column))))
s ))

Εφαρμογή:
239

(sum-of-elements array1 )

→ 100000.0

Χωρίς τις δηλώσεις DECLARE ο χρόνος εκτέλεσης αυξάνεται σημαντικά.

proclaim: Παρόμοια με την DECLARE, αλλά έχει καθολική εμβέλεια και χρησιμοποιείται
κυρίως για ειδικές μεταβλητές.
Παρά το όνομά τους, οι TYPE , DECLARE και PROCLAIM χρησιμεύουν σε διαδικαστικής
μορφής κώδικα, και χρησιμεύουν μόνο για εξοικονόμηση χρόνου εκτέλεσης.

5.4.3 Συμπεριφορά συνάρτησης και τύποι εισόδου
Αναφέραμε τη χρήση από συνάρτηση επιλογής για έλεγχο της καταλληλότητας των τιμών
εισόδου, και αντίστοιχη ανακατεύθυνση της εκτέλεσης. (πχ. "αν η είσοδος δεν είναι αριθμός,
γράψε μήνυμα…").

Μια εννοιολογικά διαφορετική χρήση των conditionals είναι για εξειδίκευση της λειτουργίας της
συνάρτησης με κάποια κριτήρια. Συνηθέστερο κριτήριο είναι ο τύπος εισόδου, που είναι δυνατό
να προσδιορίζεται σε οποιοδήποτε πεπερασμένο βάθος εξειδίκευσης τύπου (πχ. "αν η είσοδος
είναι πραγματικός αριθμός στο διάστημα [3.45 , 12.64] εφάρμοσε τον υπολογισμό Α" κλπ.).
Τέτοιες εξειδικεύσεις αποτελούν ένα τρόπο προκαθορισμού της συμπεριφοράς της συνάρτησης
κατά περίπτωση, και η τυποποίηση προσφέρει δυνατότητα επαναχρησιμο-ποίησης ίδιων
περιπτώσεων συνθηκών.
Ο τρόπος μέσω προσδιορισμού ειδικών τύπων δεν είναι ο μοναδικός: θα μπορούσαμε να
ονομάσουμε με όνομα συμβόλου μια σύνθετη συνθήκη που επαναχρησιμοποιείται, και να
θέτουμε στο conditional για έλεγχο το σύμβολο αυτό, αλλά οδηγούμαστε σε πολυπλοκότερες
εκφράσεις.
Ως συμπεριφορά συνάρτησης χαρακτηρίζουμε γενικά τον τρόπο που θα λειτουργήσει η συνάρτηση
όταν κληθεί, και μπορεί να εξαρτηθεί από συνθήκες που αφορούν οτιδήποτε που είναι δυνατό να
ελεγχθεί από τη Lisp, όπως: πόσα στοιχεία έχει ένα σύνολο, αν υπάρχει κάποιο σύμβολο, τί τύπου
τιμής είναι κάποιες μεταβλητές, αν κάποια συνάρτηση δίνει κάποιου τύπου αποτέλεσμα, αν
αληθεύει ένας συσχετισμός, κλπ.
Θα δούμε στα επόμενα και ένα γενικότερο τρόπο εξειδίκευσης της συμπεριφοράς, όταν
πρόκειται για μέθοδο που εξειδικεύεται ανάλογα με το σε τί εφαρμόζεται. Ακόμα, είναι δυνατό να
εξαρτηθεί από μεταβλητά στοιχεία, ανεβάζοντας την έννοια της συμπεριφοράς σε γενικότερα
επίπεδα
Ιδιαίτερη κατηγορία ανακατεύθυνσης της συμπεριφοράς είναι αυτή που βασίζεται σε έλεγχο
σφάλματος. Είναι στενά εξαρτημένη από τον compiler, δηλ. τους τύπους σφαλμάτων που
αναγνωρίζει και τις συναρτήσεις ανακατεύθυνσης που διαθέτει, αλλά η CL διαθέτει κάποιες
βασικές δυνατότητες (θα δούμε παρακάτω τη συνάρτηση ECASE ).

Συναφής της συμπεριφοράς έννοια, είναι η λειτουργικότητα συνάρτησης, που είναι το σύνολο των
διαφοροποιούμενων κατά περίπτωση δυνατών λειτουργιών, δηλ. το σύνολο από τους
240

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

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

Συμπεριφορά εξαρτημένη από τις μεταβολές που υπέστη μια κατάσταση
Με δεδομένο οτι η υφιστάμενη κατάσταση μπορεί να μεταβάλλεται, και οι φάσεις της μεταβολής
μπορούν να φυλάσσονται, είναι δυνατό να εξαρτήσουμε τη συμπεριφορά όχι μόνο από την
υφισταμένη κατάσταση, δηλ. το παρόν, αλλά και από προηγούμενες φάσεις απ' όπου πέρασε η
εξέλιξη, δηλ. το παρελθόν των μεταβολών που έχουν επέλθει. Αυτό είναι χρήσιμο για την
περιγραφή λειτουργιών όπου έχει σημασία από ποιες φάσεις πέρασαν συγκεκριμένα
χαρακτηριστικά (παράβαλε: "σε γρίπη δεν παίρνουμε αντιβίωση, αλλά αν είναι η δεύτερη φορά
που παθαίνει κάποιος γρίπη μέσα σε τρεις εβδομάδες, πρέπει να πάρει αντιβίωση").

Η παρακολούθηση της εξέλιξης μπορεί να βασίζεται σε λίστες που τηρούν την ιστορία των
μεταβάσεων για συγκεκριμένα σύμβολα, όπου επισυνάπτονται οι διαδοχικές τιμές που λαβαίνουν,
σε σχέση με την αντίστοιχη φάση. Έτσι είναι δυνατό να εξαρτήσουμε τη συμπεριφορά
συνάρτησης από το περιεχόμενο των λιστών αυτών. Πχ, "αν η συχνότητα ζημιών υπερβεί το 5%
σε σχέση με τη συχνότητα κερδών κατά την κίνηση πιστωτικών καρτών μιας τράπεζας, ακόμα και
241

αν τα κέρδη της τράπεζας από τους λογαριασμούς αυτούς αθροιστικά είναι μεγαλύτερα από τις
ζημίες, πρέπει να τεθεί μεγαλύτερο επιτόκιο χρέωσης, λόγω του κινδύνου που κρύβει η συχνότητα
ζημιών".
Αν και η τήρηση ιστορίας είναι απλή, θέτει ένα σημαντικό πρόβλημα, της συν τω χρόνω
διόγκωσης της περιεχόμενης πληροφορίας. Επομένως, σημαντικό είναι να αναπτυχθούν
μηχανισμοί απλοποίησης της Ιστορίας, με κριτήρια κατ' αρχήν απόρριψης της πλεονάζουσας
πληροφορίας και αν αυτό δεν είναι αρκετό, στη συνέχεια, επιλογής της σημαντικότερης,
μειώνοντας έτσι το μέγεθος των σχετικών λιστών, άρα και του υπολογιστικού φόρτου
επεξεργασίας τους [Γυφ+91].

Συμπεριφορά εξαρτημένη από την πρόβλεψη εξέλιξης κάποιας κατάστασης
Σε έναν υπολογιστικά υλοποιημένο "κόσμο", είναι δυνατό να χρειαστεί να εξετάσουμε τις
ενδεχόμενες εξελίξεις κάποιας κατάστασης Κ. Αυτό παρουσιάζει ιδιαίτερη βαρύτητα στην
περίπτωση όπου ο υλοποιημένος κόσμος αποτελεί μια προσομοίωση για ένα τμήμα του
πραγματικού κόσμου (πρότυπος κόσμος) και η θεωρούμενη κατάσταση Κ αντιστοιχεί σε ένα
σύνολο από συνθήκες του πραγματικού κόσμου. Τότε, η μελέτη των ενδεχομένων εξελίξεων της
Κ οδηγεί σε πρόβλεψη του τί θα συμβεί στον πρότυπο κόσμο.
Ακόμα ισχυρότερα, ο εντοπισμός τού από ποια άλλα στοιχεία εξαρτώνται τα στοιχεία της
κατάστασης Κ και πώς, οδηγεί κατ' αναλογία σε ευχέρεια επίδρασης στις εξελίξεις του πρότυπου
κόσμου, με επίδραση στις κατάλληλες μεταβλητές - παραμέτρους του πραγματικού συστήματος
ή/και την ενεργοποίηση / απενεργοποίηση διαδικασιών (πρόδραση) [Αναγ+92].
Νοήμονες μηχανισμοί είναι δυνατό να ωθήσουν πιο μακριά την πρόβλεψη λαβαίνοντας υπ' όψη
πιθανοθεωρητικές ή ασαφείς εκτιμήσεις, ομοιότητες της παρούσας κατάστασης με καταστάσεις
του παρελθόντος και πού οδήγησαν, σε βαρύτητα γεγονότων, κά.
Πολύ σημαντικό είναι οτι μπορούμε να εκμεταλλευτούμε για τέτοιες προβλέψεις την εγγενή
δυνατότητα της Lisp για επί μέρους, παράπλευρες εκτελέσεις συναρτήσεων: με δοκιμαστικές
εκτελέσεις ("what if") μπορούμε να δούμε τα αποτελέσματα κάποιων επεμβάσεων πάνω σε τιμές
μεταβλητών ή στην επιλογή των μεθόδων επεξεργασίας του χώρου, χωρίς να καταστρέψουμε την
κύρια λειτουργικότητα.
Τα αποτελέσματα που λαβαίνουμε με τέτοιες δοκιμές μπορούν να αξιοποιηθούν ανάλογα προς
την κατεύθυνση έγκαιρης επέμβασης στις πραγματικές συνθήκες, είτε "για να αποφύγουμε να
προκληθεί κάτι" είτε "για να προκαλέσουμε κάτι".

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

για την εγκυρότητα των αποφάσεών μας.
Τέτοιες δοκιμές είναι δυνατό να γίνουν με αλλαγές: i) σε επίπεδο τιμών παραμέτρων, ii) σε
επίπεδο οντοτήτων (επαναπροσδιορισμό συμβόλων, εκφράσεων), iii) σε επίπεδο εφαρμογών, με
επιλογή διαφορετικών ορισμάτων, και όλα αυτά στο επίπεδο συγκεκριμενοποίησης ή αφαίρεσης
που επιθυμούμε.

5.5 Ασκήσεις
1. Να σχηματίσετε συνάρτηση Κ που να παράγει όλες τις γραμματικά και νοηματικά αποδεκτές
φράσεις του παραδείγματος 5.1.2.(2), όπου κριτήριο αποδοχής να είναι το νοηματικό
ταίριαγμα των strings "έχει " και "είναι " με τα αντίστοιχα αντικείμενα.
2. Έστω οτι κάποια συνάρτηση G δέχεται προαιρετικά ορίσματα τύπου λίστας, και δημιουργεί
και επιστρέφει σύμβολο - μεταβλητή με τιμή την απλοποιημένη σε σύνολο λίστα - ένωση των
ορισμάτων της. Να γράψετε αναδρομική συνάρτηση F που να δέχεται ως όρισμά της την
(οποιαδήποτε τέτοιας μορφής) G και μια σειρά από λίστες, και να δίνει ως έξοδο τη λίστα των
συμβόλων που παράγονται διαδοχικά από την G σε κάθε κύκλο της αναδρομής, όπου μετά από
κάθε κύκλο θα κόβεται το πρώτο από τα απομένοντα ορίσματα - λίστες, μέχρι να τελειώσουν.

3. Έστω ένα σύνολο (λίστα) S συναρτήσεων οι οποίες δέχονται δύο αριθμητικά ορίσματα και
δίνουν αποτέλεσμα αριθμό. Έστω και μια λίστα L από λίστες - δυάδες αριθμών. Να δώσετε
συνάρτηση Η που να εφαρμόζει, παραμετρικά, μια από τις συναρτήσεις αυτές στη λίστα των
δυάδων και να παράγει τη λίστα των αποτελεσμάτων. Να γραφεί συνάρτηση Μ που να
σαρώνει τη λίστα S εφαρμόζοντας την Η σε κάθε ένα στοιχείο της S για κάθε στοιχείο της L ,
και να επιστρέφει τη λίστα των λιστών των αποτελεσμάτων.
4. α) Να σχηματίσετε τελεστή που να δέχεται είσοδο πολυωνυμική συνάρτηση και να επιστρέφει
την παράγωγό της.
β) Να σχηματίσετε τελεστή που να δέχεται είσοδο πολυωνυμική συνάρτηση και να επιστρέφει
το ολοκλήρωμά της.
5. Να σχηματίσετε συνάρτηση REPEAT που να δέχεται δύο ορίσματα, το πρώτο φυσικό αριθμό
Ν και το δεύτερο οποιαδήποτε φόρμα, και να εκτελεί τη φόρμα Ν φορές.
6. α) Να ορίσετε συνάρτηση ΙΡ που να δέχεται δύο ορίσματα - διανύσματα ίσων διαστάσεων,
αριθμητικών συντεταγμένων, και να δίνει το εσωτερικό τους γινόμενο. Αντίστοιχα, συνάρτηση
ΕΡ που να δίνει το εξωτερικό γινόμενο.
β) Να ορίσετε συνάρτηση CP που να δέχεται Ν ορίσματα, διανύσματα ίσων διαστάσεων, και
να δίνει τον διδιάστατο πίνακα που προκύπτει θεωρώντας το καθένα διάνυσμα ως μια γραμμή
του πίνακα.
7.

α) Να σχηματίσετε τη LET–φόρμα ενός τόπου ο οποίος να περιέχει συνάρτηση F καθολικά
243

ορατή, που να βλέπει τις τοπικές μεταβλητές του τόπου, και να ονομάσετε αυτόν τον τόπο με
σύμβολο Α. Να δώσετε ένα παράδειγμα κλήσης της F εξωτερικά του τόπου και ένα εσωτερικά
του τόπου.
β) Το ίδιο, για συνάρτηση G αόρατη έξω από τον τόπο. Να δώσετε ένα παράδειγμα κλήσης
της G .
8. Να σχηματίσετε τόπο Α με τοπικές μεταβλητές b, c, d, και:

α) Να προσδιορίσετε τεχνική που να κάνει ορατές τις b, c, d έξω από τον τόπο, με τρόπο
που αν ζητηθούν οι τιμές τους με κλήσεις (το-πρώτο-του a) , (το-δεύτερο-του a) , (το-τρίτοτου a) , να επιστραφούν οι τελευταίες τιμές που έχουν πάρει οι μεταβλητές b, c, d από την
εκτέλεση μιας συνάρτησης που προσδιορίζει τον τόπο Α, αλλά να μην είναι δυνατό στον
χρήστη να μεταβάλλει αυτές τις τιμές.

β) Να προσδιορίσετε τεχνική που να κάνει ορατές μόνο τις b, c έξω από τον τόπο A , και όχι
την d .

γ) Να προσδιορίσετε τεχνική που να καθιστά ορατές τις b, c, d μέσα από έναν άλλο τόπο P
αλλά όχι καθολικά.

9. Να σχηματίσετε δύο τόπους A και B, που να περιέχουν τοπικές μεταβλητές με ονόματα x και
y αλλά με διαφορετικές τιμές σε κάθε τόπο, και συνάρτηση F τοπικά ορισμένη στον καθένα
τόπο αλλά καθολικά ορατή, με το αυτό όνομα και τον αυτή φόρμα ορισμού και στους δύο
τόπους, η οποία να κάνει χρήση των x, y. Με βάση αυτά:

α) Να ορίσετε συνάρτηση G που να κάνει χρήση των A, B, F και ανάλογα με τον τύπο του
ορίσματος που θα δοθεί κατά την κλήση, να καλεί την F επιλεκτικά από τον τόπο A ή τον B.
(υπόδειξη: αξιοποιείστε το οτι η καθολική μεταβλητή F καθορίζεται από τον τελευταίο
υπολογισμό των A ή B)

β) Να προσδιορίσετε καθολικές μεταβλητές x, y, και συναρτήσεις D και Ε εσωτερικές
αντίστοιχα των τόπων A και B, όπου η D να καλεί την E και η E να καλεί την D , και οι
συναρτήσεις αυτές να κάνουν χρήση των τοπικών αντίστοιχων μεταβλητών x, y. Να
προσδιορίσετε καθολική συνάρτηση Κ η οποία να χρησιμοποιεί τις D και E καθώς και τις
καθολικές x, y.
10. α) Να ορίσετε τύπο divi , υποτύπο του integer , που να χαρακτηρίζει φυσικούς αριθμούς
διαιρετούς από 2 και 3 αλλά όχι από 10 .
β) Να ορίσετε τύπο vec , υποτύπο του vector , που να χαρακτηρίζει διανύσματα με
συντεταγμένες τύπου divi .
γ)
Να ορίσετε τύπο intrva που να χαρακτηρίζει κλειστά διαστήματα πραγματικών αριθμών
όπου το κάτω άκρο να είναι ακέραιος πολλαπλάσιος του 3 και το πάνω άκρο ακέραιος
διαιρετός με το 27.
δ)
Να ορίσετε τύπο intrvb που να χαρακτηρίζει κλειστά διαστήματα πραγματικών αριθμών
όπου το κάτω άκρο να είναι ακέραιος πολλαπλάσιος του 3 και το πάνω άκρο διαιρετό με το
κάτω άκρο.
244

11. Να ορίσετε συνάρτηση F που να δέχεται δύο πραγματικά ορίσματα a, b και να επιστρέφει τη
λίστα των ακεραίων τιμών στο διάστημα [a, b] (να δώσετε τις κατάλληλες συνθήκες).

245

6 Διαδικαστική Δόμηση Προγράμματος
Στο κεφάλαιο αυτό θα δούμε τις κατευθύνσεις σύνθεσης προγράμματος και αντίστοιχες τεχνικές.
Είναι ιδιαίτερα χρήσιμο να δούμε πως είναι εύκολο να οργανώσουμε τοπικά τον προγραμματισμό
με διαδικαστικό τρόπο χωρίς να εγκαταλείψουμε τη συναρτησιακή δόμηση, και αντίθετα, αν
επιλέξουμε τη διαδικαστική δόμηση, τοπικά να εκμεταλλευόμαστε τις δυνατότητες
συναρτησιακής σύνθεσης.
Θα διακρίνουμε δύο μορφές συναρτησιακής δόμησης, την επιτακτική και τη δηλωτική. Θα δούμε
ειδικότερα πώς διατυπώνεται φορμαλιστικά η πρώτη περίπτωση μέσω ειδικών συναρτήσεων που
διατίθενται, και θα αναλύσουμε το ρόλο της αναδρομής και της επανάληψης σε κάθε τρόπο
δόμησης.
Στη συνέχεια θα μελετήσουμε τρόπους αξιοποίησης αυτών των κατευθύνσεων για μαζική
επεξεργασία οντοτήτων και. για "αλυσιδωτές συνθέσεις" όπου κρίκοι της αλυσίδας μπορούν να
είναι συναρτησιακές εκφράσεις ή/και εκφράσεις εφαρμογής.

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

6.1.1 Η έννοια της συνάρτησης: ως κανόνας και ως διαδικασία

Κάθε συνάρτηση είναι μια υπολογιστική οντότητα που μπορούμε να τη δούμε σαν
μετασχηματισμό της εισόδου ή σαν διαδικασία βάσει της οποίας υπολογίζεται η έξοδος
συναρτήσει της εισόδου. Στην πρώτη περίπτωση βλέπουμε μια σχέση, στη δεύτερη ένα
μηχανισμό παραγωγής. Εφαρμογή συνάρτησης κατά το πρώτο νόημα, είναι μια παραγωγή
βάσει κανόνα, ενώ κατά το δεύτερο είναι μια διεργασία που επιτελείται βάσει του μηχανισμού
τον οποίο υλοποιεί η συνάρτηση. Σε κάθε περίπτωση, η συνάρτηση είναι ένας αλγόριθμος, και η
εφαρμογή της είναι η εκτέλεσή του, αλλά παίρνει διαφορετικό νόημα ανάλογα με το πώς
βλέπουμε τη χρήση της συνάρτησης. Εξ αιτίας από αυτό, απαιτούνται διαφορετικοί τρόποι
έκφρασης σε κάθε περίπτωση και διαφορετικές συναρτήσεις - εργαλεία.
246

Δήλωση έναντι επιταγής

Η διάκριση μεταξύ δηλωτικής (declarative) και επιτακτικής (imperative) έκφρασης στον ΣΠ
χωρίς να έχει κάποια ιδιαίτερη φορμαλιστική διαφοροποίηση, έγκειται στο ποιες συναρτήσεις
χρησιμοποιούμε και πώς. Διότι, αν υλοποιούμε τον αλγόριθμο με περιγραφή συνθέσεων,
αξιοποιώντας εξόδους - εισόδους συναρτήσεων, διαδοχικά ή ανεξάρτητα, τότε η έκφραση παίρνει
"περισσότερο δηλωτική" έννοια, ενώ αντίθετα, αν στην υλοποίηση χρησιμοποιούμε τις
συναρτήσεις ως εντολές που "προστάζουν" τα βήματα που οδηγούν στο στόχο, τότε η έκφραση
"παίρνει περισσότερο επιτακτική έννοια". Το "περισσότερο" σημαίνει οτι την ίδια έκφραση μπορεί
να την εκτιμήσει κανείς με τη μία ή την άλλη άποψη, τεχνικά ή σημασιολογικά, αλλά βαραίνει η
μία άποψη.
Αυτή η εκτίμηση είναι στενά δεμένη με το context χρήσης και τους στόχους που πρόκειται να
εξυπηρετηθούν, και κυρίως αν ο τελικός χρήστης θα χρησιμοποιεί συνθετικά τις συναρτήσεις, ή
μόνον λειτουργικά.

Από σημασιολογική άποψη, δηλωτική είναι μια φόρμα που περιγράφει μια κατάσταση, δηλ.
μπορεί να διαβαστεί «είναι έτσι», και επιτακτική είναι μια φόρμα που περιγράφει τον τρόπο
εκτέλεσης, δηλ μπορεί να διαβαστεί «κάνε αυτό». Κατά κανόνα, διακρίνουμε μέρη στο
πρόγραμμα, ακόμα και μέσα σε μια συνάρτηση, όπου:
– Δηλωτικό μέρος είναι ένα σύνολο από δηλωτικές φόρμες, με εξάρτηση ή όχι από την διαδοχή
τους. Οι συνηθέστερες δηλωτικές φόρμες συντίθενται με τις συναρτήσεις ορισμού: SETQ,
DEFUN, LAMBDA, PROG, LET, κά. (αλλά μπορούμε να τις δούμε και διαδικαστικά, πχ. τη
φόρμα (setq a 3) είτε ως "το a έχει τιμή 3" είτε ως "κάνε το a να πάρει τιμή 3").
– Επιτακτικό μέρος είναι ένα σύνολο από φόρμες οι οποίες έχουν εν γένει εξάρτηση διαδοχής.
Συνήθεις συναρτήσεις για την επιτακτική διαμόρφωση είναι οι SETF, BLOCK, PROGN,
PROG1, LOOP, DO, DO*, DOTIMES, κά. (που μπορούμε να τις δούμε και δηλωτικά).
Η διαφοροποίηση είναι σημαντική κυρίως σε δύο καταστάσεις:
i) Όταν υπεισέρχεται χρήση καθυστέρησης: σε επιτακτική έκφραση σημαίνει καθαρά "ο
υπολογισμός να γίνει τότε", με σαφή προκαθορισμό των συνθηκών και της τεχνικής που θα
προκαλέσει τον υπολογισμό, δηλαδή πρόκειται για διαχείριση της διαδοχής των διεργασιών∙ σε
δηλωτική έκφραση, αφορά "ανέβασμα σε ανώτερο αφαιρετικό επίπεδο" όπου ανεξαρτητοποιείται
ο προσδιορισμός υπολογισμού από την εκτέλεσή του, όπου η άρση της καθυστέρησης έχει είτε
δηλωτική έννοια εξειδίκευσης είτε επιτακτική έννοια ένταξης του υπολογισμού σε κάποια
διαδικασία.
ii) Όταν στόχος είναι η δημιουργία εκφράσεων προς περαιτέρω συνθετική χρήση, τότε οι
εκφράσεις παίρνουν δηλωτική σημασία. Αντίθετα, όταν στόχος είναι ή άμεση τελική εφαρμογή,
τότε οι εκφράσεις έχουν επιτακτική σημασία. Για το λόγο αυτό, οι πρώτες είναι κατά κανόνα
απλούστερες και με γενικότερο νόημα χρήσης, ενώ οι δεύτερες είναι συνθετότερες και με
συγκεκριμένο τρόπο χρήσης.
Η διαδοχή δηλώσεων – επιταγών στη Lisp δεν οφείλει να υπακούει σε κάποιους τυπικούς νόμους
(τυπική υποχρέωση αναγραφής, θέση, σύνταξη…) όπως σε άλλες γλώσσες, λαβαίνοντας υπ' όψη
μόνο τις εξαρτήσεις των εκφράσεων: όταν μια δηλωτική φόρμα καθορίζει τις συνθήκες κάτω
247

από τις οποίες θα γίνει η εκτέλεση μιας επιτακτικής φόρμας, η δηλωτική πρέπει προφανώς να
προηγείται της επιτακτικής, ενώ όταν μια επιτακτική φόρμα παράγει δεδομένα που χρησιμοποιεί
συνθετικά μια δηλωτική φόρμα, είναι αυτονόητο πρέπει η πρώτη να προηγείται της δηλωτικής.
Το "προηγείται" αναφέρεται στη διαδοχή κλήσης των αντίστοιχων φορμών στην φάση του
αντίστοιχου υπολογισμού, διαδοχή που δεν συμπίπτει πάντοτε με τη διαδοχή γραφής τους στον
κώδικα, ούτε είναι σε κάθε χρήση η ίδια (γι' αυτό είναι σημαντικό να ξεκαθαρίζουμε αν ο
compiler που χρησιμοποιούμε δέχεται δήλωση εξάρτησης οντοτήτων ανεξάρτητα της διαδοχής
γραφής τους ή όχι, όπως συχνά έχουμε προαναφέρει).
Αυτό που αποτελεί κανόνα στον ΣΠ, είναι οτι οι επιτακτικές συνθέσεις δυσχεραίνουν πολύ την
περαιτέρω συναρτησιακή διαχείρισή τους, όχι διότι δεν είναι και αυτές συναρτησιακές
συνθέσεις αλλά διότι έχουν συνταχθεί για να λειτουργήσουν για ειδικό σκοπό και είναι δύσκολα
προσαρμοζόμενες.

Η έννοια της αποτελεσματικότητας και της ορθότητας ενός συναρτησιακού
προγράμματος
Αποτελεσματικότητα αλγόριθμου είναι το αντίστροφο της πολυπλοκότητάς του, που είναι το
πλήθος των υπολογιστικών βημάτων που εκτελεί σε σχέση με το μέγεθος των τιμών εισόδου.
Αποτελεσματικότερος αλγόριθμος σημαίνει ταχύτερη εκτέλεση για τις ίδιες τιμές εισόδου (τα ίδια
ορίσματα εφαρμογής της συνάρτησης). Ένα "πραγματικό" νόημα που δίνεται στην
αποτελεσματικότητα, είναι η επιλογή ορισμένων τιμών εισόδου, από αυτές που είναι δυνατό να
δοθούν, τέτοιες που να έχουν σημασία χρήσης (με αρκετές διαβαθμίσεις σπουδαιότητας).

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

Επιδίωξη καλύτερης αποτελεσματικότητας
Μια "κλασική" μεθόδευση μείωσης της πολυπλοκότητας είναι η εφαρμογή της τεχνικής "διαίρει
και βασίλευε", που είναι ιδιαίτερα εύκολη και αποδοτική όταν ακολουθούμε συναρτησιακή
ανάπτυξη, ακριβώς λόγω της ευκολίας σύνθεσης συναρτήσεων από τμήματα, ήδη υφιστάμενα ή
248

που πρόκειται να συνθέσουμε. Κατά τη μεθόδευση αυτή, περιγράφουμε το συνολικό πρόβλημα
σαν ένα "and-or" δένδρο, και διασπούμε την επίλυση σε αντίστοιχα τμήματα, που είναι
ανεξάρτητα κλαδιά από συναρτήσεις και εφαρμογές τους. Όταν φθάσει η διάσπαση να δίνει
επαρκώς μικρά τμήματα κώδικα για την ανάπτυξη και την κατανόηση της λειτουργικότητάς τους,
διαθέτουμε τα δομικά υλικά για το "κτίσιμο" της συνολικής λύσης, που δεν είναι τίποτα άλλο από
μια συνάρτηση που "συμμαζεύει" τα τμήματα και ιεραρχεί τις εκτελέσεις τους.
Το κέρδος από την τεχνική αυτή είναι πολλαπλό:
i) είναι δυνατό να έχουμε αθροιστικό πλήθος βημάτων των αλγορίθμων - μερών μικρότερης
τάξης από αυτό που θα είχε ένας συνολικός αλγόριθμος
ii) ανάγουμε την ανάγκη εκτίμησης και μείωσης της πολυπλοκότητας στο μέρος εκείνο που
πρόκειται κατά κύριο λόγο να χρησιμοποιήσουμε
iii) η τοποθέτηση ορισμένων υπολογισμών σε χρονικές θέσεις όπου η διάρκεια της εκτέλεσης
δεν ενοχλεί, είναι ευκολότερα ελεγχόμενη
iv) οδηγεί σε αποφυγή επανάληψης των ιδίων υπολογισμών
v) μπορούμε να ανάγουμε την ανάπτυξη κάποιων μερών σε απώτερες φάσεις
vi) έχουμε συγκριτικά πολύ μεγάλη ευκολία για επιτόπια, δυναμική σύνθεση αυτού που
επιθυμούμε, αναδιαμορφώνοντας αν θέλουμε την άποψή μας, με επαναχρησιμοποίηση των
μερών που έχουμε ήδη αναπτύξει.
Το τελευταίο χαρακτηριστικό είναι αυτό που οδηγεί στην αντίληψη της συνθετικής χρήσης
προγράμματος από τον τελικό χρήστη

Κάποιοι πρακτικοί κανόνες αποτελεσματικότητας, που πρέπει να ακολουθούνται κατά την
οργάνωση ενός προγράμματος, είναι:
– Σάρωση "στο ίδιο επίπεδο": πχ. είναι ταχύτερο να είναι τα στοιχεία σε επίπεδη λίστα παρά
σε δένδρο. τιμή
Το εισόδου
πετυχαίνουμε
είτε με τήρηση των δένδρων σε μορφή επιπέδων λιστών και
1
εφαρμογή μιας συνάρτησης που να διαμορφώνει και να επιστέφει τη ζητούμενη δομή βάσει της
θέσης των στοιχείων, όταν χρειαστεί, (όπως γίνεται με τη λίστα ιδιοτήτων συμβόλου) είτε με
. . . ενός αντιγράφου του δένδρου σε λίστα, σε κάποια στιγμή ευχέρειας χρόνου της
επιπεδοποίηση
εκτέλεσης. Η πρώτη λύση εξυπηρετεί περισσότερο όταν πρόκειται για εξελισσόμενες δομές όπου
γίνονται συχνές παρεμβάσεις, ενώ η δεύτερη όταν πρόκειται για μεγάλες και σχετικά
σταθεροποιημένες δομές που χρησιμοποιούνται επανειλημμένα για να δώσουν πληροφορία.
– Γενικότερα, όταν πρόκειται να γίνει πολλαπλή διάβαση σε μια αλληλουχία συνδέσεων, είναι
σκόπιμο να γίνεται με τις υπολογιστικά θεμελιωδέστερες συναρτήσεις και όχι με τις νοηματικά
απλούστερες, χωρίς αυτό να είναι πανάκεια. Για το λόγο αυτό, πολλοί compilers συνοδεύονται με
τον πηγαίο κώδικα των συναρτήσεών τους ώστε να έχει τη δυνατότητα ο χρήστης της γλώσσας να
παρακολουθήσει τη βαθύτερη λειτουργία τους, και ενδεχομένως να την επαναπροσδιορίσει με
τιμή εισόδου 2
τρόπο που να εξυπηρετεί καλύτερα τους στόχους του.
– Δήλωση του τύπου εισόδου των συναρτήσεων όταν είναι απόλυτα βέβαιο πως δεν θα δοθεί
διαφορετικός, και αν η είσοδος κινείται σε κάποιο εύρος τύπων, εξειδίκευση του τύπου εισόδου
249

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

6.1.2 Διαδικαστική ή δηλωτική προσέγγιση στα πλαίσια του ΣΠ

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

Με δεδομένο οτι ένα συναρτησιακό πρόγραμμα μπορεί να αποσκοπεί σε συνθετική ή εξελικτική
χρήση, είναι ανάγκη να εντάξουμε στη διάκριση "επιτακτικό - δηλωτικό" όχι μόνο τον τρόπο που
ο κατασκευαστής "βλέπει" τη σύνθεση, αλλά και τον τρόπο με τον οποίο αντιμετωπίζει ο χρήστης
το πρόγραμμα και το χώρο εφαρμογής. Επομένως πρέπει να διακρίνουμε το νόημα "επιτακτικός
αλγόριθμος", δηλαδή "σειρά από βήματα που δίνουν διαταγές υπολογισμού", και αφορά τον τρόπο
λειτουργίας της εφαρμογής, από το νόημα "επιτακτικός προσδιορισμός αλγόριθμου", που σημαίνει
"βήματα κατασκευής αλγόριθμου τα οποία καθορίζονται με επιτακτική συλλογιστική" (δηλ. του
τύπου: "να κάνω το Α, μετά το Β, να τα χρησιμοποιήσω έτσι, κοκ."), και αφορά τη σκέψη του
250

προγραμματιστή. Το δεύτερο είναι σημαντικό για την οργάνωση του προγράμματος, διότι
αποδίδει τρόπο υλοποίησης μιας "προς τα εμπρός" συλλογιστικής, σε αντίθεση με τον δηλωτικό
προσδιορισμό σχέσεων που σταδιακά καταλήγουν σε αλγόριθμο.

Η Lisp διαθέτει ένα σύνολο συναρτήσεων ειδικής σύνταξης ή/και εφαρμογής ή/και δράσης, που
θα δούμε παρακάτω, οι οποίες έχουν σκοπό την υποστήριξη της οργάνωσης του προγράμματος ή
μερών του, συσχετίζοντας και ομαδοποιώντας λειτουργίες.
Μπορούμε να δούμε τη χρήση από τέτοιες συναρτήσεις διπλά:
– Από την πλευρά του τρόπου σκέψης (επιτακτικού ή όχι) η χρήση από τέτοιες συναρτήσεις
διευκολύνει τον χρήστη της γλώσσας να οργανώσει τις σκέψεις του με ένα σειριακό τρόπο, που
είναι απλούστερος διότι μπορεί να θέτει "οροθέσια", δηλ. καταστάσεις αναφοράς όπου η συνέχεια
της σκέψης να μπορεί να διακοπεί και να επανέλθει (τέτοιος τρόπος είναι η χρήση της setq πάνω
σε λ-μεταβλητές). Είναι όμως περιοριστική σε ότι αφορά τις συνθετικές δυνατότητες.
– Από την πλευρά της σύνθεσης αλγόριθμου (επιτακτικού ή όχι) η χρήση τους, παρέχει μεγάλη
ευκολία στην αποτύπωση των δράσεων που θέλουμε να εκτελεστούν. Αυτό εξυπηρετεί σημαντικά
στην οργάνωση αλγορίθμων - "μαύρων κουτιών" που απαλλάσσουν από την ανάγκη να είναι ο
χρήστης τους διαρκώς ενήμερος από το κάθε τί μέσα στον υπολογισμό: τμήματα οργανώνονται
για να εκτελούνται βλέποντας μόνο το τί κάνουν και όχι το πώς. Διευκολύνουν επομένως την
οργάνωση του προγράμματος σε ένα ανώτερο επίπεδο σύνθεσης.

Μια κατηγορία τέτοιων συναρτήσεων είναι αυτές που ομαδοποιούν το σύνολο από φόρμες που
δίνουμε εμείς προς εκτέλεση, έτσι ώστε να αποτελέσουν ενιαίο αλγόριθμο. Εξυπηρετούν είτε για
την οργάνωση μιας φόρμας για απ' ευθείας κλήση, είτε για την χρήση έτοιμου αλγόριθμου στα
πλαίσια σύνθεσης κάποιας συνάρτησης.

Μια άλλη κατηγορία είναι αυτές που αντιστοιχούν σε ένα σύνολο από φόρμες υπολογισμού, και
οι οποίες συντίθενται σε ορισμένη διαδοχή, αντί να εφαρμόζουμε μία- μία χωριστά. Δηλαδή
καθορίζουν απ' ευθείας ένα πολυσύνθετο έργο.

6.2 Διαδικαστικές τεχνικές προγραμματισμού
Στην ενότητα αυτή περιγράφουμε τις βασικές συναρτήσεις για δόμηση προγράμματος με
επιτακτικό τρόπο. Ουσιαστικό είναι, οτι μερικές από αυτές τις συναρτήσεις μπορούν να
αξιοποιηθούν και με δηλωτικό νόημα, και αυτό τους δίνει πλατύτερη θέση στον ΣΠ απ' όσο θα
είχε μια αυστηρά επιτακτική χρήση.

6.2.1 Διαδοχή και κιβωτισμός διαδικασιών

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

δυνατότητα διακοπής και εξόδου από την προκαθορισμένη διαδοχή των εκτελέσεων, οριστικά ή
κατά επίπεδα υπολογισμού, και με προκαθορισμένο το "πότε θα γίνει η διακοπή και τί θα γίνει
τότε". Ακόμα, η διαδοχή αυτή, να έχει τη δυνατότητα να πάρει τη μορφή δενδροειδούς
κιβωτισμού τμημάτων, με ευχέρεια εξόδου από τμήμα προς τμήμα ανάλογα με τις συνθήκες που
δημιουργούνται. Σ' αυτό εξυπηρετεί η συνάρτηση BLOCK :

block : <label>  <term> n → <term>
Είναι ειδικής σύνταξης συνάρτηση με την οποία προσδιορίζεται ένα αυτόνομο τμήμα κώδικα ως
αλγόριθμος προς εκτέλεση. Το πρώτο όρισμά της είναι μια ετικέτα την οποία μπορούμε να
χρησιμοποιήσουμε για παραπομπή, και τα επόμενα προσδιορίζουν τα (διαδοχικά) βήματα του
αλγόριθμου, σε μορφή από ολοκληρωμένες φόρμες υπολογισμού. Αν κάποια βήματα έχουν
κάποια αλληλεξάρτηση, επιδρούν σύμφωνα με τη διαδοχή τους∙ αν κάποια ανεξάρτητα, απλώς
εκτελούνται "όταν έρθει η σειρά τους". Αν απαιτείται, μπορεί να δοθεί επεξηγηματική έξοδος από
το BLOCK κάτω από συνθήκη.
Η απλούστερη σύνταξη της BLOCK είναι:
(block <ετικέτα> <σώμα> )
δηλαδή στην πράξη:
(block <ετικέτα> <φόρμα1> <φόρμα2> …<φόρμαk> )
όπου με το πέρας της εκτέλεσης επιστρέφει την τιμή του υπολογισμού της τελευταίας φόρμας στο
σώμα.

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

H επεξηγηματική έξοδος "ενδιάμεσα" γίνεται με χρήση της βοηθητικής συνάρτησης RETURNFROM :
(block . . . (block … (return-from <ετικέτα> < αποτέλεσμα> ) . . .)
όπου το πρώτο όρισμα της RETURN-FROM , η <ετικέτα>, δηλώνει το ποιο block τερματίζεται. Η
ετικέτα αυτή μπορεί να είναι του block μέσα στο οποίο είναι η κλήση RETURN-FROM , ή
ανωτέρου του (δηλ. εξωτερικού). To δεύτερο όρισμα του RETURN-FROM είναι το
επιστρεφόμενο από το block αποτέλεσμα, και δίνεται στη θέση του block το οποίο τερματίζεται
(δηλ. επιστρέφεται από τη συνάρτηση BLOCK προς το αμέσως ανώτερο επίπεδο, που μπορεί να
είναι άλλο block).
Προφανώς, η χρήση RETURN-FROM έχει νόημα όταν γίνεται μέσα σε conditional, όπως:
(block <ετικέτα>
<σώμαΑ>
(cond
(<έλεγχος1> (return-from <ετικέτα> <αποτέλεσμα-1> ))
...
(<έλεγχοςm> (return-from <ετικέτα> <αποτέλεσμα-m> )) )
252

<σώμαΒ> )
όπου τα (προαιρετικά) σώματα μπορούν να περιέχουν και άλλες επιστροφές υπό συνθήκες, άλλα
blocks, κοκ. Το <σώμαΒ> εκτελείται μόνο στην περίπτωση όπου κανένας έλεγχος δεν έχει
πετύχει στο conditional που προηγήθηκε.

Tο επιστρεφόμενο αποτέλεσμα μπορεί να είναι σταθερά, μεταβλητή προς υπολογισμό, ή κλήση
συνάρτησης προς υπολογισμό. Η επιτακτική μορφή που συνήθως έχει το block εξυπηρετείται με
απλούστερο τρόπο αν ως <αποτέλεσμα-...> θέσουμε μια μεταβλητή που ήδη έχει υπολογιστεί
στο σώμα που προηγήθηκε, ή μια σταθερά (προσοχή: μερικοί compilers υποστηρίζουν μόνο
επιστροφή σταθεράς).
Έξοδο από block προκαλεί είτε η εκτέλεση ( RETURN-FROM...) από αυτό (αναφερόμενη μέσα
στο ίδιο ή σε άλλο εσωτερικό του), είτε η ολοκλήρωση των υπολογισμών του block. Στην
τελευταία περίπτωση, το block επιστέφει ως τιμή το αποτέλεσμα υπολογισμού της τελευταίας
φόρμας.

Μπορούμε να προσδιορίσουμε blocks σε δενδροειδή κιβωτισμό. Κιβωτισμένα blocks (που
κατεβαίνουν επίπεδα) ή διαδοχικά blocks (στο ίδιο επίπεδο) μέσα σε block, ή συνδυασμός,
μπορούν να δοθούν σε οποιοδήποτε βάθος και πλάτος. Η χρήση της RETURN-FROM αφορά είτε
το ίδιο το block μέσα στο οποίο ευρίσκεται, είτε κάποιο από τα blocks ανωτέρου επιπέδου (δηλ.
δεν μπορεί να παραπέμψει "πιο μέσα" ούτε "δίπλα").
Επειδή η BLOCK δεν ονοματίζει τον αλγόριθμο, για να έχουμε ονομασμένο αλγόριθμο που
προσδιορίζεται ως block, πρέπει να τον "βαφτίσουμε" με όνομα συνάρτησης, πχ:
(defun b1 ( ) (block kk (setq v 3) (* v v v))))

οπότε η κλήση του αλγόριθμου θα γίνει με το όνομα που δώσαμε στη συνάρτηση:
(b1)

Μπορούμε επίσης να "βαφτίσουμε" τον αλγόριθμο με όνομα μεταβλητής:
(setq v1 '(block aa (+ 2 3)))
v1
→ (block aa (+ 2 3))
(eval v1)
→ 5

Συνοπτικά έχουμε οτι:
– Η συνάρτηση BLOCK προσδιορίζει έναν αλγόριθμο. Το όνομα του block αποτελεί ετικέτα
προς παραπομπή ή άλλη αναφορά.
– Δεν παρέχεται δυνατότητα προσδιορισμού τοπικών μεταβλητών.
– Κάθε χρήση ονόματος μεταβλητής μέσα στο block αν δεν ορίζεται αλλιώς, αναφέρεται σε
εξωτερική του block μεταβλητή (είτε καθολική του προγράμματος είτε τοπική του ανωτέρου
επιπέδου).
– Μπορούμε να προσδιορίσουμε κιβωτισμένα blocks και να κατευθύνουμε διαδικαστικά την
εκτέλεση, επιδρώντας "από μέσα προς τα έξω".
– Ένα block μπορεί να ονομαστεί με τον κοινό τρόπο ονομασίας εφαρμογών, είτε με όνομα
συνάρτησης (να δοθεί ως σώμα της συνάρτησης) είτε με όνομα μεταβλητής (να δοθεί το block
253

ως τιμή σε σύμβολο, ως "quoted" έκφραση).
Παράδειγμα
Στο παρακάτω block χρησιμοποιούνται διάφοροι διαδικαστικοί προσδιορισμοί τιμών και
επιστροφές από εσωτερικά blocks. Η εκτέλεση ζητά 5 αριθμητικές τιμές εισόδου:
(block myblock1
(print "give 5 numbers: ")
(setq var1 (*
(block myblock11
(setq var11 (+ (read) (read) (read)))
(cond
( (evenp var11)
(return-from myblock11 var11))
(t (return-from myblock11 (1- var11)))))
(block myblock12
(setq var12 (* (read) (read)))
(cond
( (> var12 100)
(return-from myblock12 (rem var12 100)))
(t (return-from myblock12 var12)) ) )))
(cond
( (and (> var1 50) (< var1 200))
(return-from myblock1
(print var1)) )
(t (return-from myblock1
(print nil)) ) ) )

6.2.2 Σύνθεση διαδοχής εφαρμογών
Εκτός της BLOCK διατίθενται και άλλες συναρτήσεις ομαδοποίησης, που καλύπτουν
συνθετότερες ανάγκες, όπως να καθορίζεται συναρτησιακά η έξοδος, η συνολική διαδικασία να
έχει όνομα, να περιέχει λ-μεταβλητές, να ορίζει τόπο, κά.

Ομαδοποίηση από φόρμες, προς διαδοχική εφαρμογή

Συχνά θέλουμε, στα πλαίσια κάποιου αλγόριθμου, να εκτελεστούν διαδοχικά ορισμένες φόρμες,
σαν να είναι το σώμα μίας συνάρτησης η οποία επιστρέφει το αποτέλεσμα της εκτέλεσης της
τελευταίας φόρμας. Δηλαδή κατά κάποιο τρόπο, να χρειάζεται να “συμμαζέψουμε” σε μορφή
διαδικασίας μια σειρά από διαδοχικές εφαρμογές, με αλληλουχία εξάρτησης ή ανεξάρτητες, έτσι
ώστε να αποτελέσουν τα βήματα ενός αλγόριθμου ο οποίος δεν είναι αναγκαίο να έχει όνομα.
Ένας απλός τρόπος είναι να χρησιμοποιήσουμε μια από τις συναρτήσεις PROGN , PROG1 ,
PROG2 , οι οποίες διαφέρουν μόνο στο επιστρεφόμενο αποτέλεσμα:
progn : <term>n → <term>
Συνάρτηση ειδικής σύνταξης, που καθορίζει "διαδοχή από εφαρμογές". Πρόκειται για συνάρτηση
με επιτακτική κυρίως σημασία, αλλά είναι χρήσιμη και στη δηλωτική περίπτωση όπου θέλουμε
254

να αντιπροσωπευθεί η διαδοχή των εκτελέσεων από μεταβλητή. Η σύνταξη είναι:
(progn <σώμα> )
δηλαδή στην πράξη:
(progn <φόρμα1> <φόρμα2> . . . <φόρμαk> )
Ως συνάρτηση, επιστρέφει το αποτέλεσμα της εκτέλεσης της φόρμαk . Πχ,
(progn (setq a 2 b 3) (setq c (+ a b)) (– c 1))

επιστρέφει 4 , έχοντας ορίσει καθολικά το a να έχει τιμή 2 , το b να έχει τιμή 3 , το c να έχει τιμή
5.

Αν και η χρήση της PROGN μοιάζει πολύ με της BLOCK, εν τούτοις χρησιμοποιούνται
διαφορετικά: η BLOCK εξυπηρετεί στην περίπτωση κιβωτισμένων διαδικασιών με διαδικαστικά
προσδιορισμένη επιστροφή κατά περίπτωση, ενώ η PROGN λειτουργεί συναρτησιακά,
επιστρέφοντας το αποτέλεσμα της τελευταίας φόρμας της.
Παρεμφερής συνάρτηση με την PROGN είναι η PROG1 :

prog1 : <term>n → <term>
H συνάρτηση PROG1 είναι ίδιας σύνταξης και παρόμοιας λειτουργίας με την PROGN , με τη
διαφορά οτι στο τέλος επιστρέφει το αποτέλεσμα της εκτέλεσης της φόρμα1
(prog1 <φόρμα1> <φόρμα2> . . . <φόρμαk> )
Βλέπουμε οτι η PROG1 διαφέρει από την PROGN στο οτι το επιστρεφόμενο αποτέλεσμα δεν
επηρεάζεται από την εκτέλεση των επομένων φορμών <φόρμα2> . . . <φόρμαk> . Επομένως
μπορούμε να χρησιμοποιήσουμε την PROG1 με τρόπο ώστε με τη <φόρμα1> να καθορίζεται η
φόρμα που θέλουμε να δράσει συναρτησιακά (μέσα στη γενικότερη σύνθεση όπου εντάσσεται η
PROG1) και οι υπόλοιπες φόρμες <φόρμα2> . . . <φόρμαk> να αποτελούν τις επιθυμητές
παράπλευρες δράσεις, που θέλουμε να εκτελεστούν στη συγκεκριμένη φάση αλλά δεν θέλουμε
να επηρεάσουν το αποτέλεσμα.
Παρόμοια με την PROG1 είναι η PROG2 :
prog2 :

<term>n → <term>

Ίδιας σύνταξης με την PROG1 διαφέρει από αυτήν κατά το οτι δεν επιστρέφει το αποτέλεσμα
της εκτέλεσης της φόρμα1 αλλά της φόρμα2
(prog2 <φόρμα1> <φόρμα2> . . . <φόρμαk>)

Χρησιμεύει στην περίπτωση όπου η <φόρμα1> απλώς καθορίζει κάποιες αρχικές συνθήκες, και
οι κύριοι υπολογισμοί γίνονται στα επόμενα σώματα, από τα οποία το πρώτο θέλουμε να αποτελεί
την εφαρμογή που θα δώσει το τελικό αποτέλεσμα της PROG2 , και τα επόμενα να αποτελούν
παράπλευρες δράσεις. Χρησιμοποιείται και για λόγους συμβατότητας με παλαιότερους compilers
της Lisp, όπου για τεχνικούς λόγους δεν ήταν δυνατό να οριστεί η PROG1 .

Προσδιορισμός (υπο-)προγράμματος με μεταβλητές
Οι συναρτήσεις BLOCK, PROGN, PROG1, PROG2 προσφέρουν ευκολία για παράθεση σειράς
255

υπολογισμών, όπου ο καθένας παίζει τον προβλεπόμενο ρόλο στη σύνθεση. Όμως οι συναρτήσεις
αυτές δεν δέχονται λ-μεταβλητές ούτε ορίζονται τοπικές μεταβλητές: απλά καθορίζουν μια
διαδοχή υπολογισμών, και σ' αυτήν, τί θα δοθεί ως τελική έξοδος.
Ένα βήμα διεύρυνσης των δυνατοτήτων ομαδοποίησης προς την κατεύθυνση της ανάπτυξης
τόπων, επιτελεί η συνάρτηση PROG , που δέχεται προσδιορισμό τοπικών μεταβλητών. Έτσι
μπορούμε να κτίσουμε (σε μορφή εκτέλεσης) ένα "κόσμο" με δικές του μεταβλητές, που είναι ο
τόπος των μεταβλητών αυτών.
prog : Ειδική συνάρτηση, για προσδιορισμό (υπο-)προγράμματος που έχει τη μορφή τόπου.
Είναι γενικότερη από τις προηγούμενες κατά το ότι δέχεται προσδιορισμό τοπικών μεταβλητών,
δηλ. που έχουν ισχύ στα πλαίσια εκτέλεσης της PROG και μόνον. Επιστρέφει το διαδικαστικά
προσδιορισμένο μέσω της RETURN αποτέλεσμα, και αν αυτό δεν έχει οριστεί, το αποτέλεσμα
της τελευταίας φόρμας.
Σε περίπτωση όπου συμπίπτουν ονόματα τοπικών μεταβλητών με ονόματα καθολικών
μεταβλητών, υπερισχύουν οι τοπικές κατά τη διάρκεια εκτέλεσης του PROG και αγνοούνται οι
καθολικές (οι οποίες παραμένουν αναλλοίωτες από κάθε δράση που μπορεί να προκαλέσει η
PROG).

Ελεγχόμενη έξοδος γίνεται με κλήση της RETURN που ως συνάρτηση χωρίς όρισμα επιστρέφει
ΝΙL , και με ένα όρισμα επιστρέφει την τιμή του ορίσματος. Η εκτέλεση της RETURN τερματίζει
τον υπολογισμό της PROG
… (RETURN <αποτέλεσμα>) …
Σε περίπτωση κιβωτισμένων PROG , οι τοπικές μεταβλητές κάθε ανωτέρου επιπέδου αποτελούν
εξωτερικές μεταβλητές για τα επόμενα εσωτερικά επίπεδα, ενώ οι τοπικές κάθε κατωτέρου
επιπέδου δεν είναι ορατές από τα ανώτερα.
Η σύνταξη της PROG είναι:
(prog
( (var1 value1) (var2 value2) . . .)

<σώμαΑ>
(cond … (return result)… )
<σώμαΒ> )

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

Χρήση παραπομπής μέσα στην PROG : η εντολή GO
Διαδικαστικά χρησιμότερη, αλλά και συνθετότερη μορφή της PROG , είναι αυτή όπου η
εκτέλεση ανακατευθύνεται εσωτερικά, με παραπομπές υπό συνθήκες, σε διαφορετικά σημεία
256

μέσα στο πακέτο της PROG , μέσω ετικετών. Αυτό γίνεται με χρήση της διαδικαστικής εντολής
GO, που παραπέμπει στο αναφερόμενο σημείο - ετικέτα .
Η παραπομπή συνήθως είναι εξαρτημένη από κάποιο έλεγχο. Η ετικέτα είναι οποιοδήποτε
αλφαριθμητικό όνομα, που δεν υπολογίζεται (άρα δεν είναι ανάγκη να είναι σύμβολο):
(prog
( <μεταβλητές> )
<ετικέτα1> <σώμα1>
<ετικέτα2> <σώμα2>
<ετικέτα3> <σώμα3>
(go <ετικέτα2> )
…)

Το σώμα κάθε ετικέτας στην οποία παραπέμπουμε, πρέπει να ακολουθείται από (RETURN) ή
(RETURN <αποτέλεσμα>) , διότι αλλιώς δεν έχει νόημα η παραπομπή. Η συνάρτηση - εντολή
GO μπορεί να χρησιμοποιηθεί μόνον μέσα στο ίδιο επίπεδο, δηλ. δεν μεταφέρει σε άλλα επίπεδα
του κιβωτισμού.
Παραδείγματα
1. Χρήση της PROG για ανακατεύθυνση υπολογισμού:

(defun myprogram (x y)
(prog
( (a x) (b y) )
(cond
( (and (numberp a) (numberp b)) (go tag1) )
( t (go tag2)) )
tag1
(print (+ a b))
(return)
tag2
(print “μη αριθμητική είσοδος”)
(return) ))

Εφαρμογή:
(myprogram 2 3) ; τυπώνει 5 και επιστρέφει NIL
(myprogram 2 'a) ; τυπώνει “μη αριθμητική είσοδος” και
; επιστρέφει NIL

2. Χρήση της PROG για ορισμό τόπου μέσα στο σώμα συνάρτησης:
(defun myprog (x y)
(prog
( (a x) (b y) )
(cond
( (and (numberp a) (numberp b)) (go tag1) )
( t (go tag2)) )
257

tag1
(defun f1-local (a b) (+ a b))
(return)
tag2
(defun f2-local (a b) (cons a b))
(return) ))
Μετά από την πρώτη εκτέλεση της MYPROG ορίζονται οι συναρτήσεις F1-LOCAL και F2LOCAL , και μόνο τότε μπορούν να κληθούν αυτόνομα.

Ανεξαρτησία / εξάρτηση τοπικών μεταβλητών

Η συνάρτηση PROG θεωρείται παράλληλη σε ότι αφορά τις τοπικές μεταβλητές, διότι αυτές
προσδιορίζονται ανεξάρτητα μεταξύ τους. Αν θέλουμε εξάρτηση των οριζομένων μεταβλητών
κατά τη διαδοχή γραφής τους, τότε χρησιμοποιούμε τη συνάρτηση PROG* (διαφοροποίηση
απολύτως ανάλογη με των LET και LET*). Η σύνταξη είναι πανομοιότυπη με της PROG
(prog*
( (var1 2) (var2 (+ var1 3)) )
(print var2))
→ 5
(η var2 εξαρτάται από την var1)

Ενώ αντίθετα:

(prog
( (var1 2) (var2 (+ var1 3)) )
(print var2))
→ Error...
(η var2 χρησιμοποιεί το μη προσδιορισμένο σύμβολο var1)

Οι συναρτήσεις PROG και PROG* μπορούν να χρησιμοποιηθούν για προσδιορισμό τόπου, όπως
και οι LET και LET* .

Δυναμική έξοδος προς άλλο υπολογισμό

Η έξοδος από ένα block ή ένα prog με χρήση της RETURN , είναι στατικός: υπακούει σε
προκαθορισμένη πορεία, με μοναδική ευελιξία την εξάρτηση της εξόδου από συνθήκες.
Διατίθεται και ένας τρόπος που επιτρέπει δυναμικό "ταίριαγμα" της εξέλιξης του υπολογισμού
πάνω σε κατεύθυνση που εξαρτάται από τα αποτελέσματα κάποιου επί μέρους υπολογισμού που
έγινε. Αυτό το πετυχαίνουμε με συνδυασμένη χρήση των συναρτήσεων CATCH και THROW :
catch , throw :
Ειδικές συναρτήσεις που συνδυάζονται για να ανακατευθύνουν την πορεία
ενός υπολογισμού. Η σύνταξη είναι:
(catch <μαρκάρισμα> <σώμα>)
(throw <μαρκάρισμα> <αποτέλεσμα>)

όπου το <μαρκάρισμα> υπολογίζεται, σε αντίθεση με το όνομα ενός block που είναι μια ετικέτα
"σταθερή", και το <σώμα> είναι ένα εννοούμενο PROGN . Οι CATCH και THROW "ταιριάζουν"
βάσει του πρώτου ορίσματός τους.
258

Ας δούμε αναλυτικά τον τρόπο "ταιριάγματος":
Κατ' αρχήν, η CATCH μόνη της, απλώς προσδιορίζει ένα block, οπότε η λειτουργικότητά της
είναι πανομοιότυπη με της BLOCK :
(setq b 2 c 3)
(catch 'a
(+ b c)
(- b c) )
→ -1 (επιστροφή από την ολοκλήρωση του υπολογισμού)

Κιβωτισμένα CATCH εκτελούνται πανομοιότυπα με κιβωτισμένα BLOC .
Χρησιμοποιώντας την THROW μέσα στο σώμα τής CATCH , πετυχαίνουμε λειτουργικότητα
ανάλογη της χρήσης RETURN μέσα σε ένα block :
(catch 'a
(print (list b c))
(throw 'a (+ b c))
(- b c) )
→ (2 3)
(εκτύπωση)
→ 5
(επιστροφή από το throw)

Αυτό που προσφέρει γενικότερα ο συνδυασμός CATCH - THROW σε σχέση με το συνδυασμό
BLOC - RETURN , είναι οτι η αντίστοιχη κλήση (THROW x …) ενός δοσμένου (CATCH x …)
μπορεί να είναι στο σώμα μιας ανεξάρτητης συνάρτησης
Παράδειγμα
(defun my-prog (arg)
(cond
( (not (eql arg 'a) )
(return-from my-prog "το όρισμα πρέπει να είναι 'a ") )
(t (catch 'a
(print "μπήκαμε στο catch που προσδιορίζει το a")
(print "τώρα το a - catch θα κλείσει με throw")
(my-throw arg))))
"συνεχίζει η συνάρτηση my-prog" )))
(defun my-throw (x)
(throw x (progn (print "ενεργοποιήθηκε το throw") (princ x) )))

Εφαρμογή:

(my-prog 'a)

"μπήκαμε στο catch που προσδιορίζει το a"
"τώρα το a - catch θα κλείσει με throw"
"ενεργοποιήθηκε το throw" A
259

"συνεχίζει η συνάρτηση my-prog"
(my-prog 'b) →
"το όρισμα πρέπει να είναι 'a "

6.2.3 Περιπτωσιολογική επιλογή και αντιμετώπιση σφάλματος

Συναρτήσεις ομαδοποίησης όπως η PROGΝ επιτελούν διαδοχική εκτέλεση των αναφερομένων
ορισμάτων τους. Βλέποντάς 'το από λογική άποψη, οι συναρτήσεις αυτές έχουν συμπληρωματική
λειτουργικότητα για την υπολογιστική λογική, απέναντι στην COND : η PROGΝ καθορίζει την
υποχρέωση διαδοχικών υπολογισμών, ενώ η COND την επιλογή του κατάλληλου υπολογισμού.
Αν και η συνάρτηση COND μπορεί να αξιοποιηθεί τόσο για διαδικαστική έκφραση όσο και για
δηλωτική, διατίθεται μια συνάρτηση με πιο εξειδικευμένη διαδικαστική λειτουργικότητα, η
CASE . Παρεμφερείς είναι οι CCASE και ECASE , που αντιμετωπίζουν και την περίπτωση
σφάλματος.

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

Περιπτωσιολογική διακλάδωση βάσει κριτηρίου «ταιριάγματος»
Είδαμε σε προηγούμενα, χρήση της συνάρτησης CASE για περιπτωσιολογική διακλάδωση. Η
συνάρτηση αυτή είναι πιο ευέλικτη από την COND όταν οι περιπτώσεις είναι δυνατό να
αναφερθούν λεκτικά:
case

:

<atom>  (<key> <term> n) → <term>

Η σύνταξη είναι:
(case
<φόρμα-κλειδί>
(<κλειδί-1> <σώμα-1> )
(<κλειδί-2> <σώμα-2> )

(<κλειδί-k> <σώμα-k> ))

Η φόρμα-κλειδί υπολογίζεται (εκτός αν είναι quoted), και η τιμή της, οδηγεί στη φόρμα - κλάδο
που έχει πρώτο όρο αυτή την τιμή (δηλαδή κάποιο κλειδί) . Το <κλειδί-…> στις φόρμες κλάδους δεν υπολογίζεται, και απλώς κρίνεται αν συμπίπτει ή όχι με την τιμή τής έκφρασης
<φόρμα-κλειδί> . Αν ταιριάζει, εκτελείται το αντίστοιχο σώμα όπως και στην COND, και το
αποτέλεσμα της τελευταίας φόρμας του σώματος του κλάδου επιστρέφεται ως αποτέλεσμα της
CASE . Όπως και η COND , επιστρέφει NIL όταν όλες οι περιπτώσεις αποτύχουν. Η τιμή της
260

φόρμας - κλειδί πρέπει να είναι ένα άτομο (σύμβολο, string, αριθμός...).

Αν έχουμε αμφιβολία οτι έχουν καλυφθεί όλες οι περιπτώσεις, ή υπάρχει το ενδεχόμενο μη
προβλεπόμενης τιμής κλειδιού, χρησιμοποιούμε ως τελευταία φόρμα - κλάδο μια με κλειδί Τ ή
OTHERWISE , που ταιριάζουν σε οποιαδήποτε τιμή κλειδιού (και θα εκτελέσει κάτι που θα
αντιστοιχεί στην περίπτωση αποτυχίας όλων των ελέγχων).
Προσοχή: τα κλειδιά δεν έχουν πρόθεμα ":", διότι δεν καλούνται ως κλειδιά, απλώς
χρησιμοποιούνται με αυτό το νόημα.
Παράδειγμα
(defun transform (x)
(case (type-of x)
(number (* x x))
(string (reverse x))
(symbol (setf (get x 'examined) t))
(otherwise "είσοδος ακατάλληλου τύπου")))

Εφαρμογή:

(transform "abcd")
→ "dcba"
(transform 3)
→ 9
(transform '(a b c)) →
"είσοδος ακατάλληλου τύπου"

Επιλογή εκτέλεσης βάσει κριτηρίου του τύπου «ανήκει σε…»

Η συνάρτηση CASE μπορεί να χρησιμοποιηθεί και με λίστα περισσοτέρων τιμών που
"ταιριάζουν ως κλειδί", και η λίστα αυτή (λίστα ελέγχου) να δίνεται ως πρώτος όρος σε φόρμα κλάδο. Με τη σύνταξη αυτή, αντί για ένα μοναδικό κλειδί στον κάθε έλεγχο μπορούμε να
δώσουμε λίστα από κλειδιά, όπου αρκεί να ταιριάζει ένα από αυτά για να πετύχει ο έλεγχος (δηλ.
είναι μια or-condition). Αυτό δίνει ένα σημαντικό πλεονέκτημα γενικότητας απέναντι στην
COND , για εξειδίκευση συμπεριφοράς συνάρτησης βάσει του τύπου εισόδου, όπου τον τύπο
μπορούμε να τον έχουμε εξειδικεύσει εμείς εκ των προτέρων.
Κατά την εφαρμογή, απλώς αναζητείται η τιμή του κλειδιού διαδοχικά μέσα στις λίστες ελέγχου
(χωρίς υπολογισμό των λιστών αυτών). Για παράδειγμα:
(defun mykey (var) (+ var 1))
(case (mykey 3)
( (1 2 3)(print "δεν ταιριάζει"))
( (4 5 6)(print "ταιριάζει")) )
→ "ταιριάζει"

Προφανώς ο σκοπός χρήσης της CASE δεν είναι να υπολογίσει κάτι που στη φάση συγγραφής
του κώδικα είναι ήδη γνωστό. Η χρησιμότητα έγκειται στο οτι η τιμή της φόρμας - κλειδί μπορεί
να προκύπτει από υπολογισμό. Δεδομένου οτι ένας υπολογισμός (εφαρμογή) που αναφέρεται με
όνομα είναι δυνατό να διαμορφωθεί καθυστερημένα (αρκεί να έχει διαμορφωθεί πριν την κλήση
261

του για εφαρμογή), με τη χρήση της CASE και αντίστοιχα της TYPECASE μπορούμε να
εξαρτήσουμε την επιλογή από απώτερη φάση όχι μόνο της κατάστασης μεταβλητών αλλά και
ορισμού συναρτήσεων. Άρα αυτή η επιλογή μπορεί να πάρει μορφή υπό καθυστέρησης επιλογής.
Παραδείγματα
1. Η περίπτωση όπου η τιμή του κλειδιού προκύπτει από το περιβάλλον:
Χρήση τοπικής μεταβλητής ως φόρμας-κλειδί.
(setq a 0)
(let (( b 1) (c 2) d )
(setq d (+ a b c))
(defun f (x)
(case d
( (1 2 3) (square x) )
( (4 5 6) (sqrt x) ) )))

Εφαρμογή:
(f 4) → 16
(setq a 3)
(f 4)
→ 2
(setq a 10)
(f 4) → NIL

2. Η περίπτωση όπου η τιμή του κλειδιού προκύπτει από την φόρμα κλήσης:
Χρήση λ-μεταβλητής ως φόρμας-κλειδί.
(defun g (x)
(case x
( (1 2 3) (square x) )
( (4 5 6) (sqrt x)) ) )

Εφαρμογή:
(g 4)
(g 2)

→ 2
→ 4

Έλεγχος σφάλματος και δυναμική ανακατεύθυνση

Είπαμε πως η CASE επιστρέφει NIL όταν δεν ταιριάζει καμμία περίπτωση, και μπορούμε να
θέσουμε μια πρόσθετη τελευταία συνθήκη, με κλειδί Τ , που ταιριάζει σε κάθε περίπτωση, όπως
και στην COND . Ενδέχεται όμως να μη θέλουμε τίποτα από τα δύο: το NIL να μην ταιριάζει ως
τιμή, και η υπόθεση - κλειδί Τ να σκεπάζει σφάλματα.

Ένας τρόπος είναι, "να προτιμήσουμε από το τίποτα" κάποια από τις διαθέσιμες λύσεις. Για
παράδειγμα: "σε μια βραδινή έξοδο για κινηματογράφο δεν παίζει κανένα από τα έργα που
επιθυμούμε και συμβιβαζόμαστε με ένα από τα διαθέσιμα". Αυτό γίνεται με χρήση της CCASE
262

αντί της CASE. Ένας άλλος τρόπος αντιμετώπισης είναι, να προκαλέσουμε σφάλμα αν δεν
ταιριάζει τίποτα. Αυτό γίνεται με χρήση της ECASE :

ccase :Παρόμοια με την CASE , με τη διαφορά ότι αν δεν ταιριάξει καμμία περίπτωση, τότε
δηλώνει στην έξοδο - πρώτο επίπεδο "σφάλμα που μπορεί να αρθεί" (continuable error),
υποδεικνύοντας τις διαθέσιμες περιπτώσεις που ταιριάζουν, και ζητά από τον χρήστη να δώσει
συνέχεια, είτε επιλέγοντας ένα από τα κατάλληλα κλειδιά είτε οδηγώντας τον υπολογισμό σε
διακοπή. Η σύνταξη της CCASE δεν δέχεται κλειδί T ή OTHERWISE , για προφανείς λόγους.
ecase :Παρόμοια με την CCASE , με τη διαφορά οτι σταματά την εκτέλεση αν δεν ταιριάξει
τίποτα. Προκαλεί σφάλμα, μεταφέροντας τον έλεγχο στον προκαθορισμένο από τον compiler
γενικό τρόπο αντιμετώπισης σφαλμάτων (debugger).
Παράδειγμα
(defun eat ( )
(ccase meal
((meat fish) (prin1 “εξαιρετικά”) (terpri))
((pizza spaghetti) (prin1 “ανεκτά”) (terpri)) ) )

Εφαρμογή:
Εδώ, meal είναι εξωτερική μεταβλητή, οπότε η κλήση τής EAT αναφέρεται στην τρέχουσα τιμή
που η meal έχει:
(setq meal 'vegetables)
(eat) →
→ Error: acceptable keys are: (meat fish pizza spaghetti) →

< φέρνει σε κατάσταση εισόδου, και μας παραθέτει τα διαθέσιμα
κλειδιά∙ δίνουμε fish :>
fish → εξαιρετικά
Αλλάζοντας τα δεδομένα του προγράμματος, προσαρμόζεται ανάλογα η συμπεριφορά της ΕΑΤ :
(setq meal 'spaghetti)
(eat)
→ ανεκτά

6.3 Συναρτησιακοί βρόχοι υπολογισμού
Στην ενότητα αυτή θα μελετήσουμε τα θέματα που αφορούν βρόχους, δηλαδή επανάληψη και
αναδρομή, από διαδικαστική και δηλωτική άποψη.

6.3.1 Γενικά χαρακτηριστικά των βρόχων
Τρόποι δημιουργίας βρόχων
Βρόχος είναι μια ανακυκλούμενη εφαρμογή. Βρόχο μπορούμε να προκαλέσουμε:
263

α'.
Με χρήση πρωτογενούς συνάρτησης που προκαλεί επανάληψη (iteration). Η Lisp διαθέτει
ένα πλήθος από τέτοιες συναρτήσεις, ειδικής μορφής, που εξυπηρετούν κυρίως την ανάπτυξη
διαδικαστικού προγράμματος. Οι πρωτογενείς συναρτήσεις καλύπτουν διάφορες ειδικές
περιπτώσεις, όπως το αν προκαθορίζεται το πλήθος των επαναλαμβανόμενων κύκλων ή αν
ελέγχεται από συνθήκη, αν δέχονται λ-μεταβλητές, αν δέχονται εξαρτημένες ή ανεξάρτητες
μεταξύ τους τοπικές μεταβλητές.
β'.
Με αναδρομή (recursion). Αναδρομική είναι μια συνάρτηση όταν στο σώμα της καλεί για
εφαρμογή τον εαυτό της (αναδρομική κλήση). Διακρίνουμε δύο είδη αναδρομικών συναρτήσεων:
την αναδρομή ουράς (tail recursion), όπου η αναδρομική κλήση είναι η τελευταία φόρμα του
σώματος, και την αναδρομή σώματος (body recursion) όπου η αναδρομική κλήση ακολουθείται
και από άλλη φόρμα υπολογισμού στο σώμα.

Παραδείγματα
1. Αναδρομή ουράς:
(defun tail-rec (x)
(cond ((> x 10) 'end)
(t (princ x) (princ " ") (tail-rec (+ x 1)))))

Κλήση:

(tail-rec 1)

→ 1 2 3 4 5 6 7 8 9 10

(εκτύπωση)
(επιστροφή)

END

2. Αναδρομή σώματος:

(defun body-rec (x)
; αναδρομή σώματος
(cond ((> x 10) 'end)
(t (body-rec (+ x 1)) (princ " ") (princ x) )))

Κλήση:

(body-rec 1)

→ 10 9 8 7 6 5 4 3 2 1
1

(εκτύπωση)
(επιστροφή)

3. Διπλή αναδρομή (υποχρεωτικά σώματος):

(defun b-rec (x)
(cond ((> x 4) 'end)
( t (b-rec (+ x 1)) (princ " ") (princ x) (b-rec (+ x 1)))))

Κλήση:

(b-rec 1)



4 3 4 2 4 3 4 1 4 3 4 2 4 3 4
END

(γιατί;)

γ'.
Με έμμεσα προκαλούμενη αναδρομή (μέσω συνάρτησης Α η οποία καλεί στο σώμα της,
συνάρτηση Β κοκ, και η τελευταία καλεί στο σώμα της τη συνάρτηση Α). Η περίπτωση αυτή
ισοδυναμεί με την β', οπότε τελικά ανάγεται σε μια από τις υποπεριπτώσεις της β' : αν όλες οι
κλήσεις είναι ουράς τότε η αναδρομή είναι ουράς, και αν τουλάχιστον μία κλήση είναι εσωτερικά
στο σώμα, τότε η αναδρομή είναι σώματος.
Παράδειγμα
(defun c-rec (x) (princ x) (princ " ") (d-rec (+ x 1)))
264

(defun d-rec (x) (e-rec (+ x 1)))
(defun e-rec (x)
(cond
((> x 30) 'end)
(t (c-rec (+ x 1)))))
(c-rec 1) → 1 4 7 10 13 16 19 22 25 28 31

Το πού θα τεθεί η συνθήκη διακοπής σε ένα κύκλο περισσοτέρων κλήσεων απαιτεί αρκετή
προσοχή, διότι καθορίζει την τελευταία εκτέλεση: στο παραπάνω, πήραμε τελευταία τιμή 31 .
δ'.
Με κατάλληλη χρήση και δέσμευση λ-μεταβλητής συνάρτησης. Είδαμε στο πρώτο
κεφάλαιο, πώς μπορούμε να προκαλέσουμε αναδρομή στο μοντέλο του λ-λογισμού όπου δεν
διατίθεται ως πρωτογενής η έννοια της αναδρομής, και μάλιστα με δύο διαφορετικές τεχνικές.
Αυτή η πρόκληση βρόχου διαφέρει από τις προηγούμενες στο οτι προκαλείται σε επίπεδο
εκτέλεσης και όχι σε επίπεδο προσδιορισμού συναρτησιακής έκφρασης. Οι τεχνικές αυτές
μπορούν να εφαρμοστούν και στη Lisp, όπως θα δούμε στο Κεφ.9.
Τονίζουμε οτι ο προσδιορισμός μιας συναρτησιακής έκφρασης είναι δυνατό να είναι σύνθετος και
κατά συνέπεια είναι δυνατό να αναμειγνύονται όλα τα παραπάνω.

Βασικά χαρακτηριστικά της ανακύκλωσης
i) Kάθε κύκλος είναι υπολογισμός που μπορεί να κάνει αναφορά στις αρχικές σταθερές
συνθήκες, ή σε συνθήκες που αναδιαμορφώθηκαν στον προηγούμενο κύκλο (ή σε ν-άδα
προηγουμένων κύκλων).
ii) Σε κάθε κύκλο μπορούμε να προσδιορίζουμε νέα σύμβολα ή να επαναπροσδιορίσουμε
υφιστάμενα, ακόμα και την ίδια την εκτελούμενη συνάρτηση (δυνατότητα που υποστηρίζεται από
μερικούς compilers, όπως συζητάμε στην επόμενη παράγραφο).
iii) Μπορούμε να δημιουργήσουμε συνθετότερη δομή ανακυκλώσεων, όπως δένδρο.
Ο περιορισμός που τίθεται, είναι: θεωρητικά να τερματίζονται οι τοπικοί κύκλοι, πρακτικά
"εγκαίρως", και τεχνικά να μη δημιουργείται πουθενά κατάσταση υπερχείλισης στοίβας.

Τεχνικές συσσώρευσης και εκπλήρωσης υποχρεώσεων προς εκτέλεση, που αφήνει η
αναδρομική κλήση
Ο προσδιορισμός υπολογισμού στον compiler έχει τη μορφή "κτισίματος" μιας στοίβας, όπου
τοποθετούνται τα διαδοχικά στοιχειώδη βήματα, διακρινόμενα ως υποχρεώσεις προς εκτέλεση,
και η εκτέλεση του υπολογισμού από τον compiler έχει τη μορφή "αδειάσματος" της στοίβας, με
εκτέλεση από το τελευταίο βήμα προς τα προηγούμενα.
Τα βήματα κτισίματος είναι αντίστοιχα των βημάτων σύνθεσης του υπολογισμού "απ' έξω προς
τα μέσα", ενώ τα βήματα εκτέλεσης ακολουθούν την αντίθετη πορεία, "από μέσα προς τα έξω".
Σε μια τέτοια πορεία τα βήματα αποτελούν προσδιορισμό από διαδοχική εφαρμογή στοιχειωδών
αλγόριθμων.
Για το κτίσιμο αυτό ακολουθούνται δύο προσεγγίσεις, πάνω στις οποίες αναπτύσσονται δύο ειδών
265

compilers: i) πρώτα να κτίζεται η στοίβα του συνολικού υπολογισμού και μετά να ξεκινά η
εκτέλεση, και ii) να κτίζεται η στοίβα του επί μέρους υπολογισμού που αναγνωρίζεται και αν
διατίθενται τα απαραίτητα στοιχεία εισόδου για την εκτέλεσή του να εκτελείται, και μετά να
συνεχίζεται το απομένον κτίσιμο της στοίβας.
Κατά την πρώτη προσέγγιση, ο εξωτερικότερος υπολογισμός καθορίζει τη συνολική στοίβα και
μόνο μετά την ολοκλήρωση του στοιβάγματος των πάντων αρχίζει η εκτέλεση, ανεξάρτητα του
αν θα ήταν δυνατό ή όχι να εκτελεστούν αυτόνομα επί μέρους υπολογισμοί. Κατά τη δεύτερη
προσέγγιση, συσσωρεύονται στη στοίβα μόνον "οφειλές" που δεν μπορούν να εκτελεστούν διότι
απαιτούν είσοδο που δεν είναι ακόμα διαθέσιμη διότι θα προκύψει από την εκτέλεση επόμενου
βήματος, κοκ∙ μόνο όταν καλυφθεί και η τελευταία οφειλή, καθίσταται δυνατό να εκπληρωθούν
και οι προηγούμενες, με διαδοχή "από το τέλος προς την αρχή".
Κάθε μια από αυτές τις προσεγγίσεις έχει πλεονεκτήματα και μειονεκτήματα:
– Η πρώτη είναι ταχύτερη, ασφαλέστερη, και μπορεί να εκμεταλλεύεται τυχόν
επαναλαμβανόμενους υπολογισμούς, αναγνωρίζοντάς τους εκ των υστέρων και εκτελώντας τους
μόνο μια φορά (lazy evaluation). Ο compiler είναι μικρότερου μεγέθους και "τρέχει" σε μηχανές
περιορισμένων πόρων. Αμφιλεγόμενο μειονέκτημα είναι οτι, σε περίπτωση πρόκλησης
σφάλματος σε κάποιο βήμα, δεν θα εκτελεστεί το προηγούμενο "σωστό μέρος" ακόμα και αν
είναι ανεξάρτητο του σφάλματος. Σημαντικό μειονέκτημα είναι οτι, είναι δυνατό σε μια
συνάρτηση με αναδρομή ουράς ο υπολογισμός πριν την αναδρομή (δηλ. ο επαναλαμβανόμενος
κύκλος) να είναι εκτελέσιμος. Αν μπορούσε να εκτελεστεί, η στοίβα, που συσσωρεύει τις
υποχρεώσεις του κύκλου, θα "άδειαζε", για να δεχτεί τις νέες υποχρεώσεις, δηλαδή τον επόμενο
κύκλο, και αυτό διαρκώς, με δυνατότητα ακόμα και ατέρμονος επανάληψης. Στην κατηγορία
αυτή ανήκει η AUTOLISP.
– Η δεύτερη είναι συγκριτικά αργότερη και απαιτεί σημαντικότερους πόρους συστήματος.
Κάθε επί μέρους εκτελέσιμη πράξη εκτελείται και "ξεχνιέται", οπότε αν υπάρχει ξανά, θα
επανεκτελεστεί. Η αποφυγή τέτοιων επαναλήψεων ανάγεται σε προγραμματιστική τεχνική που θα
εφαρμόσει κατάλληλες καθυστερήσεις. Έχουμε δύο σημαντικά πλεονεκτήματα, i) κάθε
ολοκληρωμένο τμήμα (εφαρμογή) εκτελείται, ακόμα και αν παρακάτω υπάρχει σφάλμα∙ ii) δεν
διογκώνεται η στοίβα στην περίπτωση αναδρομής ουράς (διότι δεν αφήνει οφειλές) οπότε ο
συνολικός υπολογισμός δεν περιορίζεται από το κάποιο μέγιστο πλήθος βημάτων.
Νεότεροι compilers, όπως της Allegro CL, συνδυάζουν και τις δύο προσεγγίσεις και
εκμεταλλεύονται σε μεγάλο βαθμό τα πλεονεκτήματά τους (αν και συχνά παρουσιάζουν
"περίεργη συμπεριφορά" λόγω της πολυεπίπεδης λειτουργίας της στοίβας σε επί μέρους στοίβες),
όπως να επαναλαμβάνεται μια αναδρομή ουράς, και μετά από πάρα πολλά βήματα εκτέλεσης, να
προκαλείται σφάλμα).

Απόψεις θεώρησης και χρήσης βρόχου
Από πλευράς επιταγής ή δήλωσης, μπορούμε να δούμε ένα βρόχο είτε ως διαταγή πρόκλησης
ανακύκλωσης, που μπορεί να μη λαβαίνει υπ' όψη το περιεχόμενο, είτε ως συναρτησιακή
δηλωτική σύνθεση που παρουσιάζει αυτή τη συμπεριφορά, όπου η συμπεριφορά είναι δεμένη με
266

το λειτουργικό ή σχεσιακό περιεχόμενο. Η κάθε άποψη αξιοποιείται διαφορετικά, και θέτει τις
δικές τις απαιτήσεις:

– Στην επιτακτική πρόκληση ανακύκλωσης δεν συνηθίζεται να επεμβαίνουμε με ανασυνθέσεις
στην πορεία του υπολογισμού, ούτε θέτουμε τον υπολογισμό ως τμήμα από νέες συνθέσεις∙ η
λειτουργικότητα από αυτό που θεωρούμε εκτέλεση (: "κάνε τα εξής") δεν εμπλέκεται με την
εσωτερική λειτουργικότητα, τη διασυνδεσιμότητα ή την ενδεχόμενη συνθεσιμότητα των
καλουμένων μερών. Η -ενδεχόμενη- χρήση λ-μεταβλητών σε ρόλο παραμέτρων καθυστέρησης
αποτελεί απλώς σημεία ελέγχου του υπολογισμού από ένα ανώτερο επίπεδο διαχείρισης, που
απευθύνεται είτε στο χρήστη ο οποίος εφαρμόζει διαχειριστικούς κανόνες, συνήθως τύπου
"απόφαση καθοδηγούμενη από τα γεγονότα" (event driven), πχ: "αν στον υπολογιστικό ή στον
πραγματικό κόσμο συμβεί αυτό, τότε κάνε εκείνο", είτε σε διαδικασία ανωτέρου επιπέδου, επίσης
καθοδηγούμενη από τα γεγονότα.
– Στη δηλωτική συναρτησιακή σύνθεση που λειτουργεί ανακυκλώνοντας, η ανακύκλωση
εντάσσεται στο ίδιο σημασιολογικό επίπεδο της λειτουργίας των επί μέρους διεργασιών και η
χρήση λ-μεταβλητών για επίτευξη καθυστέρησης είναι μέρος
της λειτουργικότητας
(σειριοποίηση εκτελέσεων εξαρτημένα από την αναγκαιότητα λήψης εξόδων για να δοθούν ως
είσοδοι, αφαιρετικός προσδιορισμός διεργασιών ώστε να προσαρμοστεί η σειρά εκτέλεσης στο
τρέχον περιβάλλον, κά). Ο έλεγχος του πότε θα γίνει ο κάθε υπολογισμός, μπορεί να συνδέεται με
τη συμπεριφορά που θα δείξει (εκ των υστέρων καθορισμός συμπεριφοράς, ως εξέλιξη του
υπολογισμού σε επίπεδο προσδιορισμού του).
Βλέποντας έναν υπολογισμό ως λειτουργία, διακρίνουμε την ανακύκλωση σε δύο μορφές:

α'. Ως επανάληψη που "ανεβαίνει" από τις αρχικές συνθήκες και τερματίζει σε μια τελική
κατάσταση, προκαθορισμένη ή που προκύπτει στην πορεία. Είναι ένας συσχετισμός που
καθορίζει την εξάρτηση "του επόμενου από το προηγούμενο", αλλά το "προηγούμενο" είναι
επαρκές για να εκτελεστεί το "επόμενο". Τότε, κάθε κύκλος αποτελεί μια ολοκληρωμένη,
εκτελέσιμη διαδικασία (εφαρμογή αυτόνομα εκτελέσιμου αλγόριθμου) με αποτελέσματα τα οποία
συνθέτουν τις συνθήκες εκτέλεσης του επομένου βήματος, και γι' αυτό αποκαλούμε το βρόχο
επανάληψη (iteration). Η τελική κατάσταση αποτελεί την οριακή σχέση τερματισμού

β'. Ως συσχετισμό που επίσης καθορίζει την εξάρτηση του "επόμενου βήματος απο το
προηγούμενο", αλλά το "προηγούμενο" δεν καθορίζει τις συνθήκες εκτέλεσης του επόμενου:
αυτές ανάγονται σε κάτι επόμενο, που τελικά συναντά τις δοσμένες συνθήκες. Άρα, πρόκειται για
ξεκίνημα των κύκλων από την τελική κατάσταση (το ζητούμενο) και "κατεβαίνει" προς τις
αρχικές συνθήκες. Κάθε κύκλος αποτελεί μια κλήση αλγόριθμου που οι απαραίτητες τιμές
εισόδου είναι τα αποτελέσματα του επόμενου βήματος, και κατά συνέπεια η σύνθεση είναι
επιτυχής όταν ανάγει την απαίτηση, από κύκλο σε κύκλο, στις αρχικές συνθήκες, που αποτελούν
τις τιμές εισόδου για την τελευταία κλήση. Άρα ο τελευταίος κύκλος είναι το πρώτο εκτελέσιμο
βήμα, που δίνει αποτέλεσμα στο προηγούμενό του, κοκ, φθάνοντας μέχρι το πρώτο βήμα, του
οποίου η έξοδος είναι το ζητούμενο (ή, πιο σύνθετα, ολοκληρώνει το σύνολο των απαιτήσεων).
Γι' αυτό αποκαλούμε το βρόχο αναδρομή (recursion). Οι αρχικές συνθήκες αποτελούν την
οριακή σχέση τερματισμού.
267

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

Μια επανάληψη προσδιορίζεται είτε μέσω των ειδικών συναρτήσεων επανάληψης είτε μέσω
αναδρομής ουράς (οπότε έχει τυπικά μορφή αναδρομής). Όπως προαναφέραμε, ένας σωστός
compiler θα διακρίνει την περίπτωση και θα λειτουργήσει "επαναληπτικά", δηλ. εκτελώντας τους
υπολογισμούς κάθε βήματος και όχι κτίζοντας πρώτα τη συνολική στοίβα.
Το πλήθος των αναγκαίων αρχικών συνθηκών αναδρομής ή επανάληψης είναι ίσο με το
πλήθος των προηγουμένων βημάτων - κύκλων από τα οποία εξαρτάται το αποτέλεσμα σε κάθε
κύκλο. Πχ. στη σχέση:
a(n) = a(n-1) + a(n-7)
απαιτούνται 7 αρχικοί όροι.
Αν το πλήθος των αναγκαίων τιμών είναι μεταβλητό για τους κύκλους, το ελάχιστο πλήθος
αρχικών συνθηκών καθορίζεται από τη μορφή του υπολογισμού, που προσδιορίζει το μέγιστο
πλήθος απαιτήσεων σε σχέση με τα ήδη παραχθέντα. που έχουν μορφή αρχικών βημάτων, είναι
αυτό που απαιτεί το αμέσως επόμενο υπολογιζόμενο βήμα, με την προϋπόθεση οτι η απαίτηση
εισόδου κάθε βήματος δεν υπερβαίνει το πλήθος των προηγουμένων βημάτων. Για παράδειγμα,
στο πρόβλημα "ο κάθε όρος ακολουθίας ισούται με το άθροισμα όλων των προηγουμένων του":
χρειαζόμαστε μόνο τον πρώτο όρο. Αν το ίδιο να ισχύει από τον δέκατο όρο και πέρα, πρέπει και
αρκεί να δοθούν οι εννέα πρώτοι όροι

Αναδρομή σώματος έναντι αναδρομής ουράς
Η αναδρομική κλήση στο σώμα επιβαρύνει τη στοίβα διότι πρέπει οπωσδήποτε να συμπεριλάβει
τον συνολικό υπολογισμό. Η αναδρομή στο σώμα είναι ακόμα πιο "καταναλωτική" όταν είναι
πολλαπλή, διότι αυτό προκαλεί ένα δένδρο οφειλομένων υποχρεώσεων, που οδηγεί σε εκθετικά
αυξανόμενη στοίβα, με άσκοπη επανάληψη των ίδιων υπολογισμών. Πχ. η τρίτης τάξης
αναδρομή:
a(n) = a(n-1) + a(n-2) + a(n-3) , με: α(1)=α(2)=α(3)=1
σε κάθε κύκλο απαιτεί τρεις νέους κλάδους υπολογισμού, άρα η πολυπλοκότητα είναι εκθετική,
τάξης 3n , όπου κάθε κόμβος υπολογίζεται επανειλημμένα, με εκθετικά αυξανόμενο πλήθος
επαναλήψεων.
Είναι σκόπιμο λοιπόν να μετατρέπεται η αναδρομή σώματος σε αναδρομή ουράς, αλλά αυτό
βέβαια απαιτεί νέο αλγόριθμο, που κατά κανόνα είναι πολύ δυσκολότερος στη σύλληψη.

Πρόκληση βρόχου μέσω εφαρμογής
Η κλήση συνάρτησης Α η οποία δεν είναι αναδρομική, ως όρισμα της ίδιας της συνάρτησης Α,
αυτή καθ' εαυτή δεν προκαλεί αναδρομή, διότι εκτελείται η συνάρτηση ακριβώς δύο φορές.
268

Μπορούμε όμως να προκαλέσουμε αναδρομή, προσδιορίζοντας συνάρτηση ανωτέρου επιπέδου η
οποία να εφαρμόζεται στον εαυτό της (ως όρισμα) και να δίνει αποτέλεσμα τον εαυτό της
εφαρμοσμένο στον εαυτό της. Συναντήσαμε την υπολογιστική εκμετάλλευση αυτής της
περίπτωσης στο λ-λογισμό (Κεφ.1.), στον ορισμό της συνάρτησης "recursive" ("συνάρτηση Υ")
που μέσω αυτής καταλήγουμε στον προσδιορισμό αναδρομικών συναρτήσεων.
Για τις συνατησιακές γλώσσες όπως η Lisp, αυτό δεν παρουσιάζει παρά θεωρητικό ενδιαφέρον
διότι η αναδρομή υποστηρίζεται απ' ευθείας. Θα δούμε στο Κεφ.9 παραδείγματα τέτοιας χρήσης.

Χρήση βρόχου για σάρωση λίστας ή δένδρου
Η σάρωση λίστας ή δένδρου επιτυγχάνεται άμεσα με χρήση βρόχου. Έχουμε δει περιπτώσεις
όπου κατευθύνουμε τη σάρωση δένδρου κατά βάθος ή κατά πλάτος πρώτα, ή με όποιο ειδικό
τρόπο θέλουμε (αρκεί γι' αυτό να καθορίσουμε τον κανόνα "επόμενος κόμβος").
Η σάρωση μπορεί να στοχεύει:
– σε εντοπισμό στοιχείων που πληρούν κάποια συνθήκη, προς διαμόρφωσή τους ή προς λήψη
πληροφορίας, προς χρήση τους για σχηματισμό άλλης δομής, τοπικά ή μόνιμα
– σε μετασχηματισμό της λίστας ή του δένδρου
– σε σύνθετη αναζήτηση πληροφορίας που εξαρτάται από περισσότερους κόμβους, σταθερών
και προκαθορισμένων κριτηρίων επιλογής / καθοριζομένων από τα αποτελέσματα που εν των
μεταξύ προκύπτουν / μαζί με την παρέμβαση του χρήστη / με την ενεργοποίηση διεργασιών
που συναντώνται στους κόμβους.

Βοηθητικές συναρτήσεις στην αναδρομή / επανάληψη

1+ , 1– : συναρτήσεις, ενός αριθμητικού ορίσματος. Η συνάρτηση 1+ επιστρέφει την τιμή
του ορίσματος αυξημένη κατά μία μονάδα και η 1– τη μειώνει κατά μία μονάδα.
Χρησιμοποιείται συνήθως όπου χρειάζεται μετρητής, απλώς για ευκολία. Πχ. γράφουμε: (1+ c)
αντί για (+ 1 c) , και (1– c) αντί για (– 1 c) .
break : Η συνάρτηση αυτή, ενός προαιρετικού ορίσματος, συνήθως string - μηνύματος,
προκαλεί έξοδο από την αναδρομή ή την επανάληψη (πχ. κάτω από ειδικές συνθήκες) με έξοδο το
όρισμά της.
Παράδειγμα
Επεξεργασία δένδρου:
Να αντικατασταθεί κάθε εμφάνιση του OLD μέσα στο δένδρο TREE με το NEW :
Θεωρούμε το δένδρο ως λίστα με όρους - φύλλα άτομα και όρους - κόμβους λίστες για τις
οποίες ισχύει το ίδιο, μέχρι φύλλων. Σαρώνουμε το δένδρο "depth first":

269

; θέσε new στη θέση του old μέσα στο tree
(defun substitute (new old tree)
(cond

; συνθήκη τερματισμού

((eql old tree) new)

; οριακή συνθήκη για φύλλο
((atom z) z)

; συνένωσε αυτό που θα προκύψει από την:
((cons

; αντικατάσταση του όρου πρώτα μέσα στην κεφαλή
(substitute x y (car z))

; και μετά αντικατάσταση του όρου στο σώμα
(substitute x y (cdr z)) )) ))

Είναι διπλή αναδρομή, εκθετικής τάξης πολυπλοκότητας εκτέλεσης, και το ίδιο για κατανάλωση
μνήμης. Άρα ο χρόνος εκτέλεσης αυξάνεται ραγδαία όσο μεγαλώνει το δένδρο.

6.3.2 Μείωση πολυπλοκότητας αναδρομής
Η αναδρομή στο σώμα εν γένει δεν είναι αποτελεσματική, λόγω της πολυπλοκότητας του
υπολογισμού, ενώ η αναδρομή στην ουρά είναι το ίδιο αποτελεσματική με την επανάληψη, (εφ'
όσον βέβαια ο compiler διαθέτει τη δυνατότητα να αναγνωρίζει την περίπτωση ώστε να μη κτίζει
συνολική στοίβα).
Αναδρομικές σχέσεις που δεν έχουν ορίσματα (δηλ. συναρτήσεις χωρίς λ-μεταβλητές) αποτελούν
συνήθως επιτακτικές διαδικασίες που ορίζονται αναδρομικά. Αν είναι αναδρομικές στην ουρά,
ισοδυναμούν με επαναληπτικές διαδικασίες (η συναρτησιακή έξοδος τέτοιας επανάληψης
συνήθως αγνοείται).

Μετατροπή αναδρομής στο σώμα, σε αναδρομή στην ουρά
Είναι εξαιρετικά αποδοτικό να μετατρέπουμε μια αναδρομή στο σώμα σε αναδρομή στην ουρά,
τόσο για αποφυγή υπερεκχείλισης της στοίβας, όσο και για μείωση της πολυπλοκότητας (όταν η
αναδρομή είναι πολλαπλή, με τη μετατροπή μειώνουμε την πολυπλοκότητα από εκθετική σε
γραμμική).
Παραδείγματα
1. Μετατροπή συνάρτησης με διπλή αναδρομή στο σώμα, σε επαναληπτική:
i) Η ακολουθία Fibonacci ως διπλά αναδρομική συνάρτηση:

270

(defun fibonacci (n)
(cond
( (= n 0) 0 )
;; πρώτος όρος: αν ν=0 επίστρεψε 0
( (= n 1) 1 )
;; δεύτερος όρος: αν ν=1 επίστρεψε 1
( t (+
;; αλλιώς, επίστρεψε το άθροισμα:
(fibonacci (– n 1))
; της fibonacci του n–1
(fibonacci (– n 2)) ) ) ) ) ; με τη fibonacci του n–2

Η συνάρτηση αυτή υπολογίζει το αποτέλεσμα αναδρομικά, ξεκινώντας από το τελικό
ζητούμενο,
ανάγοντας τον ζητούμενο σε κάθε βήμα - όρο της ακολουθίας, στους
προηγούμενους δύο όρους, και αυτό για κάθε όρο πάνω από τον δεύτερο. Γι’ αυτό o χρόνος
εκτέλεσης αυξάνεται εκθετικά, και, από κάποιον αριθμό και πέρα, σχετικά μικρό, είναι
απαγορευτικός. Επίσης, προκαλείται γρήγορα υπερχείλιση της στοίβας.
Ας "χρονομετρήσουμε" τον υπολογισμό τής FIBONACCI με τη συνάρτηση ΤΙΜΕ:
(time (fibonacci 33))

3524578
Execution time: 14.45 seconds

(Allegro v.3, Windows 95, Intel 200 Mhz)

ii) Υπολογισμός όρου της ακολουθίας Fibonacci μέσω επανάληψης.
Αντί για την προηγούμενη συνάρτηση, που ξεκινά από το τέλος και κτίζει μια εκθετικά
αυξανόμενη στοίβα μέχρι να φθάσει στην αρχή, μπορούμε να ορίσουμε μια συνάρτηση
αναδρομική στην ουρά, που να υπολογίζει από την αρχή προς το τέλος, δηλ. όπως θα κάναμε
σε "με το χέρι υπολογισμό", κρατώντας υπ’ όψη σε κάθε βήμα μόνο τους δύο τελευταίους
υπολογισμούς:
;; αν ο counter είναι 0 επίστρεψε την τιμή που αυτή τη στιγμή έχει το
; πρώτο όρισμα της συνάρτησης, αλλιώς επανάλαβε
; την fibonacci-iter με πρώτο όρισμα τον αριθμό n2, δεύτερο τον
; αριθμό n1 + n2 και τρίτο το counter μειωμένο κατά ένα
(defun fibonacci-iter (n1 n2 counter)
(cond ( (= counter 0) n1 )
( t (fibonacci-iter n2 (+ n1 n2) (1- counter)) ) )

)

Η συνάρτηση FIBONACCI-ITER , αν κληθεί με n1=1 , n2=1 και counter το δείκτη του όρου που
ζητάμε, θα επιστέψει τη ζητούμενη τιμή.

Για λόγους ευκολίας στην κλήση, είναι σκόπιμο να ορίσουμε μια συνάρτηση ενός ορίσματος,
έστω FAST-FIBONACCI, η οποία να καλεί την παραπάνω FIBONACCI-ITER με συγκεκριμένα τα
δύο πρώτα ορίσματα (που αντιστοιχούν στους δύο πρώτους όρους της ακολουθίας, που είναι
σταθερά) ώστε να απαλλάσσεται ο χρήστης από την υποχρέωση αυτή (που είναι τυπική αλλά
αναγκαία για τον ορισμό της FIBONACCI-ITER):
(defun fast-fibonacci (n) (fibonacci-iter 0 1 n) )
271

H τελευταία μπορεί να κληθεί με πολύ μεγάλες τιμές για το n και να δώσει ταχύτατα το ακριβές
ακέραιο αποτέλεσμα:
(time (fast-fibonacci 33)) → 3524578
–Execution time: 0.00 seconds
(fast-fibonacci 3000)

410615886307971260333568378719267…
–Execution time: 0.08 seconds
(Allegro v.3)

2. Μετατροπή αναδρομής σε επαναληπτική διαδικασία χωρίς αναδρομική κλήση:
i) Να δοθεί αναδρομική διαδικασία που να ελέγχει αν στοιχείο ανήκει σε λίστα.
Έστω οτι δεν διαθέταμε την πρωτογενή συνάρτηση MEMBER :
– Με αναδρομή στην ουρά:
(defun rec-member (el lis)
(cond ((null lis) nil)
((eql el (car lis)) t)
(t ( rec-member el (cdr lis))) ) )

– Επαναληπτικά μέσω της PROG :
(defun iter-member (elem mylist)
(prog ( )
label
(cond
((atom mylist) (return nil))
((eql elem (car mylist) (return mylist)))
(setq mylist (cdr mylist))
(go label) ))

ii) Να δοθεί αναδρομική διαδικασία που να αντιστρέφει λίστα.
Ορίζουμε τη συνάρτηση R-REVERSE :
(defun r-reverse (original result)
(cond
((null original) result)
( t (r-reverse
(cdr original)
(cons (car original) result)) )))

Παρακάτω, στη συνάρτηση DO, θα δούμε την επαναληπτική έκδοση της διαδικασίας αυτής.

Μείωση του πλήθους των επαναλήψεων με διάκριση γνωστών περιπτώσεων
Σε πολλές περιπτώσεις, η επαναληπτικά ή αναδρομικά υπολογιζόμενη έκφραση υπακούει σε
272

κανόνες του χώρου όπου ορίζεται, βάσει των οποίων μπορούμε να απαλείψουμε ορισμένους
περιττούς κύκλους.
Παράδειγμα
Εκμετάλλευση μαθηματικών ιδιοτήτων για μείωση των περιπτώσεων κλήσης:
i) Η ύψωση σε δύναμη, ως απλή αναδρομή στο σώμα, χωρίς να ληφθούν υπ' όψη ιδιότητες της
δύναμης.

H ακόλουθη συνάρτηση EXPONENTIAL υπολογίζει την ύψωση της βάσης x σε φυσικό εκθέτη y
:
(defun exponential (x y)
(cond
( (= y 0) 1)
( t (* x (exponential x (1- y )))) ) )

Εφαρμογή:

(exponential 2 3)
→ 8
(exponential 1050 2230) → 1787050914027795291570777418...

Υπολογίζει πολύ μεγάλες δυνάμεις ακέραιου εκθέτη, αλλά όπως ο υπολογισμός είναι "από το
τέλος προς την αρχή" σε κάθε κύκλο κρατάει συσσωρευτικά προς υπολογισμό τον τελευταίο
πολλαπλασιασμό. Επομένως δεν είναι αναδρομική στην ουρά (τελευταία κλήση είναι ο
πολλαπλασιασμός) και κτίζει στοίβα.
(time (exponential 1050 2230))
→ ... Execution time: 0.31 seconds

ii) Η ύψωση σε δύναμη, ως αναδρομή στο σώμα, όπου λαβαίνουμε υπ' όψη κάποιες μαθηματικές
ιδιότητες της δύναμης.
Ο αριθμός των επαναλήψεων μειώνεται δραστικά με περικοπή περιττών βημάτων, όπως με το
συλλογισμό οτι “αν ο εκθέτης είναι άρτιος, το αποτέλεσμα είναι το τετράγωνο του αριθμού για
το μισό εκθέτη, και αν ο εκθέτης είναι περιττός, το αποτέλεσμα είναι το τετράγωνο του αριθμού
για το μισό του εκθέτη μείον 1, επί τη βάση”:
(defun fast-exponential (x y)
(cond
( (not (integer y))
(break “ο εκθέτης πρέπει να είναι φυσικός αριθμός”) )
( (= y 0) 1 )
( (evenp y) (square (fast-exponential x (/ y 2))) )
( t (* x (square (fast-exponential x (/ (1- y) 2))) ) ) )

όπου η SQUARE ορίζεται κατά τα γνωστά (ή διατίθεται από τη γλώσσα) και η EVENP αν δεν
διατίθεται, μπορεί να οριστεί ως:
(defun evenp (n) (= (remainder n 2) 0) )
273

(προσέξτε στην παραπάνω συνάρτηση την απ' ευθείας επιστροφή της λογικής τιμής, χωρίς
χρήση conditional)
H χρονομέτρηση εκτέλεσης δίνει σημαντικά λιγότερο χρόνο:
(time (fast-exponential 1050 2230))

... Execution time: 0.11 seconds

6.3.3 Συναρτήσεις διαδικαστικής επανάληψης
Οι παρακάτω συναρτήσεις αποτελούν από το ένα μέρος καθαρά διαδικαστικές εντολές
επανάληψης, κατά το σύνηθες νόημα στο διαδικαστικό προγραμματισμό, και από το άλλο μέρος
συναρτήσεις που τυπικά επιστρέφουν τιμή, η οποία είναι η τιμή του τελευταίου υπολογισμού που
εκτελείται, εκτός αν ειδικά ορίζεται να επιστρέφουν κάτι άλλο.

Επανάληψη "ν φορές"

Μια συχνή περίπτωση επανάληψης σε επιτακτικής μορφής υπολογισμό, είναι "να γίνει κάποιος
προκαθορισμένος υπολογισμός ν φορές" όπου ν δοσμένος φυσικός αριθμός. Για την περίπτωση
αυτή διατίθεται η ειδικότερη συνάρτηση DOTIMES που επικεντρώνει τη λειτουργικότητα στο
συγκεκριμένο πλήθος από "φορές επανάληψης".
Πρόκειται για ένα απλό επιτακτικό προσδιορισμό επανάληψης, χωρίς ιδιαίτερες απαιτήσεις
σύνταξης και με περιορισμένες δυνατότητες έκφρασης:
dotimes :
(<var>  <integer>)  <term> n → <term>
(<var>  <integer>  <term>)  <term> n → <term>
Η σύνταξη της DOTIMES είναι:
(dotimes <λίστα> <σώμα> )
– Το πρώτο όρισμα της DOTIMES , <λίστα> , είναι η:
( <μετρητής> <πλήθος βημάτων> <αποτέλεσμα> )
όπου:
– Ο πρώτος όρος <μετρητής> είναι αριθμητική παράμετρος - τοπική μεταβλητή που ξεκινά
από το μηδέν και αυξάνεται κατά ένα σε κάθε κύκλο.

– Ο δεύτερος όρος <πλήθος βημάτων> είναι αριθμητική παράμετρος - τοπική μεταβλητή,
σταθερής τιμής σε όλη τη διάρκεια του υπολογισμού, και δηλώνει το πλήθος των βημάτων κύκλων που θα εκτελεστούν. Αν δίνεται από φόρμα υπολογισμού, αυτή υπολογίζεται πρώτα
απ’ όλα και επιστρέφει την τιμή (υποχρεωτικά να είναι ακέραιος θετικός).
– Ο τρίτος όρος είναι προαιρετικός, και δηλώνει αυτό που θα επιστραφεί όταν τελειώσουν οι
επαναλήψεις. Αν είναι όνομα μεταβλητής, θα επιστρέψει την τιμή που θα έχει πάρει μετά το
πέρας των επαναλήψεων. Αν είναι υπολογισμός, θα εκτελεστεί βάσει των συνθηκών που θα
ισχύουν μετά το πέρας των επαναλήψεων. Η επιστρεφόμενη τιμή είναι η τιμή της
274

συνάρτησης DOTIMES . Αν δεν υπάρχει ο όρος αυτός, η DOTIMES επιστρέφει NIL .
– Το δεύτερο όρισμα της DOTIMES , <σώμα> , αποτελεί αυτό που θα υπολογίζεται σε κάθε
επανάληψη. Το σώμα είναι ένα "εννοούμενο progn" , δηλ. το σώμα μπορεί να αποτελείται από n
διαδοχικές φόρμες.
Παράδειγμα
Συνάρτηση υπολογισμού τής (φυσικής) δύναμης αριθμού, με επαναληπτική μέθοδο βασισμένη στη
DOTIMES :
(defun dotimes-expo (m n)
(let ((result 1))
(dotimes (count n result)
(setf result (* m result) ) ) ) ) )

Χρόνος υπολογισμού:
(time (dotimes-expo 1050 2230)) →
–Execution time: 0.33 seconds ...

Ο χρόνος υπολογισμού είναι περίπου ο ίδιος με της προηγούμενης exponential, άρα έχουμε μόνο
εκφραστική διαφοροποίηση.

Επανάληψη "μέχρι"
Μια δεύτερη περίπτωση επιτακτικά διαδικαστικής επανάληψης είναι αυτή που ο βρόχος
εκτελείται μέχρι να επαληθευτεί μια συνθήκη. Για το σκοπό αυτό διατίθεται η ειδικότερη
συνάρτηση LOOP που εξαρτά τη λειτουργία της από συνθήκη που δεν γνωρίζουμε εκ των
προτέρων πότε θα επαληθευτεί. Είναι μια από τις απλούστερες και συνηθέστερες επιτακτικές
συναρτήσεις. έχει κάπως σύνθετο τρόπο σύνταξης, και γι' αυτό στο νεότερο πρότυπο, της ANSI
Common Lisp, η εντολή LOOP διατίθεται και σε μια πιο εξελιγμένη συντακτικά και λειτουργικά
μορφή, καθαρά διαδικαστική.

loop :
<term>n → <term>
Είναι ένας πολύ απλός τρόπος για τη διαδικαστική περιγραφή επανάληψης με συνθήκη
τερματισμού. Η απλούστερη σύνταξη είναι:
( loop <σώμα> )
όπου <σώμα> είναι η σειρά από φόρμες προς εκτέλεση. Διακόπτεται με χρήση του συνδυασμού
των βοηθητικών συναρτήσεων WHEN και RETURN
(loop
(when <έλεγχος> (return <αποτέλεσμα> ) )
<σώμα> )
όπου αν επαληθευτεί ο έλεγχος η επανάληψη σταματά και η συνάρτηση LOOP επιστρέφει την
τιμή από το <αποτέλεσμα> , όπως τελικά διαμορφώνεται.

275

Επανάληψη "ενώ" ή "εφ' όσον"
Αν και στην ANSI CL διατίθεται και το primitive "while", μπορούμε εύκολα να συνθέσουμε τη
λειτουργικότητά του, με διάφορους τρόπους, όπως:
(defun while-loop (end-condition execution &arg-list)
(loop
(when (not end-condition) )
(apply execution arg-list)))

είτε:
(defun mywhile (end-condition execution &arg-list)
(cond
((not end-condition) (setq r (apply execution arg-list)))
( t r)))

Επανάληψη όπου κάθε κύκλος αναφέρεται στις αυτές αρχικές συνθήκες

Οι προηγούμενες DOTIMES και LOOP είναι πολύ χρήσιμες σε απλές περιπτώσεις επανάληψης.
Δεν εξυπηρετούν όμως για τον προσδιορισμό επανάληψης όπου γίνεται αναφορά σε αρχικές
συνθήκες, ούτε για τον προσδιορισμό επανάληψης με χρήση τοπικών μεταβλητών.
Η επαναληπτική συνάρτηση DO έχει εύκολη χρήση και είναι δυνατό να αξιοποιηθεί και
συναρτησιακά.
do
:
(<var>, <term>) n(<Boolean>, <term>)<term> n → <term>
Είναι πιο γενική από τις προηγούμενες DOTIMES και LOOP , διότι επιδέχεται προσδιορισμό
μεταβολών στις συνθήκες κάθε κύκλου. Έχει τα εξής ορίσματα:
(do <λίστα-από-ζεύγη> (<έλεγχος> <επιστροφή>) <σώμα> )
– Το πρώτο όρισμα <λίστα-από-ζεύγη> είναι μια λίστα από λίστες δύο στοιχείων, του τύπου:
( <μεταβλητή> <αρχική-τιμή> )
δηλ. έχει τη μορφή:
( (var1 val1) (var2 val2) … (vark valk) )

(οι τιμές valΧ όπως πάντα, αν δεν αποτελούν φόρμες προς υπολογισμό, πρέπει να δίνονται
"quoted", αλλιώς θα υπολογίζονται πρώτα από όλα).

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

– Το δεύτερο όρισμα (<έλεγχος> <επιστροφή>) είναι λίστα δύο στοιχείων, όπου αν ο
<έλεγχος> υπολογιστεί ως αληθής τιμή, τότε υπολογίζεται η <επιστροφή>, η οποία δίνεται ως
έξοδος και η επανάληψη σταματά. Είναι το κριτήριο διακοπής και ταυτόχρονα προσδιορίζει την
έξοδο της συνάρτησης.
276

– Ακολουθεί το <σώμα> που υπολογίζεται σε κάθε κύκλο. Είναι ένα "εννοούμενο progn", δηλ.
αποτελείται από καμμία ή περισσότερες διαδοχικές φόρμες υπολογισμού.
Οι υπολογισμοί στο σώμα μπορούν να επηρεάσουν την τιμή κάθε τοπικής μεταβλητής και κατά
συνέπεια τον ίδιο τον έλεγχο, το νέο κύκλο υπολογισμού του σώματος, και την έξοδο.
Υπογραμμίζουμε οτι το σώμα είναι προαιρετικό∙ είναι δυνατό, οι απαιτούμενες λειτουργίες να
προσδιορίζονται στον υπολογισμό των στοιχείων των προηγουμένων ορισμάτων.
Όπως σε όλες τις επαναληπτικές συναρτήσεις, ενδέχεται να έχει ορισμένες δηλώσεις που
καθορίζουν στοιχεία του σώματος. Αυτές βέβαια είναι φόρμες που πρέπει να προηγούνται από τις
φόρμες προς εκτέλεση που κάνουν αναφορά στις δηλώσεις αυτές.
Παρaδείγματα
Επανάληψη μέσω της DO
i) Η συνάρτηση "ύψωση σε δύναμη" :
(defun do-expo (basis exponent)
(do
( (result 1) (expnt exponent) )
((zerop expnt) result)
(setf result (* basis result))

; αρχικές τιμές παραμέτρων
; έλεγχος και επιστροφή

; δήλωση, πρώτη φόρμα του σώματος
(setf expnt (- expnt 1) )) )

; δήλωση, δεύτερη φόρμα του σώματος

Ο υπολογισμός εμπεριέχεται σ' αυτές τις δηλωτικές φόρμες, και η συνάρτηση DO-EXPO έχει
πιο συμπαγή μορφή.
Χρόνος υπολογισμού:
(time (do-expo 1050 2230)) → …
–Execution time: 0.41 seconds

(χρόνος κατά το 1/3 μεγαλύτερος της προηγούμενης DOTIMES-EXPO, που σημαίνει οτι η
λειτουργία τής DOTIMES είναι πιο άμεση από της DO).
ii) Να δοθεί επαναληπτική διαδικασία ΙΤ-REVERSE που να αντιστρέφει λίστα.
(defun it-reverse (lis)
(do
( (x lis (cdr x)) (res nil (cons (car x) res)) )
((null x) res) ))

Επανάληψη με επεξηγηματική συνθήκη τερματισμού και αντίστοιχη έξοδο

Σε προηγούμενες επαναληπτικές συναρτήσεις είδαμε τερματισμό εξαρτημένο από "κάτι που θα
συμβεί". Όμως σε επιτακτικές επαναληπτικές διαδικασίες είναι χρήσιμο να διαθέτουμε κάτι
περισσότερο: ένα τρόπο επεξηγηματικής (explicit) θέσης συνθήκης τερματισμού, ανεξάρτητα
277

της συγκεκριμένης συνάρτησης που χρησιμοποιούμε. Αυτό γίνεται με συνδυασμό WHEN και
RETURN .

Αν η συνάρτηση επανάληψης προβλέπει δικό της μηχανισμό εξόδου, χρειάζεται να δώσουμε
ιδιαίτερη προσοχή στη λειτουργικότητα που θα προκύψει ενσωματώνοντας τη φόρμα (when…
return…) ).
when
: Η συνάρτηση WHEN δέχεται δύο ορίσματα, πρώτο μια μπουλιανή έκφραση (λογικός
έλεγχος) και δεύτερο μια φόρμα προς εκτέλεση (σώμα). Το σώμα εκτελείται μόνο αν η μπουλιανή
έκφραση έχει τιμή Τ (αλλιώς επιστρέφει NIL). Χρησιμοποιείται σε επαναλαμβανόμενους
κύκλους, για καθορισμό του τερματισμού.
(when (> x y) (print "ισχύει"))

unless : Η συνάρτηση UNLESS λειτουργεί αντίθετα από την WHEN : Αν δεν πετύχει ο λογικός
έλεγχος, τότε μόνο εκτελείται το σώμα. Έτσι, σε έναν επαναλαμβανόμενο κύκλο, το σώμα
εκτελείται όσο δεν πετυχαίνει ο λογικός έλεγχος.
Οι WHEN και UNLESS ελέγχουν, αλλά δεν διακόπτουν την εκτέλεση. Χρειάζεται να
συνδυαστούν με ειδική συνάρτηση διακοπής, και αυτή είναι η RETURN :

return : συνάρτηση διακοπής του υπολογισμού στο επίπεδο όπου αναφέρεται η RETURN .
Χωρίς όρισμα, ο υπολογισμός διακόπτεται και επιστρέφει NIL . Με ένα όρισμα, ο υπολογισμός
διακόπτεται και επιστρέφεται η τιμή του ορίσματος.

when – return : Είδαμε χρήση του συνδυασμού when - return στην LOOP. Οι WHEN και
RETURN σε συνδυασμό μπορούν να χρησιμοποιηθούν στα πλαίσια κάθε επαναληπτικής ή
αναδρομικής συνάρτησης, ή ακόμα και οποιασδήποτε φόρμας, επαναληπτικής, αναδρομικής ή
κοινού υπολογισμού. Η σύνταξη της συνδυασμένης χρήσης είναι:
… (when <έλεγχος> (return <αποτέλεσμα> ) ) …
Η φόρμα αυτή χρησιμοποιείται σε κάθε περίπτωση συνάρτησης επανάληψης που συντακτικά δεν
προβλέπει κριτήριο διακοπής.

Δεν αποκλείεται και η χρήση των WHEN και RETURN ανεξάρτητα (η WHEN επιστρέφει Τ ή
NIL, και η RETURN τερματίζει την τρέχουσα διαδικασία).

Προκαθορισμός μεταβολής τιμών σε επανάληψη
Μια γενικότερη χρήση της DO είναι αυτή όπου κατά την ανάθεση τιμών στις τοπικές μεταβλητές,
να δώσουμε εκτός από τις αρχικές τιμές, και αυτές που θα παίρνουν σε κάθε βήμα. Η σύνταξη
είναι:
(do
(<τριάδα1> <τριάδα2> ... <τριάδαk>)
(<έλεγχος> <επιστροφή>)
<σώμα> )
Η κάθε τριάδα είναι λίστα τριών στοιχείων:
– πρώτο στοιχείο είναι το όνομα μεταβλητής,
278

– δεύτερο στοιχείο είναι η αρχική τιμή της, και
– τρίτο στοιχείο είναι η τιμή που παίρνει στις επαναλήψεις∙ η τιμή αυτή είναι φόρμα που
υπολογίζεται, ενδεχομένως βάσει αυτών που ήδη προέκυψαν σε προηγούμενο κύκλο, και
καθορίζει την τιμή της μεταβλητής για το συγκεκριμένο κύκλο (δηλ. τον υπολογισμό του
σώματος που ακολουθεί).
Πχ, αντί για το (result 1) του προηγούμενου παραδείγματος, στη θέση του να τεθεί:
(… (result 1 (* basis result)) …)

Παράδειγμα
Με αυτή τη χρήση του DO η παραπάνω συνάρτηση DO-EXPO γράφεται:
(defun do-3-expo (basis exponent)
(do
( (result 1 (* basis result))
(expnt exponent (1- expnt )) )
( (zerop expnt) result ) ) )

Ενδιαφέρον είναι ότι στην έκφραση αυτή της εκθετικής συνάρτησης δεν υπάρχει σώμα διότι όλες
οι απαιτούμενες πράξεις εκτελούνται κατά την ανάθεση τιμών στις μεταβλητές, και ο κώδικας
είναι πολύ πιο συμπαγής. Το αποτέλεσμα της DO εδώ, είναι το αποτέλεσμα του τελευταίου
υπολογισμού, δηλ. η τιμή της μεταβλητής result με το πέρας των επαναλήψεων.
Χρόνος υπολογισμού:
(time (do-3-expo 1050 2230))

... –Execution time: 0.47 seconds
(παρόμοιος της DO-EXPO)

Επανάληψη με αλληλεξάρτηση των αρχικών συνθηκών

Στη συνάρτηση DO που εξετάσαμε προηγουμένως οι τοπικές μεταβλητές είναι παράλληλες, με
την έννοια οτι η θέση καθεμιάς δεν επηρεάζει –ούτε εξαρτάται από– τις υπόλοιπες (όπως και στις
LET και PROG). Είναι δυνατό (αντίστοιχα με τις LET* και PROG*) να προσδιορίσουμε
μεταβλητές εξαρτημένες μεταξύ τους κατά τη διαδοχή. Για το σκοπό αυτό διατίθεται η
συνάρτηση DO* :
do*

: Είναι παρόμοια με την DO αλλά διαφέρει στο εξής:

– Η συνάρτηση DO (στη μορφή σύνταξης με δυάδες) εκτελεί "παράλληλα" τους κύκλους, σε
ότι αφορά την απόδοση τιμής στις τοπικές μεταβλητές (που ορίζονται στο πρώτο όρισμα). Το
νόημα από αυτό είναι ότι: η αναφορά στις μεταβλητές αρχικών τιμών είναι ανεξάρτητη από το
πώς έχουν χρησιμοποιηθεί και μεταβληθεί αυτές κατά τη διάρκεια του κύκλου. Στην DO κάθε
ανάθεση τιμής είναι ανεξάρτητη από τις άλλες, και αναφέρονται όλες στις αρχικές συνθήκες,
που θεωρούνται αναλλοίωτες.
– Η συνάρτηση DO* εκτελεί «σειριακά» του κύκλους, με το νόημα ότι: σε κάθε κύκλο
279

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

Αντίθετα λοιπόν από την DO, στην DO* κάθε αλλαγή που γίνεται (πχ. σε τιμή μεταβλητής, σε
ιδιότητα, γενικά σε περιεχόμενο συμβόλου) αφορά και τον επόμενο κύκλο. Επί πλέον, κάθε
ανάθεση τιμής εξαρτάται από το τι τέθηκε προηγουμένως, στην ίδια φόρμα. Αυτό, προφανώς,
οδηγεί σε διαφορετικό αποτέλεσμα μόνο όταν υπάρχει αλληλεξάρτηση μεταβλητών.
Παραδείγματα
1. Επανάληψη χωρίς αλληλεξάρτηση μεταβλητών:
Μέτρηση πλήθους όρων λίστας.
(defun mycount (mylist)
(do*
;; η templist ξεκινά με είσοδο την λίστα mylist

; και την αποκεφαλίζει διαδοχικά
( (templist mylist (cdr templist))
;; το count αυξάνεται κατά 1
(count 0 (setq count (1+ count))) )

;; όταν αδειάσει η λίστα, επιστρέφεται η τιμή τού count
((null templist) count) ))

Εφαρμογή:
(mycount ’(a b c d))
→ 4
H MYCOUNT αν ήταν ορισμένη με DO αντί για DO* , θα έδινε το ίδιο αποτέλεσμα.

2. Επανάληψη με αλληλεξάρτηση μεταβλητών:
Αναστροφή λίστας.
(defun myinverse (mylist)
(do*
( (templist mylist (cdr templist))
(result (list (car mylist)) (cons (car templist) result)) )
( (null (cdr templist)) result ) ))

Η μεταβλητή result παίρνει τιμή συσσωρευτικά τα στοιχεία της λίστας mylist, αρχίζοντας από
την κεφαλή της, και έτσι προκαλούμε αναστροφή της λίστας που δίνεται ως όρισμα. Όταν
φθάσει στο τελευταίο στοιχείο της η templist, επιστρέφεται η τιμή της result .
Εφαρμογή:
(myinverse ’(a b c d)) → (d c b a)
Προσοχή: η συνάρτηση myinverse αν γραφεί με DO στη θέση του DO* , θα δώσει διαφορετικό
αποτέλεσμα: (c b a a)

280

Επαναλαμβανόμενη εκτέλεση σώματος, με μεταβλητή που σαρώνει λίστα τιμών
Η βαθειά σχέση του ΣΠ με τις λίστες δεν μπορούσε να αφήσει έξω από την επανάληψη τη
σάρωση λίστας για συγκεκριμένο σκοπό υπολογισμού. Η απλούστερη συνάρτηση που επιτελεί
αυτό στο σκοπό είναι η DOLIST :
dolist
: Ειδικής σύνταξης συνάρτηση, που συντάσσεται με δύο τρόπους, όπου ο πρώτος –και
απλούστερος– είναι περισσότερο χρήσιμος σε επιτακτική ανάπτυξη αλγόριθμου:
(dolist
( <μεταβλητή> <λίστα-τιμών> )
<σώμα> )
ενώ ο δεύτερος είναι περισσότερο χρήσιμος σε δηλωτική ανάπτυξη:
(dolist
( <μεταβλητή> <λίστα-τιμών> <φόρμα-αποτελέσματος> )
<σώμα> )
Εκτελεί το σώμα κατ’ επανάληψη, με τιμή της μεταβλητής διαδοχικά τις τιμές από τη <λίστατιμών>. Αν υπάρχει <φόρμα-αποτελέσματος> την υπολογίζει μετά το πέρας των επαναλήψεων,
βάσει αυτών που έχουν προκύψει, αλλιώς επιστρέφει NIL.
Ερωτήσεις
i) Τί κάνει η παρακάτω φόρμα;
(dolist
(x '(1 2 3 4) (print q))
(setq q (* x x)))

Απάντηση: Το q παίρνει διαδοχικά τιμές 1, 4, 9, 16 και τυπώνεται η τελευταία:
→ 16
ii)Τί κάνει η παρακάτω φόρμα;
(dolist
(x '(1 2 3 4) (print lis))
(cons x lis))

Απάντηση: Η λίστα lis διαμορφώνεται σταδιακά, από τη διαδοχική προσθήκη των στοιχείων∙
μόνο η τελική της μορφή τυπώνεται, η οποία είναι η αρχική λίστα ανεστραμμένη:
→ (4 3 2 1)

281

6.3.4 Κάθετη σάρωση ομάδας λιστών
Ο υπολογισμός που είδαμε με τη DOLIST λειτουργεί σαρωτικά πάνω στη λίστα εισόδου, αλλά
"αφήνει στο σώμα την ευθύνη" τού τί θα υπολογιστεί, που σημαίνει πως θα συνθέσουμε τον
υπολογισμό στο επίπεδο της συγγραφής της DOLIST . Ένας γενικότερος υπολογισμός είναι, να
καθοριστεί o υπολογισμός σε επίπεδο εκτέλεσης , δηλ. να δοθεί ως είσοδος η μέθοδος
υπολογισμού που πρόκειται να εφαρμοστεί (δέσμευση λ-μεταβλητής).
Διατίθενται διάφορες συναρτήσεις αυτής της κατηγορίας, με εξειδικευμένη λειτουργικότητα που
αντιμετωπίζει τη "δύσκολη" περίπτωση να προκαλεί διαδοχικά επαναλαμβανόμενη εφαρμογή της
ίδιας συνάρτησης σε ομάδα ή ομάδες στοιχείων, όπου η εφαρμοζόμενη συνάρτηση να
καθορίζεται σε επίπεδο εφαρμογής, δηλ. ως τιμή ορίσματος.
Αυτές οι συναρτήσεις έχουν κάπως "περίεργα" ονόματα, που είναι κληρονομιά από τις αρχικές
υλοποιήσεις της Lisp.

α'. Κάθετη σάρωση ομάδας λιστών

Μια βασική συνάρτηση αυτού του είδους είναι η MAPCAR , που δέχεται ν+1 ορίσματα: το
πρώτο είναι συνάρτηση που δέχεται όρισμα λίστα, και τα υπόλοιπα ν είναι μια διαδοχή από
λίστες (τουλάχιστον μία).
mapcar :

<symbol-function><list> n → <list>

Η συνάρτηση εισόδου πρέπει να είναι “κοινή” συνάρτηση, και όχι ειδικής μορφής ή macro.

Η συνάρτηση εισόδου εφαρμόζεται διαδοχικά, παίρνοντας κάθε φορά ως όρισμα μια λίστα, που
σχηματίζεται την πρώτη φορά από τις κεφαλές των ν λιστών που δίνονται, και ακολούθως τις
κεφαλές των σωμάτων των λιστών, κοκ. μέχρι να "αδειάσει η μικρότερη". Τα αποτελέσματα
συσσωρεύονται σε λίστα, η οποία επιστρέφεται ως τιμή της MAPCAR . Επομένως η λίστα που
επιστέφεται έχει μήκος ίσο με το μήκος της μικρότερης λίστας εισόδου. Η φόρμα κλήσης είναι:
(mapcar #'<όνομα-συνάρτησης> <λίστα1> <λίστα2> … <λίσταk> )
Παραδείγματα
1. Εφαρμογή της MAPCAR με πρώτο όρισμα - συνάρτηση που να δέχεται ένα όρισμα, και
δεύτερο τη λίστα των περιπτώσεων όπου θέλουμε να εφαρμοστεί:
Μετασχηματισμός στοιχείων λίστας με εφαρμογή συνάρτησης.
(defun square (x) (* x x))
(mapcar #'square '(1 3 5 7) )
→ (1 9 25 49)

Βλέπουμε οτι στην ειδική αυτή περίπτωση, η MAPCAR παίζει ένα σημαντικά γενικό ρόλο:
εφαρμόζει διαδοχικά την συνάρτηση που δίνεται ως πρώτο όρισμα πάνω στα στοιχεία της λίστας
που δίνεται ως δεύτερο όρισμα και συλλέγει τα αποτελέσματα σε λίστα, την οποία επιστρέφει.
Είναι ο κύριος τρόπος μετασχηματισμού λίστας βάσει κανόνα μετασχηματισμού.
282

2. Εφαρμογή της MAPCAR όταν το όρισμα - συνάρτηση δέχεται περισσότερα ορίσματα
(σταθερού ή ελεύθερου πλήθους) :
Πολλαπλασιασμός στοιχείων δύο ή περισσοτέρων λιστών "ένα - προς - ένα".
(mapcar #'* '(1 3 5 7) ’(8 6 4 2))
→ (8 18 20 14)
(mapcar #'* '(1 2 3) '(2 3 4) '(3 4 5))
→ (6 24 60)
Βλέπουμε οτι, στην περίπτωση συνάρτησης ελεύθερου πλήθους ορισμάτων, αυτή εφαρμόζεται σε
πλήθος ίσο προς το πλήθος των λιστών εισόδου.
3. Εφαρμογή της MAPCAR σε ανόμοιου μήκους λίστες:
Εκτελείται ο υπολογισμός μέχρι να καλυφθεί η μικρότερη λίστα:
(mapcar #'* '(1 2 3 4 5 6) '(1 3 5 7) '(2 4 6))
→ (2 24 90)

Αποτέλεσμα είναι η λίστα των γινομένων "όλα τα πρώτα", "όλα τα δεύτερα", κλπ. μέχρις
εξαντλήσεως της μικρότερης λίστας (η τελευταία εδώ).
(mapcar #'list '(a b c d) '(k l m n o p))
→ ((a k) (b l) (c m) (d n))
Αποτέλεσμα είναι οι δυάδες "πρώτο με πρώτο", "δεύτερο με δεύτερο", κλπ. μέχρις εξαντλήσεως
της μικρότερης λίστας (που είναι η πρώτη).

β'. Κάθετη σάρωση, με συλλογή αποτελεσμάτων σε επιπεδοποιημένη λίστα
Παρεμφερής με την MAPCAR είναι η MAPCAN , που διαφέρει σε δύο σημεία: i) στο οτι
εφαρμόζεται αποκλειστικά σε συνάρτηση που επιστρέφει λίστα, ii) το συλλεγόμενο αποτέλεσμα λίστα από λίστες επιπεδοποιείται, σε λίστα ενός επιπέδου.
mapcan : #'<symbol-function><list> n → <list>
Προσοχή: σε κάποιους compilers είναι καταστροφική για το πρώτο όρισμα.
Παράδειγμα
(mapcan #'list '(a b c d) '(k l m n o p))
→ (a k b l c m d n)

Παρατηρούμε πως η απάντηση που πήραμε, είναι στην ίδια διάταξη αναφοράς ατόμων με την
προηγούμενη, αλλά τώρα η λίστα είναι επιπεδοποιημένη

γ' περίπτωση: κάθετη σάρωση, επί πλέον με αποτελέσματα σε συγκεκριμένο τύπο

Είναι δυνατό να χρειαζόμαστε μετατροπή των διαδοχικών αποτελεσμάτων που συλλέγει η
MAPCAN σε συγκεκριμένο τύπο. Αντί να οργανώσουμε χωριστή συνάρτηση σάρωσης των
αποτελεσμάτων και μετατροπής τους, διαθέτουμε τη συνάρτηση MAP , που είναι χαμηλοτέρου
επιπέδου και γρηγορότερη:
283

map :
<type><symbol-function><list> n → <list>
Η συνάρτηση MAP είναι πανομοιότυπη με την MAPCAΝ με τη διαφορά οτι έχει ένα ακόμα
όρισμα, τον τύπο του αποτελέσματος, που τίθεται πρώτο:
(map '<τύπος> #’<όνομα-συνάρτησης> <ακολουθία-1> … <ακολουθία-k> )
Στην έκφραση αυτή, ακολουθία είναι οποιαδήποτε "quoted" φόρμα τύπου sequence, list ή string.
Η συνάρτηση MAP δίνει ιδιαίτερη ευχέρεια στο χειρισμό των χαρακτήρων από δεδομένα strings .
Παραδείγματα:
(char-code #\g)
→ 103
(code-char 103)
→ #\g
(map 'list #'char-code “george”) → (103 101 111 114 103 101)
(map 'string #'char-code “george”)
→ “george”
(map 'string #'code-char “george”) → “george”
(map 'list #'code-char “george”) → (103 101 111 114 103 101)

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

6.3.5 Τεχνικές και χρήσεις αναδρομής
Η χρήση αναδρομής εφαρμόζεται σε πολλές, διαφορετικής σημασίας περιπτώσεις, που
εξυπηρετούνται με διάφορες ειδικές τεχνικές, όπως: με αρχικές συνθήκες που προσδιορίζονται ως
λ-μεταβλητές της συνάρτησης ή ως καθολικές μεταβλητές, με χρήση συσσωρευτή, με
ανακύκλωση "προς τα εμπρός" (ξεκινώντας από την τελική απαίτηση και βηματικά προς τις
αρχικές συνθήκες, που καθορίζουν και το πέρας της αναδρομής) ή "προς τα πίσω" (από τις
αρχικές συνθήκες και βηματικά προς το την τελική απαίτηση, που καθορίζει και το πέρας της
αναδρομής).
Παραδείγματα
1. Πολλαπλή αναδρομή με αρχικές συνθήκες καθοριζόμενες κατά την εφαρμογή:
Οι "πύργοι του Hanoi" ως διπλή αναδρομή: "να μεταφερθούν n δίσκοι από τη στήλη a στην b με
βοηθητική την c , μετακινώντας μόνο ένα δίσκο σε κάθε κίνηση χωρίς να τοποθετείται
μεγαλύτερος πάνω σε μικρότερο".
(defun hanoi (n a b c)
(cond
( (= n 1) (move-one-disk a b) )
( t (hanoi (1- n) a c b)
(move-one-disk a b)
284

(hanoi (1- n) c b a))))
(defun move-one-disk (a b)
(princ "μετακίνησε δίσκο από τη στήλη ")
(princ a) (print " στη ") (princ b))

Η "φιλοσοφία" δόμησης αυτού του αλγόριθμου είναι: "να δώσει στον χρήστη οδηγίες εκτέλεσης
του πειράματος".
Αν αντίθετα, αυτό που θέλουμε να πετύχουμε, είναι υλοποίηση της "γνώσης" που αφορά το
πρόβλημα, δηλ. εκτέλεση του ίδιου του πειράματος από τον υπολογιστή με τρόπο που να
κατασκευάζει τους πύργους και να "ξέρει" ανά πάσα στιγμή την κατάστασή τους, αρκεί να
σχηματίσουμε τρεις αντίστοιχες λίστες.
Αν επί πλέον θέλουμε να μείνει αποθηκευμένη και η πορεία της κτέλεσης, δεν έχουμε παρά να
σχηματίσουμε την ιστορία των λιστών αυτών, πχ. σε μορφή από λίστες λιστών που θα
καταχωρούνται οι διαδοχικές φάσεις.
2. Χρήση στοίβας για συσσώρευση πράξεων που ανάγονται στον τελικό υπολογισμό:
Με απλή αναδρομή στο σώμα, να μετρηθούν όσα από τα στοιχεία δεδομένης λίστας είναι
ακέραιοι.
Δεν χρησιμοποιούμε επεξηγηματική μεταβλητή - μετρητή αλλά το ίδιο το επιστρεφόμενο
αποτέλεσμα:
(defun count-integers (lis)
(cond
; αν η λίστα lis είναι κενή, έχει πλήθος στοιχείων 0 :
((endp lis) 0)
((not (integerp (car lis)))

; αν το πρώτο στοιχείο δεν είναι ακέραιος, τότε μέτρα τα υπόλοιπα
(count-integers (cdr lis)))

; αλλιώς πρόσθεσε 1 στο μέτρημα
(t

(+ 1 (count-integers (cdr lis))))))

3. Αναδρομή που "μετρά", αποφεύγοντας τη δημιουργία στοίβας, με χρήση μιας πρόσθετης
μεταβλητής ως μετρητή βημάτων:
Να γίνει το προηγούμενο, με μέτρηση των βημάτων.
Σαρώνουμε τη λίστα και σε κάθε συνάντηση ακέραιου προσθέτουμε μια μονάδα σε ένα μετρητή
(η λ-μεταβλητή rslt):
(defun count-int (lis rslt)
(cond
((endp lis) res)
((count-int (cdr lis) (1+ rslt))))

Η εκτέλεση πρέπει να δοθεί με αρχική τιμή του μετρητή 0 . Γι' αυτό δίνουμε μια συνάρτηση
285

διευκόλυνσης του χρήστη:
(defun quick-count-integers (lis)
(count-int lis 0))

4. Χρήση μιας πρόσθετης μεταβλητής ως συσσωρευτή των επί μέρους αποτελεσμάτων της
αναδρομής:
Να γίνει επιπεδοποίηση λίστας με συσσώρευση των λαμβανομένων όρων σε λίστα.
(defun flat1 (lis rslt)
; lis είναι η λίστα εισόδου και rslt η λίστα εξόδου
(if (atom lis)
(if lis (cons lis rslt) rslt)
(flat1 (car lis) (flat1 (cdr lis) rslt))))
Επειδή η rslt είναι η έξοδος αλλά και λ-μεταβλητή της συνάρτησης, πρέπει να πάρει τιμή
εισόδου, και προφανώς λειτουργεί σωστά με αρχική τιμή NIL

286

6.4 Ασκήσεις
1. α) Να δώσετε τον αλγόριθμο επίλυσης πρωτοβάθμιου συστήματος 2 2, με τη μέθοδο
Cramer, χρησιμοποιώντας κιβωτισμένα blocks και επιστροφές.
β)
Να δώσετε τον αλγόριθμο επίλυσης πρωτοβάθμιου συστήματος Ν Ν
2. Χρησιμοποιώντας την loop να γράψετε διαδικασία με έξοδο "when - return" που να διαβάζει
(με read) από την είσοδο κατ' επανάληψη ένα string και να μετρά και να τυπώνει το πλήθος
των χαρακτήρων του, μέχρι να δοθεί το string "end".

3. α) Να προσδιορίσετε, χρησιμοποιώντας την PROGN , πρόγραμμα που να υπολογίζει το
τετράγωνο της τιμής ενός ορισμένου αριθμητικού συμβόλου, στη συνέχεια τον κύβο του
προηγούμενου αποτελέσματος, και τέλος τον λογάριθμο του προηγούμενου αποτελέσματος,
τον οποίο και να τυπώνει.
β) Να προσδιορίσετε διαδικασία PROG2 που να υπολογίζει το γινόμενο των τιμών
ορισμένων συμβόλων - μεταβλητών που δίνονται αρχικά.
γ) Να δώσετε παράδειγμα PROG και PROG* ίδιας σύνταξης όπου να διαφοροποιείται η
λειτουργικότητα και το αποτέλεσμα.
4. Να δώσετε συνάρτηση δύο ορισμάτων, όπου: i) όταν το πρώτο είναι ακέραιος θετικός και το
δεύτερο string, να επιστρέφει το χαρακτήρα του string της τάξης αυτής (τον τελευταίο
χαρακτήρα αν έχει λιγότερους), ii) όταν το πρώτο είναι ακέραιος θετικός n και το δεύτερο
αριθμός a να τυπώνει n φορές το a , και iii) οποιουδήποτε τύπου και αν είναι το πρώτο, όταν
το δεύτερο είναι λίστα, η έξοδος να αντικαθιστά τα στοιχεία της λίστας με το πρώτο.
5. Με χρήση της CCASE να οργανώσετε πρόγραμμα "menu" που να διαθέτει τρεις κατηγορίες
πιάτων (πρώτο, δεύτερο και επιδόρπιο) όπου να χαρακτηρίζονται ορισμένοι συνδυασμοί
τριάδων ως εξαιρετικοί, και να υπάρχουν τέτοιοι για κάθε συνδυασμό τριάδων Ανάλογα με το
αν ο πελάτης ζητήσει οποιοδήποτε συνδυασμό τριών πιάτων είτε να παίρνει την απάντηση
"εξαιρετικά" είτε, αν δεν χαρακτηρίζεται έτσι, με βάση το κύριο πιάτο που ζήτησε να παίρνει
οδηγίες για επιλογή των άλλων δύο.

6. α) Να γράψτε αναδρομική στο σώμα συνάρτηση τρίτης τάξης FIB3 που να υπολογίζει τον
ν-οστό όρο της ακολουθίας
an = an-1 + 2an-2 + 3an-3 , με a0 =a1 = a2 = 1
β)
Να μετατρέψετε την προηγούμενη σε αναδρομική στην ουρά, χρησιμοποιώντας
αθροιστή.
γ)

Να εκφράσετε την επανάληψη της (β) μέσω της DOTIMES

7. Να κατασκευάσετε συνάρτηση ΙΝΤΕGΕRS δύο αριθμητικών ορισμάτων, που να παράγει το
σύνολο των ακεραίων που περιέχονται στο πραγματικό διάστημα [a , b] και να το επιστρέφει
ως λίστα. Αντίστοιχα, στα πραγματικά διαστήματα (a , b) , (α , β] , [α , β) .

287

8. Να προσδιορίσετε συνάρτηση SOLVE η οποία να περιέχει φόρμα LET πέντε ακεραίων
τοπικών μεταβλητών a , b , c , d , e , των οποίων την τιμή να διαβάζει με READ από την
είσοδο. Μέσα στη LET αυτή, μια φόρμα do δύο ακεραίων συμβόλων x, y, με αρχική τιμή 0,
όπου η DO να υπολογίζει τις ακέραιες λύσεις της διοφαντικής εξίσωσης:
ax+by = c , (x, y)  [-d, d]  [-e, e]
H κλήση της συνάρτησης SOLVE να παρέχει και οδηγίες προς το χρήστη: "δώσε τα άκρα των
διαστημάτων", "δώσε τους συντελεστές".

9. Να ορίσετε συνάρτηση FLAT η οποία να επιπεδοποιεί σε απλή λίστα μια λίστα από λίστες. Να
σχηματίσετε συνάρτηση MYMAP που να λειτουργεί όπως η MAP αλλά να ορίζεται μέσω των
MAPCAR , COERCE και FLAT .

10. Να προσδιορίσετε τόπο Α που να περιέχει τοπικές μεταβλητές v1 και v2 με αρχική τιμή, και
διαδοχικά: α) να ορίζει καθολική συνάρτηση F1 η οποία να κάνει χρήση των v1 και v2, β) να
ενεργοποιεί λειτουργία Β η οποία να επαναπροσδιορίζει τις τιμές των v1 και v2 , και γ) να ορίζει
καθολική συνάρτηση F2 η οποία να κάνει χρήση των v1 και v2 . Στη συνέχεια να καλέσετε τις F1
και F2 στο toploop. Ποιές τιμές των v1 και v2 θα δουν οι αντίστοιχες κλήσεις και γιατί;

288

ΜΕΡΟΣ B
Συναρτησιακή Δόμηση και
Εφαρμογές

289

7 Οι λ-εκφράσεις ως Υπολογιστικές Οντότητες
Στο κεφάλαιο αυτό θα μελετήσουμε τη θεωρητική βάση του λ-λογισμού και το ρόλο που παίζει
στη θεμελίωση του συναρτησιακού προγραμματισμού.
Στη συνέχεια θα δούμε από κοντά τον τρόπο σύνταξης και συναρτησιακής χρήσης των λεκφράσεων στη Lisp και ιδίως τις διάφορες σημασίες που παίρνουν, ανάλογα με το πώς εμείς
εννοούμε τον υπολογισμό.
Θα δούμε διαδικαστικές μορφές χρήσης λ-εκφράσεων σε σχέση με δηλωτικές, και θα
μελετήσουμε τη βαθύτερη σχέση που έχουν συναρτήσεις, λ-εκφράσεις και μεταβλητές.

7.1 Αρχές της Τυπικής Θεωρίας του λ-Λογισμού
Στην ενότητα αυτή θα κάνουμε μια σύντομη επίσκεψη στην τυπική (formal) θεωρία του λλογισμού και θα δούμε τις θεμελιώδεις αρχές στις οποίες υπακούει αυτή η θεωρία.
"λ-λογισμός" (λ-calculus ή lambda calculus) είναι ένας Μαθηματικός Λογισμός, ο οποίος
τυποποιεί το νόημα του υπολογισμού. Οι οντότητες του λ-λογισμού, που αποκαλούνται
εκφράσεις, είναι: λ-εκφράσεις, συναρτήσεις και φόρμες εφαρμογής. Θεμελιώδες νόημα είναι το της
λ-μεταβλητής, που δεν είναι "οντότητα με αυτόνομη υπόσταση" αλλά δομικό στοιχείο για το
κτίσιμο λ-εκφράσεων.
Υπάρχουν διάφορες προσεγγίσεις θεμελίωσης του λ-λογισμού, με βασικούς κλάδους την χωρίς
τύπους (typeless) θεώρηση, όπου δεν διακρίνουμε τύπους-κατηγορίες εκφράσεων (αλλά
μπορούμε να ορίσουμε), και την με τύπους θεώρηση, όπου διακρίνουμε πρωταρχικά τους
βασικούς τύπους εκφράσεων (αριθμός, λίστα, σύμβολο…) και βάσει αυτών ορίζονται οι
αντίστοιχες κλάσεις οντοτήτων. Εδώ περιγράφουμε την πρώτη προσέγγιση, που είναι πολύ
συντομότερη.
Ο συμβολισμός στο λογισμό αυτό διαφέρει από το συμβολισμό της Lisp, αλλά και η υλοποίηση
των λ-εκφράσεων στη Lisp ακολουθεί ελαφρά διαφορετικό δρόμο από το θεωρητικό, όπως θα
δούμε. Παρ' όλα αυτά, υπάρχει "σχεδόν αμφιμονοσήμαντη" αντιστοιχία των εννοιών και
συμβολισμών, που είναι εύκολο να παρακολουθήσει κανείς.

7.1.1 Γενικές αρχές του λ-λογισμού
Ο Allonzo Church θεμελίωσε τον λ-λογισμό το 1936, για να αποδώσει φορμαλιστικά το νόημα
του υπολογισμού [Chur41]. Στα πλαίσια αυτής της θεωρίας προσδιόρισε τα θεμελιακά νοήματα
290

της λ-μεταβλητής, της συναρτησιακής έκφρασης, της εφαρμογής, και του συμβόλου. της
συνάρτησης και της μεταβλητής. και συνάρτησης, του αλγόριθμου που αναφέρεται σε μεταβλητές,
και του υπολογισμού που εκτελείται βάσει του προδιαγραμμένου αλγόριθμου για συγκεκριμένη
είσοδο τιμών των μεταβλητών. Η έννοια του αλγόριθμου δίνεται με όρους σύνθεσης
συναρτησιακής έκφρασης, και η έννοια της εκτέλεσης αλγόριθμου, με όρους εφαρμογής
συνάρτησης πάνω σε ορίσματα.
Όπως απέδειξε ο Church, ο λ-λογισμός είναι μια δομή ισοδύναμη της αφηρημένης μηχανής
Turing, με την έννοια οτι οτιδήποτε μπορεί να εκφραστεί ως μια μηχανή Turing μπορεί να
εκφραστεί και ως λ-έκφραση και το αντίθετο. 'Αρα μπορεί να θεωρηθεί ως ένας ορισμός της
έννοιας αλγόριθμος. Βάσει της λεγόμενης "θέσης των Church και Turing":
όλοι οι ορισμοί που έχουν δοθεί ή είναι δυνατό να δοθούν για την έννοια του αλγόριθμου, είναι
ισοδύναμοι
Δύο βασικές "εκδόσεις" του λ-λογισμού είναι δυνατό να αναπτυχθούν, ανεξάρτητα:
i) η χωρίς τύπους έκδοση (typeless version), όπου οι τύποι δεδομένων ορίζονται εκ των
υστέρων και χρησιμοποιούνται δυνητικά, και
ii)η με τύπους έκδοση (typed version) όπου πρωταρχικά ορίζονται οι τύποι δεδομένων και
πάνω σ' αυτούς οι ειδικότερες οντότητες, που είναι τα αντικείμενά τους.
Εδώ ακολουθούμε την πρώτη έκδοση, που είναι πολύ απλούστερη.

Χρησιμοποιούμενες έννοιες και ορισμοί
"λ-έκφραση" είναι μια παράσταση που αρχίζει με το χαρακτήρα λ , ακολουθούν το όνομα της λμεταβλητής και μια τελεία, και στη συνέχεια αναγράφεται το σώμα της λ-έκφρασης, που είναι μια
αποδεκτή στο λ-λογισμό έκφραση. Δηλαδή έχει τη μορφή:
λx.B
"έκφραση" είναι μια παράσταση η οποία είναι είτε όνομα, είτε λ-έκφραση είτε εφαρμογή.
"εφαρμογή" είναι μια παράσταση η οποία παράσταση η οποία συντίθεται από δύο μέρη, τη
συναρτησιακή έκφραση και το όρισμα, που τη σημειώνουμε με τη μορφή:
(<συναρτησιακή έκφραση> <όρισμα>)
"συναρτησιακή έκφραση" είναι μια έκφραση η οποία μπορεί να δεχθεί είσοδο, που είναι το όρισμά
της στη εφαρμογή, και όταν εφαρμοστεί στο όρισμα δίνει αποτέλεσμα σύμφωνα με υπολογισμό
που καθορίζεται από τη συναρτησιακή έκφραση.
"αλγόριθμος", "μέθοδος", "διαδικασία" είναι συνώνυμα της συναρτησιακής έκφρασης
"υπολογισμός", "διεργασία", "εκτέλεση αλγόριθμου με συγκεκριμένες τιμές εισόδου", "μηχανή
Turing με δεδομένη συμβολοσειρά εισόδου" είναι συνώνυμα της εφαρμογής.
Φορμαλιστικά, τα παραπάνω εκφράζονται ως εξής:
<έκφραση> ::= <όνομα> | <λ-έκφραση> | <εφαρμογή>
<λ-έκφραση> ::= λ<όνομα>.<έκφραση>
<εφαρμογή> ::= (<συναρτησιακή έκφραση> <έκφραση>)
<συναρτησιακή έκφραση> ::= <έκφραση>
291

Θεωρούμε οτι διαθέτουμε ένα τρόπο ονομασίας μιας λ-έκφρασης. Ονομάζουμε συνάρτηση το
όνομα μιας λ-έκφρασης. Θεωρούμε επίσης οτι διαθέτουμε ένα τρόπο ονομασίας μιας
συγκεκριμένης εφαρμογής.
Ταυτοτική λ-έκφραση: Είναι η λx.x για οποιοδήποτε όνομα x .
Η έννοια της εφαρμογής είναι οτι: πρόκειται για μια διμελή σχέση όπου το πρώτο μέλος είναι
"κάτι που εφαρμόζεται πάνω σε κάτι" και το δεύτερο μέλος είναι "το αντικείμενο εφαρμογής του
πρώτου μέλους". Τυπικά δεν τίθεται περιορισμός στο πότε και πώς είναι δυνατή η εφαρμογή,
αλλά καθορίζεται από τη φύση των συναρτησιακών εκφράσεων και των ορισμάτων. Οι πράξεις
πάνω σε λ-εκφράσεις και εφαρμογές ακολουθούν τους τρεις κανόνες που περιγράφονται
παρακάτω.

Θα χρησιμοποιούμε το σύμβολο "=" για να εκφράσουμε τη σχέση "είναι το ίδιο με", που είναι
αντιμεταθετική, και τα σύμβολα "" και "" για να εκφράσουμε τη σχέση "αποτέλεσμα από
εφαρμογή", που επίσης δηλώνουν ισότητα αλλά μετά από συγκεκριμένο υπολογισμό, που σε ότι
αφορά τη διάσταση εξέλιξης του υπολογισμού έχει φορά, και δηλώνουν, αντίστοιχα το καθένα,
από πού ξεκινάμε τον υπολογισμό σε μια έκφραση, όπως θα δούμε παρακάτω.
Με τον όρο συνάρτηση αποκαλούμε το όνομα μια συναρτησιακής έκφρασης. Θεωρούμε οτι η
αναγραφή ονόματος συνάρτησης είναι ταυτόσημη με την αναγραφή της έκφρασης που ορίζει το
όνομα (δηλ. δεν μεσολαβεί υπολογισμός για να αντικατασταθεί το όνομα με την έκφραση∙ στη
Lisp έχουμε ένα σχετικό περιορισμό).

Θεωρούμε τη γραφή:
def <όνομα> ::= <συναρτησιακή έκφραση>
ως περιγραφή της ονομασίας της συνάρτησης που ορίζει το δεύτερο μέλος (σημ: συναντώνται και
άλλα σύμβολα στη βιβλιογραφία αντί του "::=" ). Θεωρούμε οτι μετά τον ορισμό, το <όνομα>
αποτελεί πλέον συνάρτηση που αντιπροσωπεύει τη <συναρτησιακή έκφραση> . Σύμφωνα με
αυτό, δεχόμαστε πως έχουμε το δικαίωμα να χρησιμοποιούμε το όνομα έκφρασης στη θέση της,
χωρίς να μεσολαβεί βήμα υπολογισμού για την αντικατάσταση.

Οι τρεις βασικοί κανόνες μετασχηματισμού
Οι πράξεις αντικατάστασης ονόματος μεταβλητής, εφαρμογής λ-έκφρασης σε ορίσματα, και
απλοποίησης πολλαπλών "λ", εκφράζονται με τους ακόλουθους τρεις θεμελιακούς κανόνες, οι
οποίοι είναι και οι μόνοι κανόνες του υπολογισμού:
α'.

Κανόνας α-μετατροπής (α-conversion ή alpha conversion):

Σε μια λ-έκφραση μπορούμε να αντικαταστήσουμε το όνομα λ-μεταβλητής με όνομα που δεν
αναφέρεται στο σώμα, με την προϋπόθεση οτι θα αντικατασταθεί και σε κάθε εμφάνιση της λμεταβλητής παντού στην έκφραση, δηλ., στο όνομα της λ-μεταβλητής και στο σώμα:
λx.x = λz.z
Πρόκειται για τον κανόνα που επιτρέπει την αλλαγή ονόματος λ-μεταβλητής, ώστε να το
προσαρμόσουμε στο περιβάλλον όπου χρησιμοποιείται.
292

β'.
Κανόνας β-αναγωγής (β-reduction ή beta reduction):
Το αποτέλεσμα μιας εφαρμογής είναι η παράσταση που προκύπτει από το σώμα του πρώτου
μέλους όταν αντικατασταθεί η λ-μεταβλητή του με το δεύτερο μέλος, σε κάθε εμφάνισή της.
(λx.x r) → r
Πρόκειται για τον κανόνα με τον οποίο επιτελείται η εφαρμογή συναρτησιακής έκφρασης σε
όρισμα, και καθορίζει τον τρόπο δέσμευσης λ-μεταβλητής.
γ'.
Κανόνας η-αναγωγής (η-reduction ή eta reduction):
Αν το δεύτερο μέλος σε μια εφαρμογή είναι λ-έκφραση, τότε αποτέλεσμα είναι η λ-έκφραση που
έχει όνομα λ-μεταβλητής το όνομα της λ-μεταβλητής του β' μέλους και σώμα τη συναρτησιακή
σύνθεση των σωμάτων (κάθε εμφάνιση της λ-μεταβλητής στο σώμα του α' μέλους αντικαθίσταται
από την λ-έκφραση που είναι το β' μέλος). Δηλαδή:
Εάν πρόκειται για εφαρμογή ταυτοτικής λ-έκφρασης πάνω σε ταυτοτική, προκύπτει πάλι
ταυτοτική:
(λx.x λy.y) →
λy.y
Εάν πρόκειται για εφαρμογή λ-έκφρασης με σύνθετο σώμα μέσα στο οποίο αναφέρεται η λμεταβλητή, πάνω σε λ-έκφραση με επίσης σύνθετο σώμα, τότε:
(λx.w λy.u) 
λy.z
όπου z είναι το σώμα που προκύπτει από την αντικατάσταση κάθε εμφάνισης του x μέσα στο w,
με u .
Με άλλα λόγια, πρόκειται για τον κανόνα που καθορίζει τον υπολογισμό στην περίπτωση
εφαρμογής πάνω σε όρισμα που είναι συναρτησιακή έκφραση.
Με χρήση των τριών αυτών κανόνων και μόνον, προκύπτουν οι αναγωγές των λ-εκφράσεων και
των εφαρμογών, σε απλούστερες μορφές.

Τάξη υπολογισμού
Σε μια σύνθετη έκφραση είναι δυνατό να έχουμε λ-έκφραση που στο σώμα της περιλαμβάνει
υπολογισμούς (δηλ. εφαρμογές λ-εκφράσεων), καθώς και υπολογισμό που η έκφραση του ενός ή
και των δύο μελών του, περιλαμβάνουν άλλους υπολογισμούς.
Σημαντικό ζήτημα είναι οτι, εάν ξεκινήσουμε "από μέσα προς τα έξω" τους υπολογισμούς,
ενδέχεται να καταλήξουμε σε διαφορετικό αποτέλεσμα από αυτό που καταλήγουμε εάν
ξεκινήσουμε "απ' έξω προς τα μέσα" τους υπολογισμούς (όπως θα δούμε παρακάτω στο
conditional).
Ο όρος κανονική τάξη υπολογισμού (normal order evaluation) χαρακτηρίζει την πρώτη
περίπτωση, και ο όρος τάξη υπολογισμού κατά την εφαρμογή (applicative order evaluation)
χαρακτηρίζει τη δεύτερη περίπτωση. Θεωρούμε στα επόμενα οτι η εφαρμοζόμενη τάξη είναι η
κανονική, εκτός αν ειδικά ορίζεται η τάξη εφαρμογής.
Το σύμβολο

""

συνήθως χρησιμοποιείται για να δηλώσει το αποτέλεσμα εκτέλεσης
293

υπολογισμού κατά την κανονική τάξη, ενώ το σύμβολο "" για να δηλώσει το αποτέλεσμα
εκτέλεσης υπολογισμού κατά την τάξη εφαρμογής.

Χρήση λ-εκφράσεων
Με εφαρμογή των παραπάνω κανόνων, υποθέτοντας ορισμένη τη συνάρτηση "cos" έχουμε:
(λx.(cos x) π)
→ -1
Προφανώς έχουμε τα εξής:
(λx.x λz.z) → λz.z = λx.x
(λx.x λx.x) → λx.x
που σημαίνει οτι υπάρχουν εκφράσεις που μπορούν να εφαρμοστούν στον εαυτό τους.
Συναρτησιακή έκφραση με περισσότερα του ενός ορίσματα:
Θεωρούμε οτι η διαδοχική εφαρμογή:
((func arg1) arg2)
αν το αποτέλεσμα δεν επηρεάζεται από τη σειρά διαδοχής των εφαρμογών μπορεί συμβατικά να
γραφεί στο ίδιο επίπεδο, δηλ. με τα ορίσματα διαδοχικά:
(func arg1 arg2)
Άρα ισχύει:
(func arg1 arg2) = ((func arg1) arg2)
Εκφράσεις με περισσότερες μεταβλητές:
Μια λ-έκφραση μπορεί να έχει σώμα μια λ-έκφραση:
λx.(λy.<σώμα>)
όπου το <σώμα> μπορεί επίσης να είναι λ-έκφραση, κοκ.
Επομένως, εφαρμόζοντας μια έκφραση περισσοτέρων μεταβλητών πάνω σε ένα όρισμα, γίνεται
δέσμευση της πρώτης αριστερά λ-μεταβλητής. Με νέα εφαρμογή, του αποτελέσματος σε
όρισμα, γίνεται δέσμευση της δεύτερης λ-μεταβλητής, κοκ.

Υποθέτοντας την πράξη "τελεία" δεξιά προσεταιριστική, περιττεύει η αναγραφή των
παρενθέσεων. Έτσι γράφουμε:
λx.λy.λz.λw.<σώμα>
αντί για:
λx.(λy.(λz.(λw.<σώμα>)))
Όταν το σώμα λ-έκφρασης είναι πάλι λ-έκφραση, η δεύτερη λέγεται κιβωτισμένη στη πρώτη ή οτι
είναι εγκλεισμένη στην πρώτη. Διαδοχικά κιβωτισμένες λ-εκφράσεις, όπως η παραπάνω, μπορούν
να εφαρμόζονται, αλλά κατά βήματα, ακολουθώντας υποχρεωτικά τη διαδοχή από την εξωτερική
(αριστερότερη) προς την εσωτερική (δεξιότερη) λ-μεταβλητή:
( λx.(λy.(λz.<σώμα με x, y, z>)) arg1) →
→ λy.(λz.<σώμα με arg1, y, z>)
294

( λy.(λz.<σώμα με arg1, y, z>) arg2)

→ λz.<σώμα με arg1, arg2, z>
( λz.<σώμα με arg1, arg2, z> arg3)

→ <παράσταση με arg1, arg2, arg3> =
= <το αποτέλεσμα της εκτέλεσης των πράξεων>
Εάν οι αναφερόμενες λ-μεταβλητές έχουν διαφορετικά ονόματα, η διαδοχή τους, καθορίζει
εμφανώς τη σειρά δέσμευσης. Τί γίνεται όμως, αν συμβεί να έχουν κάποιες το ίδιο όνομα, όπως:
λx.λy.λx.λz.<σώμα με x, y, z>
Εξ αιτίας του εγκλεισμού των υπολογισμών κατά την σειρά (τάξη) που αναφέραμε, έχουμε:
λx.λy.λx.λz.<σώμα με x, y, z> =
= λx.(λy.(λx.(λz.<σώμα με x, y, z>)))
Κατά συνέπεια, στην παραπάνω έκφραση το όνομα x στο σώμα αναφέρεται στην
εσωτερικότερη (δεξιότερη) λ-μεταβλητή x . Αντίθετα, με την εφαρμογή της έκφρασης σε
όρισμα, έστω a, νοείται εφαρμογή της εξωτερικότερης (αριστερότερης) λ-μεταβλητής x , η
οποία δεν αναφέρεται στο σώμα, άρα είναι dummy, και παίζει μόνο ρόλο καθυστέρησης.
Επόμενες εφαρμογές θα προκαλέσουν αντικατάσταση της αντίστοιχής τους λ-μεταβλητής στο
σώμα. Επομένως, η διαδοχή των εφαρμογών ακολουθεί το σχήμα:
(λx.λy.λx.λz.<σώμα με x, y, z> a) → λy.λx.λz.<σώμα με x, y, z>
(λy.λx.λz.<σώμα με x, y, z> b) → λx.λz.<σώμα με x, b, z>
(λx.λz.<σώμα με x, b, z> c)
→ λz.<σώμα με c, b, z>
(λz.<σώμα με c, b, z> d)
→ <παράσταση με c, b, d>
Η <παράσταση με c, b, d> είναι έκφραση όπου έχουν εκτελεστεί όλες οι εφαρμογές.
Χρησιμοποιούμε συχνά λ-μεταβλητή που δεν αναφέρεται στο σώμα, για να ελέγξουμε τη φάση
εκτέλεσης του υπολογισμού: κάθε λ-μεταβλητή "περιμένει τη δέσμευσή της" και κατά συνέπεια,
σε μια διαδοχή υπολογισμών όπου φθάνουμε σε μια dummy μεταβλητή, το βήμα εφαρμογής που
θα ακολουθήσει, δεν θα προκαλέσει μεν καμμία μεταβολή στην έκφραση του σώματος, αλλά
"πρέπει να περάσει από το βήμα αυτό, έστω και χωρίς να κάνει τίποτα".

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

χρόνο της εκτέλεσης απέναντι στο χρόνο στον οποίο υπακούουν τα στοιχεία εισόδου.
Προσοχή: ορισμένοι συγγραφείς σημειώνουν τις εκφράσεις περισσοτέρων λ-μεταβλητών, όπως
λx.λy.λz.M , με: λxλyλz.M . Επίσης, σημειώνουν την εφαρμογή μιας λ-έκφρασης, έστω λx.M ,
πάνω σε όρισμα, έστω Α , με:
λx.MA

7.1.2 Μεταβλητή συναρτησιακή έκφραση∙ αυτοεφαρμογή
Στη γραφή:
(func arg)
ο όρος func είναι συναρτησιακή έκφραση και ο όρος arg είναι οτιδήποτε (αρκεί να ταιριάζει στις
απαιτήσεις που θέτει η func).
Έστω S το σύνολο των εκφράσεων που μπορούν να εφαρμοστούν στον εαυτό τους και έστω οτι
το όνομα s είναι μια τέτοια έκφραση. Δηλαδή έχει νόημα η εφαρμογή:
(s s)
Θα αποκαλούμε μια τέτοια έκφραση αυτοεφαρμογή. Συστήματα που δέχονται αυτοεφαρμογή
αποκαλούνται κατοπτρικά (reflective): το παραπάνω s είναι ένα τέτοιο σύστημα (αλγόριθμος).
Μια λ-μεταβλητή έχει τη δυνατότητα να δεσμευτεί πάνω σε συναρτησιακή έκφραση (και, τυπικά,
στον λ-λογισμό δεν έχουμε άλλες οντότητες από συναρτησιακές εκφράσεις και εφαρμογές). Αν το
σώμα αποτελείται από τη μεταβλητή, κατά την εφαρμογή πάνω σε όρισμα έχουμε απλώς
αντικατάσταση της μεταβλητής από το όρισμα. Πχ:
λx.(x arg)
Το νόημα της έκφρασης λx.(x arg) είναι οτι:
"όταν η x δεσμευτεί σε συναρτησιακή έκφραση, τότε αυτή θα εφαρμοστεί πάνω στο arg"
Με την έκφραση αυτή προσδιορίζουμε την εφαρμογή μεταβλητής συνάρτησης.
Θεωρούμε την ακόλουθη λ-έκφραση:
λs.(s s)
Η έκφραση αυτή έχει το εξής νόημα:
"s είναι η λ-μεταβλητή της λ-έκφρασης, και το σώμα της λ-έκφρασης είναι το αποτέλεσμα
της εφαρμογής από 'κάτι' στον εαυτό του, όπου εδώ αυτό το 'κάτι' είναι η ίδια η λμεταβλητή".
Με εφαρμογή των τριών κανόνων διαπιστώνουμε αμέσως οτι η έκφραση αυτή έχει τις εξής
ιδιότητες:
(λx.x λs.(s s))
(λs.(s s) λx.x)

→ λs.(s s)
→ λx.x

[ιδ.1]
[ιδ.2]
296

(λs.(s s) λs.(s s)) → (λs.(s s) λs.(s s))
Οι δύο πρώτες είναι προφανείς. Η τρίτη ισχύει διότι:

[ιδ.3]

(λs.(s s) λs.(s s))
→ λs.(λs.(s s) λs.(s s)) →
→ (λs.(s s) λs.(s s)) → …
δηλ. ο υπολογισμός της έκφρασης δίνει αποτέλεσμα την ίδια την έκφραση. Αυτή η εφαρμογή
συνδέει την έννοια του υπολογισμού με την αναδρομή, όπως θα δούμε.
Τάξη υπολογισμού και υπολογιστικό μοντέλο:
Η τάξη του υπολογισμού που θα εφαρμοστεί, αποτελεί καθοριστικό παράγοντα, όχι μόνο διότι
μπορεί να οδηγήσει σε διαφορετικά αποτελέσματα σε κάθε περίπτωση, αλλά και διότι είναι
δυνατό, κατά την κανονική τάξη, να οδηγηθεί ο υπολογισμός σε ατέρμονα βήματα, και επομένως
να μην τερματίζει, ενώ κατά την τάξη εφαρμογής τα βήματα να είναι πεπερασμένα, δηλ. να
τερματίζει.

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

Κάθε μια από τις δύο κατευθύνσεις καθορίζει ένα μοντέλο υπολογισμού, που αποτελεί τη βάση
πάνω στην οποία κτίζεται μια υπολογιστική γλώσσα, όπου τίθεται ως γενικός κανόνας εκτέλεσης
μια από τις δύο τάξεις, και αν τοπικά είναι επιθυμητό, διαμορφώνεται προγραμματιστικά η άλλη.
Για παράδειγμα: ο υπολογισμός στη Lisp ακολουθεί την κανονική τάξη, εκτός αν ειδικά μια
συνάρτηση έχει οργανωθεί με τρόπο που να ακολουθεί την τάξη εφαρμογής∙ ο υπολογισμός στην
Algol 60 ακολουθεί την τάξη εφαρμογής, εκτός αν μια διαδικασία έχει οργανωθεί με τρόπο που
να ακολουθεί την κανονική τάξη (αυτός ήταν και ο λόγος που η Algol 60 δεν πέτυχε εμπορικά
σαν γλώσσα προγραμματισμού).

297

Χρήση λ-μεταβλητών για έλεγχο του υπολογισμού
Αναστροφή ρόλου συνάρτησης - ορίσματος:
Έστω η έκφραση εφαρμογής:
(λf.(f arg) func)
Στο συναρτησιακό μέρος, λf.(f arg) , το όρισμα arg παίρνει τη θέση της λ-μεταβλητής f και
προκύπτει η έκφραση - αποτέλεσμα arg . Άρα η εφαρμογή ισοδυναμεί με:
(func arg)
Επομένως ισχύει η ακόλουθη βασική ιδιότητα που αναστρέφει το ρόλο συνάρτησης - ορίσματος,
με εισαγωγή μιας νέας λ-μεταβλητής:
(func arg) =
(λf.(f arg) func)
Η ιδιότητα αυτή είναι εξαιρετικά χρήσιμη για δύο λόγους:
i) επιτρέπει να θεωρήσουμε και τη συνάρτηση ως όρισμα, και
ii) ανοίγει δρόμο προς έλεγχο του "πότε θα γίνει ο υπολογισμός", λόγω του οτι το αποτέλεσμα
είναι λ-έκφραση που "περιμένει εφαρμογή" για να γίνει οποιοσδήποτε υπολογισμός στο σώμα
του .

Καθυστέρηση υπολογισμού:
Από τα παραπάνω συμπεραίνουμε οτι με τη σημείωση ενός υπολογισμού, όπως (func arg) ,
εννοούμε οτι εκτελείται και επιστρέφει το αποτέλεσμά του. Αν χρειάζεται να γραφεί ο
υπολογισμός αλλά να μην εκτελεστεί (πχ. για το λόγο οτι πρέπει να περιμένει κάποιο άλλο
αποτέλεσμα), όπως προαναφέραμε, μπορεί να γίνει με την γραφή του υπολογισμού ως σώματος
σε μια λ-έκφραση της οποίας η λ-μεταβλητή δεν αναγράφεται στο σώμα (func arg) , δηλ. είναι
"dummy":
λd.(func arg)
Η εφαρμογή από την έκφραση αυτή πάνω σε οτιδήποτε, δεδομένου οτι θα γίνει με δέσμευση της
d , αλλά η d δεν αναγράφεται πουθενά μέσα στον υπολογισμό (func arg) , θα προκαλέσει απλώς
υπολογισμό της έκφρασης (func arg) . Με τον τρόπο αυτό πετυχαίνουμε καθυστέρηση του
υπολογισμού (delayed evaluation) μέχρι "να τον ζητήσουμε", δηλ. με εφαρμογή όπως:
(λd.(func arg) arg1) → (func arg) → …
Η καθυστέρηση υπολογισμού προσφέρει πολλές δυνατότητες:
i) αποφυγή ατέρμονος υπολογισμού που ενδιάμεσα παρουσιάζεται αλλά αργότερα
παρακάμπτεται,
ii) ευχέρεια αποφυγής επαναλαμβανόμενου υπολογισμού της ίδιας έκφρασης όταν συναντάται
περισσότερες της μιας φορές (lazy evaluation),
iii) ευέλικτη διαχείριση του χρόνου εκτέλεσης σε υπολογιστή, με την τοποθέτηση των
χρονοβόρων υπολογισμών σε σημεία όπου ο χρόνος δεν είναι κρίσιμος.
Παρ' όλα αυτά, δεν είναι σκόπιμο να ακολουθείται ως γενικός κανόνας (δηλαδή: καθυστέρηση
των πάντων μέχρι να γίνει επεξηγηματική αίτηση εκτέλεσης του υπολογισμού) διότι τότε θα
είχαμε:
298

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

7.1.3 Επιλογή, λογικές τιμές και λογικές πράξεις
Συναρτήσεις επιλογής:
H έκφραση λx.λy.x ονομάζεται επιλογή πρώτου, για τον εξής λόγο:
Αν την εφαρμόσουμε πάνω σε ένα όρισμα, έστω a, έχουμε
(λx.λy.x a) = (λx.(λy.x) a) → λy.a
Η τελευταία, έχοντας σταθερό σώμα, οπουδήποτε και αν εφαρμοστεί, θα δίνει πάντα αποτέλεσμα
a:
(λy.a b)
Άρα ισχύει:

→ a , για κάθε b

((λx.λy.x a) b) → a , για κάθε b
Επομένως η λx.λy.x δίνει πάντα ως αποτέλεσμα, το όρισμα της πρώτης της εφαρμογής. Με
βάση αυτό, ορίζουμε συνάρτηση:
def select_first ::= λx.λy.x
Αντίστοιχα, η έκφραση λx.λy.y ονομάζεται επιλογή δεύτερου, διότι ισχύει:
((λx.λy.y a) b) → b , για κάθε a
Δηλαδή η λx.λy.y δίνει πάντα ως αποτέλεσμα, το όρισμα της δεύτερής της εφαρμογής.
Ορίζουμε συνάρτηση:
def select_second ::= λx.λy.y

Λογικές συναρτήσεις:
Ορίζοντας τα ονόματα συναρτήσεων true, false και cond ως ακολούθως, μπορούμε να
μοντελοποιήσουμε τη μπουλιανή λογική μέσα στον λ-λογισμό:
def true ::= λx.λy.x
def false ::= λx.λy.y
def cond ::= λe1.λe2.λc.((c e1) e2)
Αποκαλούμε τις συναρτήσεις true και false , λογικές τιμές.

Η συνάρτηση cond έχει τρία "σκαλοπάτια" εφαρμογής, λόγω των τριών λ-μεταβλητών της. Αν
εφαρμοστεί σε όρισμα a (δέσμευση της e1) το αποτέλεσμα είναι συναρτησιακή έκφραση δύο λμεταβλητών∙ αν αυτό το αποτέλεσμα εφαρμοστεί σε όρισμα b (δέσμευση της e2) δίνει
συναρτησιακή έκφραση μιας λ-μεταβλητής. Αν αυτό το αποτέλεσμα εφαρμοστεί στην true
(δέσμευση της c) θα επιστρέψει a , ενώ αν εφαρμοστεί στην false θα επιστρέψει b , διότι από τον
299

ορισμό της cond βλέπουμε οτι πρόκειται για εφαρμογή της c πάνω στο e1, δηλαδή το a , και το
αποτέλεσμα πάνω στο e2 , δηλαδή το b .

Αν λοιπόν το c είναι η λογική τιμή true , δηλαδή η έκφραση λx.λy.x , όπως είδαμε θα
επιστρέψει το όρισμα της πρώτης εφαρμογής, δηλαδή a , ενώ αν εφαρμοστεί στη false θα
επιστρέψει το δεύτερο, δηλαδή b .
Άρα, η συνάρτηση cond επιλέγει τον κλάδο υπολογισμού του οποίου θα επιστρέψει το
αποτέλεσμα, μεταξύ των δύο πρώτων ορισμάτων εφαρμογής, ενώ το όρισμα της τρίτης
εφαρμογής αποτελεί τη συνθήκη ελέγχου ("αληθές" ή "ψευδές") που κατευθύνει την επιλογή στον
αντίστοιχο κλάδο. Επομένως η τιμή δέσμευσης του e1 αποτελεί τον "then-κλάδο", η τιμή
δέσμευσης του e2 αποτελεί τον "else-κλάδο", και η τιμή δέσμευσης του c αποτελεί τη συνθήκη,
με την προϋπόθεση οτι η συνθήκη θα είναι true ή false και τίποτα άλλο.
Προφανώς, σε εφαρμογή που ακολουθεί την κανονική τάξη, θα υπολογιστούν πρώτα οι δύο
κλάδοι και μετά θα γίνει η επιλογή για το ποιό αποτέλεσμα θα επιστραφεί, και αυτό είναι
σπατάλη πόρων. Θα δούμε στη συνέχεια τον υπολογισμό που προκαλεί η cond , που είναι μια
"σπατάλη πόρων" και πώς θα αποφύγουμε αυτή τη σπατάλη.
Συμβολισμοί απλούστευσης:

Θεωρούμε για απλούστευση οτι η γραφή:
def <function_name> x1 x2 … xn ::= <body>
[f1]
ισοδυναμεί με:
def <function_name> ::= λx1.λx2. … λxn.<body>
[f2]
όπου διατηρούμε το νόημα των επιπέδων εφαρμογής: η λ-μεταβλητή x1 έχει σώμα
λx2. … λxn.<body> , κοκ, η xn έχει σώμα <body> , άρα η πρώτη εφαρμογή συνάρτησης στην
μορφή [f1] νοείται για την μεταβλητή x1, η επόμενη για την x2, κοκ. μέχρι την xn .

Θεωρούμε επίσης οτι η γραφή:
(f a b … k)
σημαίνει: εφαρμογή της συνάρτησης f πάνω στο όρισμα a , μετά εφαρμογή του αποτελέσματος
πάνω στο όρισμα b , κοκ, και τέλος εφαρμογή του αποτελέσματος πάνω στο όρισμα k , οπότε
λαβαίνουμε και το τελικό αποτέλεσμα.
Η γραφή [f1] εξυπηρετεί περισσότερο στην περίπτωση διαδοχικής εφαρμογής που καλύπτει το
σύνολο των λ-μεταβλητών, και ακόμα περισσότερο όταν θεωρούμε οτι οι διαδοχικές εφαρμογές
γίνονται "ταυτόχρονα", δηλ. δεν έχει σημασία η διάκριση των επιπέδων εφαρμογής. Η γραφή [f2]
εξυπηρετεί περισσότερο στην περίπτωση εφαρμογής για μερικές μόνον μεταβλητές και
γενικότερα όταν έχει σημασία η διάκριση των επιπέδων. Οπωσδήποτε η γραφή [f1] είναι άτυπη
και σε περίπτωση αμφιβολίας (που μπορεί να δημιουργηθεί όταν στο σώμα αναφέρονται λεκφράσεις) πρέπει να χρησιμοποιήσουμε τη γραφή [f2].
Επομένως η μορφή:
def cond ::=

λe1.λe2.λc.((c e1) e2)

[c1.1]
300

μπορεί να γραφεί για απλούστευση ως:
def cond e1 e2 c ::= ((c e1) e2)
[c1.2]
όπου e1 είναι ο κλάδος υπολογισμού όταν c είναι true και e2 ο κλάδος υπολογισμού όταν c είναι
false, και όπου πρώτα εφαρμόζεται στο όρισμα e1 , το αποτέλεσμα στο όρισμα e2 , και το
αποτέλεσμα στο όρισμα c . Άρα:
(cond <first> <second> <choice>) =
= (((λc.λe1.λe2.((c e1) e2) <first>) <second>) <choice>)
Αυτό σημαίνει οτι οι κλάδοι e1 και e2 υπολογίζονται πριν τον λογικό έλεγχο, κάτι που ασφαλώς
δεν το θέλουμε διότι οδηγεί σε περιττούς υπολογισμούς, αν όχι σε άνευ λόγου ατέρμονα
επανάληψη (όπως στην περίπτωση όπου ο ένας κλάδος είναι ατέρμων και ο άλλος
τερματιζόμενος, και είναι αυτός που τελικά επιλέγεται και επιστρέφεται υπολογισμένος).

Η αντιμετώπιση από αυτό το ζήτημα γίνεται με καθυστέρηση του υπολογισμού, μέχρι να κριθεί
αναγκαίος, δηλαδή μετά την επιλογή. Για το σκοπό αυτό, όπως είπαμε, είναι αρκετό να τεθεί ο
κάθε κλάδος ως σώμα μιας πρόσθετης λ-μεταβλητής, μη αναφερόμενης στο σώμα του κλάδου, ως
προς την οποία να γίνει ο τελευταίος υπολογισμός, μετά την επιλογή. Τότε, θα υπολογίζεται κατ'
αρχήν η συνθήκη ελέγχου και στη συνέχεια ο κατάλληλος κλάδος και μόνον, όπως θα δούμε
αναλυτικά παρακάτω.
Μερική εφαρμογή συνάρτησης:
Από τα παραπάνω είναι φανερό οτι, οι διαδοχικές εφαρμογές συνάρτησης περισσοτέρων λμεταβλητών είναι αυτόνομες, και επομένως μπορούμε να σταματήσουμε τη διαδοχή εφαρμογών
"όπου θέλουμε", λαβαίνοντας ως αποτέλεσμα την αντίστοιχη συνάρτηση. Παρ' όλα αυτά,
θεωρούμε οτι στη γραφή με το σύνολο των ορισμάτων (f a b … k) είναι υποχρεωτικό να
δώσουμε ορίσματα πλήθους ίσου με το πλήθος των λ-μεταβλητών της συνάρτησης, δηλαδή να
εξαντλήσουμε τις δυνατότητες διαδοχικής εφαρμογής.

Πρέπει να διευκρινίσουμε οτι η γραφή (f a b … k) είναι απλώς συμβατική και δεν σημαίνει οτι
ο μεταβλητές είναι στο ίδιο επίπεδο, οπότε θα μπορούσαν να αντιμετατεθούν μέσα στην
παρένθεση, τηρώντας την αντιμετάθεση και στα αντίστοιχα ορίσματα (θα δούμε οτι αντίθετα, στη
Lisp ισχύει εν γένει η δυνατότητα αντιμετάθεσης των μεταβλητών και αντίστοιχα των
ορισμάτων).
Λογικά συνδετικά:
α'.
Ορίζουμε συνάρτηση μιας μεταβλητής, που όταν εφαρμοστεί σε λογική τιμή να τη
αντιστρέφει:
def not ::=
λx.(((cond false) true) x)
Η έκφραση αυτή, με απλοποιήσεις βάσει των κανόνων καταλήγει στην:
λx.((x false) true)
που σημαίνει:
i) Όταν εφαρμοστεί πάνω στο true τότε το x γίνεται true, που εδώ είναι το συναρτησιακό μέρος.
301

Επειδή η συνάρτηση true είναι δύο λ-μεταβλητών, μπορεί να εφαρμοστεί σε δύο βήματα∙ η
διπλή εφαρμογή της συνάρτησης true, όπως είπαμε, επιστρέφει το πρώτο αντικείμενο
εφαρμογής, που εδώ είναι το false :
(λx.((x false) true) true) → ((true false) true) → false
ii) Όταν εφαρμοστεί πάνω στο false τότε το x γίνεται false, άρα η (διπλή) εφαρμογή επιστρέφει
το δεύτερο αντικείμενο εφαρμογής, που εδώ είναι το true.
(λx.((x false) true) false) →
Επομένως έχουμε:
(not false) →
(not true)


((x false) true)



true

true
false

β'.
Ορίζουμε το λογικό συνδετικό and ως συνάρτηση δυο μεταβλητών, θεωρώντας οτι θα
εφαρμόζεται σε λογικές τιμές, και τέτοια που να επιστρέφει true όταν και τα δύο ορίσματά της
είναι true, και false σε κάθε άλλη περίπτωση. Εξετάζοντας τις τέσσερις περιπτώσεις,
παρατηρούμε οτι η παρακάτω έκφραση αποδίδει ακριβώς αυτό το νόημα:
λx.λy.(((cond y) false) x)
Άρα μπορούμε να ορίσουμε:
def and ::=
λx.λy.(((cond y) false) x)
Η έκφραση αυτή, με απλοποιήσεις καταλήγει στην:
λx.λy.((x y) false)
γ'.
Oρίζουμε αντίστοιχα το λογικό συνδετικό or ως συνάρτηση δύο μεταβλητών που να
εφαρμόζεται σε λογικές τιμές, και όταν μία τουλάχιστον παίρνει τιμή true, τότε το αποτέλεσμα να
είναι true:
def or ::=
λx.λy.(((cond true) y) x)
Η έκφραση αυτή, με απλοποιήσεις βάσει των κανόνων καταλήγει στην:
λx.λy.((x true) y)
Αποδεικνύεται (εύκολα, με μοναδική δυσκολία τη μεγάλη σειρά βημάτων που έχει η απόδειξη,
λόγω του πλήθους των περιπτώσεων) οτι: για τις εκφράσεις and, or, not ισχύουν οι πίνακες
αλήθειας των λογικών μπουλιανών πράξεων και γενικά όλες οι ιδιότητες της άλγεβρας Boole δύο
στοιχείων.

δ'.
Για τις παράγωγες λογικές πράξεις ισχύουν τα γνωστά, δηλαδή:
Μπορούμε να ορίσουμε τα παράγωγα λογικά συνδετικά (implies, equivalent, nor, nand, xor)
εφαρμόζοντας ανάλογη πορεία με αυτή που ακολουθείται στη λογική, όπου ισχύουν οι ακόλουθες
ιδιότητες (ο συμβολισμός είναι της λογικής και όχι του υπολογισμού):
(a implies b) = ((not a) or b)
(a equivalent b) = ((a implies b) and (b implies a))
302

(a nor b) = (not (a or b))
(a nand b) = (not (a and b))
(a xor b) = ((a or b) and (not (a and b)))
όπου a και b είναι λογικές τιμές.
Επομένως ορίζουμε:
def implies x y
def equivalent x y
def nor x y
def nand x y
def xor x y

::=
::=
::=
::=
::=

(or (not x) y)
(and (implies x y) (implies y x))
(not (or x y))
(not (and x y))
(and (or x y) (not (and a b)))

7.1.4 Αφαίρεση, η συνάρτηση "Υ" και αναδρομή
Μέχρι στιγμής έχουμε εντοπίσει ένα "πρόβλημα" στον υπολογισμό: η συνάρτηση cond υπολογίζει
πρώτα τους δύο κλάδους και μετά τη συνθήκη επιλογής του αποτελέσματος. Αυτό είναι απλώς
σπατάλη υπολογιστικών δυνάμεων όταν και οι δύο κλάδοι τερματίζουν, αλλά είναι καταστροφικό
όταν ένας κλάδος δεν τερματίζει. Μια περίπτωση μη τερματισμού είναι αυτή της αναδρομικής
σχέσης.
Αναδρομική σχέση είναι μια συναρτησιακή έκφραση που προσδιορίζεται με τρόπο που να καλεί
τον εαυτό της μέσα στο σώμα της, ως εφαρμογή. Αν η έκφραση που εφαρμόζεται είναι μια
αναδρομική σχέση, έστω r, κατά την εφαρμογή της θα καλέσει τον εαυτό της για εφαρμογή, ο
οποίος θα καλέσει τον εαυτό του, κοκ Σε υπολογισμό που ακολουθεί την κανονικής τάξης
εφαρμογή, αρχίζει η εκτέλεση "από μέσα προς τα έξω", άρα από την "εσωτερικότερη" κλήση, που
όμως "πηγαίνει επ' άπειρον προς τα μέσα": Πρέπει να αντικατασταθεί η r στο σώμα της από τον
εαυτό της, και αυτό επ' άπειρον.
Αυτό οδηγεί τον υπολογισμό σε ατέρμονα αντικατάσταση του όρου r μέσα στο σώμα του και
κατά συνέπεια ποτέ δεν θα ολοκληρωθεί ο υπολογισμός (και μάλιστα, δεν θα γίνει τίποτα άλλο
από την ατέρμονα αντικατάσταση). Αυτό που χρειαζόμαστε, είναι να μην ξεκινήσει ο
υπολογισμός από αυτή την αντικατάσταση αλλά να καταλήξει σ' αυτήν, και ακόμα, να
διαθέτουμε έναν τρόπο ελέγχου του τερματισμού.
Θα εξετάσουμε πρώτα το ζήτημα του πώς θα κατευθύνουμε την επιλογή του υπολογισμού κατά
την εκτέλεση τη συνάρτησης cond πριν τον υπολογισμό των κλάδων.
Απλοποιήσεις γραφής:
Για λόγους απλούστευσης, θεωρούμε οτι:
α'. Η γραφή:
<function> <argument-1> <argument-2> … <argument-k>
σημαίνει την διαδοχική εφαρμογή:
303

(<function> <argument-1> <argument-2> … <argument-k>)
δηλ. συμβατικά, να μην αναγράφουμε τις παρενθέσεις, όταν είναι αυτονόητες.
β'. Η γραφή:
def <fname> <λname1> <λname2>…<λnamek> ::= <expression>
σημαίνει:
def <fname> ::=
λ<λname1>.λ<λname2>… .λ<λnamek>.<expression>
δηλ. συμβατικά, να γράφουμε τις λ-μεταβλητές ως παραμέτρους του ονόματος (χωρίς το
χαρακτήρα λ) .

γ'. Η γραφή:
def fn1 ::= fn2 fn3
σημαίνει οτι η fn1 απαιτεί τα αυτά (πλήθους και τύπου) ορίσματα με τη σύνθεση fn2 και fn3 ,
που παραλείπουμε να αναγράψουμε εννοώντας οτι είναι τα ίδια και στα δύο μέλη του ορισμού.
Παρατήρηση: Όταν συναντάμε ορισμό με σύνθεση συναρτήσεων στο β' μέλος, πρέπει να
αναζητούμε τον ορισμό των συναρτήσεων του β' μέλους για να συνάγουμε ασφαλή
συμπεράσματα σχετικά με τα ορίσματα της συνάρτησης του α' μέλους. Την ίδια αναζήτηση
πρέπει να κάνουμε και όταν αναγράφονται περισσότεροι των δύο όρων στο β' μέλος, διότι,
επιπρόσθετα, με την παράλειψη των παραμέτρων δεν είναι πλέον μονοσήμαντο ποιοι όροι είναι
ορίσματα ποιών συναρτήσεων, διότι δεν είναι σαφές αν η γραφή "α β γ" σημαίνει υπολογισμό "(α
(β γ))" ή "((α β) γ))".
δ)

Η γραφή:
if <condition>
then <true_choice>
else <false_choice>
σημαίνει:
cond <true_choice> <false_choice> <condition>
δηλ. συμβατικά, να μπορούμε να χρησιμοποιούμε τη γραφή του συνήθους διαδικαστικού τρόπου,
απλώς για λόγους προσαρμογής σε παραστάσεις που έχει συνηθίσει ο μελετητής.

Καθυστέρηση υπολογισμού και λογικός έλεγχος

Η αλλαγή γραφής του conditional δεν επιφέρει βέβαια αλλαγή της προτεραιότητας υπολογισμού,
που όπως είπαμε ήταν "πρώτα ο κλάδος <true_choice> , μετά ο κλάδος <false_choice> και
τέλος η συνθήκη <condition> " , δηλαδή εκτέλεση και των δύο κλάδων, πριν το λογικό έλεγχο.
Αυτή η διευκρίνιση είναι αναγκαία για να δούμε οτι η αναδρομική κλήση μέσω του "if" είναι
απλώς αλλαγή του συμβολισμού της έκφρασης, που εξακολουθεί να οδηγεί τον υπολογισμό
πρώτα σε υπολογισμό των δύο κλάδων και μετά σε επιλογή αυτού που θα δώσε το αποτέλεσμά
του ως αποτέλεσμα του conditional.
304

Σε περίπτωση ατέρμονος ανακύκλωσης, όπως συμβαίνει όταν ο ένας κλάδος του "if" είναι
αναδρομικός, είναι κρίσιμο, διότι η εκτέλεση της αναδρομής πριν τον έλεγχο θα προκαλέσει
ατέρμονα υπολογισμό, με βάση αυτά που αναφέραμε προηγουμένως: οι κλάδοι υπολογίζονται
πρώτα, άρα ο ατέρμων υπολογισμός θα εκτελεστεί πρώτα.
Η χρήση λοιπόν του ενός κλάδου για τη θέση οριακής συνθήκης, όπως κάνουμε στον συνήθη
διαδικαστικό προγραμματισμό, δεν αρκεί για να τερματίσει ο υπολογισμός. Προφανώς, αυτό που
χρειαζόμαστε είναι: να μεθοδεύσουμε τον υπολογισμό έτσι ώστε να υπολογιστεί πρώτα η
συνθήκη τερματισμού, και βάσει αυτής να ακολουθήσει η επιλογή του κλάδου (οπότε θα
μπορούμε να κατευθύνουμε τον υπολογισμό προς τον τερματισμό ή την ανακύκλωση).
Όπως είπαμε και προηγουμένως, αν μεθοδεύσουμε την εκτέλεση του conditional με τρόπο ώστε
οι κλάδοι <true_choice> και <false_choice> να τοποθετηθούν μέσα στο σώμα λ-έκφρασης με
"dummy" λ-μεταβλητή, έχουμε ένα τρόπο να υπολογιστεί πρώτα η <condition>, και βάσει το
αποτελέσματος που θα δώσει θα εκτελεστεί ο αντίστοιχος κλάδος και μόνον (δηλ. αν το
αποτέλεσμα θα είναι η select_first ή η select_second). Συγκεκριμένα, συνθέτουμε το conditional
ως εξής:
cond λd.<true_choice> λd.<false_choice> <condition>
Αυτό και μόνον, αρκεί για να εκτελεστεί πρώτα η <condition> και βάσει του αποτελέσματός της
να οδηγηθεί η cond στον ένα ή στον άλλο κλάδο. Επειδή όμως ο κλάδος "έρχεται για
υπολογισμό" αλλά δεν υπολογίζεται, λόγω της dummy μεταβλητής, χρειάζεται μια πρόσθετη
εφαρμογή, που είναι απλά τυπική για να "προχωρήσει η εκτέλεση".
Παρατηρούμε πως, αν αυτό που μας ενδιαφέρει είναι απλώς να αποφύγουμε την ατέρμονα
ανακύκλωση αναδρομικής κλήσης, αρκεί να θέσουμε μια dummy μεταβλητή για καθυστέρηση
του ανακυκλούμενου κλάδου, που έστω οτι είναι ο <false_choice>, ενώ ο <true_choice> έστω οτι
είναι η συνθήκη τερματισμού:
cond <true_choice> λd.<false_choice> <condition>
αλλά τότε υπολογίζεται πρώτα ο <true_choice>, μετά η <condition> και παραμένει σε
"εκκρεμότητα" ο κλάδος <false_choice>.
Το πρόβλημα που πρέπει να αντιμετωπίσουμε τώρα για να θέσουμε υπό έλεγχο την ατέρμονα
ανακύκλωση που προκαλεί η αναδρομή, έχει δύο σκαλοπάτια: πώς θα θέσουμε την dummy
μεταβλητή της καθυστέρησης ως παράμετρο της αναδρομικής συνάρτησης, και πώς θα
ενεργοποιήσουμε τον υπολογισμό του "καθυστερημένου" κλάδου όταν πρέπει.
Στο σημείο αυτό ανοίγονται διάφοροι δυνατοί δρόμοι. Ένας είναι ο ακόλουθος:

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

Η κεντρική ιδέα είναι να αναχθεί η αναδρομική κλήση, από αυτοαναφορά της οριζόμενης
συνάρτησης, σε αναφορά λ-μεταβλητής που παίζει συναρτησιακό ρόλο, και ο οποίος θα
καθοριστεί με την κλήση. Συγκεκριμένα, αντί για τον ορισμό:
def fu x y ::=
if <condition>
then <result1>
else fu <νέα διαμόρφωση ορισμάτων>
(όπου η f είναι συνάρτηση δύο ορισμάτων αλλά η εφαρμογή της προκαλεί αναδρομική ατέρμονα
ανακύκλωση, άρα είναι άχρηστη) να δώσουμε τον:
def fi f x y ::=
if <condition>
then <result1>
else f <νέα διαμόρφωση ορισμάτων>
(όπου η fi είναι συνάρτηση τριών ορισμάτων, μη αναδρομική, αλλά απαιτεί να εφαρμοστεί σε
κατάλληλη συνάρτηση - παράμετρο f τέτοια που ο else κλάδος, δηλ. η εφαρμογή "f <νέα
διαμόρφωση ορισμάτων>" , να επιτελεί το αναμενόμενο έργο)

Θα αποκαλούμε αυτή την μετατροπή, ανέβασμα σε ένα σκαλοπάτι αφαίρεσης. Αυτή είναι μια
τεχνική, την οποία αξιοποιούμε για να πετύχουμε τερματιζόμενη αναδρομική κλήση, όπως θα
δούμε παρακάτω. Προφανώς, στόχος μας είναι να εκτελεστεί πρώτα η συνθήκη, και βάσει αυτής
ο αντίστοιχος κλάδος και μόνον.
Συνδυασμένη χρήση αυτοαναφοράς και καθυστέρησης:
Είδαμε προηγουμένως την έκφραση (λs.(s s) λs.(s s)) η οποία, υπολογιζόμενη, δίνει πάλι τον
εαυτό της. Είδαμε και τη χρήση μιας πρόσθετης παραμέτρου ως μέσου καθυστέρησης του
υπολογισμού τής εφαρμογής αυτής, ανεβαίνοντας ένα σκαλοπάτι αφαίρεσης, αντικαθιστώντας
την λs.(s s) με την λf.λs.(f (s s)) .
Η εφαρμογή αυτής της έκφρασης σε όρισμα - συνάρτηση, έστω funct, δίνει:
(λf.λs.(f (s s)) funct)  λs.(funct (s s))
Αν τώρα εφαρμόσουμε την παραπάνω έκφραση στον εαυτό της, δηλ.:
( λs.(funct (s s)) λs.(funct (s s)) )
[r1]
παίρνουμε ως αποτέλεσμα:
(funct (λs.(funct (s s)) λs.(funct (s s)) ) )
άρα, με νέο υπολογισμό, από μέσα προς τα έξω εκτελεσμένο, θα πάρουμε:
(funct (funct (λs.(funct (s s)) λs.(funct (s s)) ) ))
κοκ, δηλαδή:
[r1] → (funct [r1] ) → (funct (funct [r1] )) →
→ (funct (funct (funct [r1] ))) → …
306

Επομένως, η έκφραση εφαρμογής [r1] με τον υπολογισμό της, δίνει αποτέλεσμα που συνίσταται
από τον εαυτό της δοσμένο ως όρισμα στην funct, και αυτό επαναλαμβάνεται ξανά, απεριόριστα.
Η συνάρτηση Υ:
Θεωρούμε τώρα μια λ-έκφραση με λ-μεταβλητή func και σώμα την [r1]. Ονομάζουμε αυτή την λέκφραση ως συνάρτηση με όνομα recursive :
def recursive ::= λfunc.(λs.(func (s s)) λs.(func (s s)))
δηλαδή, με την απλοποιημένη γραφή:
def recursive func ::= (λs.(func (s s)) λs.(func (s s)))
Η επαναλαμβανόμενη αυτοαναφορά του σώματος καθυστερεί στην recursive εξ αιτίας τού οτι η
func είναι λ-μεταβλητή, και περιμένει τη δέσμευσή της. Μόλις όμως εφαρμόσουμε την recursive
πάνω σε συνάρτηση, θα προκαλέσουμε τον παραπάνω επαναλαμβανόμενο υπολογισμό. Σε κάθε
νέο κύκλο του υπολογισμού, το αποτέλεσμα είναι πάλι υπολογισμός, που έχει συναρτησιακό
μέρος τη συνάρτηση στην οποία δεσμεύεται η func και όρισμα το εκάστοτε προηγούμενο
αποτέλεσμα.
Η συνάρτηση recursive έχει την ιδιότητα:
αν εφαρμοστεί πάνω σε συνάρτηση F η οποία προήλθε από ένα σκαλοπάτι αφαίρεσης
συνάρτησης A η οποία κάνει αυτοαναφορά, ισοδυναμεί με την A , αλλά η εφαρμογή του
αποτελέσματος θα ακολουθήσει υπολογισμό κατά την τάξη εφαρμογής, δηλαδή πρώτα τον
έλεγχο του "if", και αν δώσει "true" θα υπολογίσει τον "then-κλάδο", ενώ αν δώσει "false"
θα υπολογίσει τον "else-κλάδο".
Η συνάρτηση recursive ονομάζεται και Υ-συνάρτηση ή προσδιοριστής σταθερού σημείου
(παρατηρούμε οτι η συνάρτηση recursive ικανοποιεί τη συνθήκη x=f(x) που χαρακτηρίζει τα
σταθερά σημεία συνάρτησης).
Η χρήση της recursive για την πρόκληση αναδρομής γίνεται με τον εξής απλό τρόπο:
Βήμα 1ο: Συνθέτουμε την αναδρομική σχέση a που θα λειτουργούσε σωστά αν ο υπολογισμός
δεν έπεφτε σε ατέρμονα επανάληψη λόγω υπολογισμού πρώτα των δύο κλάδων και μετά της
συνθήκης∙ η συνάρτηση αυτή χρειάζεται, διότι αποτελεί τη βάση για την αφαίρεση που
ακολουθεί.
Βήμα 2ο: Προσδιορίζουμε ένα σκαλοπάτι αφαίρεσης b για τη σχέση a (με τον τρόπο που
προαναφέραμε, με χρήση μιας νέας παραμέτρου, όπως στην add-for-rec παρακάτω).
Βήμα 3ο: Συνθέτουμε τη ζητούμενη αναδρομική συνάρτηση ως την εφαρμογή:
(recursive b)
[βλέπε τη συνάρτηση "add" παρακάτω]
Συμβατικά, γράφουμε την πορεία των παραπάνω τριών βημάτων ως:
rec a ::= <ο βασικός αναδρομικός ορισμός a>
εννοώντας οτι, αφού είναι τυποποιημένα το σκαλοπάτι αφαίρεσης και η χρήση της recursive,
προσδιορίζονται μονοσήμαντα.
Σε ότι αφορά τις υπολογιστικές υλοποιήσεις του λ-λογισμού, στη Lisp η rec είναι πρωτογενώς
307

υλοποιημένη και εκφράζεται, για λόγους απλούστευσης, με το ίδιο όνομα συνάρτησης η οποία
προκαλεί ορισμό συνάρτησης (η DEFUN στη Lisp), οπότε τα παραπάνω έχουν αποκλειστικά
θεωρητικό ενδιαφέρον.

7.1.5 Η συνάρτηση successor και οργάνωση αριθμητικής
Τα ακόλουθα είναι δομικοί λίθοι για την οργάνωση της αριθμητικής μέσω του λ-λογισμού.
Αποτελούν το σημείο εκκίνησης για τη θεμελίωση της αριθμητικής, χωρίς βέβαια να την
ολοκληρώνουν. Ακολουθούμε τα εξής βήματα:
α'.

Ονομάζουμε με 0 την ταυτοτική έκφραση λx.x

β'.

Ορίζουμε την ακόλουθη συνάρτηση:
def successor ::= λn.λs.((s false) n)
ή ισοδύναμα:
def successor n ::= λs.((s false) n)
γ'.
Ονομάζουμε με 1 την έκφραση (successor 0) , η οποία, παρατηρούμε, είναι συνάρτηση.
Ονομάζουμε με 2 την έκφραση (successor 1) , που ισοδυναμεί με την έκφραση
(successor (successor 0)) , με 3 την έκφραση (successor 2) , κοκ. Έχουμε επομένως μια
απεριόριστη σειρά διαδοχικών ορισμών όπως:
def 4 ::= (successor 3)
Επομένως έχουμε:
(successor 2) = (successor (successor 1))
= ((successor (successor (successor 0))))
Παρατηρούμε πως με τη συνάρτηση successor μπορούμε να ορίσουμε τον επόμενο αριθμό από
τον τελευταίο που είχαμε ορίσει. Άρα παράγει με δυναμικό τρόπο τους διαδοχικούς φυσικούς
αριθμούς, οι οποίοι πάντοτε ορίζονται όλοι ως συναρτήσεις.
δ'.

Ορίζουμε τη συνάρτηση προσδιορισμού του προηγούμενου φυσικού αριθμού:
def predecessor ::= λn.(n λx.λy.y)
όπου έχουμε:
λn.(n λx.λy.y) = λn.(n select_second) = λn.(n false)
Η εφαρμογή της predecessor σε φυσικό αριθμό που έχουμε ήδη παράγει, δίνει τον προηγούμενό
του (προκαλώντας σφάλμα στο 0). Έτσι έχουμε:
(predecessor 3) → 2
(predecessor 1) → 0
(predecessor (predecessor 3)) → (predecessor 2) → 1
ε'.
Με τον τρόπο που ορίσαμε τους αριθμούς, το 0 είναι η ακραία προς τα κάτω περίπτωση,
και σε περιπτώσεις επανάληψης ή αναδρομής με μεταβλητή - αριθμό που "κατεβαίνει", είναι
308

αναγκαίο να μπορούμε να διακρίνουμε αν φθάσαμε στο 0 . Το κριτήριο αυτό μπορεί να οριστεί
ως συνάρτηση:
def is_zero ::= λn.(n λx.λy.x)
δηλαδή:
def is_zero n ::= (n λx.λy.x)
όπου το n είναι συνάρτηση - αριθμός, και προφανώς ισχύει:
λn.(n λx.λy.x) = λn.(n select_first) = λn.(n true)
Aν η "is_zero" εφαρμοστεί στο 0 , λόγω του οτι το 0 ορίζεται ως η ταυτοτική λ-έκφραση,
επιστρέφει "true", ενώ αν εφαρμοστεί σε οποιονδήποτε άλλο αριθμό, λόγω της "successor"
επιστέφει "false".
Θα δούμε τώρα τους ορισμούς των αριθμητικών πράξεων. Mπορούμε να ακολουθήσουμε δύο
διαφορετικούς δρόμους, ο ένας με χρήση αυτοεφαρμογής χωρίς άμεση αναδρομή και ο άλλος με
χρήση αναδρομής.
Ορισμός της πρόσθεσης δύο αριθμών:
Θα ορίσουμε την συνάρτηση add που προσθέτει δύο φυσικούς αριθμούς x και y , εκφράζοντας
κατάλληλα την ακόλουθη αναδρομική σχέση:
"για να βρούμε το άθροισμα δύο φυσικών αριθμών ανεβάζουμε τον πρώτο και κατεβάζουμε
τον δεύτερο μια μονάδα, και επαναλαμβάνουμε το ίδιο μέχρις ότου ο δεύτερος γίνει 0 , και
θέτουμε αποτέλεσμα του υπολογισμού να είναι η τιμή που έχει πάρει ο πρώτος":
Η πρώτη ιδέα που έχει κανείς, είναι να προσπαθήσει να ορίσει την πρόσθεση δίνοντας άμεσα μια
αναδρομική συνάρτηση, έστω την "err-add" ως εξής:
def err-add x y ::=
if is_zero y
then x
else
err-add (successor x) (predecessor y)
η οποία "φαίνεται εκ πρώτης όψεως σωστή", αλλά για τους λόγους που εξηγήσαμε θα προκληθεί
ατέρμων υπολογισμός αν εφαρμοστεί σε δύο αριθμούς.
Μπορούμε να αντιμετωπίσουμε αυτό το ζήτημα με δύο τρόπους:
α'.

Με παράκαμψη της αναδρομικής κλήσης.

Για το σκοπό αυτό, θέτουμε ως στόχο το σχηματισμό συνάρτησης, έστω it-add, η οποία με εφ'
άπαξ αυτοεφαρμογή να προκαλέσει την επιθυμητή λειτουργία (εδώ την πρόσθεση των δύο
αριθμητικών ορισμάτων της).
Σχηματίζουμε τη συνάρτηση it-add αφαιρετικά, χρησιμοποιώντας μια πρόσθετη παράμετρο συνάρτηση, έστω f :

309

def it-add f x y ::=
if is_zero y
then x
else
f f (successor x) (predecessor y)
Χρειάζεται προσοχή στη διπλή γραφή της κλήσης της f στον else-κλάδο: η δεύτερη f αποτελεί
απλώς όρισμα για την πρώτη f , δηλ. η f αντιπροσωπεύει συνάρτηση τριών ορισμάτων, που
καλύπτει τον επιδιωκόμενο στόχο "να εφαρμόσουμε την it-add στον εαυτό της". Ορίζουμε τώρα
τη συνάρτηση add-numbers ως εφαρμογή της it-add πάνω στον εαυτό της:
def add-numbers ::= it-add it-add
που σημαίνει:
def add-numbers x y ::= it-add it-add x y
δηλαδή στο σώμα (it-add it-add x y) η πρώτη αναφορά τής it-add αποτελεί την εφαρμοζόμενη
συναρτησιακή έκφραση με τρία ορίσματα εφαρμογής: τον εαυτό της, το x και το y .
Η add-numbers είναι συνάρτηση δύο ορισμάτων που υπολογίζει το άθροισμά τους, με την έννοια
των "successor"-βημάτων από το πρώτο όρισμα, τόσα όσα είναι τα "predecessor"-βήματα από το
δεύτερο όρισμα μέχρι το 0 .
Η it-add δεν είναι αναδρομική, και επομένως η "add-numbers" ορίζεται με μη αναδρομικό τρόπο.
Ανάπτυξη της παραπάνω γραφής:
Αντικαθιστώντας τον απλοποιημένης γραφής ορισμό τής it-add με την αντίστοιχη λ-έκφραση
που εννοείται, και το "if…then…else" με το αντίστοιχο cond , παίρνουμε:
it-add =
λf.λx.λy.(cond
x
(f (f ((successor x) (predecessor y))))
(is_zero y) )
Άρα:
add-numbers =
= it-add it-add =
= (it-add it-add) =
= (λf.λx.λy.(cond
x
(f (f ((successor x) (predecessor y))))
(is_zero y))
it-add )

[β-reduction: το it-add στη θέση του f] →
→ λx.λy.(cond
310

x
(it-add (it-add ((successor x) (predecessor y))))
(is_zero y))
Εφαρμόζοντας την "add-numbers" πάνω σε δύο αριθμούς, έστω τους 3 και 2 , και θεωρώντας ήδη
ορισμένους τους αναφερόμενους φυσικούς αριθμούς, παίρνουμε (με διαδοχικές αντικαταστάσεις
και υπολογισμούς):
add-numbers 3 2 → 5
Ο τρόπος αυτός έχει το πλεονέκτημα να ορίζει την πρόσθεση με μη αναδρομικό τρόπο. Έχει το
μειονέκτημα οτι δεν εκμεταλλεύεται την δυνατότητα προσδιορισμού "ενός σύνθετου υπολογισμού
από επαναλαμβανόμενη εφαρμογή ενός απλού υπολογισμού", που πετυχαίνει η χρήση αναδρομής.
β'.
Με χρήση αναδρομής:
Η ακόλουθη συνάρτηση "add-for-rec" προσδιορίζεται ως ένα σκαλοπάτι αφαίρεσης της
προηγούμενης err-add , χρησιμοποιώντας την ως βάση για την αφαίρεση:
def add-for-rec f x y ::=
if is_zero y
then x
else
f (successor x) (predecessor y)
Αν η add-for-rec δοθεί ως όρισμα στη συνάρτηση recursive, το αποτέλεσμα προσδιορίζει την
πρόσθεση, αλλά τώρα με αναδρομικό τρόπο:

def add ::= recursive add-for-rec
δηλαδή ισοδύναμα, αν αναγράψουμε και τις εννοούμενες παραμέτρους:
def add x y ::= recursive add-for-rec x y
H "add" έχει το αυτό αποτέλεσμα με την add-numbers για κάθε εφαρμογή τους σε δυο
αριθμητικά ορίσματα, και πλεονεκτεί στο οτι βασίζεται σε ενιαίο, γενικό μηχανισμό αναδρομής,
πανομοιότυπα εφαρμόσιμο σε κάθε περίπτωση, και εννοιολογικά απλούστερο.
Αναδρομικός ορισμός του πολλαπλασιασμού:
Αντίστοιχα με την "add" ορίζουμε την "mult", με το συλλογισμό:
"για να πολλαπλασιάσουμε δύο αριθμούς, προσθέτουμε τον πρώτο στο γινόμενο του πρώτου
επί τον δεύτερο ελαττωμένο κατά 1, και αν ο δεύτερος είναι 0 τότε και το γινόμενο είναι 0"
Ακολουθούμε πορεία ανάλογη με την της "add":
α'.
Βασική συνάρτηση - περιγραφή της αναδρομής (που δεν λειτουργεί λόγω της ατέρμονος
αντικατάστασης):
def err-mult x y ::=
if is_zero y
then 0
311

else
add x (err-mult x (predecessor y))
β'.
Αφαιρετικό σκαλοπάτι:
def mult-for-rec f x y ::=
if is_zero y
then 0
else
add x (f x (predecessor y))
γ'.
Τελική σύνθεση του αναδρομικού ορισμού:
def mult ::= recursive mult-for-rec
Εφαρμόζοντας την "mult" στα ορίσματα 2 και 3 , μετά από τους υπολογισμούς παίρνουμε
αποτέλεσμα 6 (το πλήθος των υπολογισμών είναι αρκετά μεγάλο).
Με το συμβατικό τρόπο γραφής της αναδρομής, ο ορισμός της "add" γράφεται ως εξής:
rec add x y ::=
if is_zero y
then x
else
add (successor x) (predecessor y)
και της "mult" ως εξής:
rec mult x y ::=
if is_zero y
then 0
else
add x (mult x (predecessor y))
Αντίστοιχα ορίζουμε διαφορά (με το συμβατικό τρόπο γραφής):
rec sub x y ::=
if is_zero y
then x
else
sub (predecessor x) (predecessor y)
Αντίστοιχα ορίζουμε δύναμη (στο συμβατικό τρόπο γραφής):
rec power x y ::=
if is_zero y
then 1
else
mult (power x (predecessor y))
312

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

Ισότητα αριθμών
Ένας τρόπος να ορίσουμε οτι δύο αριθμοί είναι ίσοι, είναι αν η απόλυτη τιμή της διαφοράς τους
είναι μηδέν:
def equal x y
::= is_zero (abs-diff x y)
def abs-diff x y ::= add (sub x y) (sub y x)
όπου is_zero είναι η συνάρτηση λn.(n select_first)
Άλλος τρόπος είναι, με αναδρομικό ορισμό της συνάρτησης "μεγαλύτερος" και λογικό
συσχετισμό (ίσα α και β, σημαίνει οτι δεν είναι α μεγαλύτερο του β και δεν είναι β μεγαλύτερο
του α).

7.1.6 Λίστες, απεριόριστες δομές
Λίστες
Θα αναφέρουμε συνοπτικά την κεντρική ιδέα που προσδιορίζει τη δομή της λίστας και οδηγεί
στις συναρτήσεις επεξεργασίας λίστας.
Ένας απλός τρόπος για να προσδιορίσουμε τη έννοια της λίστας χωρίς τη θεώρηση τύπων, είναι
να θεωρήσουμε:
– κενή λίστα (όνομα - σύμβολο nil) την έκφραση λx.λy.y
– λίστα με ένα όρο, το a, την έκφραση λd.(a nil) , όπου η μεταβλητή d είναι "dummy" (δηλ. δεν
περιέχεται στο a), και αν lis είναι λίστα:
– κεφαλή λίστας το αποτέλεσμα εφαρμογής της συνάρτησης "head" πάνω σε λίστα, όπου:
def head lis ::= lis λx.λy.x
το αποτέλεσμα της head είναι πάντα ο πρώτο όρος της λίστας.
– ουρά λίστας το αποτέλεσμα εφαρμογής της συνάρτησης "tail" πάνω σε λίστα, όπου:
def tail lis ::= lis λx.λy.y
το αποτέλεσμα της tail είναι πάντα η λίστα των όρων πλην του πρώτου.
– επισύναψη νέου όρου s σε λίστα με κεφαλή h και ουρά t , τη συνάρτηση:
def cons h t s ::= s h t
Το αποτέλεσμα της cons είναι λίστα με κεφαλή το νέο όρο s και ουρά την προηγούμενη λίστα.
Αποκαλούμε πρώτο όρο της λίστας αυτόν που εισάγαμε τελευταίο.

313

Απεριόριστες δομές
Συνδυάζοντας αναδρομή και δομή λίστας, μπορούμε να "κατασκευάσουμε" απεριόριστες δομές,
όπως το σύνολο - λίστα των φυσικών αριθμών:
rec list-of-numbers n ::=
cons n (list-of-numbers (successor n))
def all-numbers ::=
list-of-numbers 0
Ο υπολογισμός της κεφαλής της λίστας "all-numbers" δίνει 0 :
head all-numbers  0
Ο υπολογισμός της κεφαλής του σώματος της λίστας "all-numbers" δίνει 1 :
head (tail all-numbers)  1
Η κεφαλή του σώματος του σώματος της λίστας "all-numbers" είναι (μετά τους υπολογισμούς):
head (tail (tail all-numbers))  2
Για οποιαδήποτε πεπερασμένη τάξη, η κεφαλή του σώματος του σώματος του σώματος κλπ. για n
βήματα, θα δώσει τον αριθμό n .

Πρέπει να διευκρινίσουμε οτι σε κανονική τάξη υπολογισμού, με τη μέθοδο αυτή, κάθε φορά που
θα ζητήσουμε έναν αριθμό, επανυπολογίζεται ολόκληρη η ακολουθία των αριθμών μέχρι τον
ζητούμενο.

7.1.7 Τύποι, σφάλματα
H ανάπτυξη του λ-λογισμού που δώσαμε, είναι η χωρίς τύπους έκδοση (typeless version).
Ακολουθώντας μια διαφορετική πορεία από το σημείο όπου ορίσαμε τους αριθμούς, παίρνουμε
την με τύπους έκδοση (typed version). Η κεντρική ιδέα είναι η εξής:

Θεωρούμε και πάλι τη συνάρτηση successor, αλλά τώρα, αντί να θεωρήσουμε πως παράγουμε
τους αριθμούς, θεωρούμε πως παράγουμε τους τύπους. Επιλέγουμε ως αρχικό τύπο, με ορισμό
αυτόν που είχαμε δώσει για το μηδέν, το τύπο σφάλμα (error)∙ στη συνέχεια τον λογικό τύπο
(boolean), μετά τον τύπο αριθμού (number), μετά της λίστας (list), μετά της συμβολοσειράς
(string), κλπ:
def error_type
::= λx.x
def boolean_type ::= (successor error_type)
def number_type ::= (successor boolean_type)
def list_type
::= (successor number_type)
def character_type ::= (successor list_type)
Ορίζουμε τώρα μια συνάρτηση κατασκευής αντικειμένων κάποιου τύπου και κάποιας τιμής:
def typed_make_object a-type a-value ::=
λs.(s a-type a-value)
314

όπου a-type είναι κάποιος τύπος και a-value κάποια έκφραση.

Θεωρούμε ως αντικείμενο ένα ζεύγος με πρώτο μέλος τον τύπο και δεύτερο την τιμή.
Εφαρμόζοντας την έκφραση που προσδιορίζει αντικείμενο λs.(s a-type a-value) πάνω στην
select_first, δηλ. την λx.λy.x, παίρνουμε το μέλος a-type ενώ εφαρμόζοντάς την πάνω στην
select_second, δηλ. την λx.λy.y παίρνουμε το μέλος a-value . Ορίζουμε λοιπόν τις συναρτήσεις
type και value , όπου an-object είναι ένα αντικείμενο της παραπάνω μορφής :
def type an-object
::= an-object select_first
def value an-object ::= an-object select_second
Έλεγχο τύπου κάνουμε με τη συνάρτηση istype :
def is_type a-type an-object ::=
equal (type an-object) a-type
Με αντίστοιχο τρόπο δημιουργούμε κλάδους των τύπων:

Σφάλματα
Συνάρτηση που παράγει αντικείμενο τύπου σφάλματος:
def typed_make_error ::= typed_make_object error_type
< β' μέλος, με τις εννοούμενες λ-μεταβλητές> =
= λval.λs.(s error_type val)  λs.(s error_type true)
Με βάση αυτό, ορίζουμε ένα καθολικό αντικείμενο, "σφάλμα":
def error ::= make_error error_type
Συνάρτηση ελέγχου αν ένα αντικείμενο είναι τύπου σφάλματος:
def typed_is_error ::= is_type error_type
< β' μέλος> = λtyp.λobj.(equal (type obj) typ) error_type 
 λobj.(equal (type obj) error_type)

Λογικά αντικείμενα
Ακολουθούμε πανομοιότυπη πορεία με τον κλάδο των σφαλμάτων:
def typed_make_boolean ::= typed_make_object boolean_type
< β' μέλος> = λval.λs.(s boolean_type val) 
 λs.(s boolean-type true)
def typed_is_boolean ::= is_type boolean_type
< β' μέλος> = λtyp.λobj.(equal (type obj) typ) boolean_type 
 λobj.(equal (type obj) boolean_type)
Αν εφαρμόσουμε την make_error πάνω στην boolean_type, παίρνουμε συνάρτηση που καθορίζει
οτι το αντικείμενο είναι τύπου error και τύπου boolean_type :
def typed_boolean_error ::= typed_make_error boolean_type
315

< β' μέλος> = λs.(s error_type boolean_type)
Τώρα μπορούμε να ορίσουμε την "typed" έκδοση των λογικών τιμών και συνδετικών, με το
πλεονέκτημα της "τρίτης διεξόδου" στην περίπτωση σφάλματος:
def typed_true ::= make_boolean true
def typed_false ::= make_boolean false
def typed_not obj-x ::=
if is_boolean obj-x
then make_boolean (not (value obj-x))
else typed_boolean_error
def typed_and obj-x obj-y ::=
if is_boolean obj-x
then make_boolean (and (value obj-x ) (value obj-y))
else typed_boolean_error
def typed_or obj-x obj-y ::=
if is_boolean obj-x
then make_boolean (or (value obj-x) (value obj-y))
else typed_boolean_error
Αντίστοιχα, ορίζουμε το "typed" conditional:
def typed_cond obj-e1 obj-e2 obj-c ::=
if is_boolean obj-c
then
if (value obj-c)
then obj-e1
else obj-e2
else typed_boolean_error

Αντικείμενα - αριθμοί
def typed_make_number val ::=
typed_make_object number_type val
< β' μέλος> = λval.λs.(s number_type val)
def typed_number_error ::=
typed_make_error number_type
def typed_is_number n ::=
typed_make_boolean (is_number n)
def is_number ::=
is_type number_type
Τώρα, μπορούμε να προσδιορίσουμε τους αριθμούς: αρκεί να ορίσουμε το μηδέν, και συνάρτηση
ανάλογη της successor :
316

def 0 ::= typed_make_number λx.x
def typed_succ obj ::=
if is_number obj
then typed_make_number (successor (value obj))
else typed_number_error
Τα παραπάνω αποτελούν τη θεωρητική βάση για την ανάπτυξη του ΣΠ. Δεν θα επεκταθούμε
περισσότερο στον λ-λογισμό διότι ξεφεύγει από τα πλαίσια του παρόντος.

7.1.8 Παρατηρήσεις και συμπεράσματα
Σύγκριση "typed" και "typeless" έκδοσης του λ-λογισμού
Η απλότητα της "typeless" έκδοσης κάνει το μοντέλο αυτό προσιτό και εύχρηστο, και επί πλέον
δεν απαγορεύει τη χρήση από εκφράσεις του, σε περιβάλλοντα με τύπους: αν εννοείται κάποιος
τύπος, το αντικείμενο έχει αυτό τον τύπο, και η τιμή του αντικειμένου μπορεί να πάρει τη θέση
του, τότε η έκφραση μεταφέρεται στο τυποποιημένο περιβάλλον αυτούσια. Θα δούμε τέτοιες
μεταφορές στη Lisp.
Ακόμα, τίποτα δεν απαγορεύει την μετέπειτα ανάπτυξη τύπων (αν και τότε χρειάζεται να
μεταφερθούν όλες οι συναρτήσεις στην "typed" έκδοση).
Η "typed" έκδοση δίνει δύο θεμελιακές δυνατότητες: i) τον έλεγχο της περίπτωσης σφάλματος,
όπου "σφάλμα" είναι κάποια κατάσταση που είναι δυνατό να πέσει ο υπολογισμός, και επίσης
"σφάλμα" είναι ένα αντικείμενο, με τιμή διαφοροποιήσιμη από το ένα σφάλμα στο άλλο, άρα
παρέχει τη δυνατότητα ανακατεύθυνσης της υπολογιστικής λειτουργίας∙ ii) τη δενδροειδή
ανάπτυξη των αντικειμένων, όπου είδαμε μεν την σε δύο επίπεδα ανάπτυξη (τύποι, αντικείμενα)
αλλά κάθε αντικείμενο μπορεί να θεωρηθεί τύπος κάτω από τον οποίο είναι δυνατό να
αναπτύξουμε με ανάλογο τρόπο άλλο επίπεδο, εξειδικεύοντας τη λειτουργικότητα των
συναρτήσεων.

Συνεπείς επεκτάσεις του λ-λογισμού
Σύμφωνα με τη θέση του Church, ο λ-λογισμός είναι η γενικότερη δυνατή θεωρία υπολογισμού:
Αν δοθεί μια διαφορετική θεωρία υπολογισμού, τότε αυτή μπορεί πάντα να περιγραφεί με όρους
λ-λογισμού.
Επεκτάσεις της θεωρίας του λ-λογισμού, μπορούμε να προσδιορίσουμε, αρκεί οι ορισμοί νέων
εννοιών που δίνουμε να είναι συμβατοί προς τη θεωρία του λ-λογισμού.
Μια τέτοια επέκταση είναι η θεώρηση σταθερών και συμβόλων - μεταβλητών:
Για λόγους ευχέρειας στη σύνταξη συνθέτων υπολογισμών, θεωρούμε οτι, αντίστοιχα με την
ονομασία λ-εκφράσεων, μπορούμε να δώσουμε όνομα σε υπολογισμό, με την αναγραφή :
317

set <vname> ::= (func arg)
Η αναγραφή του ονόματος <vname>, που δημιουργούμε με τον τρόπο αυτό, μέσα σε μια
έκφραση, σημαίνει οτι στη θέση του νοείται η έκφραση που ονομάζει. Έτσι, από το ένα μέρος
απλοποιούμε τη γραφή συνθέτων εκφράσεων, και από το άλλο μέρος αποκτάμε την ευχέρεια
απλουστευμένης αναφοράς από σύνθετες εκφράσεις, είτε πρόκειται για λ-εκφράσεις (με ονόματα
συναρτήσεων) είτε για εφαρμογές (με ονόματα μεταβλητών). Θα αποκαλούμε σύμβολο ένα
όνομα, είτε είναι όνομα συνάρτησης είτε υπολογισμού.
Θεωρούμε οτι η αντικατάσταση τέτοιου ονόματος με την έκφραση που παριστά δεν απαιτεί βήμα
υπολογισμού, δηλ. το όνομα είναι ταυτόσημο με την έκφραση που ονοματίζει. Έχουμε δύο
δρόμους θεώρησης του περιεχομένου: i) να παριστά την έκφραση υπολογισμού πριν την εκτέλεση
του υπολογισμού, και ii) να παριστά το αποτέλεσμα του υπολογισμού. Συμβατικά θεωρούμε οτι
ισχύει το δεύτερο, δηλ. οτι ο υπολογισμός που ονοματίζεται από σύμβολο, εκτελείται πριν τη
ονομασία, άρα το σύμβολο παίρνει περιεχόμενο το αποτέλεσμα του υπολογισμού.
Είδαμε οτι πάντα μπορούμε να καθυστερήσουμε έναν υπολογισμό, απλώς προσδιορίζοντάς τον
ως σώμα λ-έκφρασης, άρα η πρώτη περίπτωση μπορεί να υποστηριχθεί με την τεχνική αυτή. Σε
ορισμένες γλώσσες προγραμματισμού, όπως η Common Lisp, είναι δυνατό να δοθεί ως τιμή σε
μεταβλητή ένας καθυστερημένος υπολογισμός, οπότε το σύμβολο παίρνει ως περιεχόμενο την
ίδια την έκφραση του υπολογισμού και όχι απλώς το αποτέλεσμά του.
Θεωρούμε πως υπάρχουν εκφράσεις υπολογισμού που είναι καταληκτικές, δηλ. έχει εκτελεστεί
κάθε δυνατός υπολογισμός και δεν είναι συναρτησιακές εκφράσεις. Αποκαλούμε μια τέτοια
έκφραση, σταθερά.

Θεωρούμε οτι έχουμε δικαίωμα να δώσουμε νέο προσδιορισμό του περιεχομένου σε ένα όνομα
που ήδη έχει δοθεί σε υπολογισμό, αναθεωρώντας έτσι τον προηγούμενο ορισμό του. Για το λόγο
αυτό, το σύμβολο που ονομάζει υπολογισμό, αποκαλείται μεταβλητή. Συνεπώς, σύμβολα είναι
ονόματα που είναι συναρτήσεις ή μεταβλητές.
Η ισότητα εκφράσεων ορίζεται και αυστηρότερα από τον τρόπο που χρησιμοποιήσαμε. Ένας
τρόπος είναι, να δώσουμε τον εξής ορισμό:
Οι εκφράσεις Α και Β είναι ίσες τότε και μόνον τότε αν οι εφαρμογές τους δίνουν σε κάθε
περίπτωση, για τα αυτά ορίσματα, τα αυτά αποτελέσματα.

Συμπεράσματα
Η θεωρία του λ-λογισμού αποτελεί μια βάση πάνω στην οποία μπορούμε να κτίσουμε "εκ του
μηδενός" το λογικό σύστημα, τους κανόνες λογικής παραγωγής, το αριθμητικό σύστημα, και το
σύστημα υπολογισμού (κανόνες και τάξη).
Οι διαδοχικές απλοποιήσεις έκφρασης όπως αυτές που είδαμε, προσφέρουν ευχέρεια στη
σύνθεση αλγορίθμων, ευκρίνεια στην ανάγνωσή τους (συγκριτικά, το conditional εκφρασμένο με
την cond ή με τη γραφή if...then...else) και συμπαγότητα (παράλειψη παρενθέσεων, εννοούμενες
λ-μεταβλητές), και έτσι κτίζουμε μια γλώσσα έκφρασης σε όσο υψηλό επίπεδο θέλουμε.
Ίσως το ουσιαστικότερο στοιχείο του λ-λογισμού, σαφές πλεονέκτημα απέναντι στα διαδικαστικά
318

μοντέλα υπολογισμού, είναι η εξαιρετική ευελιξία που έχει για τον προσδιορισμό εφαρμογών που
παράγουν συναρτησιακές εκφράσεις, και γενικά η διαχείριση των συναρτησιακών εκφράσεων ως
"αντικειμένων" του υπολογισμού.

Με την υλοποίηση του βασικού λ-λογισμού μαζί με τις επεκτάσεις που προαναφέραμε σε μια
γλώσσα προγραμματισμού, έχουμε τη δυνατότητα σύνθεσης, ονομασίας και εφαρμογής
συναρτησιακών εκφράσεων, ονομασία εφαρμογών, και θέση dummy μεταβλητής για
καθυστέρηση υπολογισμού. Έχουμε τη δυνατότητα να κτίζουμε σταδιακά οσοδήποτε σύνθετες
δομές (αλγόριθμους και εφαρμογές αλγορίθμων), και να επαναχρησιμοποιεί τις συνθέσεις που
ήδη έχει σχηματίσει, όπου οι αναφορές σε ονόματα είναι δυναμικές, και όπου η διαδοχή των
υπολογισμών είναι ελεγχόμενη. Με τον τρόπο αυτό, ο χρήστης της γλώσσας μπορεί να
δημιουργήσει μια δική του γλώσσα έκφρασης αλγορίθμων και εφαρμογών υπολογισμού.
Γενικότερα, μπορούμε να πούμε οτι ο λ-λογισμός, εκτός από θεωρητική δομή, είναι και μια
ιδεατή γλώσσα προγραμματισμού (όπως είναι και οι μηχανές Turing), τέτοια που να επιτρέπει τα
πάντα, με την έννοια οτι, οτιδήποτε είναι δυνατό να προγραμματιστεί σε άλλη γλώσσα, μπορεί να
περιγραφεί ως αλγόριθμος εκφρασμένος με όρους λ-λογισμού.

Υλοποίηση σε Lisp των εκφράσεων του λ-λογισμού
Υπάρχει φορμαλιστική αντιστοιχία υπολογισμού κατά το λ-λογισμό και κατά τη Lisp, όταν
πρόκειται για όνομα συνάρτησης που εφαρμόζεται σε σταθερά ή σε σύμβολο - μεταβλητή∙
σημειώνουμε πανομοιότυπα:
(f n)
Αλλά όταν πρόκειται για εφαρμογή λ-έκφρασης ή μεταβλητής συνάρτησης πάνω σε λ-έκφραση ή
συνάρτηση, όπως όταν στo λ-λογισμό θεωρούμε τις ακόλουθες εκφράσεις (όπου οι f και g να
είναι συναρτήσεις):
(λx.x λz.z)
[εκφρ.1]
λf.(f g)
[εκφρ.2]
στη γλώσσα Lisp πρέπει να "ενημερώσουμε" επιπρόσθετα τον compiler για τα εξής:
α'. οτι πρόκειται για εφαρμογή λ-έκφρασης (της λx.x στην περίπτωση [εκφρ.1]) ή μεταβλητής
συνάρτησης (της f στην περίπτωση [εκφρ.2]) , και αυτό γίνεται με χρήση της συνάρτησης
funcall,
β'. οτι το αντικείμενο εφαρμογής είναι συνάρτηση (η g στην περίπτωση [εκφρ.2]) , και αυτό
γίνεται με χρήση του κατάλληλου προθέματος.
Όπως θα δούμε, οι αντίστοιχες εκφράσεις στη Lisp είναι οι:
(funcall #'(lambda (x) x) '(lambda (z) z))
(lambda (f) (funcall f #'g))

Η γλώσσα Scheme πλεονεκτεί εκφραστικά σ' αυτό, διότι δεν χρειάζεται "παράκαμψη" μέσω της
funcall, αλλά είναι θέμα απλώς τυπικής γραφής, και όχι δυνατοτήτων.

319

7.2 λ-εκφράσεις στη Lisp
Όπως προαναφέραμε, η Lisp, είναι γλώσσα που βασίζεται στον λ-λογισμό, και μάλιστα τα πάντα
εσωτερικά στη Lisp προσδιορίζονται ως λ-εκφράσεις και εφαρμογές λ-εκφράσεων. Αν και το
θεωρητικό μοντέλο του λ-λογισμού δεν τηρείται απόλυτα, οι "παραβάσεις" είναι συγκεκριμένες,
και θέτουν μεν κάποιους περιορισμούς αλλά δεν δημιουργούν ασυνέπειες.
Ο συμβολισμός στα επόμενα ακολουθεί τη σύνταξη της Lisp.

7.2.1 S-εκφράσεις και λ-εκφράσεις

Αναφέρουμε εδώ συνοπτικά τα βασικά στοιχεία που αφορούν τις λεγόμενες S-εκφράσεις (Sexpressions), που καθορίζουν τη σημειολογία που ακολουθεί η Lisp και γενικότερα ο ΣΠ.
Ορίζεται ως S-έκφραση μια παράσταση σύμφωνα με τον ακόλουθο φορμαλισμό (η κάθετος
σημαίνει "ή"):
<S-έκφραση> ::= <άτομο> | (<S-λίστα>)
<S-λίστα>
::= <ζεύγος> | ( <S-έκφραση> <S-λίστα> )
<ζεύγος>
::=
( <S-έκφραση> . <S-έκφραση> )
Δηλαδή: "S-έκφραση" είναι ή "άτομο" ή "S-λίστα", όπου "S-λίστα" είναι ή S-έκφραση ή "ζεύγος"
S-εκφράσεων ή S-έκφραση που ακολουθείται από "S-λίστα".
Ο παραπάνω φορμαλισμός συμπεριλαμβάνει στις S-λίστες και τις "dotted lists". Κοινή λίστα
παίρνουμε όταν το τελευταίο δεξιά στοιχείο S-λίστας είναι το άτομο – κενή λίστα.

Η σύνταξη της Lisp βασίζεται σχεδόν εξ ολοκλήρου στις S-εκφράσεις, με ελάχιστες
διαφοροποιήσεις. Οι διαφοροποιήσεις αυτές αφορούν κυρίως τη χρήση από τα σύμβολα ' ` ,
# @ :
Το επιπρόσθετο χαρακτηριστικό που έχουν οι φόρμες της Lisp σε σχέση με τις S-εκφράσεις, είναι
οτι στη σύνταξη από φόρμες που δεν είναι άτομα, το πρώτο μέλος νοείται πάντοτε ως συνάρτηση.

Αυτό έχει ως συνέπεια οτι: στη Lisp ξεκινάμε με θεμελιακή πρωτογενή έννοια την εφαρμογή, και
ο προσδιορισμός συναρτησιακής έκφρασης είναι μια ειδική περίπτωση εφαρμογής, αντίθετα από
το λ-λογισμό όπου ξεκινάμε από την πρωτογενή θεμελιακή έννοια της συναρτησιακής έκφρασης,
βάσει της οποίας προσδιορίζεται η έννοια της εφαρμογής.

Αυτό είναι ένα ουσιαστικό χαρακτηριστικό που διαφοροποιεί τη Lisp από τον λ-λογισμό, και
έχει προκαλέσει ποικίλες επιστημονικές συζητήσεις για το κατά πόσον η Lisp είναι γλώσσα που
υλοποιεί τον λ-λογισμό. Εν πάσει περιπτώσει το θέμα αυτό είναι περισσότερο θεωρητικό, διότι
στην πράξη έτσι κι αλλιώς διαθέτουμε επαρκές σύνολο πρωτογενών συναρτήσεων που
επιτρέπουν τη σύνθεση συναρτησιακών εκφράσεων, έστω και σε μορφή εφαρμογής.
320

Σύνταξη λ-εκφράσεων σε μορφή S-εκφράσεων

Η σύνταξη λ-εκφράσεων στη Lisp γίνεται σε μορφή S-εκφράσεων, με τον ακόλουθο φορμαλισμό:
Μια λ-έκφραση αποδίδεται ως S-λίστα, όπου:
– Πρώτος όρος είναι το άτομο LAMBDA (είναι συνάρτηση και χρησιμοποιείται ως συνάρτηση)
– Δεύτερος όρος η λίστα των λ-μεταβλητών, η λεγόμενη λ-λίστα, που είναι οι “εσωτερικές”
μεταβλητές της έκφρασης <σώμα> που ακολουθεί∙ οι μεταβλητές αυτές είναι πεπερασμένου
πλήθους, ενδεχομένως καμμία (κενή λίστα).
– Ακολουθεί το <σώμα> , το οποίο, εάν υπάρχει, είναι μια παράσταση που δίνεται ως Sέκφραση ή ως πεπερασμένη ακολουθία S-εκφράσεων:
(lambda (v1 v2 …) <σώμα> )
ήτοι:
(lambda (v1 v2 …) S1 S2 S3 …Sn)

7.2.2 Σχέση λ-έκφρασης και συνάρτησης

Το νόημα μιας λ-έκφρασης για τη Lisp, είναι: συνάρτηση χωρίς όνομα. Όπως και στις
συναρτήσεις, οι αναφερόμενες λ-μεταβλητές, επειδή έχουν νόημα εσωτερικό για την λ-έκφραση,
ως ονόματα δεν σχετίζονται με τυχόν όμοια ονόματα καθολικών ή εν γένει εξωτερικών
μεταβλητών, και υπερισχύουν των εξωτερικών αν συμβεί να έχουν ίδια ονόματα, ακριβώς όπως
στις συναρτήσεις.

Αντί της μορφής που έχει η γραφή λ-έκφρασης στον λ-λογισμό, στη Lisp συντάσσουμε μια λέκφραση σε μορφή εφαρμογής συνάρτησης όπου συνάρτηση είναι η LAMBDA , ακολουθεί ως
πρώτο όρισμα η λίστα των λ-μεταβλητών (από καμμία μέχρι n , και δέχεται όλα τα λ-κλειδιά των
συνήθων συναρτήσεων) και επόμενα ορίσματα είναι οι διαδοχικές φόρμες του σώματος (γραφή
και νόημα πανομοιότυπο με τη μορφή ορισμού συνάρτησης. Με άλλα λόγια, στον προσδιορισμό
λ-έκφρασης, σε αντιστοιχία με τον προσδιορισμό συνάρτησης, αντί για το τμήμα "defun
<όνομα>" υπάρχει το "lambda" .
Στο σημείο αυτό διαφέρει ο προσδιορισμός της έννοιας "υπολογισμός" στη Lisp από τον λλογισμό: στον τελευταίο λ-έκφραση είναι μια πρωτογενής έννοια, ενώ στη Lisp δεν είναι τίποατ
άλλο παρά εφαρμογή της συνάρτησης LAMBDA .
Για παράδειγμα:

(lambda (x) (+ x 1))

; αποδίδει το νόημα της αλγεβρικής παράστασης “x+1”

(lambda (x) x )

; αποδίδει το νόημα της ταυτοτικής λ-έκφρασης

(lambda (x y z) (+ x y z))

; αποδίδει το "άθροισμα τριών μεταβλητών"

(lambda (x y) (+ (* a x) (* b y))

;; είναι η παράσταση ax+by, όπου x , y είναι
; λ-μεταβλητές και a , b εξωτερικές μεταβλητές
321

Το σώμα λ-έκφρασης μπορεί να αποτελείται από περισσότερους του ενός όρους, ακριβώς όπως
στις συναρτήσεις της Lisp. Όπως είπαμε και στις συναρτήσεις, λέγεται "εννοούμενο progn" διότι
η έκφραση - φόρμα:
( lambda (x) (print (+ x 1)) (– x 2) )

ισοδυναμεί με την :
( lambda (x) (progn (print (+ x 1)) (– x 2)) )

Ισχύουν οι τρεις βασικοί κανόνες του λ-λογισμού που αναφέρονται στο Κεφ.1, και συγκεκριμένα:

– Ο πρώτος κανόνας, ο λεγόμενος α-conversion (α-κανόνας ή κανόνας μετατροπής) αφορά την
αντικατάσταση ονόματος λ-μεταβλητής. Ο κανόνας δηλώνει οτι τα ονόματα των λ-μεταβλητών
μπορούν να αλλάξουν, αρκεί “αυτό να γίνει παντού”, από την αναφορά τους στη λίστα των λμεταβλητών μέχρι το σώμα. Αυτό σημαίνει πως η έκφραση:
(lambda (x) (+ x (* x 4))

είναι ταυτόσημη με την:
(lambda (z) (+ z (* z 4))

– Ο δεύτερος κανόνας, ο λεγόμενος β-reduction (β-κανόνας ή κανόνας αναγωγής) αφορά την
εφαρμογή λ-έκφρασης πάνω σε οντότητα. Η εφαρμογή προκαλεί δέσμευση της αντίστοιχης λμεταβλητής πάνω στην οντότητα αυτή. Ο κανόνας δηλώνει οτι η εφαρμογή λ-έκφρασης έχει ως
αποτέλεσμα μια έκφραση που σχηματίζεται από το σώμα της λ-έκφρασης στο οποίο έχει
αντικατασταθεί η λ-μεταβλητή από την οντότητα. Αυτό σημαίνει πως η έκφραση:
(lambda (x) (+ x (* x 4)) )

αν εφαρμοστεί πάνω στην οντότητα - αριθμό 3 , θα δώσει την έκφραση:
(+ 3 (* 3 4))

που υπολογίζεται βάσει των ορισμών των πράξεων και δίνει 15 , άρα, θεωρητικά, έχουμε:
((lambda (x) (+ x (* x 4))) 3) → 15
Θα δούμε οτι στην CL δεν επιτρέπεται αυτή η γραφή, λόγω του οτι η συναρτησιακή έκφραση
(lambda (x) (+ x (* x 4))) δεν είναι όνομα συνάρτησης, όπως περιμένει η Lisp, και "χρειάζεται κάτι
ακόμα".

– Ο τρίτος κανόνας, ο λεγόμενος η-reduction (β-κανόνας ή κανόνας απλοποίησης) αφορά την
εφαρμογή λ-έκφρασης πάνω σε λ-έκφραση: Ο κανόνας αυτός δηλώνει οτι, τέτοια εφαρμογή δίνει
αποτέλεσμα λ-έκφραση, όπου η δεύτερη λ-έκφραση αντικαθιστά την αντίστοιχη λ-μεταβλητή της
πρώτης λ-έκφρασης παντού στο σώμα της, και λ-μεταβλητή του αποτελέσματος είναι η λμεταβλητή της δεύτερης. Αυτό σημαίνει πως η λ-έκφραση:
(lambda (x) (+ x (* x 4)))

αν εφαρμοστεί πάνω στην λ-έκφραση:
(lambda (y) (* y y))

θα δώσει ως αποτέλεσμα την έκφραση:
(lambda (y) (+ (* y y) (* (* y y) 4)))

Θα δούμε παρακάτω αναλυτικά το συγκεκριμένο τρόπο έκφρασης της εφαρμογής λ-έκφρασης.
322

7.2.3 Υπολογισμός εφαρμογής λ-έκφρασης

Μια λ-έκφραση στη Lisp, ως φόρμα εφαρμογής της συνάρτησης LAMBDA , παίζει και ρόλο
προσδιορισμού της λ-έκφρασης, πανομοιότυπα με τον τρόπο που η φόρμα (DEFUN F …) είναι
φόρμα εφαρμογής της συνάρτησης DEFUN και ταυτόχρονα παίζει ρόλο προσδιορισμού της
συνάρτησης. F . Ως συνάρτηση έχει έξοδο, που είναι αυτή καθ’ εαυτή η παράσταση που ορίζει το
σώμα. Δηλ. η φόρμα: (lambda (x) (+ x 1)) ουσιαστικά παριστά τη μαθηματική παράσταση x+1 .
H Lisp αναγνωρίζει τέτοιες παραστάσεις, και αυτό επιτρέπει να χρησιμοποιηθεί η παράσταση ως
υπολογιστική οντότητα οπουδήποτε έχει νόημα αυτό, δηλ. να δοθεί ως είσοδος σε συνάρτηση,
να εφαρμοστεί, να δοθεί ως απάντηση στο πρώτο επίπεδο, να αποκτήσει ιδιότητες, να συνδεθεί σε
ζεύγος με άλλη οντότητα, κλπ.

Θεωρητικά, οι λ-εκφράσεις χρησιμοποιούνται με τον ίδιο τρόπο που χρησιμοποιούνται οι
συναρτήσεις:
(<λ-έκφραση> όρισμα1 όρισμα2 … όρισμαk}
Όμως, κατά τo πρότυπο της Common Lisp, η εφαρμογή λ-έκφρασης πάνω σε ορίσματα δεν
γίνεται απ' ευθείας αλλά μέσω κλήσης της από την FUNCALL , με τη γνωστή σύνταξη:
(funcall #'<λ-έκφραση> όρισμα1 όρισμα2 … όρισμαk)
Για παράδειγμα:
(funcall #'(lambda (x) (+ x 1)) 2)
→ 3
(δηλ: "εφάρμοσε την λ-έκφραση… πάνω στο όρισμα 2", και αυτό δίνει αποτέλεσμα 3)
Επίσης μπορεί να γίνει μέσω κλήσης της από την APPLY, με τη γνωστή σύνταξη:
(apply #'<λ-έκφραση> '(όρισμα1 όρισμα2 … όρισμαk) )
Σε κάθε περίπτωση μπορούμε να βλέπουμε τις λ-εκφράσεις ως ανώνυμες συναρτήσεις, άρα να τις
χρησιμοποιούμε αντί του ονόματος συνάρτησης, στη θέση συνάρτησης (και όπου χρειάζεται, να
τις καλούμε για εφαρμογή θέτοντάς 'τες ως όρισμα της FUNCALL ή της APPLY με πρόθεμα #' ).
Συνοψίζουμε στα εξής:

Η εφαρμογή λ-έκφρασης πάνω σε ορίσματα γίνεται με το συνήθη τρόπο εφαρμογής συνάρτησης
πάνω στα απαιτούμενα ορίσματά της, μέσω της FUNCALL ή της APPLY : Δίνουμε μια φόρμα λίστα που πρώτο όρο έχει τη συνάρτηση FUNCALL , δεύτερο τη λ-έκφραση με πρόθεμα #' και
επόμενους τα αντίστοιχα ορίσματα στα οποία θα δεσμευτούν οι λ-μεταβλητές (με την ίδια σειρά
που είναι στη λίστα των λ-μεταβλητών και με το ίδιο πλήθος). Από τον β' κανόνα (αναγωγής)
λαβαίνουμε τον υπολογισμό του αποτελέσματος:
(funcall
#'(lambda (x) (+ x 1))
2)
→ 3
(διότι 2+1=3)
(setq n 4)
(funcall
323

#'(lambda (x) (+ x 1))
n)
→ 5
(διότι 4+1=5)
(funcall
#'(lambda (x y z) (+ x y z))
2 4 6)
→ 12
(διότι 2+4+6=12)

Η σύνταξη εφαρμογής λ-έκφρασης είναι ακριβώς η γνωστή σύνταξη εφαρμογής συνάρτησης σε
ορίσματα, δηλ. η προηγούμενη ισοδυναμεί με:
(defun add (x y z) (+ x y z))
(add 2 4 6)
→ 12

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

Εφαρμογή λ-έκφρασης πάνω σε λ-έκφραση
Θεωρητικά ακολουθεί τον η-κανόνα (απλοποίησης), αλλά πρέπει να λάβουμε υπ' όψη μας τη
ιδιομορφία κλήσης λ-έκφρασης προς εφαρμογή (δηλ. χρήση της FUNCALL) :
Αυτό δεν αποκλείει τον προσδιορισμό λ-έκφρασης που να απαιτεί συναρτησιακή έκφραση για να
εφαρμοστεί, ακριβώς όπως προσδιορίζουμε συναρτήσεις που παίρνουν όρισμα όνομα
συνάρτησης (με πρόθεμα #' ). Προφανώς, το όρισμα πρέπει να δίνεται "quoted" ή με πρόθεμα
#' .
Παράδειγμα
; εκτέλεσε την ακόλουθη εφαρμογή:
; εφάρμοσε το πρώτο όρισμα πάνω στο δεύτερο

(funcall

#'(lambda (f x) (funcall f x))

; όπου πρώτο όρισμα είναι η λ-έκφραση
#'(lambda (x) (+ x 3))

; και δεύτερο όρισμα είναι ο αριθμός 5
5)

→ 8

(διότι 3+5=8)

Διαφοροποίηση του προθέματος "function-quote" (#') από το απλό "quote"
Μια λ-έκφραση ισοδυναμεί με όνομα συνάρτησης, και επομένως η κλήση της ως όρισμα
συναρτησιακής έκφρασης οφείλει να έχει το πρόθεμα #' . Παρ' όλα αυτά, η Lisp αναγνωρίζει το
324

ρόλο της συνάρτησης LAMBDA, και αν τεθεί μόνο το πρόθεμα "quote", το τελικό αποτέλεσμα
εφαρμογής είναι το ίδιο με αυτό όπου χρησιμοποιήσαμε το πρόθεμα #' :
(funcall '(lambda (x) (* x x x)) 3)
→ 27
(funcall #'(lambda (x) (* x x x)) 3) → 27
Όμως, υπάρχει μια ουσιαστική διαφοροποίηση:
– Στην πρώτη περίπτωση το πρόθεμα "quote" αποτρέπει τον compiler από το να υπολογίσει την
έκφραση που ακολουθεί∙ η FUNCALL προκαλεί εφαρμογή του πρώτου ορίσματός της, οπότε "επί
τόπου" υπολογίζεται και αναγνωρίζεται το όρισμα ως συναρτησιακή έκφραση (δηλ. περνά από
μεταγλώττιση στη φάση εφαρμογής, "από μέσα προς τα έξω").
– Στη δεύτερη περίπτωση το πρόθεμα "function" (#) δηλώνει στον compiler οτι η έκφραση που
ακολουθεί είναι συναρτησιακή (και περνά από μεταγλώττιση στη φάση ανάγνωσης, "απ' έξω
προς τα μέσα") ενώ το δεύτερο πρόθεμα "quote" δηλώνει πως δεν θα γίνει εφαρμογή της (θα γίνει
μετά, λόγω της κλήσης της FUNCALL ).
Έτσι, αν δώσουμε:
(setq lambda1 '(lambda (x y) (expt x y)))

και καλέσουμε:
lambda1

θα πάρουμε απάντηση:
(lambda (x y) (expt x y))

ενώ αν δώσουμε:
(setq lambda1 #'(lambda (x y) (expt x y)))

και καλέσουμε:
lambda1

θα πάρουμε (στην Allegro v.5) απάντηση της μορφής:
#<function 2 #xE9E764>

όπου το #<...> δηλώνει οτι πρόκειται για ειδική έκφραση, function είναι ο τύπος του αντικειμένου
που προσδιορίστηκε (παρατηρούμε πως, παρ' όλο που lambda1 είναι σύμβολο - μεταβλητή,
αναγνωρίζεται ο ρόλος του), 2 είναι ο αριθμός των ορισμάτων της συναρτησιακής έκφρασης
(σ.ε.), #xE9E764 είναι το εσωτερικό όνομα της σ.ε. που έδωσε ο compiler. Η τυπική έκφραση
που επιστρέφεται από τον υπολογισμό είναι αναγνωρίσιμη από τη Lisp (δηλ. μπορούμε να την
αντιγράψουμε και να τη χρησιμοποιήσουμε) και επίσης είναι αναγνωρίσιμο το εσωτερικό όνομα
που δίνει στην έκφραση.
Η διαφοροποίηση αυτή έχει ουσιαστικό νόημα όταν οι λ-εκφράσεις συμμετέχουν σε
συναρτησιακή σύνθεση: δεν είναι δυνατή η σύνθεση έκφρασης περισσοτέρων βημάτων, όπου ένα
ενδιάμεσο συναρτησιακό σκαλοπάτι ορίζεται με χρήση απλού "quote", διότι ο compiler δεν
αναγνωρίζει οτι πρόκειται για συναρτησιακή έκφραση).

325

λ-έκφραση με χρήση λ-μεταβλητών που δεν αναφέρονται στο σώμα
Έστω η λ-έκφραση:
(lambda (x ) (+ 3 4))

Η μη-αναφορά της λ-μεταβλητής x στο σώμα έχει ως συνέπεια, κατά την εφαρμογή αυτής της λέκφρασης πάνω σε όρισμα, να δεσμεύεται μεν η λ-μεταβλητή x στο όρισμα αλλά αυτό να μην
έχει καμμία επίδραση στον υπολογισμό που προσδιορίζει το σώμα: απλώς εκτελείται:
(funcall
#'(lambda (x ) (+ 3 4))
10)
→ 7

Η θέση μιας τέτοιας λ-μεταβλητής έχει ως αποτέλεσμα την πρόκληση καθυστέρησης του
υπολογισμού, παρ' όλο που είναι προσδιορισμένος ως εφαρμογή, μέχρι "να τον καλέσουμε". Η
παρεμβαλλόμενη χρήση λ-μεταβλητής η οποία δεν υπεισέρχεται στον υπολογισμό, μας επιτρέπει
να κατευθύνουμε χρονικά την εκτέλεση:
(setq a '(+ 3 4))
(setq b '(lambda (x) a))
(setq c '(lambda (x) (eval a)))
(funcall b 10)
→ (+ 3 4)
(funcall c 10)
→ 7
(eval (funcall b 15)) → 7

Τα παραπάνω αποτελέσματα είναι ανεξάρτητα της εισόδου.
Είδαμε το ρόλο που παίζει αυτή η θέση καθυστέρησης: στην αναδρομή, στην αποθήκευση
φόρμας υπολογισμού ως τιμή μεταβλητής, στον προσδιορισμό της διαδοχής εκτέλεσης
διαφοροποιημένα από τη διαδοχή γραφής.

7.2.4 Εγκλεισμός λ-εκφράσεων και οριοθέτηση μεταβλητών

Παλαιότερες εκδόσεις της Lisp έθεταν αρκετές δυσκολίες στην αποδοχή λ-έκφρασης ως στοιχείο
του σώματος άλλης λ-έκφρασης. Αυτό το πρόβλημα έχει ξεπεραστεί σήμερα, με την πλήρη
υποστήριξη του πρότυπου της CL.
Έχουμε εγκλεισμό λ-εκφράσεων, όταν λ-έκφραση περιέχεται στο σώμα άλλης. Ο εγκλεισμός
μπορεί να πάρει καθαρά δενδροειδή μορφή, αλλά όχι ανακυκλούμενη ή διασταυρούμενη, όπως με
εγκλεισμό συναρτήσεων. Η τοπικότητα που δημιουργείται είναι σαφής και αν δεν
χρησιμοποιήσουμε συναρτήσεις επέμβασης σε καθολικά σύμβολα, με τρόπο εξαρτημένο από
συνθήκες εσωτερικές του υπολογισμού, δεν "ανοίγονται παράθυρα", όπως με ορισμό καθολικών
συναρτήσεων στο σώμα άλλων.
Ο εγκλεισμός μπορεί να έχει μορφή αμέσου και μοναδικού βήματος στο σώμα, ή να είναι ένα από
περισσότερα βήματα εκτέλεσης στο σώμα:

326

(lambda (x)
(lambda (y) (+ x y)))
(lambda (x)
(print (* x x))
(lambda (y) (+ x y)))

Η πρώτη έκφραση, εφαρμοζόμενη σε αριθμό, επιστρέφει:
(funcall (lambda (x) (lambda (y) (+ x y))) 3)
→ #<Interpreted Function (unnamed) @ #x20baa0e2>

είναι συναρτησιακή έκφραση που παριστά το 3+y όπου #x20baa0e2 είναι το εσωτερικό όνομα
που της δίνει ο compiler (κάθε Lisp compiler δίνει κάτι παρεμφερές).
Με δεύτερη εφαρμογή σε αριθμό, θα δώσει:
(funcall (funcall (lambda (x) (lambda (y) (+ x y))) 3) 4) → 7

Θέση "quote" σε λ-έκφραση που αποτελεί το σώμα λ-έκφρασης
Ένα σημείο προσοχής σε τέτοια σύνθεση, είναι το πότε απαιτείται / επιτρέπεται / δεν πρέπει να
τεθεί "quote" πριν από λ-έκφραση. Η έκφραση:
(lambda (x) (lambda (y) (+ x y)))

δεν διαφοροποιείται λειτουργικά από την:
(lambda (x) '(lambda (y) (+ x y)))

για τον απλό λόγο οτι η εφαρμογή της LAMBDA ουσιαστικά επιστρέφει τον αλγόριθμο, δηλαδή
με ορολογία Lisp την ίδια τη λ-έκφραση, δηλαδή τον εαυτό της: κατά συνέπεια, η πρώτη δίνει τη
δεύτερη. Διαφοροποιούνται όμως τεχνικά, κατά το οτι η δεύτερη δεν περνά από compilation λόγω
του "quote", άρα στην παρακάτω δεν αναγνωρίζεται ο ρόλος του ορίσματος 3 :
(funcall (lambda (x) '(lambda (y) (+ x y))) 3)
→ (LAMBDA (Y) (+ X Y))

και γι' αυτό η δεύτερη εφαρμογή θα προκαλέσει σφάλμα:
(funcall (funcall
(lambda (x) '(lambda (y) (+ x y)))
3)
4) →
→ Error…

ενώ:
(funcall
(funcall
(lambda (x)
#'(lambda (y) (+ x y)))
3)
4) →
327

→ 7

Χρειάζεται ιδιαίτερη προσοχή στο οτι η "επιστροφή του εαυτού της" για την εφαρμογή μέσω της
συνάρτησης LAMBDA δεν είναι αποδεκτή από όλους του Lisp compilers: ορισμένοι απαιτούν
υποχρεωτικά θέση από "quote" πριν τη γραφή λ-έκφρασης ως όρισμα ή σώμα συνάρτησης.

Ο χρήστης της γλώσσας πρέπει να ελέγξει λεπτομερώς αυτή τη λεπτομέρεια (πχ. στην Allegro
v.3 δεν γίνεται δεκτή η γραφή λ-έκφρασης σε εσωτερική θέση χωρίς "quote" ή #' , αλλά στην
v.5.0.1 γίνεται δεκτή ελεύθερα). Εάν είναι δεκτή η γραφή λ-έκφρασης χωρίς πρόθεμα, μπορούμε
να γράψουμε την παραπάνω έκφραση ως:
(funcall
(funcall
(lambda (x)
(lambda (y) (+ x y)))
3)
4)

και το αποτέλεσμα θα είναι πάλι 7

Οριοθέτηση μεταβλητών
Κατά τον εγκλεισμό λ-εκφράσεων, όπως και στον εγκλεισμό συναρτήσεων, οι λ-μεταβλητές της
(κάθε) εξωτερικότερης έκφρασης αποτελούν τοπικά σύμβολα για την (κάθε) εσωτερικότερη,
ανεξάρτητα του πλήθους των βημάτων εγκλεισμού, με εξαίρεση των λ-μεταβλητών της
εξωτερικής που έχουν το ίδιο όνομα με λ-μεταβλητές της εσωτερικής: οι τελευταίες έχουν
προτεραιότητα απέναντι στις πρώτες.
Η έκφραση:
(lambda (x)
#'(lambda (y)
(+ (* x x) y))

είναι η (κατά τον λ-λογισμό) λ-έκφραση λx.λy.(+ (* x x) y)) της οποίας η πρώτη εφαρμογή
αναφέρεται στη μεταβλητή x και η εφαρμογή του αποτελέσματος αναφέρεται στη μεταβλητή y .
Σπάζοντας τον υπολογισμό σε δύο σκαλοπάτια εφαρμογής, έχουμε:
(funcall
#'(lambda (x)
'(lambda (y) (+ (* x x) y)) )
2) →
→ (lambda (y) (+ 4 y))

Ως δεύτερο βήμα εφαρμογής:
(funcall
#'(lambda (y) (+ 4 y))
3) →

328

→ 7
Αν θέσουμε πρόθεμα # στην εσωτερική έκφραση:
(funcall
#'(lambda (x)
#'(lambda (y) (+ (* x x) y)) )
2) →
→ #<Interpreted Function (unnamed) @ #x35cab1e1>

είτε ισοδύναμα (για compiler που δέχεται αυτή τη σύνθεση):
(funcall
(lambda (x)
#'(lambda (y) (+ (* x x) y)) )
2)

Και τα δύο βήματα μαζί, μπορούν να δοθούν είτε με προθέματα (αναγνωρίσιμο πάντοτε):
(funcall
(funcall
#'(lambda (x) #'(lambda (y) (+ (* x x) y)) )
2)
3) →
→ 7

είτε χωρίς προθέματα (αναγνωρίσιμο από εξελιγμένους compilers):
(funcall
(funcall
(lambda (x)
(lambda (y) (+ (* x x) y)) )
2)
3) →
→ 7

Για να περάσουν οι μεταβλητές στον εσωτερικό υπολογισμό, πρέπει να μην εμποδίζεται από
"απλό quote" η αναγνώρισή τους από τον compiler.
Σκόπιμο είναι, τα βήματα να διαχωρίζονται με <enter> και <tab>, για λόγους αναγνωσιμότητας:
(funcall
(funcall
(lambda (x)
(lambda (y) (expt x y)))
4)
5)
→ 1024

Προσοχή: Επειδή δεν είναι δυνατό να περάσουν ευθέως τιμές λ-μεταβλητών σε εσωτερικότερο
329

υπολογισμό που έχει ίδια ονόματα λ-μεταβλητών, αν χρειαζόμαστε κάτι τέτοιο πρέπει είτε να
αλλάξουμε τα ονόματα των λ-μεταβλητών, είτε να περάσουμε τις τιμές σε σύμβολα με
διαφορετικά ονόματα.

7.3 Σημασιολογία των λ-εκφράσεων
Στα πλαίσια του ΣΠ η έννοια του υπολογισμού βασίζεται σε λ-εκφράσεις, αλλά στα περισσότερα
θέματα προσφέρονται "ζαχαρωμένες" λύσεις μέσω συναρτήσεων ευκολίας, με τρόπο που να μη
χρειάζεται να χρησιμοποιήσει κανείς λ-εκφράσεις στα συνηθέστερα κοινά ζητήματα
υπολογισμού.
Κάθε "μυστηριώδης" λειτουργικότητα συνάρτησης έχει μια εξήγηση μέσω λ-εκφράσεων, αρκετά
δύσχρηστη όμως για να εφαρμοστεί κατ' ευθείαν, και γι' αυτό προσφέρονται συναρτήσεις που
απαλλάσσουν το χρήστη της γλώσσας από την ανάγκη να ανάγει τα πάντα σε θεμελιακό επίπεδο.
Παρ' όλα αυτά, υπάρχουν θέματα όπου η χρήση λ-εκφράσεων προσφέρεται, είτε διότι
πετυχαίνουν λειτουργικότητα που δεν παρέχεται μέσω συναρτήσεων, είτε είναι σκόπιμο να
κατέβουμε πιο χαμηλά για να εκμεταλλευτούμε όλες τις συναρτησιακές δυνατότητες, πιο άμεσα
και πιο αποδοτικά.

7.3.1 Αξιοποίηση και οπτικές γωνίες θεώρησης λ-εκφράσεων

Θέματα όπου προσφέρεται η χρήση λ-εκφράσεων είναι:
– για επιτόπιο ορισμό και εφαρμογή συναρτησιακής έκφρασης, αντίστοιχα του FLET (και πολύ
ευκολότερα)
– σε εγκλεισμό υπολογισμών, αντίστοιχα του LET και κιβωτισμένων LET
– σε υπό καθυστέρηση αναφορά υπολογισμού: για να θέσουμε μια πρόσθετη, "dummy" λμεταβλητή πάνω από υπολογισμό, για να "περιμένει" την εφαρμογή της (δηλ. αυτό που κάνει
η QUOTE )
– για προσδιορισμό και αποθήκευση ανώνυμης συναρτησιακής έκφρασης σε ένα ευρύτερο
πλαίσιο εκφράσεων (ως όρο λίστας συναρτησιακών εκφράσεων η οποία δίνεται ως τιμή σε
μεταβλητή, ως υποδοχή αντικειμένου, ως ιδιότητα συμβόλου, και όλα αυτά, είτε σε πηγαίο
είτε σε compiled κώδικα)
– το ίδιο για προσδιορισμό και αποθήκευση εφαρμογής που προσδιορίζεται επί τόπου ως
εκτελέσιμος αλγόριθμος
– για εφαρμογή συναρτησιακής έκφρασης πάνω σε συναρτησιακή έκφραση (είτε τελεστικά
είτε για διάκριση σκαλοπατιών εφαρμογής)
– για την εγκατάσταση ενός μηχανισμού παραγωγής τιμών μέσα σε ένα ευρύτερο λειτουργικό
χώρο (θα δούμε την περίπτωση αυτή στις λίστες παραγωγής)
– για εφαρμογή όπως και των συναρτήσεων, πάνω σε σταθερές ή πάνω σε συναρτησιακές
εκφράσεις (αρκεί για το τελευταίο να προβλέπεται στο σώμα η κατάλληλη κλήση μέσω
APPLY ή FUNCALL )
330

– για απόδοση επιτακτικής διαδικασίας ή δηλωτικής επιλυμένης σχέσης, αλγόριθμου ή
μαθηματικής έκφρασης
Οι λ-εκφράσεις και οι εφαρμογές τους αποκτούν διαφορετική σημασία, ανάλογα με την οπτική
γωνία που τις βλέπουμε. Μπορούμε να δούμε τις λ-εκφράσεις με πολλά νοήματα, που ανοίγουν
πλατείς δρόμους για την υλοποίηση εννοιών:
– ως φόρμες της Lisp που προσδιορίζουν (ανώνυμη) συναρτησιακή έκφραση, για επιτόπια
χρήση ή που προσδιορίζουν έκφραση σε ανώτερα αφαιρετικά επίπεδα
– ως επιπρόσθετη θέση καθυστέρησης πάνω σε φόρμα υπολογισμού (θέτοντας τον υπολογισμό
στο σώμα λ-έκφρασης με dummy λ-μεταβλητή) ή ως τρόπο καθυστέρησης στην είσοδο τιμών
αλγόριθμου (: να εισαχθούν αργότερα από την έναρξη της εκτέλεσης)
– ως συνδετικό κρίκο ανάμεσα στον προσδιορισμό συναρτησιακής έκφρασης και εφαρμογής
(δηλ. προσδιορισμός σ.ε. μέσω εφαρμογής)
– ως υπολογιστική οντότητα προς εφαρμογή ή ως εν δυνάμει αντιπρόσωπο ενός απεριόριστου
συνόλου οντοτήτων
– ως κανόνα που δηλώνει επιλυμένα ένα συσχετισμό και διέπει τον τρόπο αντίδρασης ενός
κόσμου απέναντι σε γεγονότα (βλέπε "events")
– ως αφαιρετική έκφραση απέναντι στις ειδικότερες εκφράσεις που είναι δυνατό να προκύψουν
από εφαρμογές της
– ως υπολογιστική οντότητα η οποία με το να είναι αναγνωρίσιμη από την εκτέλεση άλλης
φόρμας, επιτρέπει την εκ των υστέρων εισαγωγή υπολογισμού στον εκτελούμενο αλγόριθμο∙
ως τρόπο δόμησης εκφράσεων κατά σκαλοπάτια σύνθεσης (με την εφαρμογή της λ-έκφρασης
πάνω σε λ-έκφραση)
– ως τρόπο απόδοσης συναρτησιακών οντοτήτων στο ίδιο επίπεδο με σταθερές
– ως τρόπο εγκλεισμού υπολογισμών, και αν θέλουμε, σε πολλαπλά επίπεδα, και από τον
εγκλεισμό αυτό να δημιουργήσουμε τόπους, κά. κά.
Ας δούμε αναλυτικά τις θεμελιακής σημασίας δυνατότητες των λ-εκφράσεων, που ανοίγουν το
δρόμο για την υλοποίηση και εκμετάλλευση των παραπάνω εννοιών.

Η λ-έκφραση ως φόρμα παραγωγής
Μια λ-έκφραση μπορεί να χρησιμοποιηθεί ως φόρμα η οποία όταν εφαρμοστεί, θα δώσει ως
αποτέλεσμα μια έκφραση, εξαρτημένη από την είσοδο που της δώσαμε. Το αποτέλεσμα μπορεί
να είναι σταθερό ή συναρτησιακή έκφραση.
Έστω, για παράδειγμα, η λ-έκφραση που αποδίδει ένα μαθηματικό τύπο μιας μεταβλητής, όπως
το νόημα “τριώνυμο” με τη φόρμα ax2+bx+c :
(lambda (x) (+ (* a x x) (* b x) c))

όπου η μεταβλητή του τριωνύμου παρίσταται με τη λ-μεταβλητή x της έκφρασης, και a, b, c είναι
εξωτερικές της έκφρασης μεταβλητές (σύμβολα - μεταβλητές, ή μεταβλητές του ανωτέρου
επιπέδου), πχ:
(setq a 3 b 4 c -1)

Σε εφαρμογή αυτής της λ-έκφρασης σε σταθερό αριθμό, για κάθε τιμή εισόδου παίρνουμε μια
331

σταθερή τιμή, τη τιμή του τριωνύμου:
(funcall
#'(lambda (x) (+ (* a x x) (* b x) c))
8)
→ 223

Η παραπάνω λ-έκφραση δεν μπορεί να εφαρμοστεί σε λ-έκφραση, διότι οι αριθμητικές πράξεις
ορίζονται για αριθμητικό τύπο. Για να δέχεται είσοδο λ-έκφραση, πρέπει να προβλέπεται στο
σώμα μια κλήση που θα διαχειριστεί αυτό τον τύπο εισόδου, και τέτοια διαχείριση είδαμε με τις
συναρτήσεις APPLY και FUNCALL , όπως οι:
(lambda (x z) (print x) (print z) (apply x z) )
(lambda (x y z) (funcall y (funcall x z)))

που δέχονται είσοδο λ-έκφραση αλλά επιστρέφουν σταθερό αποτέλεσμα:
(funcall
#'(lambda (x z) (print x) (print z) (apply x z) )

; είναι συναρτησιακή έκφραση δύο μεταβλητών
#'+ '(3 4 5) )

; είναι τα δύο ορίσματα
→ #<function 0 #xD58884>

(εκτύπωση του x )
(3 4 5)
(εκτύπωση του z )
12
(τελική επιστροφή τιμής της funcall )
Η #<function 0 #xD58884> είναι η εσωτερική αναπαράσταση του x , που είναι η συνάρτηση "+"
Κλήση εφαρμογής λ-έκφρασης τριών μεταβλητών:
(funcall
#'(lambda (x y z) (funcall y (funcall x z)))
#'cube #'square 3)
→ 729

Μπορούμε να έχουμε είσοδο και έξοδο συναρτησιακή έκφραση, όπως στην ταυτοτική:
(lambda (x) x)

η οποία, εφαρμοζόμενη στον εαυτό της, δίνει πάλι την ταυτοτική:
(funcall
(lambda (x) x)
(lambda (x) x))
→ #<Interpreted Function (unnamed) @ #x20b7592a>

Συνθετότερα, μπορούμε να έχουμε εφαρμογή με παράπλευρη μόνιμη δράση:
(setq input-list nil)
(funcall
#'(lambda (x) (push x input-list) x)
#'(lambda (x) x))
→ #<Interpreted Function (unnamed) @ #x20bb1faa>
332

input-list
→ (#<Interpreted Function (unnamed) @ #x20bb1faa>)

Αν ο compiler δέχεται εφαρμογή έκφρασης πάνω σε λ-έκφραση, η παραπάνω έκφραση μπορεί να
γραφεί απλούστερα και φυσικότερα ως:
(funcall
(lambda (x) (push x input-list) x)
(lambda (x) x))

Ένα τρόπο αξιοποίησης λ-εκφράσεων για απεριόριστη παραγωγή τιμών, θα δούμε στις λίστες
παραγωγής, που τις χρησιμοποιούμε για τη δημιουργία ρευμάτων.

Η λ-έκφραση ως ανώνυμη συνάρτηση
Μπορούμε να δούμε μια λ-έκφραση ως ανώνυμη συνάρτηση: και πραγματικά, συνάρτηση δεν
είναι τίποτα άλλο από μια ονομασμένη λ-έκφραση. Κάτι τέτοιο είναι χρήσιμο σε πολλές
περιπτώσεις με διαφορετική σημασία, όπως είναι η χρήση λ-έκφρασης αντί για συνάρτηση, όταν
δεν πρόκειται να επαναληφθεί η εφαρμογή της συνάρτησης (οπότε είναι περιττό να οριστεί
σχετικό σύμβολο).
Παραδείγματα
i) Τετράγωνο αριθμού:
(lambda (x) (* x x))

Εφαρμογή στην τιμή 3 :
(funcall #'(lambda (x) (* x x)) 3)

→ 9
ii) Μια μαθηματική έκφραση δύο μεταβλητών:
(lambda (x y) (+ (* 2 x y) (* 4 x) 1))
Εφαρμογή στις τιμές 3 , 5 :
(funcall
#'(lambda (x y) (+ (* 2 x y) (* 4 x) 1))
3 5)
→ 43

iii) Το τριώνυμο 2x 2+3x+5
(lambda (x) (+ (* 2 x x) (* 3 x) 5) )

Εφαρμογή στην τιμή 2 :
(funcall
#'(lambda (x) (+ (* 2 x x) (* 3 x) 5) )
2)
→ 19

333

Ενσωμάτωση λειτουργικού περιεχόμενου σε σύμβολο - μεταβλητή
Θέλουμε να αποδώσουμε στο σύμβολο "ζων-οργανισμός" την ιδιότητα "τρέφεται" με τιμή λέκφραση η οποία περιέχει τη μέθοδο υπολογισμού των θερμίδων, βάσει του είδους και της
ποσότητας τροφής.
Χρησιμοποιούμε τη λίστα ιδιοτήτων, καταχωρώντας ως τιμή της ιδιότητας "παίρνει_θερμίδες" τη
λ-έκφραση που υπολογίζει τις θερμίδες βάσει του είδους της τροφής και της ποσότητας:
(setf (get 'ζων_οργανισμός 'παίρνει_θερμίδες)
'(lambda (x y)
(cond
((eql x 'κρέας) (* y 200))
((eql x 'ψωμί) (* y 150)))))

Η ιδιότητα παίρνει_θερμίδες έχει περιεχόμενο συναρτησιακή έκφραση, άρα θα ενεργοποιείται
προς εφαρμογή μέσω της FUNCALL :

(funcall
(get 'ζων_οργανισμός 'παίρνει_θερμίδες)
'ψωμί 100)
→ 15000
Η παραπάνω φόρμα (get…) δεν έχει πρόθεμα #' διότι η FUNCALL καλεί για εφαρμογή το

αποτέλεσμά της.

Διαδικασίες εκφρασμένες μέσω λ-εκφράσεων
Μια λ-έκφραση αποδίδει έναν αλγόριθμο, άρα μπορεί να αποτελεί περιγραφή μιας επιτακτικής
διαδικασίας.
Ειδικότερα, μια διαδικασία που δεν έχει τιμές εισόδου κατά την κλήση της, αποδίδεται από μια λέκφραση η οποία περιγράφει τα βήματα του αλγόριθμου, με κενή τη λ-λίστα. Πχ.
Το άθροισμα του γινομένου δύο αριθμών που διαβάζει από την κυρία είσοδο, με τρίτο αριθμό που
επίσης διαβάζει από την κυρία είσοδο, και καταχωρεί το αποτέλεσμα ως τιμή της καθολικής
μεταβλητής α :
(lambda ( )
(setq b (+ (* (read) (read)) (read) ) ))

Στην περίπτωση αυτή, η λ-έκφραση δεν εφαρμόζεται βέβαια σε ορίσματα, αφού δεν έχει λμεταβλητές, και η κλήση της μέσω της FUNCALL θα προκαλέσει τον υπολογισμό της:
(lambda ( ) (setq b (+ (* (read) (read)) (read) ) ))

→ <κατάσταση εισόδου∙ δύνουμε:>

6 <space> 7 <space> 8 <enter>
→ 50
(εδώ η FUNCALL δεν χρειάζεται, διότι η LAMBDA είναι συνάρτηση της Lisp, και αποτελεί τον

πρώτο όρο της φόρμας λόγω της μη ύπαρξης ορισμάτων)
334

Γενικά:
(lambda ( ) <σώμα υπολογισμού>) ) → <αποτέλεσμα υπολογισμού>
Η διαμόρφωση επιτακτικής διαδικασίας δεν αποκλείει βέβαια να υπάρχουν λ-μεταβλητές.

Πολυεπίπεδες συναρτησιακές εφαρμογές μέσω λ-εκφράσεων∙ καλύμματα
Όπως έχουμε αναφέρει στο Κεφ1, η λ-έκφραση περισσοτέρων της μιας λ-μεταβλητών ουσιαστικά
είναι διαδοχικός προσδιορισμός λ-έκφρασης με σώμα λ-έκφραση κοκ.:
λ(x y).B = λy.λx.B
Ας δούμε αυτό το "σπάσιμο" του υπολογισμού σε διαδοχικά βήματα εφαρμογής, στην έκφραση
"άθροισμα δύο αριθμών":
Ως ένα, ενιαίο βήμα, δύο μεταβλητών:
(lambda (x y) (+ x y))

Εφαρμογή:
(funcall
#'(lambda (x y) (+ x y))
3 4)
→ 7

Ως δύο διαδοχικά βήματα, μιας μεταβλητής το καθένα:
(lambda (y)
(funcall
#'(lambda (x) (+ x y)) ))

Εφαρμογή:
(funcall
#'(lambda (y)
(funcall
#'(lambda (x) (+ x y))
4) )
3)
→ 7

Ο υπολογισμός αυτός ξεκινά από μέσα προς τα έξω. Το εσωτερικό βήμα είναι η κλήση:
(funcall #'(lambda (x) (+ x y)) 4)

που δίνει αποτέλεσμα μια συναρτησιακή έκφραση που παριστά το 4+y . Η έκφραση αυτή, στο
επόμενο βήμα (εξωτερικό) θα εφαρμοστεί πάνω στο 3 και θα δώσει 7
Η ύπαρξη διαδοχικών βημάτων εφαρμογής στη δεύτερη έκφραση, παρέχει τη δυνατότητα
διάκρισης των βημάτων: στο παραπάνω, το πρώτο βήμα δίνει συναρτησιακή έκφραση και το
δεύτερο την τελική τιμή.
Εάν συμβεί η έξοδος να είναι σε μορφή συναρτησιακής έκφρασης, αποτελεί το λεγόμενο
κάλυμμα (closure) του υπολογισμού. Η διαφορά ανάμεσα σε "closure" και "function" είναι οτι η
δεύτερη έχει εσωτερικό όνομα μέσω του οποίου ο compiler την καλεί για εφαρμογή ενώ η πρώτη
335

είναι έκφραση που χρησιμοποιείται μόνον "επί τόπου".
Το πλήθος των δυνατών "σκαλοπατιών εφαρμογής" είναι μεγαλύτερο ή ίσο του πλήθους των λμεταβλητών:
– το πλήθος των βημάτων διαδοχικής εφαρμογής πάνω σε σταθερές είναι ίσο με το πλήθος των
λ-μεταβλητών
– εφαρμογή πάνω σε λ-έκφραση μιας λ-μεταβλητής, όταν επιτρέπεται, δημιουργεί ένα ακόμα
σκαλοπάτι δέσμευσης
– είναι δυνατό σε μια εφαρμογή, το όρισμα που αντιστοιχεί σε μια λ-μεταβλητή να
προσδιορίζεται ως λ-έκφραση περισσοτέρων μεταβλητών. σε τέτοια εφαρμογή αυξάνεται το
αρχικό πλήθος των σκαλοπατιών δέσμευσης.
Παράδειγμα
Έστω οτι εφαρμόζουμε την:
(lambda (x) (+ x 4))

πάνω στην:
(lambda (y) (* y 3))

η οποία εφαρμόζεται πάνω στον αριθμό 5 :
(funcall
#'(lambda (x) (+ (funcall x 5) 4))
#'(lambda (y) (* y 3)))
→ 19
(διότι είναι το 3*5+4)

Ήτοι: το x δεσμεύεται πάνω στο 3y , το y δεσμεύεται στο 5 , και στο αποτέλεσμα προστίθεται 4 .
Εδώ δεν "σκοντάφτει" ο υπολογισμός στο ζήτημα του τύπου εισόδου των πράξεων, διότι, όπως
εκτελείται από μέσα προς τα έξω, όταν δοθεί η τιμή εισόδου σε κάθε πράξη, τότε είναι
αποτέλεσμα της προηγούμενης πράξης και έχει τον κατάλληλο τύπο.

7.3.2 Περιπτώσεις σύνθεσης και χρήσης λ-εκφράσεων
λ-εκφράσεις χωρίς μεταβλητές
Είδαμε οτι μπορούμε να έχουμε λ-εκφράσεις χωρίς μεταβλητές. Η σύνταξη τέτοιων εκφράσεων
είναι:
(lambda ( ) <σώμα> )
Μια τέτοια έκφραση δεν μπορεί βέβαια να εφαρμοστεί πάνω σε κάτι, διότι δεν περιέχει λμεταβλητή(-ές). Η εφαρμογή της, γίνεται με απλή κλήση της (δεν απαιτείται χρήση της
FUNCALL , διότι LAMBDA είναι συνάρτηση).
(lambda ( ) (print “hello”))
; τυπώνει “hello” και επιστέφει “hello”
(setq v1 2 v2 4 v3 8)
(lambda ( ) (+ v1 v2 v3))

→ 14
Ισοδυναμεί με λ-έκφραση που έχει μια dummy μεταβλητή.
336

λ-εκφράσεις που αποδίδουν διαδικασίες
Μια λ-έκφραση με σημασία διαδικασίας μπορεί να περιέχει λ-μεταβλητές που να αναφέρονται ή
να μην αναφέρονται στο σώμα. Η διαδικασία μπορεί να αποτελείται από έναν ή περισσότερους
διαδοχικούς συναρτησιακούς υπολογισμούς, που αποτελούν το σώμα της λ-έκφρασης, όπως
ακριβώς και το σώμα μιας συνάρτησης που παριστά (ονομασμένη) διαδικασία.
Παράδειγμα
(setq a 'a)
(lambda (x) (cons a x))

Εφαρμογή της λ-έκφρασης πάνω στη λίστα (b c) :
(funcall
#'(lambda (x) (cons a x))
'(b c))
→ (a b c)

Χρήση συμβόλων σε λ-εκφράσεις
Μπορεί να γίνει χρήση συμβόλων (καθολικών μεταβλητών, συναρτήσεων, τοπικών μεταβλητών
που έρχονται από ανώτερο επίπεδο) μέσα στο σώμα μιας λ-έκφρασης, ακριβώς όπως και στο
σώμα συνάρτησης. Πχ:
(setq x 2 y 3)
; καθολικές μεταβλητές x , y
Έστω η λ-έκφραση που εκφράζει το “άθροισμα των τετραγώνων δύο αριθμών”:
(lambda (x y) (+ (* x x) (* y y)))

Τα x και y αυτής της λ-έκφρασης είναι λ-μεταβλητές που δεν σχετίζονται με τις εξωτερικές
μεταβλητές x και y (που ορίστηκαν από το παραπάνω SETQ), και έχουν προτεραιότητα απέναντί
τους.
Στην επόμενη έκφραση, τα x και y που αναφέρονται στη λ-λίστα της λ-έκφρασης είναι οι λμεταβλητές της, ενώ τα x και y που αναφέρονται στο σώμα εφαρμογής είναι καθολικές
μεταβλητές:
(funcall
#'( lambda (x y) (+ (* x x) (* y y)) )
x y)
→ 13
(διότι είναι το 2*2+3*3)

Αν το αντικείμενο εφαρμογής δεν είναι λ-έκφραση και αναφέρονται ονόματα μεταβλητών,
εννοείται οτι είναι ονόματα εξωτερικών της έκφρασης μεταβλητών και υπολογίζονται πριν την
εφαρμογή, όπως σε κάθε συνήθη συνάρτηση.
► Εκτός από τη FUNCALL , για εφαρμογή λ-έκφρασης μπορούμε να χρησιμοποιήσουμε την
APPLY , ακολουθώντας τον τρόπο που συντάσσεται:
(apply #'(λ-έκφραση) '(όρισμα1 όρισμα2 …))
ισοδύναμο του:
337

(funcall #'(λ-έκφραση) όρισμα1 όρισμα2 … )
Παράδειγμα
(apply #'(lambda (x) (+ 3 x)) '(4)) →
→ 7
(funcall #'(lambda (x) (+ 3 x)) 4)

→ 7

Αναφορά λ-εκφράσεων μέσα στην τιμή συμβόλου
Έστω οτι δίνουμε στην καθολική μεταβλητή b περιεχόμενο μια λ-έκφραση χωρίς μεταβλητές,
αλλά αυτή η έκφραση προκαλεί μια δράση που αναφέρεται στην καθολική μεταβλητή a :
(setq a nil)
; τιμή του a είναι η κενή λίστα
(setq b 3)

Έστω η λ-έκφραση:
(lambda ( ) (push b a))

Αυτή η λ-έκφραση δεν έχει λ-μεταβλητές, και έχει νόημα διαδικασίας που θα εκτελεστεί με την
κλήση τής λ-έκφρασης, και αυτό θα επισυνάψει την τρέχουσα τιμή της μεταβλητής b στη
λίστα a . Μπορούμε να ονομάσουμε τη διαδικασία αυτή:
(setq c '(lambda ( ) (push b a)) )

Η κλήση (eval c) εκτελεί τη διαδικασία, δηλ. επισυνάπτει τον όρο - τρέχουσα τιμή της
μεταβλητής b στη λίστα a :
(eval c)
→ (3)
Τώρα η μεταβλητή a έχει τιμή τη λίστα (3) :
a →
(3)
Αν καλέσουμε διαδοχικά φορές την (eval b) αλλά εν των μεταξύ αλλάξει η τιμή της b, θα
πάρουμε διαδοχική επισύναψη των τιμών αυτών:
(setq
(eval
(setq
(eval
(eval

b 4)
c)
b 5)
c)
c)

κάθε κλήση προκάλεσε προσθήκη (από τα αριστερά) της αντίστοιχης τιμής στη λίστα - τιμή του
a , και έτσι:
a → (5 5 4 3)
Η τεχνική αυτή απλώς υποκαθιστά το ρόλο της συνάρτησης, αλλά είναι δυνατό να πάρει
πολυπλοκότερη μορφή, θέτοντας ως τιμή συμβόλου μια λίστα λ-εκφράσεων:
(setq exprs '( <λ-έκφραση1> <λ-έκφραση2> ... <λ-έκφρασηk> ))

338

Τυποποίηση λ-μεταβλητών
Οι μεταβλητές μιας λ-έκφρασης σχετίζονται με τύπο, που είναι ο τύπος της τιμής τους, και
προσδιορίζεται: είτε με ειδική δήλωση, είτε έμμεσα από τη μορφή των πράξεων στο σώμα (δηλ.
ποιος είναι ο γενικότερος τύπος που δέχονται ως ορίσματα), είτε από τον τύπο των αντικειμένων
εφαρμογής.
Έστω η λ-έκφραση:
( lambda (x y)
(+ (* (first x) (first y)) (* (last x) (last y)) ) )

Από το σώμα της, προσδιορίζεται έμμεσα οτι οι λ-μεταβλητές της, x και y , μπορούν να
δεσμευτούν πάνω σε λίστες των οποίων το πρώτο και το τελευταίο στοιχείο είναι αριθμοί.
Επιστρέφει το άθροισμα των γινομένων των αντιστοίχων πρώτων και τελευταίων στοιχείων. Αν
εφαρμοστεί στις λίστες '(1 a 3) και '(4 Κώστας 6) θα επιστρέψει τον αριθμό 22 :
(funcall
#'(lambda (x y)
(+ (* (car x) (car y)) (* (last x) (last y))))
'(1 a 3) '(4 Kώστας 6) )
→ 22

λ-έκφραση με επιλογή στο σώμα της
Μια λ-έκφραση μπορεί να κάνει χρήση από conditionals στο σώμα της (όπως και να καλεί
οποιαδήποτε συνάρτηση) με τον ίδιο ακριβώς τρόπο που μια συνήθης συνάρτηση κάνει τέτοια
χρήση:
(lambda (x)
(cond
((> x 1) (+ x 2))
((<= x 1) (– x 4)) ))

Ορισμός συνάρτησης που χρησιμοποιεί λ-εκφράσεις
Είναι ισοδύναμο του ορισμού και επί τόπου εφαρμογής συνάρτησης, μέσα στο σώμα συνάρτησης.
Παράδειγμα

Σε προηγούμενα ορίσαμε το άθροισμα των τετραγώνων αριθμών με χρήση της MAPCAR και της
SQUARE . Αντί για την τελευταία, μπορούμε να δώσουμε απ' ευθείας την λ-έκφραση που
αποτελεί το σώμα της SQUARE στη θέση της:
(defun lambda-sum-of-squares (&rest x)
(apply
#'+
(mapcar
339

#'(lambda (x) (* x x))
x)))

Εφαρμογή:
(lambda-sum-of-squares 3 4 5)

→ 50

λ-εκφράσεις με προαιρετικές μεταβλητές
Πανομοιότυπα με τον ορισμό συνάρτησης με προαιρετικές μεταβλητές (με τα γνωστά λ-κλειδιά)
μπορούμε να δώσουμε φόρμα λ-έκφρασης με προαιρετικές λ-μεταβλητές:
(funcall
#'(lambda (&rest x) (apply #'+ x))
3 4 5 6)
→ 18
(funcall
#'(lambda (&optional x y z w) (list x y z w))
3 4)
→ (3 4 NIL NIL)

► Προσοχή: Ορισμένοι Lisp compilers δεν δέχονται γενικό τύπο - ρίζα, οπότε ο προσδιορισμός
λ-μεταβλητής πρέπει να συγκεκριμενοποιείται σε υπαρκτό τύπο (που πρέπει να προσδιορίζεται
είτε μέσα στο σώμα είτε στο αντικείμενο εφαρμογής). Πχ, η έκφραση:
(lambda (x) x)

σε κάποιους compilers ενδέχεται να προκαλέσει σφάλμα, λόγω μη προσδιορισμού τύπου της
μεταβλητής x, ενώ αντίθετα η έκφραση:
(lambda (x) (+ 0 x))

γίνεται δεκτή, διότι προσδιορίζει οτι η μεταβλητή x είναι αριθμητική.

Αξιοποίηση του προσδιορισμού και εφαρμογής λ-έκφρασης

Σε προηγούμενα, είχαμε δει τη συνάρτηση INTEGRAL-F που προσδιόριζε το ορισμένο
ολοκλήρωμα συνάρτησης, με εσωτερικό ορισμό και κλήση συναρτήσεων. Τώρα, ας δούμε το ίδιο
θέμα με χρήση λ-εκφράσεων:
Πρόβλημα
Να δοθεί αντίστοιχη μορφή συνάρτησης INTEGRAL βασισμένη σε λ-εκφράσεις.

Ακολουθούμε την ίδια προσέγγιση ορισμένου ολοκληρώματος συνάρτησης f(x) από a έως b με
τη σειρά, και χρησιμοποιούμε ξανά τη βοηθητική συνάρτηση SERIES (θα μπορούσαμε να
δώσουμε επί τόπου την αντίστοιχη λ-έκφραση):
[ f(a + dx/2) + f(a + dx + dx/2) + f(a + 2∙dx + dx/2) + …] ∙ dx
Η έκφραση αυτή, ως συνάρτηση δίνεται από την ακόλουθη:
340

(defun integral (f a b dx)
(* (series f
'(lambda (x) (+ x dx))
(+ a (/ dx 2))
b)
dx ) )

Είναι ισοδύναμη της INTEGRAL-F που αναφέραμε.

Συνάρτηση με σώμα λ-έκφραση
Ουσιαστικά πρόκειται για θέση μιας καθυστέρησης. Αποτελεί ένα πολύ ισχυρό μηχανισμό
κλιμάκωσης του υπολογισμού, σε διακρινόμενα βήματα εφαρμογής.
Παράδειγμα
Ας δώσουμε ως σώμα συνάρτησης μια "quoted" λ-έκφραση:
(defun f1 ( ) '(lambda (x y) (expt x y)))

Καλώντας την για εκτέλεση, παίρνουμε ως απάντηση το σώμα της:
(f1) → (lambda (x y) (expt x y))

Αν το σώμα δοθεί με πρόθεμα #' , η εκτέλεση της F1 θα επιστρέψει τη συναρτησιακή έκφραση
στην εσωτερική αναπαράσταση. Και στις δύο περιπτώσεις, το αποτέλεσμα κλήσης της F1
εφαρμόζεται σε δυο αριθμητικά ορίσματα:
(funcall (f1) 3 4) → 81
Σε compiler που δέχεται λ-έκφραση ως σώμα συνάρτησης, μπορούμε να δώσουμε:
(defun f2 ( ) (lambda (x y) (expt x y)))

που είναι ισοδύναμο της δεύτερης περίπτωσης:
(funcall (f2) 3 4) → 81
Χωρίς την παρεμβολή του "quote" μπορούμε να χειριστούμε πιο άνετα τη "διάσπαση" του
υπολογισμού σε βήματα:
(defun f3 (x) (lambda (y) (expt x y)))

Η F3 περιέχει πάλι την εκθετική συνάρτηση x y , αλλά τώρα η εφαρμογή της F2 σε ένα αριθμητικό
όρισμα, έστω 3 , επιστρέφει τη συναρτησιακή έκφραση του ενδιάμεσου βήματος, δηλαδή 3 y , του
οποίου η εφαρμογή σε αριθμητικό όρισμα, έστω 4 , επιστρέφει το τελικό αποτέλεσμα 3 4 :
(f3 3)
→ #<closure … >
(δίνει την εσωτερική αναπαράσταση της 3 y)
(f3 5)
→ #<closure … >
(δίνει την εσωτερική αναπαράσταση της 5 y )
(funcall (f3 3) 4) → 81
(funcall (f3 5) 6) → 15625

341

7.4 Σύμβολα - μεταβλητές με λειτουργικό περιεχόμενο
Μια λ-έκφραση είναι μια φόρμα, που μπορούμε να χειριστούμε όπως οποιαδήποτε άλλη φόρμα.
Είναι επίσης μια συναρτησιακή έκφραση, δηλαδή φόρμα που μπορεί να εφαρμοστεί.
Συνδυάζοντας αυτά τα χαρακτηριστικά πετυχαίνουμε πολύ ενδιαφέροντα αποτελέσματα.

7.4.1 Ονομασία λ-έκφρασης
Προσδιορισμός λ-έκφρασης ως τιμή μεταβλητής
Δίνοντας μια λ-έκφραση ως τιμή σε μεταβλητή, με απλή θέση του προθέματος "quote",
καταχωρείται αυτούσια η έκφραση:
(setq lam-var1 '(lambda (x y) (+ (* x x) (* y y))))

H τιμή της μεταβλητής lam-var είναι ακριβώς η λ-έκφραση:
lam-var1 → (lambda (x y) (+ (* x x) (* y y)))
Η μεταβλητή lam-var1 έχει περιεχόμενο μια συναρτησιακή φόρμα, την λ-έκφραση, αλλά δεν
αποτελεί συνάρτηση: εξακολουθεί να είναι μεταβλητή, με τιμή. Μπορεί όμως να κληθεί μέσω της
FUNCALL προς εφαρμογή σε συγκεκριμένα (κατάλληλου τύπου) ορίσματα:
(funcall lam-var1 3 4)
→ 25
Η έκφραση αυτή ισοδυναμεί με εφαρμογή της παρακάτω συνάρτησης FUN :
(defun fun (x y) (+ (* x x) (* y y)))
(fun 3 4) → 25

Εάν αντίστοιχα δώσουμε:
( setq lam-var2 #'(lambda (x y) (+ (* x x) (* y y))) )

ο υπολογισμός διαφοροποιείται μόνο κατά το οτι το πρόθεμα #' καθορίζει πως πρόκειται για
συνάρτηση, και καταχωρείται ως τιμή της μεταβλητής lam-var το "κάλυμμα" που είναι
αποτέλεσμα της μεταγλώττισης, αντί τον πηγαίο κώδικα της συναρτησιακής έκφρασης:
lam-var2 →
#<closure …>
(funcall lam-var2 3 4)→ 25
Η κλήση εφαρμογής επιστρέφει το αυτό αποτέλεσμα και στις δύο περιπτώσεις (δηλ. απλό "quote"
ή #' ) με την προϋπόθεση οτι προκύπτει σταθερά και οτι δεν προσδιορίζονται εσωτερικότεροι
υπολογισμοί.

Ονομασία λ-έκφρασης ως συνάρτησης
Ο ορισμός συνάρτησης ακριβώς αυτό κάνει, δίνει όνομα σε λ-έκφραση:
(defun add-args (x y) (+ x y))
→ ADD-ARGS
Οι παρακάτω κλήσεις είναι ισοδύναμες, με τον περιορισμό που αναφέραμε (το "quote" εμποδίζει
342

τη μεταγλώττιση του περιεχομένου, και αν υπάρχει εσωτερικό στοιχείο που παίζει ρόλο στον
περαιτέρω υπολογισμό, θα προκληθεί σφάλμα)
(add-args 3 4)
→ 7
(funcall #'add-args 3 4)
→ 7
(funcall #'(lambda (x y) (+ x y)) 3 4) → 7
(funcall '(lambda (x y) (+ x y)) 3 4) → 7

7.4.2 Η διπλή υπόσταση των συμβόλων
Τι θα γίνει αν ορίσουμε ως συνάρτηση το ίδιο όνομα συμβόλου που έχουμε ορίσει ως μεταβλητή;
Θα προκληθεί σφάλμα, θα υπερισχύσει το ένα του άλλου, ή θα "συνυπάρξουν" και τα δύο, ως δύο
όψεις του ιδίου νομίσματος; Έχει σημασία ποιο θα οριστεί πρώτο; Στην ενότητα αυτή, θα
εξετάσουμε αυτά τα ερωτήματα.

Σύμβολο προσδιορισμένο με διπλό ρόλο: και ως μεταβλητή και ως συνάρτηση
Ας ξαναδούμε τη μεταβλητή lambda2 :
( setq lambda2 #'(lambda (x y) (+ (* x x) (* y y))) )

Τώρα έχουμε ορίσει μεταβλητή lambda2 με περιεχόμενο μια συναρτησιακή έκφραση.

Έστω οτι ορίζουμε, ανεξάρτητα της μεταβλητής lambda2 , και τη συνάρτηση lambda2 , ακριβώς
με το ίδιο σώμα:
(defun lambda2 (x y) (+ (* x x) (* y y)))

Τώρα, το σύμβολο LAMBDA2 αποτελεί καί μεταβλητή, με περιεχόμενο τη λ-έκφραση, καί
συνάρτηση, όπου συμβαίνει εδώ να έχει η συνάρτηση σώμα το ίδιο με την λ-έκφραση. Το
σύμβολο μπορεί να κληθεί με διπλό τρόπο ως συνάρτηση: με απ' ευθείας εφαρμογή της
συνάρτησης κατά τα γνωστά, και με συναρτησιακή εφαρμογή (μέσω της FUNCALL) του
περιεχομένου της μεταβλητής:
(lambda2 2 3)
→ 13
(η lambda2 χρησιμοποιήθηκε ως συνάρτηση)
(funcall lambda2 2 3) → 13
(η lambda2 χρησιμοποιήθηκε ως μεταβλητή)
(funcall #'lambda2 2 3) → 13
(η lambda2 χρησιμοποιήθηκε ως συνάρτηση)
Λάβαμε το ίδιο αποτέλεσμα σε όλες στις περιπτώσεις, διότι το σώμα ορίστηκε το ίδιο. Θα
μπορούσαμε να έχουμε δώσει στη μεταβλητή διαφορετικό συναρτησιακό περιεχόμενο από τη
συνάρτηση, οπότε θα παίρναμε διαφορετικό αποτέλεσμα από τις αντίστοιχες εφαρμογές:
(setq d-symbol #'(lambda (x y) (+ (* x x) (* y y))) )
(defun d-symbol (x y) (+ (* x x x) (* y y y)))
343

→ 25
(εφαρμογή του περιεχόμενου της μεταβλητής d-symbol )
(d-symbol 3 4)
→ 91
(εφαρμογή της συνάρτησης d-symbol )
(funcall #'d-symbol 3 4) → 91
(εφαρμογή της συνάρτησης d-symbol )
(funcall d-symbol 3 4)

Ο προσδιορισμός της μεταβλητής d-symbol με συναρτησιακό περιεχόμενο έχει ως αποτέλεσμα
την άμεση μεταγλώττιση του περιεχομένου:
d-symbol → #<function 2 #xE81BA0>
Εδώ, #xE81BA0 είναι το εσωτερικό όνομα της συνάρτησης δύο μεταβλητών, που ορίζεται από
το περιεχόμενο της μεταβλητής d-symbol .
Αντίθετα, αν δώσουμε:
(setq d-symbol '(lambda (x y) (+ (* x x) (* y y))) )

τότε ορίζεται ως περιεχόμενο αυτή κααθ' εαυτή η έκφραση υπολογισμού:
d-symbol → (lambda (x y) (+ (* x x) (* y y)))
που δεν ορίζει συνάρτηση διότι το "quote" αποτρέπει τη Lisp να μπει μέσα στην έκφραση∙ θα
περάσει από μεταγλώττιση τη στιγμή που θα κληθεί για εφαρμογή.
Μπορούμε να αξιοποιήσουμε πολύπλευρα τέτοια καταχώρηση, όπως: να έχουμε τον πηγαίο
κώδικα αλγόριθμου με απλό "quote" ως τιμή του συμβόλου - μεταβλητής, και να είναι ορατός με
απλή κλήση της, και επίσης να έχουμε τον ίδιο κώδικα ως σώμα συνάρτησης με το ίδιο όνομα,
οπότε θα διαθέτουμε και την μεταγλωττισμένη μορφή του κώδικα έτοιμη για χρήση.
Παράδειγμα
Σύμβολο συνάρτηση με σώμα το ίδιο σύμβολο ως μεταβλητή, που έχει τιμή μια λ-έκφραση
Έστω το σύμβολο symb-a , ορισμένο ως μεταβλητή και ως συνάτηση :
(setq symb-a '(lambda (x y) (* x y)))
(defun symb-a (x y) (* x y))

Έτσι, έχουμε:
– Εφαρμογή της συνάρτησης symb-a (δεν έχει μεταβλητές):
(symb-a )
→ (lambda (x y) (* x y))
– Εφαρμογή του αποτελέσματος εφαρμογής της συνάρτησης symb-a :
(funcall (symb-a) 3 4)→ 12
– Εφαρμογή του περιεχομένου του συμβόλου symb-a , που είναι συναρτησιακή έκφραση:
(funcall symb-a 3 4)
→ 12

Αλλαγή του περιεχομένου της μεταβλητής symb-a δεν περνά στη συνάρτηση symb-a , διότι
ορίζονται ανεξάρτητα:
(setq symb-a '(lambda (x y) (+ x y)))
344

(funcall (symb-a) 3 4)→ 12
(funcall symb-a 3 4)
→ 7

Αν όμως ορίσουμε τη συνάρτηση βάσει του μέρους περιρχομένου του συμβόλου, τότε αλλαγή
του περιεχομένου της μεταβλητής περνά στo μέρος συνάρτησης:
(setq symb-b '(lambda (x y) (+ (* x x) (* y y)) ))
(defun symb-b (x y) symb-b )
(funcall (symb-b) 3 4) → 25

– Αλλαγή του περιεχομένου της μεταβλητής symb-b :
(setq symb-b '(lambda (x y) (+ (* x x) (* y y) 2 ) ))
(funcall (symb-b) 3 4) → 27

Συμπέρασμα
Παρατηρούμε οτι η χρήση των συμβόλων ως μεταβλητών καλύπτει τη χρήση τους ως
συναρτήσεων. Αυτό γεννά το ερώτημα:
Αφού μπορούμε να εκφράσουμε τα συναρτησιακά νοήματα μόνο με χρήση μεταβλητών και λεκφράσεων, τί χρειαζόμαστε τις συναρτήσεις;

Η απάντηση βρίσκεται αφ' ενός στην πρακτική δυσχέρεια (αναγκαστική χρήση της FUNCALL ή
της APPLY σε κάθε κλήση εφαρμογής λ-έκφρασης) και αφ' ετέρου στην εκμετάλλευση των
έτοιμων συναρτήσεων της Lisp. Δεν υπάρχει άλλος τυπικός λόγος.

Προσοχή: Η συναρτησιακή υπόσταση που παίρνει ένα σύμβολο - μεταβλητή όταν πάρει τιμή λέκφραση, εκλαμβάνεται από ορισμένους compilers και ως συναρτησιακή λειτουργικότητα του
συμβόλου (δηλ. μπορούμε να το καλέσουμε και ως συνάρτηση) μέχρι να δώσουμε ειδικό
συναρτησιακό ορισμό (μέσω DEFUN). Άρα ο προσδιορισμός του συμβόλου - μεταβλητής μέσω
της SETQ ή της SETF είναι δυνατό να λειτουργήσει και ως τιμή της μεταβλητής και ως
συνάρτηση. Αν ακολουθήσει ορισμός του συμβόλου ως συνάρτησης μέσω DEFUN υπερισχύει ο
τελευταίος, αλλά μόνο σε ότι αφορά το σύμβολο ως συνάρτηση∙ ως μεταβλητή, εξακολουθεί να
λειτουργεί σύμφωνα με τον αρχικό ορισμό που δώσαμε.
Όταν προσδιορίζουμε σύμβολο και με τα δύο μέρη, μεταβλητής και συνάρτησης, θα αποκαλούμε
το πρώτο "μέρος περιεχομένου του συμβόλου" και το δεύτερο "συναρτησιακό μέρος του
συμβόλου".

Ιδιότητες συνάρτησης
Ένα σύμβολο - συνάρτηση μπορεί να έχει και ιδιότητες. Επομένως μπορούμε να συνδυάσουμε
πολύπλευρα τη δυνατότητα να καταχωρήσουμε σε ένα σύμβολο τιμή, ιδιότητες και κύρια
συναρτησιακή λειτουργικότητα. Πχ:
(defun symb-b (x y)
(+ (square x) (cube y)))

345

(setf (get symb-b 'functional-part)
'(lambda (x y) (+ (square x) (cube y)))))
(setf (get symb-b 'inputs-and-type)
'(integer integer))

Κλήση:

((get symb-b 'functional-part)
→ (lambda (x y) (+ (square x) (cube y)))
(symb-b 4 3)→ 43

Συνδυασμένη χρήση συμβόλου ως μεταβλητής και ως συνάρτησης

Μπορούμε να αξιοποιήσουμε τη "διπλή υπόσταση" των συμβόλων προσδιορίζοντας διαφορετικές
όψεις "του ιδίου νομίσματος", (πχ. στο μέρος μεταβλητής να καταχωρήσουμε τον πηγαίο κώδικα
του κώδικα που είναι καταχωρημένος μεταγλωττισμένος στο συναρτησιακό μέρος ). Επίσης, είναι
δυνατό να εξαρτήσουμε το ένα μέρος από το άλλο, αποφεύγοντας την ανακύκλωση (χωρίς να
απαγορεύεται), αλλά με προκαθορισμένο το "ποιο πρέπει να κληθεί πρώτα", για να έχει νόημα
αλγόριθμου η εξάρτηση.
Βασικό χαρακτηριστικό της δίπλευρης χρήσης συμβόλου είναι οτι τα αντίστοιχα περιεχόμενα
είναι ανεξάρτητα. Αν επομένως χρειαστούμε κάποιο συσχετισμό, πρέπει να τον προσδιορίσουμε,
όπως πχ. να καλεί η συνάρτηση στο σώμα της την μεταβλητή προς εκτέλεση, και η εκτέλεση της
μεταβλητής να καθορίζει την αντίστοιχη περίπτωση:
; καταχώρηση που αφορά το μέρος περιεχομένου
(setq a
'(lambda (s r x y)
; καταχώρηση ιδιοτήτων που αφορούν το σύμβολο a
(setf (get a 'radius) r)
(setf (get a 'center) (list x y)) ))

; καταχώρηση που αφορά το συναρτησιακό μέρος του συμβόλου
(defun a (r s x y)
(funcall a r s x y)
(* pi (square r)) )

; εδώ a είναι το μέρος περιεχομένου

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

346

7.4.3 Καταστάσεις και "κόσμοι" σε εξέλιξη∙ προσομοιώσεις
Ως κόσμο θεωρούμε ένα χώρο συμβόλων, με τέτοιο τρόπο συνδεδεμένα που ο χώρος να
παρουσιάζει ενοποιημένη λειτουργικότητα, δηλαδή, με την ενεργοποίηση (εφαρμογή) μιας ή
διαφόρων -αλλά συγκεκριμένων για το σκοπό αυτό- συναρτήσεων, να ανταποκρίνεται προς τα
"ερεθίσματα" του εξωτερικού περιβάλλοντος (τιμές εισόδου), εξελίσσοντας την κατάσταση στην
οποία βρίσκεται.
Χαρακτηρίζουμε ως λειτουργικότητα ενός κόσμου το σύνολο των λειτουργιών που μπορεί να
επιτελέσει, όπου λειτουργία είναι το σύνολο των δράσεων που επιτελούνται σε μια ενότητα
διαδοχικών κλήσεων εφαρμογών στα πλαίσια του κόσμου, ενότητα που μπορεί να νοείται ως
ενιαία διεργασία (process), ως σύνολο αλληλεπιδρώντων βημάτων χρήστη - μηχανής (session),
που γενικότεραμπορεί να περιλαμβάνει και αλληλεπιδρώσες διεργασίες (θα δούμε το θέμα αυτό
στα επόμενα).
Χαρακτηρίζουμε ως φάσεις μιας λειτουργίας του κόσμου τις διαφοροποιούμενες καταστάσεις
στις οποίες μπορεί να βρεθεί ο κόσμος κατά τη διάρκεια της λειτουργίας, όπου η διαφοροποίηση
νοείται ως προς κάποιο context αναφοράς.
Χαρακτηρίζουμε ως κατάσταση ενός κόσμου ένα σύνολο συμβόλων με το αντίστοιχο περιεχόμενο
/ ορισμό, σε μια δεδομένη φάση.

Κόσμοι προσομοίωσης
Ι. Προσομοίωση είναι ένας κόσμος ο οποίος αναπαριστά ένα τμήμα του πραγματικού κόσμου,
παίρνοντας αντίστοιχα με την πραγματικότητα δεδομένα και παρουσιάζοντας συμπεριφορά
αντίστοιχη της πραγματικής. Ακριβέστερα:
α'.
Πρώτα σχηματίζουμε ένα πραγματικό μοντέλο, καθορίζοντας, με κατάλληλες
απλοποιήσεις για να γίνει εφικτή η υλοποίηση αλλά και για να εντοπιστεί η μελέτη στα
θεμελιακά χαρακτηριστικά του τμήματος του πραγματικού κόσμου, τα εξής:

i) Τα δεδομένα που ισχύουν, δηλ. τις σταθερές, ποιές είναι οι ποσότητες ("αγαθά") την
κατανομή τιμών εισόδου, τη ροή ποσοτήτων, τα σημεία όπου θα ασκήσουμε λειτουργικό
έλεγχο, τα δεδομένα εξόδου που πρέπει να λάβουμε.
ii) Ποιές είναι οι οντότητες του συστήματος (πχ. "ο πελάτης του μαγαζιού", "η ουρά αναμονής",
"ο εξυπηρέτης", "η αποθήκη") και ποιά είναι τα γεγονότα (πχ. "μπήκε πελάτης", "περιμένει
εξυπηρέτηση", "εξυπηρετείται", "φεύγει, ικανοποιημένος ή όχι", "μετακινούνται αγαθά από...
σε...").
ii) Τους κανόνες ή μεθόδους που ισχύουν ή εφαρμόζονται στην πραγματικότητα και αφορούν τις
οντότητες, καθώς και τους τρόπους που αλληλεπιδρούν μεταξύ τους∙ επίσης, ποιές οντότητες
ασκούν έλεγχο και σε τί, , και πώς αναπροσαρμόζεται η λειτουργία κάτω από συνθήκες.
iii) Την επικοινωνία αυτού του τμήματος με το περιβάλλον του: κάπου πρέπει να αποκόψουμε το
τμήμα του πραγματικού κόσμου, αλλά δεν παύει να συνδέεται με τον υπόλοιπο κόσμο,
347

διατηρώντας μια σειρά από χαλαρότερες συνδέσεις.
iv) Τους στόχους λειτουργίας που θα εξυπηρετήσει η προσομοίωση: αυτό που θέλουμε να
μάθουμε είναι η συμπεριφορά του τμήματος του πραγματικού κόσμου κάτω από διάφορες
συνθήκες και δεν είναι εφικτό να τις δημιουργήσουμε στην πραγματικότητα.
β'.
Στη συνέχεια σχηματίζουμε ένα υπολογιστικό μοντέλο που να προσεγγίζει όσο γίνεται
πιστότερα το πραγματικό, δηλαδή για αντίστοιχες εισόδους / επιδράσεις να δίνει αντίστοιχα
αποτελέσματα / συμπεριφορά. Το μοντέλο αυτό φέρνει σε επαφή το πραγματικό μοντέλο με την
υπολογιστική γλώσσα.
Συνήθως η προσέγγιση αυτή δεν είναι δυνατή σε απόλυτο βαθμό, εξ αιτίας της ανάγκης
απόδοσης των πραγματικών εννοιών στο υπολογιστικό περιβάλλον, με διαφορετικά πρωτογενή
στοιχεία, που πρέπει να αντιστοιχηθούν έμμεσα, και με διαφορετικές δυνατότητες επεξεργασίας,
που πρέπει να εκφράσουμε τις πραγματικές μέσω των υπολογιστικών.
Ένα σημείο ιδιαίτερης προσοχής είναι η διατήρηση των σημείων επαφής του υπολογιστικού
κόσμου με τον πραγματικό: τα δεδομένα εισόδου - εξόδου και ο τρόπος άσκησης ελέγχου να είναι
όσο γίνεται ίδιας μορφής με τα πραγματικά, με όσο γίνεται λιγότερες παραδοχές αντιστοίχησης,
ώστε η "μίμηση" της πραγματικότητας να μην απαιτεί από το χρήστη να δράσει ως
"μεταφραστής" των εννοιών.
ΙΙ.

Σκοπός μιας προσομοίωσης είναι, να μας δώσει πληροφορίες σχετικά με:

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

Η λεγόμενη ανάλυση εισόδου αποτελεί τη μελέτη που καθορίζει τη μορφή των δεδομένων
(κατανομή, πυκνότητα) και τον αναγκαίο πειραματισμό. Η λεγόμενη ανάλυση εξόδου αποτελεί
τη μελέτη των λαμβανομένων απαντήσεων σε κάθε πειραματισμό και για κατάλληλα
διαμορφωμένες ομάδες πειραματισμών, που θα συνθέσει την αξιολόγηση του πραγματικού
κόσμου και τις αποφάσεις που θα ληφθούν σε πραγματικό επίπεδο (χρησιμεύει επίσης και για
αναδιαμόρφωση του υπολογιστικού κόσμου, από τις ελλείψεις που διαπιστώνονται).
Βασική τεχνική της προσομοίωσης είναι η εκμετάλλευση της σχέσης γεγονότων (events) διεργασιών (processes) και της αλληλεπίδρασης διεργασιών (βλέπε Κεφ.9). Γίνεται εφικτή στον
ΣΠ με τον φυσικότερο τρόπο, δεδομένου οτι ο προσδιορισμός οντοτήτων μπορεί να συμπεριλάβει
σ' αυτά οποιοδήποτε υπολογιστικό νόημα.
Το θέμα της ανάπτυξης ενός προγράμματος προσομοίωσης ξεφεύγει από τα πλαίσια του
παρόντος, και θα το δούμε σε επόμενο τεύχος.
348

Χρήση συμβόλων - μεταβλητών για υλοποίηση εξελισσόμενου κόσμου

Παρά την επικάλυψη χρήσης των εννοιών συνάρτηση - μεταβλητή, αν δούμε τη χρήση των
συμβόλων ως μεταβλητών με τη δυνατότητα επαναπροσδιορισμού του περιεχομένου τους,
παρατηρούμε οτι οι μεταβλητές καλύπτουν μια διάσταση ουσιωδώς διαφορετική των
συναρτήσεων: Οι μεταβλητές προσφέρονται για επαναλαμβανόμενη καταχώρηση μιας
κατάστασης που μεταβάλλεται και μάλιστα με τρόπο εξαρτημένο από την προηγούμενη
κατάσταση.

Αντίθετα, οι συναρτήσεις καλύπτουν ευχερέστερα τη λειτουργικότητα που προκαλεί τις
μεταβολές. Άρα η χρήση του περιεχομένου ενός συμβόλου - μεταβλητής για αποθήκευση
συναρτησιακής έκφρασης, εξυπηρετεί στην περίπτωση όπου θέλουμε να αντιμετωπίζουμε
εξελικτικά τον ορισμό συναρτησιακής έκφρασης.
Παράδειγμα
Τήρηση προγενεστέρων καταστάσεων με χρήση διαφορετικών μεταβλητών
Διμόρφωση διαδοχικών αποτελεσμάτων, με τήρηση αρχικής και προηγούμενης τιμής: ύψωση του
2 σε δυνάμεις διαδοχικούς φυσικούς αριθμούς.
(setq initial-value 1
current-value initial-val
collected-values nil)
(setq previous-value
(cond (null (cdr collected-values) nil)
(t (cadr collected-values))))
(setq var-function1
'(lambda ( )

; έστω η σχέση που προσδιορίζει την τρέχουσα τιμή:
(setq current-value (* 2 current-value))
(push current-value collected-values)) )

Εφαρμογές:

(funcall var-function1) → (2)
(funcall var-function1) → (4 2)
(funcall var-function1) → (8 4 2)
current-value
→ 8
initial-value
→ 1
previous-value
→ 4

Παρακολούθηση της εξέλιξης ενός κόσμου σε λειτουργία
Ένας κόσμος μπορεί να αποδοθεί ακόμα και από ένα και μοναδικό σύμβολο, πχ. με το μέρος
περιεχομένου να αναλαμβάνει την τήρηση δεδομένων και σχέσεων, και το συναρτησιακό μέρος
να αναλαμβάνει την τρέχουσα δράση.
349

Η ενεργοποίηση της λειτουργίας μπορεί να ανατεθεί στην κλήση του ενός μέρους του συμβόλου,
από τον χρήστη ή μέσω κλήσης από άλλο σύμβολο:
(defun a (x y z)

; οι τρέχουσες τιμές εισόδου
(setf (get a 'current-input) (list x y z) )

; τήρηση της ιστορίας εισόδου
(push (list x y z) (get a 'input-history))

; τήρηση ιστορίας τιμών εξόδου, βάσει του υπολογισμού

(push (+ (* x x) (* y y y) (* z z z z)) (get a 'output-history))

; ο κύριος υπολογισμός:

(+ (* x x) (* y y y) (* z z z z)) ) )

Μια συνάρτηση INITIALIZE μπορεί να διαγράφει όλες τις καταχωρήσεις ώστε να επανεκκινήσει η
λειτουργία από "μηδενική" κατάσταση:
(defun initialize (symbol)
(cond
( (not (symbolp symbol))
(setq symbol nil)
(setf (get symbol 'current-input)
(setf (get symbol 'input-history)
(setf (get symbol 'input-history)
(t
(setf (get symbol 'current-input)
(setf (get symbol 'input-history)
(setf (get symbol 'input-history)

nil)
nil)
nil) )
nil)
nil)
nil) ) ) )

Εφαρμογές:
(initialize a)
→ NIL
(a 3 4 5)
→ 698
(a 5 6 8)
→ 4337
(a 2 4 3)
→ 149
(get a 'current-input)
→ (2 4 3)
(get a 'input-history)
→ ((2 4 3) (5 6 8) (3 4 5))
(get a 'output-history) → (149 4337 698)

Εξειδικευόμενη συμπεριφορά συμβόλου

Είδαμε στις χρήσεις από conditionals (COND, CASE, TYPECASE…) πως μπορεί να αξιοποιηθεί
η ανακατεύθυνση βάσει υποθέσεων για καθορισμό της "πρέπουσας συμπεριφοράς" συνάρτησης,
τόσο ανάλογα με την είσοδο όσο και με εξωτερικές καταστάσεις (περιεχόμενο άλλων συμβόλων).
Το μειονέκτημα είναι οτι, αυτή η ανακατεύθυνση είναι στατική: οι περιπτώσεις πρέπει να
προβλέπονται μέσα στο conditional το οποίο είναι "αμετάβλητο", με το νόημα οτι κάθε μεταβολή
350

ορισμού συνάρτησης απαιτεί ριζική παρέμβαση (επαναπροσδιορισμό με DEFUN) που μπορεί να
φανεί δύσχρηστη έως καταστροφική όταν το σύμβολο συμμετέχει σε συνδέσεις.

Ένας τρόπος να παρακαμφθεί αυτή η δυσχέρεια, παρέχεται από τη δυνατότητα καταχώρησης
συμπεριφοράς σε μεταβλητή, όπου, αν και δεν διαφέρει λειτουργικά από συνάρτηση, εν τούτοις
παρουσιάζει ριζική διαφοροποίηση σε ότι αφορά τη μεταβλητότητα: αρκεί ένας απλός
επαναπροσδιορισμός περιεχομένου (setq…) για να δηλωθεί η μεταβολή, που, επιπρόσθετα,
μπορεί να αναφέρεται στην προηγούμενη κατάσταση περιεχομένου.
Αυτό είναι πολύ χρήσιμο στην περίπτωση που το περιεχόμενο περιλαμβάνει μια λίστα
στοιχειωδών αλγορίθμων (πχ. σε μορφή από μια "quoted" λίστα από συναρτησιακές εκφράσεις),
ενώ η κλήση να προσδιορίζει την "τρέχουσα" συναρτησιακή σύνθεση, που θα επιλέγει /
ανασυνθέτει διαφορετικά τους στοιχειώδεις αλγόριθμους.
Η κλήση της κατάλληλης λ-έκφρασης προς εφαρμογή, δεν απαιτεί παρά εντοπισμό της
κατάλληλης λ-έκφρασης, που γίνεται με απλή εφαρμογή συναρτήσεων που εισέρχονται σε λίστα,
πχ. FIRST , SECOND , CDR κλπ.
(setq lambdas-list
'( (lambda (x) (square x))
(lambda (x) (cube x))
(lambda (x) (/ x 2)) ) )
(funcall (second lambdas-list) 3)

→ 27
H διαφορά ανάμεσα στον τρόπο αυτό καταχώρησης πολλαπλής συμπεριφοράς και στην
ανακατευθυνόμενη συμπεριφορά βάσει συνθηκών που ελέγχονται από conditional, έγκειται στα
εξής:
α'. μπορεί να προσδιοριστεί διαδικαστικά η κατάλληλη κλήση συναρτησιακής έκφρασης, και
όχι λογικά όπως με τη χρήση από conditional, σε οποιοδήποτε επίπεδο (από το χρήστη, από
συνάρτηση, ακόμα και από το συναρτησιακό μέρος του συμβόλου στο οποίο περιέχονται)
β'. ο προσδιορισμός αυτός μπορεί να γίνει οποτεδήποτε, με κριτήρια που θα καθοριστούν τότε
(ενώ ένα conditional χρειάζεται προκαθορισμό των κριτηρίων)
γ'. η λίστα των λ εκφράσεων μπορεί να αναδιαμορφωθεί ακόμα και "runtime":
(…<περιβάλλον> …
(push '(lambda (x) (sqrt x)) lambdas-list )



(setq n <υπολογισμός1>) (setq p <υπολογισμός2>)
(funcall (nth n lambdas-list) p) … )

Στο επόμενο κεφάλαιο θα δούμε άλλους δρόμους, πολύ πιο αποδοτικούς, για διασύνδεση
υπολογισμών με οντότητες, και για έκφραση της εξειδίκευσης.

351

7.5 Ασκήσεις
1. Να γράψετε λ-εκφράσεις που να αποδίδουν τα ακόλουθα νοήματα:
α) "τετράγωνο του κύβου",
β) "ο κύβος αθροίσματος ελεύθερου πλήθους ορισμάτων",
γ) η λίστα των διπλασίων των αριθμών που θα δοθούν ως είσοδος, με ελεύθερο πλήθος τιμών
εισόδου".
2. Tί παριστάνουν οι παρακάτω εκφράσεις, ως συναρτησιακές εκφράσεις, και πώς εφαρμόζονται;
(lambda
(lambda
(lambda
(lambda
(lambda

(x y) (lambda (x)
(x) (lambda (x y)
(x) (lambda (x) (*
(x) '(lambda (x) (*
(x) '(lambda (x y)

(+ 1 x y)))
(+ x y)))
x x)))
x x)))
(+ x y)))

3. α)
Να σχηματίσετε σύμβολο EMBAΔON το οποίο να έχει ως τιμή μια (quoted) λίστα από
λ-εκφράσεις, οι οποίες να αποτελούν αντίστοιχα τον υπολογισμό εμβαδού τριγώνου (βάση,
ύψος), τετραγώνου (πλευρά) και κύκλου (ακτίνα).
β)
Να ορίσετε τύπους συμβόλων "τρίγωνο", τετράγωνο" και "κύκλος", και σε κάθε τύπο να
προσδιορίσετε από δύο σύμβολα, με τιμή τη λίστα των απαιτουμένων μεταβλητών για τον
υπολογισμό του εμβαδού του αντιστοίχου σχήματος.
γ)
Στη συνέχεια να ορίσετε συνάρτηση EMBAΔON με είσοδο σύμβολο, που να
αναγνωρίζει τον τύπο του και ανάλογα να εντοπίζει τον κατάλληλο υπολογισμό εμβαδού, και
να υπολογίζει το εμβαδόν του σχήματος που δόθηκε ως είσοδος.
4. Έχουμε ένα διαιτολόγιο, με "φαγητά" που έχουν "εκατοστιαία σύνθεση" με βασικά συστατικά
"πρωτεΐνες", "υδατάνθρακες", "λίπη", και έχουν "θερμίδες ανά 100 gr". Θέλουμε να μάθουμε:
α) Αν η ημερήσια ποσότητα που προτείνει ο χρήστης είναι ισορροπημένη σε αναλογία, και αν
είναι ελλιπής, επαρκής ή πλεονάζει σε ποσότητα.
β) Το ίδιο, εκφρασμένο με "πιάτα" και με αθροιστικό έλεγχο των πιάτων που προτείνει ο
χρήστης, σε επίπεδο γεύματος, ημέρας, εβδομάδας.
5. Nα προσδιορίσετε λ-έκφραση που να έχει το νόημα "υπολογισμός φθίνουσας σειράς με γενικό
όρο an" και κριτήριο διακοπής "ο όρος να έχει γίνει μικρότερος του ε".
Βάσει αυτής, να κατασκευάσετε συναρτήσεις που να υπολογίζουν, με ε=10 -10
α) Διπλή σειρά, από m=1 και n=1 με γενικό όρο a m+n/(m+n) , για 0<a<1
β) Το συνημίτονο αριθμού ορισμένο μέσω σειράς.
γ) Σειρά του Taylor που δίνεται στη γενική μορφή n όρου, και μέθοδο άθροισης, ως τελεστή
πάνω στον ν-όρο και με κριτήριο διακοπής το παραπάνω.

352

Τυπική μορφή λ-εκφράσεων:

6. α) Να ορίσετε τυπικά τις λογικές συναρτήσεις "implies" και "equivalent"
β) Να δείξετε οτι οι παρακάτω εκφράσεις είναι ισοδύναμες για οποιοδήποτε μη μηδενικό
όρισμα:
λx.(successor (predecessor x)) , λx.(predecessor (successor x))
7. Να μελετήσετε τη συμπεριφορά των παρακάτω εκφράσεων: i) ως προς τη διάκριση ακριβώς
δύο καταστάσεων, ii) ως προς τη διάκριση περισσοτέρων καταστάσεων, iii) σε σχέση με τις
"true" και "false":
λx.λy.λz.x , λx.λy.λz.y , λx.λy.λz.z , λx.λy.λz.λw.x , λx.λy.λz.λw.y , λx.λy.λz.λw.z ,
λx.λy.λz.λw.w

353

8 Κλάσεις Αντικειμένων και Μέθοδοι

Στο κεφάλαιο αυτό θα μελετήσουμε έναν ιδιαίτερο κλάδο των τύπων, που ξεκινά από τον κόμβο τύπο structure, και έχει ένα κεντρικής σημασίας υποτύπο, τον τύπο structure-class. Ο κλάδος
αυτός έχει κάποιες ιδιομορφίες απέναντι στους άλλους κλάδους:
α'. οι κόμβοι που δημιουργούμε στο δένδρο αυτό (που αποκαλούμε κλάσεις) προσδιορίζονται
ως ειδικές οντότητες που έχουν συγκεκριμένα χαρακτηριστικά, και κληρονομούν τα
χαρακτηριστικά των γονικών κόμβων
β'. μπορούμε να δημιουργήσουμε αντικείμενα (που αποκαλούμε στιγμιότυπα) κάθε τύπου κόμβου (που αποκαλούμε γεννήτορα) στο δένδρο αυτό, με ειδικές τιμές των χαρακτηριστικών
του κόμβου
γ'. μπορούμε να συνδέσουμε μεθόδους επεξεργασίας με κόμβους του δένδρου
δ'. κλάσεις και στιγμιότυπα κληρονομούν μεθόδους, με ιεράρχηση προτεραιότητας
Θα μελετήσουμε διεξοδικά τον τύπο structure, τις οντότητες (τύπους και στιγμιότυπα) που
δημιουργούμε ιεραρχικά κάτω από αυτόν, και την εφαρμογή μεθόδων επεξεργασίας.
Στη συνέχεια, θα δούμε σε συντομία το χώρο του CLOS (Common Lisp Object System), που
είναι ένα σύνολο συναρτήσεων προσδιορισμού και επεξεργασίας γενικών κλάσεων, στιγμιοτύπων
τους και μεθόδων που αφορούν ειδικά τον τύπο structure-class.

8.1 Μια πρώτη επαφή με τις κλάσεις: Ο τύπος "structure"

Όλοι οι βασικοί τύποι οντοτήτων της Lisp παριστούν "κλάσεις αντικειμένων" τόσο με το νόημα
οτι ένας τύπος αντιπροσωπεύει αυτές τις οντότητες, όσο και με το νόημα οτι κάθε οντότητα
ανήκει σε τύπο (και αν ανήκει σε τύπο, ανήκει και σε κάθε υπερτύπο, με την έννοια των
υπερσυνόλων). Όμως οι τύποι που αναφέρθηκαν δεν είναι εύχρηστοι σε ότι αφορά τη δημιουργία
νέων οντοτήτων. Ένας τύπος που δίνει εξαιρετική ευχέρεια σ' αυτό, είναι ο τύπος της ιεραρχικής
δομής (structure) διότι επιτρέπει από τον κόμβο structure του δένδρου των τύπων και κάτω:
– δημιουργία νέων οντοτήτων τύπου structure, που είναι υποτύποι του
– ανάπτυξη των υποτύπων σε δενδροειδή διακλάδωση, οποιουδήποτε πεπερασμένου βάθους
και πλάτους (κόμβοι του δένδρου)
– για κάθε κόμβο αυτού του δένδρου, δημιουργία ειδικών οντοτήτων που ανήκουν στον κόμβο
αυτό∙ οι οντότητες αυτές αποκαλούνται στιγμιότυπα του κόμβου και ο κόμβος αποκαλείται
κλάση των στιγμιοτύπων του
– κληρονόμηση των χαρακτηριστικών ενός κόμβου, δυναμικά από κάθε κατώτερό του κόμβο,
και στατικά από κάθε στιγμιότυπό του
– κληρονόμηση των χαρακτηριστικών των κόμβων από κάθε στιγμιότυπο κατωτέρου κόμβου,
στη φάση της δημιουργίας του στιγμιότυπου
– δημιουργία μεθόδων υπολογισμού σε μορφή συναρτήσεων εξειδικευόμενης συμπεριφοράς
354

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

8.1.1 Iδιομορφίες του τύπου "structure"
Ο τύπος structure απέναντι στους λοιπούς τύπους

O τύπος structure, σε σύγκριση με τους άλλους τύπους, έχει ορισμένες ιδιομορφίες:
α'. Μπορούμε να δημιουργήσουμε νέες οντότητες που να είναι υποτύποι του τύπου structure με
τρόπο που να θεωρούνται οτι συντίθεται από ένα σύνολο χαρακτηριστικών, που αποκαλούνται
υποδοχές (slots). Αποκαλούμε τα χαρακτηριστικά αυτά και με τους όρους πεδία (fields) ή
είσοδοι (inputs). Οι οντότητες αυτές είναι δενδροειδώς τοποθετημένοι τύποι κάτω από τον
κόμβο structure του δένδρου των τύπων.
Οι υποδοχές αποτελούν κατά κάποιο τρόπο ένα σύνολο ιδιοτήτων, που είναι προκαθορισμένο
από τον ορισμό της δομής και αναλλοίωτο, των οποίων οι ειδικές τιμές καθορίζουν το κάθε
ειδικό αντικείμενο (στιγμιότυπο) αυτού του τύπου.
Αποκαλούμε αυτές τις οντότητες δομές τύπου structure και είναι ιεραρχικές δομές, και το
όνομά τους αποτελεί τον υποτύπο. Τις αποκαλούμε επίσης κλάσεις, και κάθε κλάση είναι
υπερτύπος κάθε κλάσης που δημιουργείται από κει και κάτω, και αντιπροσωπεύει το σύνολο
των στιγμιότυπων της.
Πρέπει να τονίσουμε πως τα ονόματα των κόμβων αυτών, κατά το πρότυπο της CL δεν είναι
συνήθη σύμβολα, διότι είναι τοποθετημένα σε άλλο κλάδο στο δένδρο των τύπων, απ' ευθείας
κάτω από τη ρίζα∙ όμως συμβαίνει συχνά σε compilers, τα ονόματα αυτά να είναι κοινά
σύμβολα.
β'. Οι υποτύποι κάθε δομής τύπου structure κληρονομούν τις υποδοχές της γονικής δομής, με
τιμή την αρχική εφ' όσον έχει τιμή η υποδοχή της κλάσης και εφ' όσον δεν
επαναπροσδιορίζεται ειδικά για την υπο-δομή, αλλά μπορούν να έχουν και πρόσθετες
υποδοχές. Αποκαλούμε τους υποτύπους αυτούς υποκλάσεις της κλάσης που καθορίζει ο
γονικός κόμβος.
γ'. Μπορούμε να δημιουργούμε ειδικές οντότητες για κάθε τύπο - δομή από τους παραπάνω, τα
λεγόμενα αντικείμενα - στιγμιότυπα του συγκεκριμένου κόμβου, κατά κάποιο τρόπο
χρησιμοποιώντας τον "σαν καλούπι". Τα στιγμιότυπα έχουν τύπο ακριβώς το όνομα του
κόμβου αυτού. Τα στιγμιότυπα είναι φυσικές οντότητες, και μπορούν να πάρουν όνομα.
δ'. Μπορούμε να προσδιορίζουμε συναρτήσεις συσχετισμένες με τον κάθε συγκεκριμένο
355

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

Διαφοροποίηση του τύπου structure από τους λοιπούς τύπους

Ως τύπος, μια δομή διαφοροποιείται σε σχέση με τους άλλους τύπους ως προς τα ακόλουθα:
α'.
Οι υποδοχές προσδίδουν εσωτερική συνθετότητα στη δομή, που επί πλέον από τις συνήθεις
σύνθετες οντότητες (λίστες, πίνακες…), συνδέεται δυναμικά με την δημιουργία υποδομών της
δομής, στιγμιοτύπων της, σχετικών με τη δομή μεθόδων, με σαφή και συγκεκριμένο τρόπο.
β'.
Συγκρίνοντας υπο-δομές με κοινούς τύπους που κατασκευάζουμε με την DEFTYPE,
έχουμε οτι:
– Οι τελευταίοι προσδιορίζονται είτε "κατά περιορισμό" πάνω σε δεδομένους τύπους, όπως πχ.
ο τύπος "special-integer" να είναι "οι ακέραιοι αριθμοί οι μικρότεροι του 1000 που διαιρούνται
με το 3", είτε με συλλογή από αυτόνομες οντότητες "κάτω από κοινή ομπρέλα" των οντοτήτων
δεδομένου(-ων) τύπου(-ων), όπως πχ. στον τύπο "εβδομάδα" να ανήκουν οι "Δευτέρα, Τρίτη,
κλπ", αλλά πάντοτε πάνω σε ένα προκαθορισμένο, προϋπάρχον σύνολο αντικειμένων.
– Έχουμε δυνατότητα επισύναψης προσθέτων υποδοχών σε κάθε νέο υποτύπο (υπο-δομή) σε
σχέση με τον γονέα του.
γ'.
Ως τύπος, κάθε τέτοια δομή, υποδομή κοκ. αντιπροσωπεύει τόσο το σύνολο των
στιγμιοτύπων που δημιουργήσαμε όσο και αυτά που μπορούμε να δημιουργήσουμε, και η
δημιουργία στιγμιοτύπων μπορεί να γίνει είτε με επέμβαση του χρήστη είτε να προκύψει από την
εκτέλεση εφαρμογής.
δ'.
Μπορούμε να συσχετίσουμε με κάθε τέτοια δομή, μεθόδους που δημιουργούμε δυναμικά
όποτε και όσες χρειαστούμε. Ο υπολογισμός που επιτελεί μια μέθοδος είναι ένας αλγόριθμος με
τουλάχιστον ένα όρισμα, και εξειδικεύεται ανάλογα με τον τύπο εισόδου, ή τους τύπους εισόδου
αν δέχεται περισσότερα του ενός ορίσματα, όπου εννοείται οτι τα ορίσματα έχουν τύπο κάτω από
τον κόμβο structure. Η εξειδίκευση της συμπεριφοράς του αλγόριθμου προκύπτει από την
ιεράρχηση των κόμβων, είναι δυναμικά επεκτεινόμενη και δυναμικά προσαρμόσιμη στην
"πλησιέστερη λύση" όταν δεν υπάρχει απ' ευθείας προσδιορισμένη μέθοδος στην περίπτωση.
Συγκρίνοντας τις μεθόδους με τις κοινές συναρτήσεις, έχουμε οτι:
356

– Μια συνάρτηση έχει στατικό νόημα: με αρκετή δυσχέρεια μπορούμε να προσδιορίσουμε νέες
λειτουργικές εξειδικεύσεις, που εκτός από τη δυσχέρεια του επανορισμού της συνάρτησης μετά
από κάθε επέμβαση, σταδιακά γίνονται ολοένα και δυσκολότερες, όσο πληθαίνουν οι
περιπτώσεις, περιπτώσεις περιπτώσεων, κοκ.
– Αντίθετα, με χρήση μεθόδων, προστίθεται μια νέα διάσταση στον προγραμματισμό, διότι νέα
συμπεριφορά μπορεί εύκολα να προστεθεί ακόμα και "run-time", από το χρήστη ή από την
εκτέλεση κάποιας συνάρτησης, απλώς ως νέα μέθοδος με το ίδιο όνομα.

Μια δομή – οντότητα τύπου structure, ως κλάση των αντικειμένων της
Μια δομή – οντότητα τύπου structure, αλλά και κάθε υποτύπος της, είναι δυνατό να
χρησιμοποιηθεί ως καλούπι - γεννήτορας παραγωγής αντικειμένων. Έμμεσα, αποτελεί
αντιπρόσωπο των αντικειμένων - στιγμιοτύπων της, διότι είναι ο τύπος τους και βάσει αυτού
ορίζονται οι μέθοδοι που επεξεργάζονται στιγμιότυπα, και είναι σημείο αναφοράς για τον
καθορισμό συμπεριφοράς συναρτήσεων - μεθόδων.

Είναι δυνατό επίσης να χρησιμοποιηθεί ως καλούπι - γονέας για τη δημιουργία υπο-δομών, που
έχουν τις ίδιες δυνατότητες. Μπορούμε να δούμε μια τέτοια οντότητα ως γονέα που
κληρονομείται από τις δομές - παιδιά και απογόνους, στιγμιότυπά του και στιγμιότυπά τους. Το
κτίσιμο από υπο-δομές είναι απεριόριστο προς τα κάτω (εξειδικεύσεις εξειδικεύσεων) ή προς τα
πλάγια από το δεύτερο επίπεδο και κάτω (αδέλφια των παιδιών, όπου το ψηλότερο δυνατό
επίπεδο είναι νέες εξειδικεύσεις της αρχικής κλάσης τύπου structure), και γίνεται τόσο στη φάση
δόμησης του προγράμματος όσο και εκτέλεσης εφαρμογών. Δημιουργούμε έτσι μια οικογένεια
κλάσεων τύπου structure, δυναμικά επεκτεινόμενη.
Όλα αυτά τα πετυχαίνουμε χρησιμοποιώντας μια σειρά από πολύ απλές και εύχρηστες
συναρτήσεις, που θα δούμε στη συνέχεια.
Ουσιαστικό σημείο για τη χρήση τύπων-δομών και υποτύπων τους, είναι η ταύτιση
φορμαλισμού και σημασιολογίας σε ότι αφορά: την ιεραρχική σχέση των κόμβων, τη σχέση
"ανήκει" των στιγμιοτύπων τους, το συσχετισμό μεθόδων με κόμβους, και της έννοιας της
κληρονομικότητας.
Διευκρινίζουμε οτι οι όροι "δομή" και "κλάση" είναι ευρύτεροι της σημασίας που δίνουμε στην
ενότητα αυτή, και θα τους χρησιμοποιούμε μόνον όταν δεν δημιουργείται σύγχυση. Αλλιώς θα
χρησιμοποιούμε τον όρο "κλάση τύπου structure" ή "δομή τύπου structure".
Έχουμε δύο κεντρικές κατευθύνσεις δημιουργίας τέτοιων δομών:
i) με χρήση της συνάρτησης DEFSTRUCT με την οποία προσδιορίζουμε κλάσεις τύπου
structure, και δημιουργούμε ένα καθαρό δένδρο ιεραρχικών τύπων - κλάσεων με ρίζα τον τύπο
structure∙

ii) με χρήση της συνάρτησης DEFCLASS, με την οποία προσδιορίζουμε οντότητα - κλάση
κάτω από τον κόμβο structure-class που είναι παιδί του κόμβου structure, ένα συνθετότερο
κατευθυνόμενο άκυκλο γράφημα.
357

Υποδοχές δομής / αντικειμένου έναντι ιδιοτήτων συμβόλου

Oι υποδοχές μιας δομής, αν και μοιάζουν με τις ιδιότητες συμβόλου, διαφέρουν από αυτές στο
οτι:
– Είναι προκαθορισμένες και αναλλοίωτες για την κάθε κλάση τύπου structure και τα
δημιουργούμενα από αυτή στιγμιότυπα: μόνο το περιεχόμενο των υποδοχών μπορεί να
αλλάζει κατά την στιγμιοτυποποίηση, σε αντίθεση με τις ιδιότητες συμβόλου που μπορούν να
μεταβάλλονται ελεύθερα, ακόμα και μετά τη δημιουργία του συμβόλου.
– Είναι άμεσα προσβάσιμες, σε αντίθεση με τις ιδιότητες συμβόλου, των οποίων η αναζήτηση
ακολουθεί τις διαδοχικές συνδέσεις των στοιχείων της λίστας ιδιοτήτων, και η
αποτελεσματικότητα αναζήτησης ιδιότητας είναι αντιστρόφως ανάλογη του πλήθους των
ιδιοτήτων.
– Οι υποδοχές και τυχόν προσδιορισμένες τιμές τους σε μια κλάση, κληρονομούνται από τους
απογόνους της, και οι απόγονοι παρακολουθούν εκ των υστέρων μεταβολές των τιμών των
υποδοχών σε προγόνους.
– Οι υποδοχές και τυχόν προσδιορισμένες τιμές τους σε μια δομή τύπου structure ή υποτύπων
της δομής, κληρονομούνται από κάθε στιγμιότυπο κατά τη δημιουργία του, αλλά τα
στιγμιότυπα δεν παρακολουθούν τυχόν μεταγενέστερες μεταβολές των τιμών υποδοχών της
γεννήτριας κλάσης.
Μια ιδιομορφία των υποδοχών είναι οτι, το όνομα της κλάσης τύπου structure μέσα στην οποία
ορίζονται και το όνομα της κάθε υποδοχής μπορούν να συνδυαστούν, δημιουργώντας ονόματα
συμβόλων - συναρτήσεων, που είναι ακριβώς οι συναρτήσεις που δημιουργούν στιγμιότυπα.
Έτσι, το όνομα της συνάρτησης που γεννά στιγμιότυπα συνδέεται με τη δημιουργία αυτή τόσο
φορμαλιστικά (για τη γλώσσα) όσο και εννοιολογικά (για το πώς το αντιλαμβάνεται ο χρήστης),
πχ:
(defstruct αυτοκίνητο

; "αυτοκίνητο" είναι όνομα κλάσης τύπου structure
(κατηγορία 'επαγγελματικό) )

; "κατηγορία" είναι όνομα υποδοχής
(setq Datsun
(make-αυτοκίνητο))
; make-xxx είναι συνάρτηση δημιουργίας στιγμιότυπου

Εφαρμογή:
(αυτοκίνητο-κατηγορία 'Datsun)

→ επαγγελματικό

αυτοκίνητο-κατηγορία είναι η αυτόματα παραγόμενη συνάρτηση, από το όνομα της κλάσης και

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

8.1.2 Δημιουργία κλάσεων-δομών και στιγμιοτύπων τους
Μια κλάση - δομή έχει υποδοχές των οποίων το περιεχόμενο (μπορεί να-) εξειδικεύεται για κάθε
358

στιγμιότυπο αυτής της κλάσης. Αν και, κατά κανόνα, διαφορετικά στιγμιότυπα διαφέρουν κατά
μία τουλάχιστον τιμή, αυτό δεν είναι προαπαιτούμενο: είναι δυνατό να έχουμε διαφορετικά
στιγμιότυπα, που να είναι απόλυτα ίδια. Η διαφοροποίησή τους έγκειται στο οτι καθένα,
δημιουργούμενο, αποτελεί φυσική οντότητα, που έχει τη δική της υπόσταση ανεξάρτητα
περιεχομένου υποδοχών.
Τα ζητήματα που θα μελετήσουμε είναι:
– πώς ορίζουμε μια δομή
– πώς δημιουργούμε νέο στιγμιότυπο πάνω στον τύπο της
– πώς ελέγχουμε αν ένα στιγμιότυπο ανήκει (ή δεν ανήκει) σε δομή
– πώς ορίζουμε υπο-δομές μιας υφισταμένης δομής
– πώς εντοπίζουμε συγκεκριμένα χαρακτηριστικά αντικειμένων (δηλ. την τιμή κάποιας ή
κάποιων υποδοχών).

Ορισμός κλάσης (δομής) τύπου structure
defstruct

: Συνάρτηση ειδικής μορφής (macro) με πρώτο όρισμα το όνομα της κλάσης τύπου
structure που θέλουμε να δημιουργηθεί, και επόμενα τις υποδοχές. Ως υποδοχές συνήθως δίνουμε
απλά ονόματα. Κάθε υποδοχή είναι δυνατό να προσδιορίζεται με ή χωρίς αρχική τιμή. Η φόρμα
δημιουργίας κλάσης τύπου structure με υποδοχές - ονόματα χωρίς αρχικές τιμές είναι:
(defstruct <όνομα-δομής>
<υποδοχή-1> <υποδοχή-2> … <υποδοχή-κ> )
Στην περίπτωση αυτή, νοείται ως αρχική τιμή κάθε υποδοχής το NIL .
Η φόρμα δημιουργίας κλάσης τύπου structure με υποδοχές - ονόματα με αρχικές τιμές είναι:
(defstruct <όνομα-δομής>
( <υποδοχή-1> <αρχική-τιμή-1> )
( <υποδοχή-2> <αρχική-τιμή-2> )
...
( <υποδοχή-κ> <αρχική-τιμή-κ> ) )
Τα ονόματα των υποδοχών αποτελούν αυτομάτως κλειδιά για τη δημιουργία στιγμιότυπων.
(προσοχή: τα κλειδιά στον ορισμό κλάσης δεν έχουν πρόθεμα ":", αλλά θα πάρουν στην κλήση
δημιουργίας στιγμιότυπου)
Μπορούμε να προσδιορίσουμε ανάμεικτα τους δύο παραπάνω τρόπους.
H συνάρτηση DEFSTRUCT παίζει διπλό ρόλο διότι το σύμβολο που δημιουργεί: i) είναι ένας
κατασκευαστής αντικειμένων - στιγμιότυπων, ii) είναι ένας νέος τύπος, υποτύπος τού structure.

Κλάση τύπου structure χωρίς αρχικές τιμές
Ας υποθέσουμε οτι ο χώρος αντικειμένων αναφέρεται σε φοιτητές διαφόρων Πανεπιστημίων.
Προσδιορίζουμε τη γενική κλάση - δομή "φοιτητής", και κάθε ένα φοιτητή ως στιγμιότυπο της
κλάσης, με τις ειδικές τιμές των χαρακτηριστικών του, τα οποία έστω οτι είναι τα ακόλουθα:
(defstruct φοιτητής
359

επίθετο όνομα όνομα_πατρός ημερ_γέννησης
τόπος_καταγωγής ίδρυμα σχολή τμήμα αριθμός_μητρώου
έτος_εγγραφής)

Με την εκτέλεση της DEFSTRUCT δημιουργείται η κλάση - δομή "φοιτητής", δηλ. μια
οντότητα τύπου structure, με υποδοχές τα ορίσματα που ακολουθούν το όνομα και αποτελούν τα
κλειδιά για τη δημιουργία στιγμιοτύπων.
Το ζήτημα της επιλογής του κατάλληλου ονόματος για τη δομή και τις υποδοχές της, είναι
σημαντικό για την κατανοητή χρήση, αλλά το πλήθος των υποδοχών είναι το πραγματικά κρίσιμο
μέγεθος, διότι δεν μεταβάλλεται εκ των υστέρων.

Κλάση τύπου structure με αρχικές τιμές

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

Δημιουργία στιγμιότυπων μιας κλάσης τύπου structure

Συγκεκριμένα στιγμιότυπα από δεδομένη κλάση τύπου structure, μπορούμε να δημιουργήσουμε
με δύο τρόπους. Ο πρώτος είναι, με μια συνάρτηση που σχηματίζεται με κάπως ιδιόμορφο τρόπο:
make-<όνομα δομής> : Συνάρτηση της οποίας το όνομα σχηματίζεται από δύο συνθετικά,
ενωμένα με ενωτικό (hyphen) όπου πρώτο είναι η λέξη make και δεύτερο το όνομα της κλάσης.
Τα ορίσματα της συνάρτησης προσδιορίζουν τις συγκεκριμένες τιμές των υποδοχών για το
κατασκευαζόμενο στιγμιότυπο. Είναι προαιρετικά, και πρέπει να προηγείται στην αναφορά τους
ένα κλειδί, που είναι το όνομα της αντίστοιχης υποδοχής.
Προσοχή στο οτι τα κλειδιά χρησιμοποιούνται τώρα με πρόθεμα ":", και δεν θέτουμε παρενθέσεις
στις δυάδες υποδοχών - τιμών.
Παράδειγμα
Στην παραπάνω κλάση τύπου structure "φοιτητής" μπορούμε να δημιουργήσουμε στιγμιότυπα,
χρησιμοποιώντας ως κλειδιά τις υποδοχές της κλάσης, ως εξής:
(make-φοιτητής
:επίθετο
"Χρηστίδης"
:όνομα
"Χρήστος"
:όνομα_πατρός "Κυριάκος"
:ίδρυμα
'Πανεπιστήμιο_Αθηνών
:σχολή
'Φιλοσοφική
:τμήμα
'Γαλλική_Φιλολογία
:αριθμός_μητρώου 333333
:έτος_εγγραφής
1994
360

:ημερ_γέννησης '(22 4 1975)
:τόπος_καταγωγής '(Ελλάδα Μαγνησία Μακρινίτσα) )

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

Η τιμή - λίστα, όπως στις υποδοχές ημερ_γέννησης και τόπος_καταγωγής , είναι ένας εύκολος
και άμεσος τρόπος για να δώσουμε ως τιμή κάποιο σύνθετο (ακολουθιακά ή συνολοθεωρητικά)
περιεχόμενο, με ελευθερία πλήθους αναφερομένων στοιχείων. Τα στοιχεία της λίστας μπορούν
να έχουν ιδιαίτερη σημασία, καθοριζόμενη από τη θέση τους: Στη λίστα τόπος_καταγωγής ,
πρώτο στοιχείο είναι το κράτος, δεύτερο ο νομός, τρίτο η πόλη, κλπ, όπου δεν είναι απαραίτητο
να δώσουμε πληροφορία για τη διεύθυνση κατοικίας, αλλά δεν είναι αδύνατο αν θέλουμε.
Γενικά, μπορούμε να δώσουμε ως τιμή υποδοχής οποιαδήποτε φόρμα, η οποία αν είναι "quoted"
θα καταχωρηθεί ως έχει και θα υπολογιστεί με κατάλληλη κλήση της.
Δεν είναι αναγκαίο να έχει υποδοχές μια κλάση - δομή:
(defstruct thing)
(make-thing) → #S(THING)

(αυτό είναι ένα αντικείμενο - στιγμιότυπο)

(make-thing)

→ #S(THING)
(αυτό είναι ένα άλλο στιγμιότυπο)

Εναλλακτικός τρόπος κατασκευής στιγμιότυπων: με τον "κατασκευαστή"
Δεύτερος τρόπος δημιουργίας στιγμιοτύπων είναι με τον "κατασκευαστή" (constructor). Είναι ένα
κλειδί, με όνομα :constructor (προσοχή στο ":" ) το οποίο ακολουθεί ένα σύμβολο, που
καθορίζει μια ειδική συνάρτηση, με την οποία μπορούμε να κατασκευάζουμε στιγμιότυπα της
κλάσης:
(defstruct
(str1 (:constructor create_inst_of_str1))
(s1 0) (s2 3) (s3 1) (s4 5) )

(προσοχή στην παρένθεση που κλείνει το όνομα της δομής και τον constructor, που είναι η
συνάρτηση - σύμβολο create_inst_of_str1 ).
Δημιουργία συμβόλου - μεταβλητής με όνομα της επιλογής μας, και περιεχόμενο ένα νέο
στιγμιότυπο της κλάσης str1 :
(setq str1_1 (create_inst_of_str1))
→ #S(STR1 S1 0 S2 3 S3 1 S4 5)

(εσωτερική αναπαράσταση στην Αllegro v.5))
Η μορφή του αποτελέσματος (το ειδικό πρόθεμα #S σε λίστα) δηλώνει οτι η μεταβλητή str1_1
έχει περιεχόμενο ένα στιγμιότυπο της κλάσης str1 , με υποδοχές: s1 με τιμή 0 , s2 με τιμή 3 , s3
361

με τιμή 1 , s4 με τιμή 5 .
Αν θέλουμε, μπορούμε να δώσουμε, κατά την εφαρμογή του κατασκευαστή, διαφορετικές τιμές
υποδοχών για το στιγμιότυπο:
(setq str1_2
(create_inst_of_str2
:s1 7 :s4 10))
→ #S(STR2 S1 4 S2 6 S3 8 S4 1 S5 2)

(εσωτερική αναπαράσταση)

Η φόρμα του αποτελέσματος δηλώνει πως η μεταβλητή str1_2 έχει περιεχόμενο ένα νέο
στιγμιότυπο της κλάσης str1 , με υποδοχές: s1 με τιμή 7 , s2 με τιμή 3 , s3 με τιμή 1 , s4 με τιμή
10 .
Μια πιο ευέλικτη μορφή του κατασκευαστή:
Για λόγους ευελιξίας, το κλειδί :constructor , μετά το όνομα - συνάρτηση κατασκευής, μπορεί να
δεχτεί προαιρετικά και ένα όρισμα - λίστα υποδοχών. Τότε η συνάρτηση κατασκευής
στιγμιότυπου δέχεται απ' ευθείας ως ορίσματα τις υποδοχές αυτές (αντί να τις δώσουμε μέσω των
κλειδιών - ονομάτων των υποδοχών):
(defstruct
(str3
(:constructor create_inst_of_str3 (s1 s2 s3) ) )
(s1 0) (s2 0) (s3 0) (s4 1) (s5 2) )

Κλήση:
(setq str2_1 (create_inst_of_str3 4 6 8))
→ #S(STR3 S1 4 S2 6 S3 8 S4 1 S5 2)

Τώρα η μεταβλητή str2_1 έχει περιεχόμενο ένα νέο στιγμιότυπο της κλάσης str2 με υποδοχές: s1
με τιμή 4 , s2 με τιμή 6 , s3 με τιμή 8 , s4 με τιμή 1 , s5 με τιμή 2 .
Έχουμε τη δυνατότητα να ορίσουμε περισσότερους του ενός κατασκευαστές, όπου ανάλογα με
την είσοδο (δηλ. με πόσα ορίσματα θα κληθεί) θα εφαρμόζεται η αντίστοιχη περίπτωση.
Επιτρέπεται να χρησιμοποιήσουμε ως όνομα του κατασκευαστή το ίδιο όνομα με την κλάση.
Η ύπαρξη κατασκευαστή δεν απαγορεύει τη χρήση του συνδυασμού MAKE-XXX .
Παράδειγμα
(defstruct (τρόφιμα (:constructor τρόφιμο))
( βασικά_συστατικά_%
'( (πρωτεΐνες 0) (υδατάνθρακες 0) (λίπη 0) ) )
υλικά_κατασκευής_gr
συνταγή_κατασκευής
(ημέρες_διατήρησης 0) )
(setq ψωμί
(τρόφιμο
:βασικά_συστατικά_%
362

'( (πρωτεΐνες 0) (υδατάνθρακες 38) (λίπη 6) )
:υλικά_κατασκευής_gr
'((αλεύρι 1000) (μαγιά 5) (νερό 300) (λάδι 15))
:συνταγή_κατασκευής
'(ζυμώνουμε αφήνουμε_να_φουσκώσει ψήνουμε)
:ημέρες_διατήρησης 3) )
(setq παντεσπάνι (τρόφιμο . . . ))
; κλπ.

Εντοπισμός αντικειμένων μέσω ονομασίας τους ή μέσω φυσικής θέσης τους
Το κάθε αντικείμενο - στιγμιότυπο κλάσης τύπου structure, αν κατασκευαστεί χωρίς κάποια
σύνδεση, μένει "ξεκρέμαστο" και η αναφορά σ' αυτό δεν είναι δυνατή αν δεν έχουμε προβλέψει
κάποιο δρόμο πρόσβασης σ' αυτό. Αυτό μπορεί να γίνει, όπως είδαμε, με ονομασία του
αντικειμένου, ταυτόχρονα με τη δημιουργία του, πχ. χρησιμοποιώντας την SETQ :
(setq φοιτητής1 (make-φοιτητής …))

Μπορούμε εναλλακτικά, να το τοποθετήσουμε σε "θέση" προσδιοριζόμενη από άλλα στοιχεία
(πχ. συγκεκριμένη θέση σε λίστα, θέση σε πίνακα, κλπ), όπως:
(setq λίστα_φοιτητών nil)
; αρχική τιμή
(push (make-φοιτητής ….) λίστα_φοιτητών)

; παραγόμενο στιγμιότυπο, τοποθετείται στη λίστα
(push (make-φοιτητής ….) λίστα_φοιτητών)

; επόμενο παραγόμενο στιγμιότυπο, τοποθετείται στη λίστα
(push (make-φοιτητής ….) λίστα_φοιτητών)

; επόμενο παραγόμενο στιγμιότυπο, τοποθετείται στη λίστα
λίστα_φοιτητών →
→ <λίστα από τις εσωτερικές αναπαραστάσεις των στιγμιοτύπων>
Τα αντικείμενα ταυτοποιούνται βάσει της θέση τους στη λίστα αυτή, και μπορούν αργότερα να
κληθούν, και αν θέλουμε, να ονομαστούν (προσοχή, πρώτο είναι το τελευταίο στοιχείο που
εισήχθη):
(setq φοιτητής3 (third (reverse λίστα_φοιτητών)))

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

Αν η συνάρτηση αυτή κληθεί με όρισμα το αντικείμενο (με οποιοδήποτε τρόπο προσδιοριζόμενο,
πχ. μέσω του ονόματός του ή της θέσης του), επιστρέφει την τιμή της αντίστοιχης υποδοχής για
363

το αντικείμενο αυτό:
(<δομή>-<υποδοχή> <στιγμιότυπο>) →
<τιμή υποδοχής για το στιγμιότυπο>
Η προσπέλαση είναι ταχύτατη, λόγω του οτι τα κλειδιά θέτουν pointers προς την αντίστοιχη
θέση. Δεν βλέπουν την αντίστοιχη υποδοχή του γεννήτορα, κάτι που αν το χρειαστούμε το
πετυχαίνουμε με τις κλάσεις που δημιουργούνται με την DEFCLASS , όπως θα δούμε.
Παράδειγμα
Θεωρούμε την προηγούμενη κλάση "φοιτητής", και τρία στιγμιότυπά της, με ονόματα που
εντάσσονται στο context μιας φιλικής παρέας:
Παραγωγή στιγμιότυπων:
(setq Νίκος (make-φοιτητής
:επίθετο "Ιωάννου" :όνομα "Νικόλαος"
:όνομα_πατρός "Χρήστος" :όνομα_μητρός "Μαρία"
:γένος_μητρός "Παππά" :ημερ_γέννησης '(18 12 1990) ))

; τα λοιπά χαρακτηριστικά είναι τα αρχικά της κλάσης - γεννήτορα
; φοιτητής
(setq Τάκης (make-φοιτητής … ) )
; παρόμοια
(setq Μάρω (make-φοιτητής … ) )
; παρόμοια
Ανάγνωση χαρακτηριστικών στιγμιότυπου:
Έστω οτι θέλουμε να βρούμε, για το στιγμιότυπο "Νίκος" (το οποίο, για την ακρίβεια, είναι η τιμή
του συμβόλου Νίκος), την τιμή της υποδοχής ημερ_γέννησης :
(φοιτητής-ημερ_γέννησης Νίκος)
→ '(18 12 1990)

Χρησιμοποιώντας τα ονόματα των στιγμιότυπων μπορούμε να εντοπίσουμε την αναζήτηση σε
λεπτομέρεια μέσα στην τιμή υποδοχής, μέσω κάποιας κοινής συνάρτησης:
(defun γενέθλια (όνομα) (list
(car (φοιτητής-ημερ_γέννησης όνομα))

; η μέρα της ημερομηνίας γέννησης
(cadr (φοιτητής-ημερ_γέννησης όνομα)) ))

; ο μήνας της ημερομηνίας γέννησης
Εφαρμογή:
(γενέθλια Νίκος)
→ (18 12)

Παρατηρούμε οτι η συνάρτηση γενέθλια είναι μια συνήθης συνάρτηση που κάνει χρήση της
δομής στην οποία ανήκει το όρισμά της, και μέσω αυτής, του χαρακτηριστικού ημερ_γέννησης.
Αυτό εντάσσεται στην ευρύτερη δυνατότητα να χρησιμοποιούμε αντικείμενα ως συναρτησιακές
οντότητες.

364

Σταθεροποιημένες υποδοχές
Μπορούμε να δηλώσουμε αρχικά, σε επίπεδο κλάσης, αν δεν επιτρέπεται μεταβολή της τιμής
υποδοχής, με το κλειδί :read-only που αναγράφεται μετά την τιμή και συνοδεύεται από την τιμή
Τ . Ο προκαθορισμός αυτός μπορεί να μεταβληθεί αργότερα, στο ίδιο επίπεδο ή σε επίπεδο
υποκλάσης ή στιγμιότυπου, και χρησιμεύει για να αποφύγουμε μη επιτρεπτές μεταβολές, ιδίως
από επεμβάσεις του χρήστη.
Παράδειγμα
Έστω οτι μια δομή που παριστά ένα κινητό σώμα, με σταθερή μάζα που δίνεται ως δεκαδικός
απλής ακρίβειας, όνομα που μπορεί να αλλάξει, και τα διάφορα στιγμιότυπά του τη θέση του σε
καρτεσιανές συντεταγμένες (x , y , z) στο χώρο. Έστω οτι η θέση κάθε στιγμιότυπου
καθορίζεται με υπολογισμό των x, y, z συναρτήσει του χρόνου.
(defstruct (κινητό (:constructor θέση_κινητού (x y z)))
(όνομα "η κόκκινη μπάλα")
(x 0.0 ) (y 0.0 ) (y 0.0 )
(μάζα 15.0 :read-only t))
(setq θέση-1 (make-instance θέση_κινητού a b c) )

;; εδώ τα a, b, c είναι σύμβολα που εννοείται οτι παίρνουν τιμή από
; άλλον υπολογισμό

Ανάγνωση χαρακτηριστικών κλάσης - δομής
Η σύνθεση:
( <όνομα κλάσης>-<όνομα υποδοχής> <αντικείμενο> )
είναι ένας προσδιορισμός φυσικής θέσης, και επιστρέφει την τιμή της υποδοχής μόνον όταν το
αντικείμενο είναι φυσικά προσδιορισμένο ως τύπου structure, δηλ. στη φόρμα:
#S( <όνομα κλάσης> <όνομα υποδοχής1> <τιμή υποδοχής1> … )
η οποία επιστρέφεται από εφαρμογή της MAKE-<όνομα κλάσης> .
Ο απλούστερος τρόπος για να λάβουμε τα χαρακτηριστικά κλάσης, είναι να δημιουργήσουμε ένα
αντικείμενο της κλάσης, χωρίς αλλαγή τιμών υποδοχών, και να αναφερθούμε σ' αυτό:
(defstruct foi (s1 4) (s2 7))
(foi-s2 (make-foi)) → 7

Όμως έτσι δημιουργούμε περιττή επιβάρυνση του χώρου, διότι σε κάθε κλήση της make-foi
έχουμε νέα δημιουργία αντικειμένου Γι' αυτό είναι προτιμότερο να προσδιορίσουμε σύμβολο με
περιεχόμενο ένα αντικείμενο, μια φορά δημιουργημένο, και να καλούμε το σύμβολο:
(setq foi_inst1 (make-foi))
(foi-s2 foi_inst1) → 7
(expt (foi-s1 foi_inst1) (foi-s2 foi_inst1) ) → 16384

Αναδιαμόρφωση χαρακτηριστικών στιγμιότυπου

Επέμβαση σε υποδοχή στιγμιότυπου
είναι δυνατή, με εφαρμογή της SETF πάνω στη φυσική θέση
b
365

που προσδιορίζει την υποδοχή στο στιγμιότυπο, δηλ. :
(setf (foi-s1 foi_inst1) 33)

τώρα το στιγμιότυπο-περιεχόμενο του συμβόλου foi_inst1 έχει τη νέα τιμή υποδοχής:
(foi-s1 foi_inst1) → 33
Αυτό δεν επηρέασε την κλάση, και η δημιουργία νέου στιγμιότυπου (ας το αποδώσουμε ως
περιεχόμενο στο σύμβολο foi_inst2 ) θα λάβει την παλαιά τιμή υποδοχής:
(setq foi_inst2 (make-foi))
(foi-s1 foi_inst2) → 4

Αναδιαμόρφωση χαρακτηριστικών κλάσης - δομής

Ο απλούστερος τρόπος επαναπροσδιορισμού ενός χαρακτηριστικού κλάσης - δομής, είναι να την
επανορίσουμε με την DEFSTRUCT , δηλαδή με το ίδιο όνομα και μεταβάλλοντας το
χαρακτηριστικό που θέλουμε, επαναλαμβάνοντας τα λοιπά. Η κληρονόμηση των νέων
χαρακτηριστικών είναι "αυτόματη" από τις υποκλάσεις, δεδομένου οτι "δείχνουν" στα
χαρακτηριστικά του γονέα τους, αλλά δεν επηρεάζονται τα στιγμιότυπα, δεδομένου οτι
αντιγράφουν υποδοχές και τιμές (και κλειδιά όπως το :read-only ).
Εν γένει, είναι δυνατό να δημιουργήσει περιπλοκές μια τέτοια παρέμβαση, λόγω της πιθανής
ασυμβατότητας νέων χαρακτηριστικών με παλαιά, και γι' αυτό οφείλουμε να ελέγξουμε σε όλο το
βάθος του κλάδου τί επίδραση έχει ο επαναπροσδιορισμός
κόμβου.
c

8.1.4 Συσχέτιση κλάσης-δομής με ένα context αναφοράς
Συσχέτιση αντικειμένων με ένα συγκεκριμένο context αναφοράς

Μπορούμε να προσδιορίσουμε στιγμιότυπα σε σχέση με ένα συγκεκριμένο context αναφοράς,
όπου ορισμένα μόνον χαρακτηριστικά έχουν νόημα ή είναι αρκετά για τον προσδιορισμό και
επεξεργασία του αντικειμένου, απλώς αδιαφορώντας για τα υπόλοιπα.

– Θα αποκαλούμε νοηματικό συσχετισμό αντικειμένου με το context αναφοράς, τις εξής δύο
ενέργειες: κατ' αρχήν την επιλογή ονόματος του αντικειμένου με τρόπο που να αποδίδει το νόημα
του αντικειμένου στο context αυτό,
car καιcdrστη συνέχεια την επιλογή ενός υποσυνόλου από τις
υποδοχές της κλάσης, αυτών που έχουν σχέση με το context αναφοράς.

– Θα αποκαλούμε φυσικό συσχετισμό αντικειμένου με το context αναφοράς, τις εξής δύο
ενέργειες, που θα δούμε παρακάτω: κατ' αρχήν τη δημιουργία νέας κλάσης, αντίστοιχης του νέου
context, και αλλαγή κλάσης του αντικειμένου. Αυτή είναι μια καταστροφική δράση και πρέπει να
γίνει με προσοχή.
Παράδειγμα
Νοηματικός συσχετισμός.
Στα πλαίσια μιας κλήρωσης υποτροφιών για φοιτητές, όπου χρειάζεται: {όνομα, επίθετο,
b

c
a

366

d

nil

ημερ_γέννησης, τόπος_καταγωγής, ίδρυμα, τμήμα}, θέλουμε να αξιοποιήσουμε την πληροφορία
που δίνουν τα δεδομένα της κλάσης "φοιτητής".
Δίνουμε στα αντικείμενα ονόματα ενταγμένα στο context αναφοράς:
(setq υποψήφιος1
(make-φοιτητής
:όνομα
"Λεωνίδας"
:επίθετο
"Πάππος"
:ημερ_γέννησης '( 4 12 1979)
:τόπος_καταγωγής '(Κέρκυρα)
:ίδρυμα
'Πανεπιστήμιο_Αθηνών
:τμήμα
'Γαλλική_Φιλολογία ) )

Προφανώς, η μη εγγραφή τιμής για τις λοιπές υποδοχές δεν σημαίνει τίποτα ιδιαίτερο, διότι δεν
παύουν να υπάρχουν και παίρνουν την αρχική τιμή τους ( NIL αν δεν ορίζεται).
Μεταβολή του context αναφοράς:
Σε μεταβολή του context αναφοράς, με την προϋπόθεση οτι αναφερόμαστε στα αυτά
χαρακτηριστικά, δεν έχουμε παρά να ομάσουμε κατάλληλα τα ονόματα των αντικειμένων, αρκεί
να είναι προσπελάσιμα. Πχ:
c

nil

– Για ονομασία του αντικειμένου προς σαφέστερο νοηματικό συσχετισμό του με το context
αναφοράς, πχ, "όπου οι άνθρωποι χαρακτηρίζονται με τον τόπο καταγωγής τους":
; νέο όνομα για τον φοιτητή - περιεχόμενο της μεταβλητής υποψήφιος1
(setq ο_Κερκυραίος υποψήφιος1)

– Για νοηματική ένταξη αντικειμένου σε μια υποτιθέμενη κλάση, χωρίς να δημιουργούμε
υπολογιστικά την αντίστοιχη δομή:
(setq ο_καλύτερος_φοιτητής
υποψήφιος1)
a
b
(setq καλός_φοιτητής_1 υποψήφιος1)
(setq αυτός_που_κάθεται_στην_τρίτη_θέση υποψήφιος1)

Εδώ, η συσχέτιση ονόματος συμβόλου με την κατηγορία εξυπηρετεί σε καθαρά νοητικό
επίπεδο, διευκολύνοντας τους συλλογισμούς μας, αλλά δεν σχετίζεται με τον υπολογισμό.
d
nil
Μπορούμε όμως να έχουμε και υλοποιημένους τέτοιους προσδιορισμούς,
όπως:

– Για φυσική τοποθέτησή του σε συγκεκριμένη θέση:
Υποθέτουμε οτι έχουμε ορίσει κλάση εκδρομή_με_πούλμαν με στιγμιότυπα τα διάφορα
πούλμαν που συμμετέχουν (ένα στιγμιότυπο υποτίθεται οτι είναι το πούλμαν_2 ) και ως δομή
έχει διάφορες υποδοχές όπου κάποια είναι αρχηγός :
(setf (εκδρομή_με_πούλμαν-αρχηγός πούλμαν_2) ο_Κερκυραίος )

Αυτό που έχουμε εκφράσει με την παραπάνω εγγραφή, ως φυσικό νόημα είναι: «στην εκδρομή
με πούλμαν, στο δεύτερο πούλμαν, αρχηγός είναι ο Κερκυραίος, που ως γνωστό είναι ένας
φοιτητής». Ως υπολογιστικό νόημα, είναι το: «στη δομή εκδρομή_με_πούλμαν , στιγμιότυπο
πούλμαν_2 , η υποδοχή αρχηγός παίρνει ως τιμή την τιμή του συμβόλου ο_Κερκυραίος , η οποία
367

a

είναι στιγμιότυπο της δομής φοιτητής ».
Εάν το ζήτημα προσαρμογής στο νέο context αφορά ονόματα υποδοχών, επειδή μεταβολή
ονόματος υποδοχής δεν γίνεται, μεταθέτουμε τη λύση σε επίπεδο εφαρμογής μιας συνάρτησης
επεξεργασίας μέσω της οποίας θα γίνεται η πρόσβαση, πχ:
(defun οι-υποψήφιοι-υπόστροφοι-από (τόπος λίστα-φοιτητών)
<κριτήριο επιλογής φοιτητή> <επιστεφόμενη λίστα>)

Διαμόρφωση χαρακτηριστικών αντικειμένου, σε νέο context

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

Για αντιμετώπιση της ενδεχόμενης μελλοντικής έλλειψης κατάλληλων υποδοχών, σχετική
ευελιξία προσφέρει η πρόβλεψη κάποιων κενών υποδοχών "για κάθε ενδεχόμενο", ή υποδοχών δοχείων (πχ. με τιμή λίστα, όπου μπορούμε πάντα να επισυνάπτουμε νέους όρους). Σε
πλεονασμό, δηλ. αν κάποιες από τις υπάρχουσες υποδοχές δεν χρειάζονται για τη νέα χρήση που
θα κάνουμε του στιγμιότυπου, δεν έχουμε παρά να τις αγνοήσουμε.
Μια αντιμετώπιση της προσαρμογής στο context είναι να δώσουμε ως τιμή υποδοχής μια λίστα
συσχέτισης, με όρους "context - τιμή" (λύση ευέλικτη λόγω της ευχέρειας επέκτασης της λίστας).
Για παράδειγμα: κλάση άνθρωπος, υποδοχή χαρακτήρας , με τιμή:
(defustruct άνθρωπος
χαρακτήρας <κλπ> )
(setq Γιάννης (make-άνθρωπος
:χαρακτήρας
'( (σπουδαστικό . επιμελής) (εργασιακό . αποδοτικός)
(κοινωνικό . ανοιχτόκαρδος))
<κλπ> ) )

Ομαδοποίηση αντικειμένων σε σχέση με κάποιο context

Ο προγραμματισμός που συσχετίζει εννοούμενο context χρήσης, υλοποιημένη δομή και
υλοποιημένο context αναφοράς, θέτει πολλές προϋποθέσεις στην κλήση, και αυτό αντιβαίνει τις
αρχές καλής οργάνωσης συναρτησιακού προγράμματος: για να είναι λειτουργική μια εφαρμογή,
θα απαιτηθεί κατά την οργάνωσή της, κατάλληλη υποταγή ή προσαρμογή σε εννοούμενα
στοιχεία (πχ. συνδέσεις που εμείς εννοούμε αλλά δεν είναι υλοποιημένες, όπως η παρακάτω
αναφερόμενη "γενέθλια" → "γιορτή" → "φίλοι" ).
Εν πάσει περιπτώσει, απόλυτο φορμαλισμό δεν είναι εφικτό να έχουμε: κάτι πάντα εννοούμε,
ακόμα και στην πιό αυστηρά τυπική φόρμα. Επομένως, το θέμα ανάγεται στην αναζήτηση της
368

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

Παράδειγμα
Ομαδοποίηση αντικειμένων σε λίστα, όπου η ομάδα να μην αποτελεί τυπικά κλάση:
Έστω τα πρόσωπα - στιγμιότυπα της κλάσης τύπου structure "φοιτητής" που θεωρήσαμε
προηγουμένως, και έστω η λίστα "παρέα" με όρους φοιτητές. Η συνάρτηση PARTY , με όρισμα
ημερομηνία, εντοπίζει "ποιος από την παρέα γιορτάζει, και ποιοί οργανώνουν το party προς τιμή
του":
(setq παρέα '(Νίκος Τάκης Μάρω))
(defun party (lis day month)
(setq co_lis (copy-list lis))

;αντίγραφο της λίστας - παρέας, για να κρατηθεί η αρχική λίστα
`(princ "Στις ") (princ day) (princ " ")(princ month) (princ "ου ")
(cond
((γενέθλια (car lis))
(princ "οργανώνουν party οι ")
(princ (set-difference co_lis (car lis)))
(princ " επειδή γιορτάζει ") (princ (car lis)) )
(t (party (cdr lis) day month) ) ))

Εφαρμογή:
(party παρέα 18 12)

→ Στις 18 12ου οργανώνουν party οι (Τάκης Μάρω) επειδή
γιορτάζει Νίκος

Ένταξη και επεξεργασία οντότητας σε διάφορα contexts
Έμμεσα αντιμετωπίσουμε την περίπτωση συσχέτισης νοηματικού αντικειμένου με περισσότερα
contexts: οργανώνουμε μιας βασική κλάση που περιλαμβάνει μόνο τα κύρια και αναλλοίωτα για
κάθε context χαρακτηριστικά, και για κάθε context μια κλάση που περιλαμβάνει τα
χαρακτηριστικά που σχετίζονται με το context. Κάθε οντότητα - νοηματικό αντικείμενο θα
αποτελείται από μια λίστα στιγμιοτύπων, όπου το πρώτο είναι της βασικής κλάσης και τα
υπόλοιπα αφορούν το εκάστοτε context. Πχ:
(setq ο-Γιάννης
'( <στιγμιότυπο της κλάσης πρόσωπο >
369

<στιγμιότυπο της κλάσης εργαζόμενος_στη ΔΕΗ >
<στιγμιότυπο της κλάσης μέλος_συλλόγου_φίλων_πτηνών > ) )
Το σύμβολο ο-Γιάννης συμπεριλαμβάνει κάθε σχετική με τον Γιάννη πληροφορία, που είναι
εύκολα εξερευνήσιμη, επεκτάσιμη, κληρονομεί τις αντίστοιχες "επί μέρους" κλάσεις, και η
απαιτούμενη σύνθεση χαρακτηριστικών για συγκεκριμένο context είναι εύκολη, μέσω
συνάρτησης. Κύρια και αμετάβλητη πληροφορία είναι τα χαρακτηριστικά του πρώτου
στιγμιότυπου, αλλά αν αλλάξει επάγγελμα ο Γιάννης, απλώς αλλάζουμε τον δεύτερο όρο, και
παρόμοια, αν αποκτήσει και άλλης κατηγορίας χαρακτηριστικά (πχ. στιγμιότυπο της κλάσης
ψάλτης) απλώς επισυνάπτουμε το νέο στιγμιότυπο.
Εχουμε ένα σχετικό μειονέκτημα, οτι η παραπάνω ευελιξία προσαρμογής της πληροφορίας που
περιέχεται στη λίστα, δεν συνοδεύεται από ευελιξία προσαρμογής των σχετικών μεθόδων, που
κατ' ανάγκη θα είναι οργανωμένες για δεδομένη σύνθεση της λίστας αυτής (αφού κάθε μέθοδος
θα πρέπει να έχει ένα όρισμα για κάθε όρο της λίστας). Το πρόβλημα αυτό λύνεται με αρκετή
δυσκολία, με επαναπροσδιορισμό των μεθόδων μετά κάθε μεταβολή, είτε "χειρωνακτικά" είτε
μέσω νοημόνων συναρτήσεων που θα οργανώνουν την μέθοδο που θα εφαρμοστεί, βάσει των
δεδομένων που ισχύουν τη στιγμή της κλήσης. Είναι θέμα προς διερεύνηση.
Η προσέγγιση αυτή είναι διαφορετική από την προηγούμενη, όπου "ανοίγουμε ένα παράθυρο
σύνδεσης" κατ' ευθείαν στην κλάση, με υποδοχή που δέχεται τα διάφορα contexts και τις ειδικές
αντίστοιχες τιμές ανά στιγμιότυπο, σε μορφή από λίστα συσχέτισης. Εδώ η οργάνωση των
μεθόδων είναι απλούστερη, και η λαμβανόμενη πληροφορία πιό άμεση, δεδομένου οτι οι
συνδέσεις "βλέπουν" χωρίς υπολογισμό την οντότητα σύνδεσης.
Και τα δύο αυτά μοντέλα επιτρέπουν την κατασκευή από επεκτάσιμους κόσμους.

8.1.5 Υποκλάσεις-δομές και κληρονόμηση χαρακτηριστικών
Ορισμός υπο-δομών (υποκλάσεων της δομής)

Μπορούμε να δημιουργήσουμε υποκλάσεις μιας κλάσης - δομής τύπου structure, τέτοιες ώστε να
κληρονομούν ότι ισχύει για την αρχική κλάση (που θα την αποκαλούμε υπερκλάση των
υποκλάσεών της, ή υπερ-δομή των υπο-δομών της).

Υποκλάση μιας δομής είναι μια άλλη δομή η οποία έχει όλες τις υποδοχές της δομής και επί
πλέον κάποιες νέες. Άρα, αποτελεί μια εξειδίκευση της κλάσης τύπου structure. Ο τρόπος
ορισμού μιας υποκλάσης κάποιας δομής είναι απλός, και γίνεται πάλι με χρήση της
DEFSTRUCT , χρησιμοποιώντας το κλειδί :include , και κληρονομεί όλες τις υποδοχές και
αρχικές τιμές τους της αρχικής κλάσης. Η σύνταξη είναι:
(defstruct
(<υποκλάση>
(:include <αρχική δομή>))
(<νέα υποδοχή1> <αρχική τιμή1>)
(<νέα υποδοχή2> <αρχική τιμή2>) . . . )
370

Επαναπροσδιορισμός αρχικών τιμών

Αν θέλουμε να επαναπροσδιορίσουμε στην οριζόμενη υποκλάση τις αρχικές τιμές κάποιων από
τις υποδοχές, αρκεί να τις αναγράψουμε κατά τον ορισμό της νέας υποκλάσης, αλλά μετά το
κλειδί :include , στην ίδια παρένθεση μετά το όνομα της κλάσης στην οποία αναφέρονται, και
όχι μαζί με τις νέες υποδοχές:
(defstuct
(<υποκλάση>
(:include <κλάση>
(<παλαιά υποδοχή1> < νέα αρχική τιμή1>)
(<παλαιά υποδοχή2> <νέα αρχική τιμή2>) …))
(<νέα υποδοχή1> <αρχικ τιμή1>)
(<νέα υποδοχή2> <αρχική τιμή2>)
... )

Σύνδεση υπο-δομής με τη δομή - γονέα

Οι υποκλάσεις κάποιας κλάσης τύπου structure "γνωρίζουν" από ποια κλάση προήλθαν, με το
νόημα οτι είναι υποτύποι του τύπου της, αλλά δεν περιέχουν επεξηγηματική πληροφορία γι' αυτό.
Αν χρειαστούμε να περιέχεται τέτοια πληροφορία μέσα στην κλάση, μπορούμε να
προσδιορίσουμε μια υποδοχή με τιμή την υπερκλάση, με όνομα πχ. υπερκλάση και περιεχόμενο
το όνομα της υπερκλάσης:
(defstuct (<όνομα υποκλάσης> (:include <όνομα υπερκλάσης>))
(υπερκλάση '<όνομα υπερκλάσης>) . . . )
Παράδειγμα
Κλάση τύπου structure "αυτοκίνητο", υπο-δομή "φορτηγό":
(defstruct αυτοκίνητο
εργοστάσιο έτος_παραγωγής )
(defstruct (φορτηγό (:include αυτοκίνητο))
(υπερκλάση 'αυτοκίνητο) καύσιμο κυβισμός)

Σύνοψη και παρατηρήσεις
► Οι υποδοχές μιας δομής μπορούν να μεταβληθούν ως προς το περιεχόμενό τους (και αυτό με
χρήση της SETF) αλλά όχι ως προς το πλήθος τους.
► Δημιουργώντας μια νέα δομή, υποκλάση προηγούμενης, μπορούμε να προσθέσουμε νέες
υποδοχές. Η υπο-δομή αποτελεί εξειδίκευση της γονικής κλάσης (τύπου) structure.
► Η δημιουργία στιγμιότυπων βασίζεται στις υποδοχές της αναφερόμενης δομής: τις
χρησιμοποιεί υποχρεωτικά και δεν μπορεί να αφαιρέσει υπάρχουσες ή να προσθέσει νέες. Μη
αναφορά υποδοχών της κλάσης κατά τη δημιουργία στιγμιότυπου, σημαίνει κληρονόμηση των
υποδοχών με την αρχική τους κατάσταση από την δομή.
371

Παράδειγμα
Έστω η ακόλουθη ιεραρχική δομή, και τα αντίστοιχα στιγμιότυπα που σημειώνονται:

Η υλοποίηση γίνεται με χρήση των DEFSTRUCT , ΜΑΚΕ-XXX και SETQ :
Κλάσεις και υποκλάσεις:
;; κλάση τύπου structure "πρόσωπο":
(defstruct πρόσωπο
(επίθετο " ")
(όνομα
" ")
(αριθμός_ταυτότητας " ") )

;; κλάση "υπάλληλος", ως υπο-δομή της δομής "πρόσωπο":
(defstruct (υπάλληλος (:include πρόσωπο))
(ειδικότητα " ")
(βαθμός 0 )
(σύμβαση_απο_μέχρι '(1990 2000) )

;; κλάση "ιδιωτικός_υπάλληλος", ως υπο-δομή της "υπάλληλος":
(defstruct (ιδιωτικός_υπάλληλος
(ασφάλιση 'ΙΚΑ)
(απόδοση 0) )

(:include υπάλληλος) )

;; κλάση "δημόσιος_υπάλληλος", ως υπο-δομή της "υπάλληλος":
(defstruct (δημόσιος_υπάλληλος (:include υπάλληλος) )
(ασφάλιση 'δημοσίου) )

;; κλάση "επαγγελματίας", ως υπο-δομή της δομής "πρόσωπο":
(defstruct (επαγγελματίας (:include πρόσωπο))
(επάγγελμα " " ) )

;; κλάση "ασφαλισμένος_ΤΕΒΕ", ως υπο-δομή της "επαγγελματίας":
372

(defstruct (ασφαλισμένος_ΤΕΒΕ (:include επαγγελματίας ))
(αριθμός_μητρώου 0 )
κατηγορία_ασφάλισης )

Στιγμιότυπα:
;; στιγμιότυπα της δομής "πρόσωπο", ονομασμένα:
(setq Θανάσης (make-πρόσωπο
:επίθετο "Κωστής"
:όνομα"Αθανάσιος"
:αριθμός_ταυτότητας "Ζ-123456" ) )
(setq Νικολέτα (make-πρόσωπο
. . . ))

;; στιγμιότυπα της υπο-δομής "ιδιωτικός_υπάλληλος":
(setq Μαρία (make-ιδιωτικός_υπάλληλος
:επίθετο
"Νικολαϊδου"
:όνομα
"Μαρία"
:αριθμός_ταυτότητας "Α-333333"
:ειδικότητα 'δικηγόρος
:βαθμός
4
:σύμβαση_απο_μέχρι '(1997 2002) ))
(setq Ελένη (make-ιδιωτικός_υπάλληλος … ))

;; στιγμιότυπο της υπο-δομής "ασφαλισμένος_ΤΕΒΕ":
(setq Πάνος (make-ασφαλισμένος_ΤΕΒΕ
:επίθετο
"Νικόδημος"
:όνομα
"Παναγιώτης"
:αριθμός_ταυτότητας "Α-333334"
:επάγγελμα
'μηχανικός
:αριθμός_μητρώου
123123123
:κατηγορία_ασφάλισης 1) )

;; στιγμιότυπα της υπο-δομής "δημόσιος_υπάλληλος":
(setq Γιάννης (make-δημόσιος_υπάλληλος
:επίθετο "Οικονόμου"
...
))
(setq Γιάννα (make-δημόσιος_υπάλληλος . . .

))

Αναζήτηση τιμής υποδοχής στιγμιότυπου:
Αναγράφουμε το όνομα της δομής της οποίας είναι στιγμιότυπο το αναφερόμενο αντικείμενο, και
με ενωτικό την υποδοχή που ζητάμε την τιμή της. Η σύνθεση αυτή αποτελεί τη συνάρτηση
αναζήτησης, στην οποία δίνουμε όρισμα το αντικείμενο (με οποιοδήποτε τρόπο προσδιορισμένο,
πχ. με το όνομα που του έχουμε δώσει):
(πρόσωπο-επίθετο Θανάσης)
→ "Κωστόπουλος"
(πρόσωπο-αριθμός_ταυτότητας Θανάσης) → "Ζ-123456"
(υπάλληλος-ειδικότητα Μαρία)
→ δικηγόρος
373

(υπάλληλος-βαθμός Μαρία)
→ 4
(ασφαλισμένος_ΤΕΒΕ-επάγγελμα Πάνος)
→ μηχανικός
(ασφαλισμένος_ΤΕΒΕ-αριθμός_μητρώου Πάνος) → 123123123

► Το μέγεθος του κώδικα σε ένα τέτοιο πρόγραμμα δεν προκαλεί δυσχέρειες γραφής διότι είτε
με τεχνική "cut and paste" (στον editor) είτε με εφαρμογή τής COPY-ΧΧΧ (στο toplevel ή μέσα
στο πρόγραμμα), επαναλαμβάνονται οι ίδιες εγγραφές, όπου συνήθως αρκούν κάποιες μικρές
επεμβάσεις προσαρμογής.

8.1.6 Οι κλάσεις-δομές τύπου ως τύποι
Αντιμετώπιση μιας κλάσης τύπου structure ως τύπο
Όπως προαναφέραμε, καθεμία δημιουργούμενη δομή είναι ένας τύπος, στον οποίο ανήκουν όλα
τα στιγμιότυπα που δημιουργούμε από τη δομή αυτή. Ο τύπος μιας δομής αντιπροσωπεύει και
όλα τα στιγμιότυπα υποκλάσεων της δομής, διότι οι υποκλάσεις της δομής είναι υποτύποι της.
Όπως οι περισσότεροι τύποι, έτσι και οι δομές αυτές με την κατάληξη "-p" παράγουν αντίστοιχα
ένα κατηγόρημα ελέγχου τύπου:
(φοιτητής-p Νίκος)
→ Τ
(φοιτητής-p Λεωνίδας)
→ ΝΙL
(δημόσιος_υπάλληλος-p Πάνος) → Τ
(υπάλληλος-p Πάνος)
→ Τ
(πρόσωπο-p Πάνος)
→ Τ
(δημόσιος_υπάλληλος-p Μαρία) → ΝΙL
Όπως είναι αναμενόμενο (διότι αυτό συμβαίνει παντού στο δένδρο τυποποίησης της Lisp) αν ένα
αντικείμενο είναι κάποιου τύπου Α, είναι και του τύπου κάθε υπερκλάσης του τύπου Α.
Η χρήση δομής (και υπο-δομής) ως τύπου γίνεται με τις γνωστές συναρτήσεις που αναφέρονται
σε τύπο:
(type-of Νίκος)
→ φοιτητής
(typep Νίκος 'φοιτητής) → Τ
δεν θέσαμε "quote" στο σύμβολο Νίκος διότι το αντικείμενο δεν είναι το σύμβολο αλλά η τιμή
του: Νίκος είναι το όνομα του αντικειμένου.
Επίσης, αν θέλουμε, μπορούμε να δώσουμε δικό μας όνομα στον τύπο που ορίζει η κλάση τύπου
structure, μέσω του κλειδιού :predicate
(defstruct
(str5
(:predicate mypred))
(s1 3) (s2 4))

374

Περιγραφή κλάσης ή αντικειμένου

Αν χρειαστούμε τη συνολική περιγραφή κάποιου αντικειμένου ή δομής ή γενικά οντότητας της
Lisp, μπορούμε να την πάρουμε απ' ευθείας με τη συνάρτηση describe η οποία ως γνωστό
επιστρέφει όλα τα χαρακτηριστικά του ορίσματός της:
(describe 'φοιτητής)

θα τυπώσει την πλήρη περιγραφή της κλάσης τύπου structure "φοιτητής" (υποδοχές, τύπους και
αρχικές τιμές τους, υπερκλάση αν υπάρχει, κλπ.)
(describe Νίκος)

; χωρίς quote διότι χρειαζόμαστε την τιμή του συμβόλου Νίκος
θα τυπώσει την πλήρη περιγραφή του αντικειμένου που είναι τιμή του συμβόλου Νίκος (κλάση,
υποδοχές και αντίστοιχες τιμές, κλπ.).
Η συμπεριφορά της DESCRIBE εξαρτάται σε μεγάλο βαθμό από τον χρησιμοποιούμενο
compiler.

Αντιγραφή αντικειμένου
Υπάρχουν βασικοί λόγοι που κάνουν χρήσιμη την αντιγραφή αντικειμένου, όπως:
α'.
Για εφαρμογή συνάρτησης που μεταβάλλει χαρακτηριστικά αντικειμένου, πάνω σε
αντικείμενο του οποίου θέλουμε να κρατήσουμε την αρχική κατάσταση, είναι σκόπιμο να μη
χρησιμοποιούμε το ίδιο το αντικείμενο αλλά ένα αντίγραφό του (συχνή περίπτωση σε
δοκιμαστικές εφαρμογές).
β'.
Αν χρειαστούμε ένα νέο αντικείμενο με πολλά κοινά χαρακτηριστικά με ένα ήδη υπάρχον,
είναι απλούστερο να χρησιμοποιήσουμε ένα αντίγραφο του υπάρχοντος και να αλλάξουμε αυτά
που διαφέρουν.
Αντιγραφή κλάσης γίνεται με συνάρτηση της οποίας το όνομα συντίθεται:
COPY-<όνομα της κλάσης>

copy-<κλάση> : Συνάρτηση που δέχεται όρισμα ένα στιγμιότυπο, το οποίο οφείλει να είναι της
κλάσης αυτής. Επιστρέφει νέο στιγμιότυπο, της ίδιας κλάσης, πανομοιότυπο του αρχικού:
(setq Θανάσης (copy-φοιτητής Νίκος))

Μπορούμε να διαμορφώσουμε τις τιμές των υποδοχών του αντίγραφου, με την SETF :
(setf (φοιτητής-ημερ_γέννησης Θανάσης) '(3 3 1970) ) ; κλπ.

Υποδοχές με τιμή λ-έκφραση ή λίστα λ-εκφράσεων
Είδαμε πως μια υποδοχή παίρνει τιμή οποιουδήποτε τύπου. Η τιμή μπορεί να είναι μια "quoted"
λ-έκφραση, ή μια "quoted" λίστα από λ-εκφράσεις, παρόμοια με τον τρόπο που δώσαμε τέτοιες
τιμές σε μεταβλητή. Με τον τρόπο αυτό, μπορούμε να συνδέσουμε αντικείμενο με
λειτουργικότητα (η οποία καθορίζεται από τη λ-έκφραση) ή με επιλεγόμενη συμπεριφορά (η
διαφοροποίηση της συμπεριφοράς μπορεί να βασίζεται στην επιλογή θέσης στη λίστα των λ375

εκφράσεων).
Δεδομένου οτι περισσότερες της μιας υποδοχές μπορούν να παίξουν τέτοιο ρόλο, διαθέτουμε ένα
εξαιρετικά πλούσιο κόσμο καθορισμού λειτουργικότητας, προσδιορισμένης είτε απ' ευθείας με το
στιγμιότυπο, είτε στη συγκεκριμένη κλάση όπου ανήκει, είτε σε προγονική κλάση. Με τον τρόπο
αυτό μπορούμε να ενσωματώσουμε λειτουργικότητα στις κλάσεις ή τα στιγμιότυπα.

Η ενεργοποίηση τέτοιας λειτουργίας θα γίνει με την FUNCALL ή την APPLY και όρισμα που
προσδιορίζει τη θέση της κατάλληλης συναρτησιακής έκφρασης.

Υποδοχές με τιμή συνάρτηση
Ακριβώς αντίστοιχα με το προηγούμενο, ισχύουν τα ίδια αν, αντί για "quoted" λ-έκφραση,
θέσουμε τιμή σε υποδοχή κάποια συνάρτηση (με πρόθεμα #" ). Μια τέτοια σύνδεση συναρτήσεων
με δομή, επιτρέπει εφαρμογή συνάρτησης συσχετισμένη με την υποδοχή και με το αντικείμενο
εφαρμογής.
Όμως, όπως είναι φανερό στο παράδειγμα που ακολουθεί, αν και υπολογιστικά είναι
αποτελεσματικό μέσον η ενσωμάτωση λειτουργικότητας, αρχίζει μια εκφραστική πολυπλοκότητα
που χάνει εύκολα ο χρήστης το "τί συνδέεται με τί και πώς". Στο ζήτημα αυτό δίνεται πληρέστερη
απάντηση στην επόμενη ενότητα.
;; η αναλογία παραγωγής ανά εργαζόμενο είναι το πηλίκο
; της παραγωγήςπρος το πλήθος των εργαζομένων:
(defun αναλογία_παραγωγής ( )
(/ παραγωγή_εταιρίας πλήθος_εργαζομένων) )

;; η απόδοση εργαζομένου προσδιορίζεται από την σύνδεση της μεθόδου
; "αναλογία_παραγωγής" με την υποδοχή "απόδοση" για τη
; συγκεκριμένη κλάση και το στιγμιότυπο του συγκεκριμένου ατόμου:

(setf (ιδιωτικός_υπάλληλος-απόδοση Μαρία)
#'αναλογία_παραγωγής) )

;; ο υπολογισμός θα γίνει με κλήση της μεθόδου για το συγκεκριμένο
; πρόσωπο, που είναι καταχωρημένη ως τιμή της υποδοχής:

(funcall (ιδιωτικός_υπάλληλος-απόδοση Μαρία) )

Ακόμα πιο πρακτικό είναι να εκμεταλλευτούμε και την τοπικότητα που μπορούμε να
δημιουργήσουμε, με συναρτήσεις όπως η LET, προσδιορίζοντας περιεχόμενο υποδοχών με
συγκεκριμένο εύρος χρήσης των συμβόλων που περιέχει, ως τόπο συσχετισμένο με την κλάση ή
το στιγμιότυπο.
Στο παραπάνω παράδειγμα δεν χρησιμοποιήσαμε μέσα στο σώμα της συνάρτησης,
χαρακτηριστικά του ίδιου του αντικειμένου, όπως θα μπορούσε να γίνει, πχ. "υπολογισμός ετών
υπηρεσίας".

Οι κλάσεις - δομές ως "πλαίσια"
Τα πλαίσια (frames) αποτελούν μια πολύ εξυπηρετική μεθόδευση οργάνωσης από χώρους με
αντικείμενα που έχουν ομοιόμορφα χαρακτηριστικά: είναι χρήσιμο να διαθέτουμε ένα τρόπο όπου
καθορίζοντας τον τύπο των απαιτουμένων χαρακτηριστικών, να έχουμε ένα "καλούπι" πάνω στο
376

οποίο να μπορούμε να κτίζουμε αντικείμενα. Σε παλαιότερες εκδόσεις της Lisp που δεν διέθεταν
το CLOS είχαν αναπτυχθεί τεχνικές οργάνωσης πλαισίων, που περιττεύουν σήμερα, με τη χρήση
κλάσεων. Χρησιμοποιώντας δομές τύπου structure ως πλαίσια, έχουμε το πλεονέκτημα της
αποσύνδεσης των τιμών των υποδοχών των στιγμιοτύπων από την κλάση - καλούπι, και συνεπώς
την αυτονόμηση των πλαισίων.

Το σύνολο των κλάσεων - δομών τύπου structure
Η ιεραρχημένη δόμηση κάθε κλάσης τύπου structure προσδιορίζει το συνολικό χώρο των δομών
και υπο-δομών που έχουμε κατασκευάσει ως ένα σύνολο δένδρων (αυτό δεν ισχύει για τις
κλάσεις που δημιουργούνται μέσω της DEFCLASS) .
Η ρίζα κάθε τέτοιου δένδρου είναι ακριβώς ένα σκαλοπάτι κάτω από τον κόμβο structure, και
έχει διπλή δυνατότητα ανάπτυξης:
– προσδιορισμού υποκλάσεων∙ αυτές εννοούνται οργανωμένες βάσει μιας σχέσης ταξινόμησης,
όπως η σχέση συνολοθεωρητικού εγκλεισμού και αναδρομικής ως προς την κληρονόμηση
χαρακτηριστικών (κόμβοι του δένδρου)
– προσδιορισμού ειδικών αντικειμένων για κάθε κόμβο - κλάση, που εννοούνται οργανωμένα
βάσει μιας σχέσης προσδιορισμού "ανήκει", συνδεδεμένης με την προηγούμενη για την
εφαρμογή κληρονομικότητας.

8.2 Σύνδεση υπολογισμού με κλάσεις τύπου structure
Στην ενότητα αυτή θα δούμε τρόπους σύνδεσης υπολογισμών με συγκεκριμένη κλάση,
συγκεκριμένο στιγμιότυπο, καθώς και με ιεραρχία κλάσεων. Θα δούμε τρόπους ομοιόμορφης
δημιουργίας υπολογισμού προσαρμοζόμενου σε κάθε κλάση.

8.2.1 Προσδιορισμός και κληρονόμηση μεθόδων
Το πρόβλημα:
Μέχρι τώρα είδαμε τον τρόπο δημιουργίας νέας δομής, δημιουργίας υπο-δομών της, και
δημιουργίας στιγμιοτύπων τους.
Χρησιμοποιώντας ως τύπους τις δομές, μπορούμε να προσδιορίσουμε περιπτωσιολογικά
εξειδικευμένη συμπεριφορά μιας γενικής συνάρτησης, έτσι ώστε όταν εφαρμοστεί σε
αντικείμενα, να συμπεριφερθεί ανάλογα με τον τύπο τους, δηλ. με τη θέση τους στην ιεραρχία.
Ας θεωρήσουμε τη δομή "πρόσωπο" που ορίσαμε προηγουμένως, και ας υποθέσουμε οτι τα
εισοδήματα προσώπων υπολογίζονται βάσει κάποιων σχέσεων όπως οι παρακάτω:
(setq βασικός_μισθός 100000)
377

(defun εισόδημα (κάποιος)
(cond
( (ιδιωτικός_υπάλληλος-p κάποιος)
(+ αρχικός_μισθός (* (έτη_εργασίας κάποιος) επίδομα ))
( (δημόσιος_υπάλληλος-p κάποιος)
(+ βασικός_μισθός ΑΤΑ))
( (εισοδηματίας-p κάποιος) ……)))
(setq επίδομα 20000)
(defun έτη_εργασίας (άτομο) (– τρέχον_έτος έτος_έναρξης))

; κλπ.
Το πρόβλημα με αυτή την προσέγγιση, είναι οτι οι περιπτώσεις πρέπει να είναι
προκαθορισμένες για να οριστεί η συνάρτηση, και κάθε νέα προσθήκη περίπτωσης στη δομή
πρέπει να συνοδεύεται από ανάλογο editing της συνάρτησης. Αλλά και αυτό να γίνει, η
συνάρτηση διογκώνεται και γίνεται ολοένα και πιο πολύπλοκη, δύσχρηστη στην ανάγνωση και
τη λειτουργία της, και σταδιακά, έστω και λίγο κάθε φορά, χάνει την αποτελεσματικότητά της.
Επίσης πρόβλημα είναι οτι τα επίπεδα της δομής πρέπει να αντιστοιχηθούν σε πολλαπλά
κιβωτισμένα conditionals ελέγχου του τύπου εισόδου, που απαιτούν εξαιρετικά καλή οργάνωση
και πρόβλεψη, για να μη προκληθούν σφάλματα εξ αιτίας "στοιχείου εισόδου που τυπικά
αναμενόταν σε άλλο επίπεδο από αυτό που δόθηκε, αλλά ουσιαστικά θα μπορούσε να δοθεί και σε
αυτό", και αυτό οδηγεί σε ακόμα πιο πολύπλοκη γραφή της συνάρτησης.
Άλλος τρόπος είναι, ο ορισμός ξεχωριστών συναρτήσεων για κάθε τύπο εισόδου (ή για κάθε
δυνατό συνδυασμό τύπων εισόδου, αν δέχεται περισσότερα του ενός ορίσματα). Αυτό
υπολογιστικά μπορεί να υλοποιηθεί, όπως και το προηγούμενο, και έχει το πλεονέκτημα οτι δεν
οδηγεί σε διόγκωση μιας βασικής συνάρτησης διότι με τον τρόπο αυτό δεν υπάρχει μία τέτοια,
και αφ' ετέρου είναι επεκτάσιμο (με νέους κόμβους - συναρτήσεις).

Όμως, οδηγεί γρήγορα σε πολύ μεγάλο πλήθος συμβόλων, αντιστοίχων των κόμβων του
δένδρου, που δύσκολα μπορεί να τα θυμάται ο χρήστης, και αναγκάζει σε ορισμό συναρτήσεων
ευκολίας, όπως menu ή μια συνάρτηση που θα οριστεί εκ των υστέρων και θα περιέχει όλες τις
περιπτώσεις, υποπεριπτώσεις κλπ. Αυτό οδηγεί στην προηγούμενη δυσχέρεια: και πάλι η
επέκταση δεν εξυπηρετείται με κάποιο ευέλικτο τρόπο, και η πολυπλοκότητα των εκφράσεων
αυξάνεται απαγορευτικά.

Η λύση:
Αντί για τις περιπλοκές αυτές, οι δομές διαθέτουν ένα μηχανισμό επισύναψης υπολογισμού,
εξαιρετικά απλό και γρήγορο στη σύνθεση, επεκτάσιμο χωρίς να αυξάνεται η πολυπλοκότητα,
και χωρίς να "αγγίζει" ο χρήστης τις ήδη γραμμένες συναρτησιακές εκφράσεις. Αυτός είναι η
κατασκευαστική συνάρτηση DEFMETHOD :
defmethod : Συνάρτηση macro η οποία ορίζει συνάρτηση εξαρτημένα από τον τύπο των
ορισμάτων της. Στην απλούστερη σύνταξη και χρήση, η μορφή σύνταξης της DEFMETHOD
είναι:
378

(defmethod <όνομα μεθόδου>
( (<λ-μεταβλητή> <όνομα κλάσης>) )
<σώμα που αναφέρεται στη λ-μεταβλητή> )
όπου <όνομα κλάσης> είναι σταθερά που καθορίζεται από το όνομα της δομής ή υπο-δομής, στις
υποδοχές της οποίας αναφέρεται η μέθοδος, και η τιμή εισόδου για την λ-μεταβλητή οφείλει να
είναι στιγμιότυπο αυτής της δομής.
► Η διπλή παρένθεση της δεύτερης γραμμής οφείλεται το οτι, όπως θα δούμε παρακάτω, η
εξωτερική παρένθεση μπορεί να περιέχει και άλλες δυάδες.
Μπορούμε να δώσουμε πολλές συναρτήσεις - μεθόδους ορισμένες με το ίδιο όνομα, αρκεί η
σταθερά παράμετρος <όνομα κλάσης> να είναι διαφορετική. Το αναφερόμενο <όνομα κλάσης>
καλείται εξειδικευτής παραμέτρου (parameter specializer).

Ο έλεγχος ταιριάγματος της εισόδου με τον τύπο δεν γίνεται σε επίπεδο σώματος, δηλ. κατά τον
υπολογισμό, όπως στις συνήθεις συναρτήσεις, αλλά σε επίπεδο εισόδου, δηλ. πριν τον
υπολογισμό.
Μια συνάρτηση - μέθοδος μπορεί να οριστεί πολλαπλά, με νέο ορισμό για κάθε διαφορετική
περίπτωση τύπου εισόδου, με σώμα τον κατάλληλο για την περίπτωση υπολογισμό. Έτσι,
μπορούμε να ορίσουμε όσες συναρτήσεις θέλουμε με το ίδιο όνομα συμβόλου, και να ορίζουμε
εκ των υστέρων νέες όταν προκύψει ανάγκη. Το ταίριαγμα τιμής εισόδου και αναφερομένου
τύπου οδηγεί σε επιλογή της κατάλληλης για την περίπτωση συνάρτησης.
Το σώμα μπορεί να αναφέρεται σε υποδοχές του αναφερομένου κόμβου (και βέβαια, τότε έχει
ουσιαστικό νόημα ο προσδιορισμός μεθόδου), όπου οι υποδοχές αναφέρονται κατά τα γνωστά:
σύμβολο που γεννάται από τη σύνθεση με ενωτικό, του ονόματος της δομής και της υποδοχής,
που επιστρέφει ως τιμή του την τιμή της υποδοχής.
Προφανώς, ο υπολογισμός μπορεί να αναφέρεται τόσο σε πληροφορίες που περιέχει το
στιγμιότυπο (τιμές υποδοχών) όσο και σε σύμβολα του προγράμματος (μεταβλητές ή
συναρτήσεις, καθολικές ή τοπικές).
Παράδειγμα
1. Μέθοδος με αναφορά υποδοχών:
(defmethod εισόδημα ((άτομο υπάλληλος))
(* υπάλληλος-βαθμός 100000))
; άτομο είναι λ-μεταβλητή, υπάλληλος είναι ο εξειδικευτής τύπου
; βαθμός είναι το όνομα υποδοχής της κλάσης υπάλληλος

2. Μέθοδος χωρίς αναφορά υποδοχών:
(defmethod εισόδημα ((άτομο εισοδηματίας)) (print "άγνωστο"))

Εφαρμογές:

(εισόδημα Μαρία)
→ 400000
(εισόδημα Θανάσης) → Error

(δεν έχει οριστεί μέθοδος για την κλάση αυτή)
379

(εισόδημα Πάνος)

→ "άγνωστο"

Κληρονόμηση μεθόδων
Στο παραπάνω παράδειγμα, η συνάρτηση "εισόδημα" δεν θα είχε προκαλέσει σφάλμα αν είχε
οριστεί, εντελώς τυπικά χωρίς να κάνει τίποτα, στο ανώτερο επίπεδο μια μέθοδος:
(defmethod εισόδημα ((x t)) )

που επιστρέφει nil στην εκτέλεση κλήσης σε περίπτωση αντικειμένου για τον τύπου του οποίου
δεν έχει οριστεί μέθοδος.

Αν δεν δώσουμε εξειδικευτή τύπου, εννοείται ο γενικός υπερτύπος Τ (στην περίπτωση αυτή η
αναγραφή της λ-μεταβλητής γίνεται κατά τα συνήθη, με απλή παρένθεση, όπως και στην
DEFUN). Σε τέτοια περίπτωση, κάθε είσοδος - στιγμιότυπο οποιασδήποτε κλάσης, ταιριάζει.
Έτσι, η παραπάνω έκφραση ισοδυναμεί με:
(defmethod εισόδημα (x) )

Με ένα τέτοιο ορισμό μεθόδου, καλύπτεται κάθε περίπτωση υπο-τύπων όπου δεν έχει οριστεί η
συνάρτηση "εισόδημα", επιστέφοντας NIL όπου δεν έχει οριστεί ειδική μέθοδος και δεν υπάρχει
άλλη από ψηλότερο κόμβο για να κληρονομηθεί. Αν πάλι θέλουμε να επιστρέφει κάτι άλλο, είναι
απλό:
(defmethod εισόδημα (x)
(print "δεν υπάρχει κανόνας για την περίπτωση αυτή"))

Έχουμε τον εξής γενικό κανόνα κληρονόμησης μεθόδου:
"αν ταιριάζει κάποιος τύπος εισόδου σε μέθοδο, τότε και κάθε υποτύπος του για τον οποίο δεν έχει
οριστεί μέθοδος με το ίδιο όνομα, ταιριάζει"
Αυτό σημαίνει οτι αν η μέθοδος είναι ορισμένη σε κάποιο επίπεδο, τότε ισχύει και σε κάθε
ειδικότερο επίπεδο εκτός αν έχει ειδικότερα οριστεί. Γι' αυτό λέμε οτι οι μέθοδοι
κληρονομούνται. Συμπεραίνουμε πως, αν δεν έχει οριστεί μέθοδος με το όνομα αυτό και για τον
τύπο του αναφερόμενου στιγμιότυπου, καλείται η πλησιέστερη προς τα πάνω υπάρχουσα
μέθοδος.

Μέθοδοι με περισσότερες της μιας παραμέτρους, ίδιου ή διαφόρων τύπων
Μπορούμε να δώσουμε περισσότερες της μιας παραμέτρους - αντικείμενα σε μια μέθοδο, με
παρόμοιο τρόπο:
(defmethod <μέθοδος>
( (<όρισμα1> <τύπος1>) (<όρισμα2> <τύπος2>) …)
<σώμα> )

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

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

8.2.2 Αρχέτυπες συναρτήσεις και μέθοδοι
Είναι, όπως είδαμε, δυνατός ο προσδιορισμός μεθόδων με το ίδιο όνομα, οι οποίες αφορούν έναν
ή περισσότερους κόμβους της ίδιας κλάσης τύπου structure ή διαφορετικών κλάσεων τύπου
structure, και διαφοροποιούνται μεταξύ τους είτε κατ' όνομα, όπως και οι κοινές συναρτήσεις,
είτε απλώς με τη διαφοροποίηση των τύπων εισόδου.
Προσδιορίζοντας περισσότερες της μιας τέτοιες μεθόδους με το ίδιο όνομα, δημιουργείται
αυτόματα μια αφηρημένη υπολογιστική οντότητα τύπου function, υποτύπου generic-function, με
όνομα το κοινό όνομα των μεθόδων και με σώμα ειδικά διαμορφωμένο ώστε να περιλαμβάνει ως
ειδικούς κλάδους επιλογής τις αντίστοιχες μεθόδους. Αυτή η συνάρτηση, όταν κληθεί το όνομά
της, "προσφέρει" την κατάλληλη συνάρτηση κατά περίπτωση τύπων εισόδου. Δηλαδή είναι μια
συνάρτηση που αντιπροσωπεύει ένα σύνολο συναρτήσεων και εντοπίζει την κατάλληλη που θα
εφαρμοστεί κατά περίπτωση. Μια τέτοια συνάρτηση αποκαλείται αρχέτυπη (generic).
Επομένως, στην περίπτωση μεθόδων μιας λ-μεταβλητής με το ίδιο όνομα και εξειδικευτές που
ακολουθούν την ιεραρχία μέσα σε μια δομή, η αντίστοιχη αρχέτυπη συνάρτηση προσδιορίζει μια
ιεραρχία μεθόδων, απεικονιζόμενη "1:1 - εντός" στην ιεραρχία της δομής.
Η ιδιομορφία που έχουν οι αρχέτυπες συναρτήσεις είναι οτι έχουν τη δυνατότητα να
συμπεριλαμβάνουν δυναμικά τις νέες περιπτώσεις μεθόδων που ορίζουμε, και να δέχονται
επαναπροσδιορισμό μεθόδων που έχουν ήδη δοθεί, απλώς με τον κατάλληλο ορισμό μεθόδου,
χωρίς να χρειάζεται να αναφερθούμε στην ίδια την αρχέτυπη συνάρτηση (θα δούμε παρακάτω
πως αυτό δεν αποκλείεται).
Ειδικότερα, σε ότι αφορά τα ορίσματα στα οποία εφαρμόζεται μια μέθοδος:

– Όταν κληθεί μια μέθοδος με ένα όρισμα, εάν διατίθεται μέθοδος με το όνομα αυτό, ενός
ορίσματος και προσδιορισμένη στο επίπεδο του τύπου του ορίσματος, καλείται αυτή ακριβώς.
Εάν δεν υπάρχει, αναζητείται αυτόματα προς τα πάνω, η πλησιέστερη μέθοδος που ταιριάζει.
Έτσι, αν υπάρχουν πολλές που αναφέρονται σε διάφορους υπερτύπους του δεδομένου τύπου,
καλείται η συγγενέστερη προς τα πάνω.
Όταν κληθεί μέθοδος που δέχεται περισσότερα του ενός ορίσματα, η αντίστοιχη αρχέτυπη
συνάρτηση (δηλ. με το ίδιο όνομα και τον ίδιο αριθμό ορισμάτων) εντοπίζει την κατάλληλη
μέθοδο που να ταιριάζει για τη ν-άδα των ορισμάτων, με τον εξής τρόπο: Κατ' αρχήν εντοπίζει τη
μέθοδο που ταιριάζει στο πρώτο όρισμα, με την παραπάνω συλλογιστική. Αν η μέθοδος που
εντοπίστηκε για το πρώτο όρισμα ταιριάζει και για το δεύτερο, καλώς∙ αν όχι, αναζητείται σε
ανώτερο επίπεδο, πάλι για το πρώτο όρισμα, μέθοδος που να καλύπτει και το δεύτερο, κοκ, μέχρι
να βρεθεί μέθοδος που να καλύπτει και το δεύτερο. Επαναλαμβάνεται το ίδιο για το τρίτο όρισμα,
κοκ. Δηλαδή, αν για το ελεγχόμενο όρισμα ταιριάζει αυτή που εντοπίστηκε για τα προηγούμενα
381

ορίσματα, έχει καλώς∙ αν όχι, αναζητείται μέθοδος σε προγονική κλάση του ελεγχομένου
ορίσματος. Αυτό επαναλαμβάνεται για όλα τα ορίσματα.
Έχουμε λοιπόν τον κανόνα:
"η μέθοδος που ταιριάζει ανάλογα με τον τύπο των ορισμάτων εισόδου, είναι το κατώτερο φράγμα
των ανωτέρων ορίων, με προτεραιότητα προς τα αριστερά (με άλλα λόγια, η ειδικότερη δυνατή,
εξαντλώντας τις δυνατότητες από τα αριστερά)"
Το κύριο "γνωστικό" χαρακτηριστικό των αρχέτυπων συναρτήσεων είναι οτι μπορούν να
παρακολουθούν δυναμικά τις προσθήκες νέων μεθόδων ή μεταβολές υφισταμένων, χωρίς να
χρειάζεται να γνωρίζει ο χρήστης την ύπαρξή τους.
Μια αρχέτυπη συνάρτηση είναι εν γένει επεξεργάσιμη σε χαμηλά επίπεδα προγραμματισμού και
συνήθως δεν προσφέρεται για χρήση από τον τελικό χρήστη∙ ο ρόλος της περιορίζεται στο να
παράσχει την κατάλληλη συνάρτηση – μέθοδο κατά περίπτωση εισόδου.

8.2.3 Διασύνδεση στιγμιότυπων

Τα στιγμιότυπα που δημιουργούμε, δεν είναι κατ΄ ανάγκη τελεσίδικα σταθεροποιημένα: μπορούν
να εξελίσσονται, είτε λόγω επέμβασης στην κλάση τους (μόνο γι' αυτά που δημιουργούνται με
την DEFCLASS), είτε λόγω επέμβασης στα ίδια (δυνατό για όλα, με την SETF). Η εξέλιξή τους
συχνά χρειάζεται αλληλοενημέρωση μεταξύ στιγμιότυπων, άρα την εγκατάσταση κάποιου είδους
συσχέτισης.

Έμμεση συσχέτιση
Μέσω μεθόδου που αναφέρεται σε δύο ή περισσότερα στιγμιότυπα, με αντίστοιχο για το καθένα
εξειδικευτή τύπου, μπορούμε με διαδικαστικό τρόπο να συσχετίσουμε την περιεχόμενη
πληροφορία: πχ, να πάρουμε τις τιμές κάποιων υποδοχών του ενός και να διαμορφώσουμε βάσει
αυτών την τιμή μιας υποδοχής του άλλου. Τέτοια σύνδεση είναι έμμεση, διότι εξαρτάται από την
εκτέλεση συνάρτησης.

Άλλος τρόπος έμμεσης σύνδεσης είναι, χρησιμοποιώντας ένα "τρίτο μέσον" και μεθόδους
εγγραφής πληροφορίας σ' αυτό, που να προέρχεται από κάποιο(-α) αντικείμενο(-α), και
ανάγνωσης πληροφορίας για ενημέρωση αντικειμένων. Στην περίπτωση αυτή, χρήσιμο είναι να
ελέγχουμε τη μεταφορά της πληροφορίας μέσω συνάρτησης ή μεθόδου, προσδιορίζοντας
προτεραιότητες, υποχρεώσεις, δυνατότητες. Για παράδειγμα, "τα χρήματα που κερδίζει και
ξοδεύει μια οικογένεια" μπορούν να τηρούνται σε μια λίστα που να περιλαμβάνει κάθε σχετική
πληροφορία και να ενημερώνεται τόσο για υποχρεωτικές δράσεις (πχ. "να πληρωθεί το
ηλεκτρικό", "να φέρουν αυτό το μήνα τη συμμετοχή στα έξοδα όλα τα μέλη").
Παράδειγμα
Συσχέτιση αντικειμένων από διαφορετικά δένδρα (κλάσεις τύπου structure), όπως το δένδρο
"επιστήμονας" με το δένδρο "βιβλιοθήκη", και συνάρτηση - μέθοδο που υπολογίζει "ποιός πρέπει
382

να διαβάσει ποιό βιβλίο" βάσει κριτηρίων:
(defstruct βιβλιοθήκη
(θέση_τοποθέτησης_βιβλίου 'ράφι1)
(κατηγορία_δανεισμού 'ελεύθερο)
(έτος_αγοράς 1998))
(defstruct (τομέας_βιβλιοθήκης (:include βιβλιοθήκη))
(κατηγορία_περιεχομένου 'Ιατρική)
(λέξεις_κλειδιά ( ) ) )
(defstruct (βιβλίο (:include τομέας_βιβλιοθήκης))
(συγγραφέας " ")
(τίτλος ( ) )
(topics ( ) ) )
(defstruct επιστήμονας
επιστήμη ειδικότητα)
(defstruct άτομο-επιστήμονας
όνομα βαθμός ίδρυμα)
(setq τα-βιβλία
(list (make-βιβλίο
:συγγραφέας "Anton Smith"
:τίτλος (Εισαγωγή στην Οφθαλμολογία)
:λέξεις_κλειδιά
'(χιτών κερατοειδής αμφιβληστροειδής φακός πυθμένας)
:topics '(φυσιολογία παθολογία χειρουργική οπτική))
; τα λοιπά χαρακτηριστικά κληρονομούνται
(make-βιβλίο …)
(make-βιβλίο …)
…) )
(setq οι_επιστήμονες
(list
(make-άτομο_επιστήμονας
:όνομα
"Νάκος Ιωάννης"
:επιστήμη 'Ιατρική
:ειδικότητα 'οφθαλμολογία
:βαθμός
'επιμελητής_α
:ίδρυμα
'ΝΙΜΙΤΣ )
(make-άτομο_επιστήμονας . . .)
(make-άτομο_επιστήμονας . . .) … ) )
(defun διάβασμα (βιβλ κάποιος)
(princ "να διαβάσει ") (princ κάποιος)
(princ "το ") (princ βιβλ) (terpri) )
(defmethod μελέτη_βιβλίου
( (ένας άτομο-επιστήμονας) (κάτι βιβλίο) )
(dolist (ένας οι_επιστήμονες ) ; σάρωση επιστημόνων
(dolist (κάτι τα_βιβλία)
; σάρωση βιβλίων
(διάβασμα κάτι ένας )) ) )
; διασύνδεση επιστήμονα με βιβλίο
;; ένας είναι λ-μεταβλητή του τύπου άτομο-επιστήμονας
;; κάτι είναι λ-μεταβλητή του τύπου βιβλίο
;; οι DOLIST σαρώνουν τις λίστες οι_επιστήμονες και τα_βιβλία ,

; και δημιουργούν όλες τις δυνατές τιμές για τις
383

; μεταβλητές ένας και κάτι .

Άμεση συσχέτιση
Ένας διαφορετικός τρόπος επικοινωνίας στιγμιότυπων, καθαρά συναρτησιακής υφής, είναι η
εγκατάσταση μόνιμης σύνδεσης μεταξύ τους, διότι δεν προϋποθέτει την εκτέλεση συνάρτησης
(μεθόδου) για να "δει" το κάθε μέλος πληροφορία από το άλλο, κάθε φορά που υπάρχει
μεταβολή: είναι σαν ένα παράθυρο ανοικτό προς την άλλη μεριά. Μπορούμε να διακρίνουμε δύο
ειδών άμεσες συσχετίσεις:
α'. Σύνδεση που να "δείχνει" σε στοιχεία εσωτερικά των αντικειμένων:
Τοποθετώντας ως τιμή υποδοχής σε ένα αντικείμενο μια σύνδεση προς την υποδοχή άλλου,
έχουμε κατά κάποιο τρόπο πετύχει μια αυτόματη ενημέρωση. Ακόμα μπορούμε να ενημερώνουμε
αυτόματα τρίτο αντικείμενο, δίνοντας ως τιμή υποδοχής μια λίστα, με όρους που να "δείχνουν" σε
υποδοχές μιας ομάδας αντικειμένων.
Τέτοια σύνδεση μπορούμε να πετύχουμε εύκολα, πχ. ονομάζοντας τη φυσική θέση που θέλουμε
να "βλέπει" ο αποδέκτης και να εξαρτήσουμε συναρτησιακά την τιμή της κατάλληλης υποδοχής
του αποδέκτη από αυτό το όνομα, ή και απ' ευθείας αναφέροντας τη φυσική θέση - εκπομπό μέσα
στην τιμή της υποδοχής του αποδέκτη.
Παράδειγμα
Έστω οτι θέλουμε να ρωτάμε κάποιον "αν είναι παντρεμένος κάποιος που ξέρει", και αυτός να
γνωρίζει αυτόματα αν "αυτοί που ξέρει έχουν παντρευτεί".

Έστω οτι η κλάση πρόσωπο έχει, μεταξύ άλλων, υποδοχή οικ_κατάσταση με δυνατές τιμές "εγγ",
"αγ", "διαζ", και υποδοχή τί_ξέρει , που για τα στιγμιότυπα παίρνει τιμή λίστα, με όρους που
δείχνουν στην υποδοχή οικ_κατάσταση άλλων αντικειμένων. Έστω οτι Γιάννης , Καίτη και
Μαρία είναι σύμβολα που ονομάζουν στιγμιότυπα προσώπου, και έστω οτι "η Μαρία είναι
παντρεμένη":
(setf (πρόσωπο-τί_ξέρει Καίτη)
'( (πρόσωπο-οικ_κατάσταση Γιάννης)
(πρόσωπο-οικ_κατάσταση Μαρία) …) )

Το αντικείμενο Καίτη έχει "ενήμερη γνώση" τού αν κάποιος που γνωρίζει είναι έγγαμος, άγαμος ή
διαζευγμένος, λόγω της καθυστέρησης του αναφερόμενου υπολογισμού. Έτσι, μπορούμε να
"ρωτήσουμε" κάποιον για πληροφορία που αφορά άλλον∙ σχηματίζουμε συνάρτηση "ρώτα" με
ορίσματα "ποιόν ρωτάμε" και "για ποιόν ρωτάμε" :
(defun ρώτα (ποιόν για_ποιόν)
(ask-aux (ποιόν για_ποιόν lis) ))

Η βοηθητική συνάρτηση ASK-AUX δημιουργεί τη λίστα των γνώσεων του ανθρώπου που ρωτάμε,
και τη σαρώνει, καλώντας αναδρομικά την "ρώτα":
(defun ask-aux (ποιόν για_ποιόν lis)
(setq lis (πρόσωπο-τί_ξέρει ποιόν))
384

(cond
( (and (not (null lis)) (equal (second (car lis) για_ποιόν))
(eval (second (car lis) )))
( (not (null lis))
(ρώτα ποιόν για_ποιόν (cdr lis)) )
( t
(print "δεν υπάρχει πληροφορία") ) ) )

Ερώτηση:
(ρώτα Καίτη Μαρία) → "έγγ"

Μεταβολή κατάστασης αντικειμένου:
(setf (πρόσωπο-οικ_κατάσταση Μαρία) "διαζ")

Ερώτηση:
(ρώτα Καίτη Μαρία) → "διαζ"

δηλαδή, μέσω της σύνδεσης, το οτι "η Μαρία πήρε διαζύγιο" οδήγησε στο οτι "η Καίτη το
ξέρει", αλλά "η Μαρία ξέρει για την Καίτη μόνο τα σχετικά με την οικογενειακή κατάστασή της ".

β'. Σύνδεση που να δείχνει σε ολόκληρα τα στιγμιότυπα:
Τοποθετώντας ως τιμή της υποδοχής Υ του αντικειμένου Α ένα όνομα αντικειμένου ή μια λίστα
ονομάτων αντικειμένων, έστω Α1, Α2, …, το Α "βλέπει" εξ ολοκλήρου τα αντικείμενα αυτά, και
μέσω αυτών τις τυχόν αλλαγές των τιμών υποδοχών τους, πχ, αν ένα πρόσωπο έχει υποδοχή
όνομα_συζύγου, και οτι Μαρία και Γιάννης είναι πρόσωπα:
(setf (πρόσωπο-όνομα_συζύγου Μαρία) Γιάννης))

Σε οποιαδήποτε αλλαγή στο συνδεδεμένο, πχ. "να αλλάξει αριθμό ταυτότητας ή επάγγελμα ο
Γιάννης", το συνδεόμενο αντικείμενο Μαρία έχει πρόσβαση σ' αυτή την αλλαγή, μέσω της
υποδοχής που το συνέδεσε (και προσδιορίζουμε υπολογισμό μέσω κατάλληλης μεθόδου).
Αντίστοιχα, μπορούμε να δημιουργήσουμε ένα σύμβολο με περιεχόμενο εξαρτημένο από μια
ομάδα αντικειμένων, οπότε κάθε μεταβολή μέσα σε αντικείμενο θα είναι προσπελάσιμη από το
σύμβολο, πχ:
(setq Α (list Α1 Α2 …ΑΝ))

385

8.3 Κλάσεις και αντικείμενα: Eισαγωγή στο CLOS
Οι δομές που ορίζονται μέσω της DEFSTRUCT είναι ένας τρόπος υλοποίησης του νοήματος της
κλάσης, που έχει όμως τον περιορισμό οτι η δομή θα είναι ιεραρχική∙ έτσι δεν είναι δυνατή η
θεώρηση πολλαπλής κληρονομικότητας. Ακόμα, δεν είναι δυνατή η παρακολούθηση αλλαγών
στις τιμές υποδοχών του γεννήτορα από το στιγμιότυπο. Χρειαζόμαστε επομένως ένα τρόπο
ορισμού κλάσεων με το γενικότερο νόημά τους, που θα επιτρέπει τέτοιες δυνατότητες. Αυτό το
πετυχαίνουμε χρησιμοποιώντας τη συνάρτηση DEFCLASS αντί της DEFSTRUCT .
Η συνάρτηση DEFCLASS ανήκει σε μια ομάδα συναρτήσεων δημιουργίας και διαχείρισης
κλάσεων και αντικειμένων, που αποκαλείται CLOS (Common Lisp Object System). Στους
σύγχρονους compilers η ομάδα αυτή είναι ενσωματωμένη στο "Common Lisp" πακέτο (package)
του compiler. Η ομάδα αυτή επιτρέπει την "στροφή" του προγραμματισμού προς τα αντικείμενα,
μέσα από τον συναρτησιακό προγραμματισμό, με μεγαλύτερη πληρότητα και ευελιξία σε
σύγκριση με αυτά που προσφέρουν οι δομές.

Πολλοί μελετητές υποστηρίζουν οτι η DEFSTRUCT και οτιδήποτε μπορεί κανείς να κτίσει με
αυτή, αχρηστεύεται από την εκφραστική δύναμη της DEFCLASS και των περιφερειακών της
συναρτήσεων: τα πάντα όσα είδαμε οτι μπορούμε να κατασκευάσουμε με την πρώτη, μπορούν να
γίνουν με γενικότερο και πιο ευέλικτο τρόπο με τη δεύτερη, και επί πλέον αυτή συνοδεύεται από
υλοποιημένες συναρτήσεις επεξεργασίας κλάσεων και αντικειμένων, χαμηλότερου ή ψηλότερου
επιπέδου [Keen89].

Όμως είναι γεγονός οτι η χρήση της DEFSTRUCT είναι πολύ απλούστερη, δεν απαιτεί
ιδιαίτερη εμπειρία, δεν οδηγεί εύκολα σε συγκρούσεις, και δεν δημιουργούνται καταστάσεις που
να απαιτούν χαμηλού επιπέδου προγραμματισμό.

Επί πλέον, οι κλάσεις τύπου structure (δηλ. που ορίζονται μέσω της DEFSTRUCT)
συνδυάζονται άνετα εκ των υστέρων με τις κλάσεις τύπου structure-class (δηλ. που ορίζονται
μέσω της DEFCLASS) και του ειδικότερου standard-class (που είναι ο default υποτύπος των
κλάσεων του CLOS). Ο κανόνας είναι: "ότι εφαρμόζεται για κλάσεις τύπου structure, εφαρμόζεται
και για κλάσεις τύπου structure-class αλλά όχι και το αντίθετο".

8.3.1 Δημιουργία κλάσεων και στιγμιοτύπων τους

Οι ουσιαστικότερες πρόσθετες δυνατότητες που παρέχουν οι κλάσεις τύπου structure-class, δηλ.
αυτές που ορίζονται μέσω της DEFCLASS , είναι:
– πολλαπλή κληρονομικότητα, που προέρχεται από τη δυνατότητα προσδιορισμού
περισσοτέρων του ενός γονέων κλάσης
– ιεραρχημένο ψάξιμο για εντοπισμό της κατάλληλης μεθόδου στη λίστα προτεραιότητας της
κλάσης (class precedence list)
– ορισμός στα αντικείμενα, τόσο ιδιωτικών υποδοχών (private ή local slots) όσο και υποδοχών
κλάσης (class slots)
386

– τοποθέτηση φόρμας υπολογισμού της τιμής υποδοχής σε κλάση, που λειτουργεί
καθυστερημένα, υπολογίζοντας την τιμή στη φάση δημιουργίας ενός στιγμιότυπου και όχι
στη φάση ορισμού της κλάσης
Επίσης προσφέρουν πλήθος από μηχανισμούς που δίνουν ευελιξία στον προγραμματισμό:
– αναδιαμόρφωση του τρόπου αναζήτησης της κατάλληλης μεθόδου
– διαδικαστική ανατοποθέτηση αντικειμένου σε νέα κλάση
– διαμόρφωση της επικοινωνίας κλάσεων, μέσω μεταντικειμένων, κλπ.

Δημιουργία νέας κλάσης

Ο ορισμός κλάσης γίνεται με τη συνάρτηση DEFCLASS , όπου με διάφορα κλειδιά (βλέπε
[Keen89]) μπορούμε να προσδιορίσουμε τα χαρακτηριστικά κάθε υποδοχής:
( defclass <κλάση> ( <υπερκλάση1> <υπερκλάση2> …)

(

)
)

( <όνομα υποδοχής 1>
:accessor <συνάρτηση κατασκευής>
:initform <αρχική τιμή>
:initarg :<κλειδί>
:reader <μέθοδος>
:allocation :<κλειδί class ή instance>
)
(<όνομα υποδοχής 2> ……)
<άλλες υποδοχές>

( :metaclass <όνομα κλάσης> )

Διευκρινίσεις:
Τα κλειδιά είναι προαιρετικά. Αναφέραμε εδώ μόνο τα βασικότερα.
– Το κλειδί :accessor ακολουθείται από το σύμβολο - συνάρτηση κατασκευής στιγμιότυπου.
Επίσης κατασκευάζουμε στιγμιότυπα και με τη γνωστή από τις δομές τύπου structure λεκτική
σύνθεση <κλάση>-<υποδοχή> . Ακόμα, έχουμε τη δυνατότητα να χρησιμοποιήσουμε τη
γενική συνάρτηση κατασκευής στιγμιότυπων MAKE-INSTANCE , που θα δούμε παρακάτω.
– Το κλειδί :initform ακολουθείται από την αρχική τιμή, που δίνεται είτε ως σταθερά είτε ως
φόρμα υπολογισμού, που εκτελείται στη φάση χρήσης της κλάσης.

– Το κλειδί :initarg
ακολουθείται από ένα κλειδί (με πρόθεμα ":") μέσω του οποίου
αρχικοποιούμε την τιμή της υποδοχής όταν δημιουργήσουμε στιγμιότυπο∙ συνήθως δίνουμε
το ίδιο το όνομα της υποδοχής.
– Το κλειδί :reader ακολουθείται από όνομα μεθόδου, με την οποία υπολογίζεται η τιμή τής
υποδοχής κατά τη στιγμή της δημιουργίας του στιγμιότυπου, και δίνεται ως τιμή της
αντίστοιχης υποδοχής του στιγμιότυπου.
387

– Το κλειδί :allocation ακολουθείται από κλειδί :class ή :instance
– Στην πρώτη περίπτωση, για κάθε στιγμιότυπο η υποδοχή αυτή έχει τιμή που "δείχνει
δυναμικά" στην τρέχουσα τιμή της υποδοχής της κλάσης, στη φάση που θα αναζητηθεί η
τιμή. Χαρακτηρίζεται ως κοινή (shared) για όλα τα στιγμιότυπα.
– Στη δεύτερη περίπτωση, για κάθε στιγμιότυπο η υποδοχή αυτή παίρνει τιμή την τιμή που
έχει η υποδοχή της κλάσης, και αποσυνδέεται∙ ενδεχόμενη αλλαγή της τιμής στην κλάση
δεν επηρεάζει τα στιγμιότυπα. Χαρακτηρίζεται ως ιδιωτική (prived).

Αν δεν αναφερθεί :allocation στο επίπεδο της αρχικής κλάσης εννοείται :instance . Στις
υποκλάσεις, μεταφέρεται με το χαρακτηρισμό που έχει. Μπορεί να αλλάξει ο χαρακτηρισμός,
από κάπου και κάτω.
– Το κλειδί :metaclass ακολουθείται από όνομα κλάσης, που καθορίζει τον τύπο της κλάσης∙
είναι δυνατό να επαναληφθεί, με την προϋπόθεση της συμβατότητας των τύπων∙ μια
μετακλάση δεν στιγμιοτυπείται.

– Το όνομα συμβόλου που προσδιορίζεται ως κλάση μέσω της DEFCLASS είναι τύπος,
υπερτύπου structure-class που είναι υποτύπος - παιδί του structure. Αν έχουμε ορίσει
μετακλάση, ορίζεται κάτω από αυτήν, αλλιώς ορίζεται αυτόματα τύπου standard-class , που
είναι υποτύπος - παιδί τού structure-class .
– Αν η κλάση είναι ρίζα, τότε η λίστα των υπερκλάσεων θα δοθεί κενή.
Παράδειγμα
1. Κλάση "αυτοκίνητο", με υποδοχές "τύπος", "μάρκα" (τιμές κληρονομούμενες) και "ιδιοκτήτης"
(τιμή ιδιωτική).
(defclass αυτοκίνητο ( )
( (τύπος
:accessor τύπος
:initform 'βενζινοκίνητο
:initarg :τύπος
:allocation :class)
(μάρκα
:accessor μάρκα
:initform NIL
:initarg :μάρκα
:allocation :class) )
(ιδιοκτήτης
:accessor ιδιοκτήτης-αυτοκίνητου
:allocation :instance)
)
)
(defclass επιβατικό (αυτοκίνητο)
( (τύπος
:initform 'πόλης) )
388

2. Κλάση τρίγωνο , υποκλάση της σχήμα που υποτίθεται δοσμένη, με υποδοχές πλευρά_α ,
πλευρά_β , πλευρά_γ με ιδιωτικές τιμές, και υποδοχή πλήθος_πλευρών κοινή:
(defclass τρίγωνο (σχήμα)
( (πλευρά_α :accessor πλευρά_α :initarg :πλευρά_α)
(πλευρά_β :accessor πλευρά_β :initarg :πλευρά_β)
(πλευρά_γ :accessor πλευρά_γ :initarg :πλευρά_γ)
(πλήθος_πλευρών
:reader πλήθος_πλευρών_τριγώνου
:initform 3 :allocation :class) ) )
(defmethod πλήθος_πλευρών_τριγώνου
( (var1 τρίγωνο) )
3)

Προσοχή: Εδώ έχουμε πλεονασμό πληροφορίας (η τιμή 3 αφορά το ίδιο νόημα, το πλήθος των
πλευρών του τριγώνου, αλλά δίνεται ως τιμή της υποδοχής από την :initform , παρ' όλο που
προκύπτει ως αποτέλεσμα της μεθόδου που καλεί ο :reader ).

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

Αντιγραφή κλάσης
Μπορούμε να πάρουμε αντίγραφο κλάσης, για οποιαδήποτε χρήση, με την COPY-CLASS :
(copy-class όνομα_κλάσης όνομα_αντίγραφου)
Το αντίγραφο είναι νέα κλάση, και έχει πανομοιότυπα χαρακτηριστικά με το πρωτότυπό της.
Η σκοπιμότητα τέτοιας αντιγραφής εντοπίζεται περισσότερο στη δημιουργία μιας πανομοιότυπης
κατάστασης για διαφορετική εξέλιξη (πειραματισμούς, διαφορετική διακλάδωση παρακάτω στο
δένδρο) παρά στην ευκολία με την οποία δημιουργούμε ένα "αδέλφι" της κλάσης.

Δημιουργία νέου στιγμιότυπου

Η δημιουργία στιγμιότυπου γίνεται είτε με τη MAKE-INSTANCE είτε με κατασκευαστή που
έχουμε προσδιορίσει μέσα στην κλάση.
Τα στιγμιότυπα των κλάσεων έχουν το πλεονέκτημα, απέναντι στα στιγμιότυπα των κλάσεων 389

δομών, στο οτι μπορούν να βλέπουν μόνιμα τις τιμές κάποιων υποδοχών της κλάσης - γεννήτορα:
οι υποδοχές κλάσης διακρίνονται σε: i) κοινής τιμής (shared), που "δείχνουν" προς την
αντίστοιχη τιμή του γεννήτορα, και βέβαια δεν έχει νόημα ο επαναπροσδιορισμό τους, και ii)
ιδιωτικής (prived), που, αν δεν δώσουμε νέα τιμή αντιγράφουν την αντίστοιχη αρχική τιμή του
γεννήτορα χωρίς να παρακολουθούν μεταγενέστερες μεταβολές που συμβαίνουν στην κλάση γεννήτορα, ενώ αν δώσουμε νέα τιμή κρατούν αυτή.
Χωρίς επαναπροσδιορισμό τιμών ιδιωτικών υποδοχών:
(make-instance '<όνομα κλάσης> ) ; προσοχή στο "quote"
Με επαναπροσδιορισμό τιμών ιδιωτικών υποδοχών:
(make-instance '<όνομα κλάσης>
:<ιδιωτική_υποδοχή1> <τιμή1>
:<ιδιωτική_υποδοχή2> <τιμή2> … )
Προφανώς κάθε επανάληψη της κλήσης MAKE-INSTANCE δημιουργεί νέο στιγμιότυπο.
Ισχύουν αυτά που έχουμε αναφέρει στη δημιουργία στιγμιοτύπων μιας κλάσης-δομής:
– ονομασία στιγμιότυπου με σύμβολο μέσω της SETQ ή της SETF ,
– ειδική αναφορά σε τιμή υποδοχής στιγμιότυπου,
– μεταβολή τιμών υποδοχών με την SETF,
– αντιγραφή στιγμιότυπου, κλπ.
Παράδειγμα
Χρησιμοποιώντας την κλάση "τρίγωνο" που περιγράψαμε, μπορούμε να δώσουμε:
(make-instance 'τρίγωνο
:πλευρά_α 14
:πλευρά_β 22
:πλευρά_γ 33 )

Αν θέλουμε, μπορούμε να δώσουμε μια συνάρτηση "κατασκευής τριγώνων":
(defun κάνε_τρίγωνο (x y z)
(make-instance 'τρίγωνο
:πλευρά_α x :πλευρά_β y :πλευρά_γ z ) )

Μπορούμε να ονομάσουμε ένα συγκεκριμένο τρίγωνο που κατασκευάζουμε:
(setq τρίγωνο1 (κάνε_τρίγωνο (3 4 5)))

Ορισμός μεθόδου σχετικής με μια ή περισσότερες κλάσεις
Ορισμός μεθόδου γίνεται πανομοιότυπα με τις μεθόδους δομών:
(defmethod όνομα_μεθόδου
( (<λ-μεταβλητή 1> <εξειδικευτής κλάσης 1> )
(<λ-μεταβλητή_2> <εξειδικευτής κλάσης 2> ) …)
<σώμα> )
Οι όροι <εξειδικευτής κλάσης …> είναι ονόματα κλάσεων, που δεν είναι υποχρεωτικό να
390

ανήκουν σε "συσχετιζόμενες οικογενειακά" κλάσεις.
Παράδειγμα
Στην παραπάνω κλάση "τρίγωνο", μπορούμε να ορίσουμε μέθοδο υπολογισμού εμβαδού:
(defmethod εμβαδόν
((τριγων τρίγωνο))

; "τρίγωνο" είναι ο εξειδικευτής κλάσης,
;"τριγων" είναι η λ-μεταβλητή που δεσμεύεται σε στιγμ. τριγώνου
;; ο παρακάτω υπολογισμός καλεί συνάρτηση, για ευκολία
(triangle_area
(πλευρά_α τρίγωνο)
(πλευρά_β τρίγωνο)
(πλευρά_γ τρίγωνο) ))
; πλευρά_Χ είναι ο accessor της αντίστοιχης υποδοχής
(defun triangle_area (a b c &aux taf)

; υπολογ. εμβαδού από τις τρεις πλευρές

(setq taf (/ (+ a b c) 2) )
(sqrt (* taf (- taf a) (- taf b) (- taf c))) )

Καλύτερη λύση στο πρόβλημα αυτό, είναι να ενσωματώσουμε στην κλάση τρίγωνο μια
υποδοχή εμβαδόν με :reader τη μέθοδο υπολογισμού εμβαδού:
(defclass τρίγωνο (σχήμα)
( (πλευρά_α :accessor πλευρά_α :initarg :πλευρά_α)
(πλευρά_β :accessor πλευρά_β :initarg :πλευρά_β)
(πλευρά_γ :accessor πλευρά_γ :initarg :πλευρά_γ)
(πλήθος_πλευρών :reader πλήθος_πλευρών_τριγώνου
:initform 3 :allocation :class) ) )
(εμβαδόν :reader εμβαδόν :initarg :εμβαδόν )))

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

Παρατηρήσεις

Όπως και στις κλάσεις τύπου structure, μπορούμε να ορίσουμε διαφορετικές μεθόδους με το ίδιο
όνομα, που προσαρμόζουν τη συμπεριφορά της εκτελούμενης συνάρτησης ανάλογα με τον τύπο
του ή των ορισμάτων. Και στη περίπτωση των κλάσεων τύπου structure-class δημιουργείται
αυτόματα η αρχέτυπη συνάρτηση που καλύπτει όλες τις περιπτώσεις μεθόδων με το ίδιο όνομα,
και συγκεκριμένα:
– με την πρώτη δημιουργία μεθόδου με συγκεκριμένο όνομα, δημιουργείται η αρχέτυπη
συνάρτηση με το όνομα αυτό
391

– κάθε νέα μέθοδος που ορίζεται με αυτό το όνομα προκαλεί αυτόματα την κατάλληλη
επέκταση της αρχέτυπης συνάρτησης
– αν ορίσουμε μέθοδο με ίδιου τύπου ορίσματα (κατά πλήθος και αντίστοιχη κλάση), τότε
επαναπροσδιορίζεται η μέθοδος διαγράφοντας την προηγούμενη
– όπως θα δούμε, έχουμε και τη δυνατότητα να παρέμβουμε επαναπροσδιορίζοντας το σώμα
αρχέτυπης συνάρτησης (τότε καταστρέφουμε τις υπάρχουσες μεθόδους με αυτό το όνομα).
Όπως και στις κλάσεις τύπου structure, αν έχουμε περισσότερες μεθόδους με το ίδιο όνομα, ενός
ορίσματος, ορισμένες για διάφορες κλάσεις, τότε:
– αν κληθεί για στιγμιότυπο κλάσης για την οποία έχει δοθεί ορισμός μεθόδου, ισχύει αυτός
– αν δεν έχει δοθεί ορισμός μεθόδου για την κλάση αυτή, κληρονομείται ο ορισμός μεθόδου ο
αναφερόμενος στην πλησιέστερη υπερκλάση που διαθέτει τέτοιο ορισμό, όπου η
"πλησιέστερη" καθορίζεται από τη λίστα προτεραιότητας της κλάσης∙ συγκεκριμένα:
– αν η κλάση είναι σε ιεραρχικό δένδρο, δεν υπάρχει πρόβλημα προσδιορισμού της αλυσίδας
των υπερκλάσεων: ο γονέας κάθε κλάσης είναι μοναδικός, μέχρι τη ρίζα
– αν η κλάση έχει περισσότερους του ενός γονείς, ή, αν ψάχνοντας τους προγόνους για μέθοδο
φθάσουμε σε κόμβο που έχει περισσότερους του ενός γονείς, τότε πλησιέστερος είναι ο
αριστερότερος στη λίστα προτεραιότητας της κλάσης. Θα δούμε παρακάτω πώς
προσδιορίζεται η λίστα προτεραιότητας
Οι κλάσεις-δομές που προσδιορίζονται με την DEFSTRUCT και οι κλάσεις που προσδιορίζονται
με την DEFCLASS δεν είναι ασύμβατες μεταξύ τους: είναι του ίδιου γενικού τύπου structure . Οι
πρώτες είναι τύπου structure, οι δεύτερες είναι τύπου structure-class που είναι υποτύπος του
structure, και αν δεν προσδιορίζεται αλλιώς, είναι "by default" υποτύπου standard-class, που
είναι υποτύπος του structure-class . Δεν διαφοροποιούνται στον υπολογισμό αν γίνεται χρήση
συναρτήσεων που αφορούν και τις δύο περιπτώσεις. Επομένως μπορούν να συνυπάρχουν, και να
εφαρμόζουμε μεθόδους αναφέροντάς 'τες σαν να είναι κλάσεις, αδιάφορα του τρόπου που
γεννήθηκαν.

Από το όνομα κλάσης στην ίδια την κλάση - φυσική οντότητα
Η συνάρτηση find-class δέχεται όρισμα το όνομα κλάσης και επιστρέφει την κλάση ως φυσική
θέση (σε εκτύπωση, δίνει την εσωτερική αναπαράστασή της).
Παράδειγμα
(defclass my-class-1 ( ) (s1 s2 s3))
(defclass my-class-1-1 (my-class-1) (s4))
(setq a (make-instance 'my-class-1)) →

#<MY-CLASS-1 #xE70DE4>

(είναι το όνομα της κλάσης και το εσωτερικό όνομα του στιγμιότυπου)
(find-class 'my-class-1-1 ) →
→ #< STANDARD-CLASS MY-CLASS-1-1 #xE6CA2C >
Το όνομα αυτής της κλάσης είναι my-class-1-1 και η ίδια η κλάση αποδίδεται εσωτερικά από την
έκφραση #<STANDARD-CLASS MY-CLASS-1-1 #xE6CA2C> που δηλώνει τύπο, όνομα και
392

εσωτερικό όνομα (το εσωτερικό όνομα είναι εξαρτημένο από τον compiler και τη φάση
εκτέλεσης).

Η λίστα προτεραιότητας μιας κλάσης
Η συνάρτηση compute-class-precedence-list δέχεται όρισμα κλάση (εσωτερική αναπαράσταση)
και επιστρέφει την λίστα προτεραιότητας της κλάσης, από τον εαυτό της μέχρι τη ρίζα:
(compute-class-precedence-list (find-class 'clas11)) →
→ ( #< STANDARD-CLASS MY-CLASS-1-1 #xE6CA2C >
#< STANDARD-CLASS MY-CLASS-1
#xE6B7FC >
#< STANDARD-CLASS STANDARD-OBJECT #x8481A4 >
#< BUILT-IN-CLASS
T
#x8480F8 > )

Από την έκφραση αυτή βλέπουμε την προτεραιότητα των προγόνων της MY-CLASS-1-1 , από τον
συγγενέστερο προς τον πιο απόμακρο:
(MY-CLASS-1-1 MY-CLASS-1 STANDARD-OBJECT T)

Ας προσδιορίσουμε τώρα και ένα αδέλφι της MY-CLASS-1-1 με ίδια υποδοχή s3 :
(defclass my-class-1-2 (my-class-1) (s3))

και ένα κοινό παιδί των MY-CLASS-1-1 και MY-CLASS-1-2 :
(defclass my-class-1-12-1 (my-class-1-1 my-class-1-2 ) ( ))

Προσδιορίζουμε τη λίστα προτεραιότητας της MY-CLASS-1-12-1 :
(compute-class-precedence-list (find-class 'my-class-1-12-1)) →
→ ( #< STANDARD-CLASS MY-CLASS-1-12-1 #xE71D90 >
#< STANDARD-CLASS MY-CLASS-1-1 #xE6BC94 >
#< STANDARD-CLASS MY-CLASS-1
#xE6AA6C >
#< STANDARD-CLASS STANDARD-OBJECT #x8481A4 >
#< BUILT-IN-CLASS
T #x8480F8 >
#< FORWARD-REFERENCED-CLASS MY-CLASS-1-2
#xE71BA8 > )

Βλέπουμε πως εξαντλείται πρώτα η αλυσιδωτή ιεραρχία της πρώτης αναφερόμενης υπερκλάσης
και μετά προχωρά στη δεύτερη αναφερόμενη υπερκλάση. Αυτό σημαίνει πως η υποδοχή s3
παίρνει κληρονομικά τιμή από την my-class-1-1 (πιο κοντινή) και όχι από την my-class-12.

Αντί να κινηθούμε με χαμηλού επιπέδου συναρτήσεις όπως η παραπάνω COMPUTE-CLASSPRECEDENCE-LIST , μπορούμε να ορίσουμε μια υποδοχή parent-classes σε κάθε κλάση, και να
θέσουμε τιμή ως τιμή της υποδοχής αυτής τη λίστα των γονέων της κλάσης. Προσδιορίζοντας μια
"κοινή" συνάρτηση ή μέθοδο που να σαρώνει προς τα πάνω (αναδρομικά) τους κόμβους,
μπορούμε να εντοπίσουμε τους προγόνους της κλάσης. Ειδικότερα:

– Αν σαρώνει με την ίδια προτεραιότητα που ακολουθεί η συνάρτηση compute-class393

precedence-list (σάρωση "depth first") θα επιστρέφει λίστα με την ίδια διάταξη που έχει η λίστα

προτεραιότητας, αλλά τώρα όροι θα είναι μόνον τα ονόματα των αντιστοίχων κλάσεων.
– Αν σαρώνει προς τους πλησιέστερους συγγενείς (breadth first) η λίστα των συγγενών που θα
βρίσκει, δεν θα έχει την ίδια διάταξη με τη λίστα προτεραιότητας της κλάσης, και αποτελεί ένα
τρόπο αναδιοργάνωσης της προτεραιότητας των μεθόδων (μπορούμε να ανακατευθύνουμε και τον
τρόπο αυτόματου προσδιορισμού της μεθόδου που θα κληθεί).
Εδώ χρειάζεται να δοθεί προσοχή σε ένα ζήτημα εξαρτημένο από τον compiler:
Το όνομα κλάσης, σε ορισμένες υλοποιήσεις της CL είναι κοινό σύμβολο, που υπακούει σε
συναρτήσεις που δρουν πάνω σε σύμβολο. Επειδή στις υλοποιήσεις αυτές ουσιαστικά η δόμηση
των κλάσεων γίνεται μέσω της λίστας ιδιοτήτων του συμβόλου ή μέσω συνδεδεμένου πίνακα
κατακερματισμού, προφανώς η εφαρμογή συναρτήσεων επεξεργασίας ιδιοτήτων ή της λίστας
ιδιοτήτων είναι εφικτή αλλά θα καταστρέψει την κλάση, και επειδή η εξάρτηση κλάσεων
ακολουθεί μια αλληλουχία μέσα από τη λίστα αυτή, η παραμικρή παρέμβαση θα καταστρέψει
ολόκληρο το σύστημα αντικειμένων. Παρ' όλα αυτά, σε χαμηλού επιπέδου προγραμματισμό,
μπορούμε να αξιοποιήσουμε τη δυνατότητα επέμβασης στις συνδέσεις που δημιουργούν το
σύστημα των αντικειμένων.

Σκιώδεις μέθοδοι

Από τις μεθόδους που ταιριάζουν σε μια κλήση, μία και μόνο μία είναι η πλησιέστερη, όπως
είπαμε. Οι λοιπές που θα μπορούσαν να ταιριάξουν, αποκαλούνται σκιώδεις (shadowed). Είναι
δυνατό να ζητήσουμε να μην εφαρμοστεί η πλησιέστερη, αλλά ή επόμενη (μεθεπόμενη κλπ.), με
εφαρμογή της CALL-NEXT-METHOD (μέσα από την καλούμενη μέθοδο, ή καλύτερα,
χρησιμοποιώντας μια "around" μέθοδο, που αναφέρουμε παρακάτω).
Η συνάρτηση call-next-method (αν κληθεί χωρίς ορίσματα) προκαλεί διαδικαστικά μια
μετατόπιση της κλήσης προς τα δεξιά, στην (ιδεατή) λίστα μεθόδων της κλάσης, και οδηγεί στην
επόμενη μέθοδο που ταιριάζει. Η λίστα μεθόδων δημιουργείται βάσει της λίστας προτεραιότητας
της κλάσης.

Βοηθητική είναι η συνάρτηση - κατηγόρημα call-next-method-p που ελέγχει αν υπάρχει στη
συνέχεια μέθοδος που να ταιριάζει.

Μια περίπτωση που αξιοποιείται με μεγάλη ευκολία μέσω της CALL-NEXT-METHOD , είναι η
εξής: Αν ορίσουμε νέα μέθοδο με όνομα και τύπους ορισμάτων που έχουν ήδη δοθεί, αντικαθιστά
αυτόματα την ήδη υπάρχουσα. Παρ' όλα αυτά, μπορούμε επιτακτικά να επισυνάψουμε απ' ευθείας
στο σώμα της αρχέτυπης συνάρτησης (με επανορισμό της) αμέσως δεξιά από την μέθοδο της
οποίας θέλουμε να προσδιορίσουμε μια εναλλακτική, τη μέθοδο που αποτελεί ακριβώς αυτή την
εναλλακτική μέθοδο (και άλλες, αν θέλουμε). Κανονικά, η κλήση της μεθόδου θα εντοπίσει την
πρώτη, και έτσι οι εναλλακτικές δεν πρόκειται να κληθούν αυτόματα. Μπορούμε όμως να τις
εντοπίσουμε και να τις καλέσουμε επιτακτικά, με χρήση τής CALL-NEXT-METHOD.

394

Το κλειδί - προσδιοριστής μεθόδου: οι "πριν" (before), "μετά" (after) και "γύρω"
(around) μέθοδοι

Σε μια μέθοδο μπορούμε να προσδιορίσουμε με ένα κλειδί - προσδιοριστή (qualifier), τον ρόλο
που πρόκειται να παίξει η μέθοδος. Κλειδί είναι ένα από τα :around , :before και :after . Το
κλειδί τοποθετείται αμέσως μετά το όνομα της μεθόδου, κατά τον ορισμό της:
(defmethod <όνομα> :around
( (<λ-μεταβλ1> <εξειδικευτής τύπου1>) ...) <σώμα>)
i) Χωρίς κλειδί - προσδιοριστή, ορίζουμε κοινή (standard) μέθοδο που θα χρησιμοποιηθεί
κατά τα γνωστά, και θα αποτελέσει την κύρια (primary) μέθοδο κατά την κλήση.
ii) Με το κλειδί :around ορίζουμε μέθοδο που παίρνει τον έλεγχο (control), πριν από την
κυρία μέθοδο, αγνοώντας την (τυχόν) ύπαρξη "before" και "after" μεθόδων. Είναι ο βασικός
διαδικαστικός τρόπος προσδιορισμού του τί θα εκτελεστεί.

Χρησιμοποιούμε μια "around" μέθοδο για να καλέσουμε τη συνάρτηση CALL-NEXTMETHOD . Όταν γίνεται κλήση της CALL-NEXT-METHOD δεν προχωρά η "around" μέθοδος σε
κλήση της κυρίας μεθόδου, και έτσι μπορούμε να καθορίσουμε τη συνολική λειτουργικότητα,
υπερβαίνοντας τον αρχικό τρόπο επιλογής και εφαρμογής μεθόδου. Όταν δεν γίνεται τέτοια
κλήση, λειτουργεί σαν "before" μέθοδος, με τη διαφορά οτι μια "before" μέθοδος είναι
ανεξάρτητη της κυρίας μεθόδου ενώ μια "around" μέθοδος περιβάλλει την κυρία μέθοδο,
μεταφέροντας τιμές των μεταβλητών.

Με χρήση "around" μεθόδων που χρησιμοποιούν τις CALL-NEXT-METHOD και CALL-NEXTMETHOD-Ρ είναι δυνατό να ανακατευθύνουμε την επιλογή μεθόδου ακόμα και προς την
απώτερη δυνατή μέθοδο, ή να οργανώσουμε ένα δένδρο εφαρμοζομένων μεθόδων (με το ίδιο
όνομα) που να εφαρμοστούν όλες και με τη διαδοχή που ορίζουμε εμείς, παρακάμπτοντας τον
αυτόματο προσδιορισμό κυρίας μεθόδου.
iii) Με το κλειδί :before προσδιορίζουμε μέθοδο που θα κληθεί πριν την κλήση της κυρίας
μεθόδου που έχει το ίδιο όνομα. Αν υπάρχουν περισσότερες της μιας που μπορούν να
εφαρμοστούν για τους δεδομένους τύπους εισόδου, καλείται η πλησιέστερη, με το συνήθη τρόπο
επιλογής, και στη συνέχεια καλούνται κατά σειρά απομάκρυνσης οι επόμενες. Χρησιμοποιείται
για αρχικοποίηση συνθηκών. Η ουσιαστική διαφορά μιας "before" μεθόδου από μια "around"
είναι οτι η πρώτη δεν μεταφέρει αποτελέσματά της στην κυρία μέθοδο, ενώ η δεύτερη τα
μεταφέρει, αποτελώντας μέρος του συνολικού αλγόριθμου που συντίθεται.
iv)
Με το κλειδί :after προσδιορίζουμε μέθοδο που θα κληθεί μετά την κλήση της κυρίας
μεθόδου που έχει το ίδιο όνομα. Συνήθως χρησιμοποιούνται για τακτοποίηση εκκρεμοτήτων που
άφησε η εκτέλεση της κυρίας μεθόδου. Λειτουργούν συμπληρωματικά προς τις "before"
μεθόδους.

395

Οργάνωση πολυεπίπεδης λειτουργικότητας μέσω "πριν", "μετά" και "γύρω"
μεθόδων
Η λειτουργία που τελικά πετυχαίνουμε με τη σύνθεση κλήσεων μεθόδων όπως "before" + "κύρια
μέθοδος" + "after", θα μπορούσε να περιγραφεί ως ενιαίος αλγόριθμος, με μια απλή μέθοδο. Ο
λόγος διάσπασης της λειτουργίας σε μέρη "before" και "after", ή "around" από την κυρία μέθοδο,
είναι οτι ενδέχεται να χρειαζόμαστε να εκτελεστεί κάτι στο επίπεδο της κλάσης τού αντικείμενου
(αυτό το επιτελεί η κυρία μέθοδος), και κάτι άλλο στο επίπεδο μιας ή περισσοτέρων
υπερκλάσεων (αυτό το επιτελούν οι "before" και "after", μέθοδοι). Ακόμα, ενδέχεται να
χρειαζόμαστε διαφορετική κλήση κυρίας μεθόδου (αυτό το επιτελούν οι "around" μέθοδοι).

Η κλήση μεθόδου υπερκλάσης η οποία θα ακολουθηθεί από την κυρία μέθοδο, δηλ. με χρήση
"before" μεθόδου (ή "around" χωρίς κλήση call-next-method) έχει την έννοια εκτέλεσης ενός
γενικού κανόνα, πριν τον ειδικό κανόνα που χρειαζόμαστε. Υπάρχει επίσης δυνατότητα
εκτέλεσης περισσοτέρων "before" (ή "around") μεθόδων, που έχει την έννοια ταξινόμησης σειράς
γενικών κανόνων (δηλ. που αναφέρονται σε υπερκλάσεις) ώστε να εκτελεστούν με ορισμένη
διαδοχή.
Η κλήση μεθόδου υπερκλάσης που ακολουθεί την κυρία μέθοδο, έχει την έννοια ολοκλήρωσης
του πλαίσιου που "άνοιξε" η "before" μέθοδος.
Επομένως, η χρησιμότητα των "before" και "after" μεθόδων έγκειται στο οτι μπορούμε να
προσαρμόζουμε το περιβάλλον όπου θα λειτουργήσει η κυρία μέθοδος, σε κάποιο ανώτερο
επίπεδο γενικότητας. Η χρησιμότητα των "around" μεθόδων έγκειται στο οτι επί πλέον αποκτάμε
την ευχέρεια διαδικαστικού χειρισμού των μεθόδων που θα κληθούν.
Αναφέραμε απλώς ενδεικτικά τις "before", "after" και "around" μεθόδους για να φανούν οι
δυνατότητες διαχείρισης του υπολογισμού, που δεν σταματούν εδώ: έχουμε και τρόπο να θέσουμε
γενικό κανόνα αναδιαμόρφωσης της κλήσης των μεθόδων που έχουν κάποιο συγκεκριμένο
όνομα, αλλά το θέμα ξεφεύγει από τα πλαίσια του παρόντος.

8.3.2 Κλάσεις και αρχέτυπες συναρτήσεις∙ προτεραιότητα
Θα ξαναδούμε εδώ αναλυτικότερα το θέμα του προσδιορισμού και της κλήσης μεθόδων.
Η ιδέα που αντιπροσωπεύει μια αρχέτυπη συνάρτηση (generic function) για τις κλάσεις, είναι η
αυτόματη προσαρμογή της συμπεριφοράς ανάλογα με τον τύπο των αντικειμένων όπου
εφαρμόζεται. Πρόκειται λοιπόν για συμπεριφορά καθοδηγούμενη από τα αντικείμενα εφαρμογής,
που με όρους κλάσεων σημαίνει οτι είναι εξαρτημένη από το σε ποια κλάση ανήκει το
αντικείμενο. Όπως παραστατικά περιγράφουν οι Winston και Horn, αυτή η προσαρμογή της
συμπεριφοράς μοιάζει με:
"το πώς θα φάμε ένα φαγητό εξαρτάται από το τί είναι αυτό το φαγητό"
Μια αρχέτυπη συνάρτηση προσδιορίζεται:

– έμμεσα με την πρώτη κλήση της συνάρτησης DEFMETHOD με όρισμα το όνομα της
αρχέτυπης συνάρτησης (και πρακτικά αυτό κάνουμε)
396

– άμεσα, με εφαρμογή της συνάρτησης DEFGENERIC , που συνήθως ορίζουμε χωρίς σώμα:
συνδέονται αυτόματα οι μέθοδοι που θα ορίσουμε για τις ίδιου πλήθους λ-μεταβλητές, και
συνθέτουν το σώμα της αρχέτυπης συνάρτησης, αλλά βέβαια αν θα θέλαμε, θα μπορούσαμε
να ορίσουμε απ' ευθείας εκεί τις μεθόδους:
(defgeneric όνομα (λ-μεταβλητές) )
; για σύνδεση με τη ρίζα Τ
(defgeneric όνομα ((<λ-μεταβλητή> <κλάση>)) )
; για σύνδεση με την <κλάση>
(defgeneric όνομα
((<λ-μεταβλητή1> <κλάση1>) (<λ-μεταβλητή2> <κλάση2>) …) )
; πολλαπλών κλάσεων
Αν εφαρμόσουμε την DEFGENERIC πάνω σε νέο όνομα, δημιουργούμε νέα αρχέτυπη
συνάρτηση∙ αν την εφαρμόσουμε σε υπάρχουσα αρχέτυπη συνάρτηση ή κοινή συνάρτηση,
αντικαθιστούμε την παλιά με το νέο ορισμό.
Ορίζοντας μέθοδο με την DEFMETHOD εφαρμοσμένη στο όνομα υπάρχουσας αρχέτυπης
συνάρτησης, επισυνάπτουμε την οριζόμενη μέθοδο στο σώμα της αρχέτυπης. Αν υπάρχει ήδη
μέθοδος με αντίστοιχα ίδιου τύπου ορίσματα, η νέα την αντικαθιστά.

Ο ψηλότερος κόμβος - κλάση που είναι δυνατό να αναφερθεί μέθοδος, είναι ο Τ (αντικείμενα
τύπου Τ , που είναι τα πάντα εκτός από το NIL). Κατά συνέπεια, αν έχουμε προσδιορίσει μέθοδο
στο επίπεδο αυτό, δεν θα είναι δυνατό να προκύψει σφάλμα. Μέθοδος έχει προσδιοριστεί, αν έχει
προσδιοριστεί αρχέτυπη συνάρτηση με τα αντίστοιχα ορίσματα, αντίστοιχου τύπου. Άρα, αν
ξεκινήσουμε τη δημιουργία μεθόδων με κάποιο όνομα, έστω METHO τριών ορισμάτων, δίνοντας
τον ορισμό της αντίστοιχης αρχέτυπης συνάρτησης στη ρίζα, αποφεύγουμε το σφάλμα:
(defgeneric metho ((x t) (y t) (z t)))

; αρκεί αυτό να γίνει πριν τον ορισμό μεθόδου
Ισοδύναμο είναι να δώσουμε:
(defmethod metho ((x t) (y t) (z t)))

; αυτό μπορεί να γίνει και εκ των υστέρων
Αν δεν δώσουμε προσδιοριστή τύπου, εννοείται Τ :
(defgeneric metho (x y z))

Αν ενοχλεί η επιστροφή NIL μπορούμε να δώσουμε κάτι άλλο, πχ:
(defmethod metho (x y z ) t )

Παράδειγμα
Έστω οτι αναφερόμαστε σε μέθοδο που δέχεται ορίσματα "περιοδικό" και "άρθρο". Θέλουμε
η διαχείριση να γίνεται πιο συγκεκριμένη όσο ειδικότερες είναι οι κατηγορίες του περιοδικού, του
άρθρου, και του αναγνώστη, και να μην υπάρχει περίπτωση σφάλματος.
Ορίζουμε τυπικά μια μέθοδο στο ανώτερο δυνατό επίπεδο, που να μην κάνει τίποτα:
(defmethod process_paper ( (journl t) (artcl t) (readr t) )
που λέει οτι "αν η μέθοδος PROCESS_PAPER κληθεί με τρία ορίσματα, που για τον συνδυασμό
397

τύπων τους δεν προβλέπεται μέθοδος, απλώς να μη γίνει τίποτα".

Δίνουμε τις μεθόδους που απαιτούνται για συγκεκριμένο συνδυασμό τύπων, πχ. "γενικά, να
κρατήσουμε ένα αντίγραφο του άρθρου για όποιον ενδιαφέρεται" , "τα επιστημονικά περιοδικά
με άρθρα Φυσικής τα δανείζονται οι φυσικοί", "αν το επιστημονικό περιοδικό Φυσικής έχει
άρθρο Πυρηνικής Φυσικής, να πάρει αντίγραφο ο επιστήμονας πυρηνικός φυσικός" , κλπ. κλπ.
(εννοείται οτι έχουν οριστεί κλάσεις journal , article , reader , υποκλάσεις τους όπως journalscientific , journal-scientific-physics , κλπ, και στιγμιότυπα)
(defmethod process_paper
( (journl journal) (artcl article) (readr reader) )
(print "keep a copy"))

Σε κλήση της process_PAPER με ορίσματα στιγμιότυπα αντίστοιχα τύπων journal, article,
reader, θα εφαρμοστεί αυτή η διαδικασία. Σε κλήση με ορίσματα στιγμιότυπα τύπων που
τουλάχιστον ένα είναι υποτύπος των journal, article, reader, θα εφαρμοστεί πάλι αυτή η μέθοδος,
εκτός αν έχει οριστεί ειδικότερη, πχ:
(defmethod process_paper
( (journ scientific-journal) (artcl article) (rdr scientist) )
(print (list "inform about" artcl rdr)))

Η λειτουργική δράση εδώ είναι μια απλή εκτύπωση, αλλά μπορεί να προσδιορίζει ενημέρωση
αντίστοιχης υποδοχής αντικειμένου δέσμευσης, πχ. της λ-μεταβλητής rdr .
► Στον ορισμό μιας μεθόδου ο πρώτος όρος στις δυάδες των ορισμάτων είναι λ-μεταβλητή και
ο δεύτερος όνομα τύπου-κλάσης (ο εξειδικευτής τύπου). Αν χρησιμοποιήσουμε και στα δύο μέλη
το ίδιο όνομα, υπολογιστικά δεν δημιουργείται σύγχυση, διότι το πρώτο στη δυάδα όνομα είναι
το όνομα της λ-μεταβλητής και το δεύτερο ο εξειδικευτής τύπου, και στο σώμα η αναφορά του
ονόματος σημαίνει την λ-μεταβλητή.
(defmethod process_paper
( (journal journal) (article article) (reader reader) …) ... )

Μια πολύ σημαντική παρατήρηση:
Όλοι οι βασικοί τύποι της Lisp είναι κλάσεις, και συνεπώς μπορούμε να δημιουργήσουμε
στιγμιότυπα και να συνδέσουμε μεθόδους. Όπως τα ονόματα των κλάσεων είναι τύποι, και
μπορούμε να εφαρμόσουμε σ' αυτά συναρτήσεις επεξεργασίας τύπων, έτσι και τα ονόματα των
βασικών τύπων είναι κλάσεις, οπότε μπορούμε να τα χρησιμοποιούμε αδιάκριτα από τις κλάσεις
(δημιουργώντας υποκλάσεις, στιγμιότυπα, σχετικές μεθόδους). Όπως είδαμε, δεν αποτελούν
βασικό τύπο όλοι οι τύποι της Lisp: μόνο αυτοί που δώσαμε στον σχετικό πίνακα (καθορίζονται
σε επίπεδο ιδεατής CL , αλλά εν γένει το σύνολό τους εξαρτάται από την έκδοση του compiler).

Προτεραιότητα επιλογής μεθόδου περισσοτέρων λ-μεταβλητών
Όπως περιγράψαμε στις κλάσεις-δομές, στην περίπτωση όπου κληθεί μια μέθοδος περισσοτέρων
του ενός ορισμάτων, για εφαρμογή σε συνδυασμό τύπων των ορισμάτων που δεν έχει οριστεί
398

αντίστοιχη μέθοδος, θα αναζητηθεί η πλησιέστερη προς τα πάνω μέθοδος, δίνοντας
προτεραιότητα στην κλάση-δομή δέσμευσης της πρώτης λ-μεταβλητής, μετά της δεύτερης, κοκ.
Το ίδιο ισχύει και για τις μεθόδους κλάσεων: και στις δύο περιπτώσεις, αυτό που ακολουθούμε
είναι η λίστα προτεραιότητας του κάθε προσδιοριστή κλάσης. Η διαφορά είναι οτι εδώ
ακολουθούμε τυπικά την πλήρη διάταξη που καθορίζεται από τη λίστα προτεραιότητας του
προσδιοριστή κλάσης, με προτεραιότητα στον αριστερότερο αναφερόμενο προσδιοριστή κλάσης,
όπου αφού εξαντλήσουμε τη λίστα προτεραιότητας του, πηγαίνουμε στον επόμενο, κοκ.
Παράδειγμα

Έστω journal7 το όνομα στιγμιότυπου της κλάσης physics-journal, υποκλάσης της scientificjournal, υποκλάσης της journal, article1 το όνομα στιγμιότυπου της κλάσης article, και reader3 το
όνομα στιγμιότυπου της κλάσης mathematician, υποκλάσης τής scientist, υποκλάσης τής person.
Επομένως η τριάδα των εξειδικευτών τύπου είναι:
{physics-journal , article , mathematician}
Έστω οτι δεν έχει οριστεί μέθοδος process_paper για την τριάδα αυτή, αλλά οτι έχει οριστεί για
τις τριάδες:
{journal , article , scientist} ,
{journal , article , person} ,
{scientific-journal , article, scientist} ,
{scientific-journal , article , physician} .
Η κλήση της μεθόδου:
(process_paper journal7 article1 reader3 )

έχει αντίστοιχη τριάδα εξειδικευτών τύπου την
{physics-journal , article , mathematician}
που προφανώς θα αποτύχει να βρει μέθοδο πάνω στη συγκεκριμένη τριάδα εξειδικευτών τύπου,
και θα αναζητήσει την πλησιέστερη: αναζητά ανώτερο κόμβο του πρώτου εξειδικευτή τύπου που
να έχει σχετική μέθοδο∙ επειδή είναι ιεραρχικές οι δομές, ο πλησιέστερος είναι προφανώς ο
scientific-journal .
Παρατηρούμε πως υπάρχουν περισσότερες της μιας κατάλληλες τριάδες:
{scientific-journal , article , scientist}
{scientific-journal , article , person}
Κατατάσσονται μεταξύ τους βάσει του εξειδικευτή τύπου της επόμενης λ-μεταβλητής, όπου
πλησιέστερος στον mathematician είναι ο τύπος scientist . Άρα η μέθοδος που θα εφαρμοστεί,
είναι η αναφερόμενη στην τριάδα:
{scientific-journal , article , scientist}

Επισύναψη προσθέτων χαρακτηριστικών μέσω δημιουργίας κοινού απόγονου
Όταν θέλουμε να δημιουργήσουμε αντικείμενο με επιπρόσθετα χαρακτηριστικά, σε σχέση με τον
γονέα του, δεν έχουμε παρά να κατασκευάσουμε μια κλάση με τα πρόσθετα χαρακτηριστικά,
399

υποκλάση των δύο, και στιγμιότυπό της.
Παράδειγμα
Με μόνιμη επισύναψη χαρακτηριστικών:
(defclass ορθογώνιο ( )
; βασικά χαρακτηριστικά
((ύψος :initform 0.0 :initarg :ύψος :accessor ύψος)
(πλάτος :initform 0.0 initarg :πλάτος :accessor πλάτος)))
(defclass χρωματιστό ( )
; πρόσθετα χαρακτηριστικά
((χρώμα :initform 'άσπρο :initarg :χρώμα :accessor χρώμα)))
(defclass χρωματιστό-ορθογώνιο (χρωματιστό ορθογώνιο))
(setq πόρτα1 (make-instance 'χρωματιστό-ορθογώνιο
:ύψος 210 :πλάτος 70 :χρώμα 'άσπρο))
(defgeneric βάψιμο (x y z w) )

; δηλώνεται 4 ορισμάτων και απλώς συνδέεται με τη ρίζα Τ
(defmethod βάψιμο
((αντικείμενο χρωματιστό-ορθογώνιο) νέο-χρώμα τρόπος βαφέας)
(<συνάρτηση - αλγόριθμος βαφής> νέο-χρώμα τρόπος βαφέας)
(setf (slot-value αντικείμενο 'χρώμα) νέο-χρώμα))

Κλήση:
(βάψιμο πόρτα1 'μπλε 'πινέλο 'Γιάννης)

Τώρα το αντικείμενο πόρτα1 έχει χρώμα μπλε.
Ο τρόπος και ο βαφέας καθορίζουν την τιμή από ανάλογες υποδοχές ή άλλες καταστάσεις, πχ.
να αυξάνουν την τιμή από ένα καθολικό σύμβολο - μετρητή "πόσες πόρτες έβαψε ο Γιάννης"
Η <συνάρτηση - αλγόριθμος βαφής> έχει ορίσματα μπλε, πινέλο, Γιάννης και μπορεί να αφορά
υποδοχές του αντικειμένου πόρτα1 ή/και υποδοχές άλλων αντικειμένων, όπως το αντικείμενο
Γιάννης με υποδοχές αμοιβή, κρατήσεις και περιεχόμενο αθροιστικό, ή να αφορά σύμβολα μεταβλητές του προγράμματος, που αφορούν πχ. την ποσότητα χρώματος που χρησιμοποιήθηκε.

Οπαραπάνω τρόπος είναι απλός, αλλά έχει ένα εγγενές μειονέκτημα: η δεύτερη κλάση
χρωματιστό αποτελεί αναπόσπαστα πλέον μέρος του κάθε στιγμιότυπου που δημιουργείται από
τις δύο κλάσεις. Αν θα θέλαμε να δώσουμε μια μορφή προσωρινότητας στα επιπρόσθετα
χαρακτηριστικά όπως το χρώμα, θάπρεπε να επαναδημιουργούμε κάθε φορά το αντίστοιχο
αντικείμενο, διαγράφοντας το προηγούμενο.
Είδαμε δύο "λύσεις" σ' αυτό, με τη δημιουργία λιστών αντικειμένων ή λίστας συσχέτισης, αλλά
αυτές οι λύσεις ξεφεύγουν από τα πλαίσια της επεξεργασίας αντικειμένων. Μια άλλη λύση είναι
να δημιουργηθεί η κλάση των προσθέτων χαρακτηριστικών (ή όσες κλάσεις θέλουμε) και να
συνδέσουμε μέσω μεθόδου τα χαρακτηριστικά αυτά.
Τέτοιες μέθοδοι μπορούν να είναι πολλές, συνδυάζοντας η καθεμιά το κυρίως αντικείμενο
(τύπο του) με διάφορες κατηγορίες προσθέτων χαρακτηριστικών, σε διάφορα επίπεδα
γενικότητας και ανάλογα με το context αναφοράς, ακόμα και με χρονικούς περιορισμούς.
400

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

Διαμόρφωση κλήσεων μεθόδων
Στο "κλασικό" μοντέλο αντικειμένων υπάρχει μια θεμελιακή έννοια που αφορά τις μεθόδους, ο
εγκλεισμός ή όπως αλλιώς λέγεται, η οριοθέτηση (encapsulation). Στο CLOS δεν καθορίζεται
ρητά αλλά δημιουργείται έμμεσα.

Σε γενικές γραμμές, δεν έχουμε τεχνητά φράγματα στο "ποιοί βλέπουν" αντικείμενα και
μεθόδους, ούτε στο "τί βλέπουν" αυτά, για δύο απλούς λόγους:
i) ο εγκλεισμός των υπολογισμών (λ-έκφραση στο σώμα λ-έκφρασης) είναι η κυρίαρχη έννοια
εγκλεισμού μεταβλητών και δεν μπορεί να παραβιαστεί, και επιπλέον επαρκεί για οποιαδήποτε
ανάγκη τοπικότητας υπολογισμών
ii) δεν χρειαζόμαστε τη διαδικαστική έννοια του εγκλεισμού μεθόδων (με δηλώσεις όπως
"public", "privet") διότι είναι αυτονόητο ποια μέθοδος εφαρμόζεται κατά περίπτωση, σύμφωνα με
τη λίστα προτεραιότητας που συνοδεύει κάθε αντικείμενο ή, αν δεν θέλουμε αυτή τη μεθόδευση,
σύμφωνα με τη σειρά κλήσεων που δηλώνουμε (με χρήση της CALL-NEXT-METHOD).

8.3.3 Γραφικά
Το νεότερο standard της ANSI CL περιλαμβάνει και γραφικά, και αναφέρεται ένα μεγάλο πλήθος
σχετικών ειδικών συναρτήσεων. Την πλήρη ανάπτυξη των γραφικών δυνατοτήτων μπορεί να βρει
κανείς στο ANSI standard της CL και στην έκδοση Allegro CL.
Χαρακτηριστικό των γραφικών είναι οτι αναπτύσσονται κάτω από το CLOS. Κατ' αρχήν
απαιτείται δημιουργία παράθυρου γραφικών (δηλ. τύπου που να υποστηρίζει γραφικά), και στη
συνέχεια δημιουργία του γραφικού αντικειμένου. Όλα βέβαια γίνονται με εφαρμογή
συναρτήσεων, αλλά ο χρήστης οφείλει να έχει υπ' όψη του πότε πρόκειται για συναρτήσεις
δημιουργίας και επεξεργασίας αντικειμένων και πότε κοινών συναρτήσεων, διότι αλλιώς μπορεί
να βρεθεί "προ εκπλήξεων". Πχ, η MAKE-WINDOW δημιουργεί παράθυρο γραφικών, στιγμιότυπο
της κλάσης window, που με χρήση της SETQ το ονοματίζουμε∙ αν μεταβάλλουμε το περιεχόμενο
του ονόματος, το παράθυρο θα παραμείνει ως αντικείμενο αλλά ανώνυμο πλέον, μη προσεγγίσιμο
σε ψηλό επίπεδο.
Στην κατασκευή των γραφικών, μπορούμε να χρησιμοποιήσουμε τις συναρτήσεις κατασκευής με
στόχο απλά "να δούμε τα γραφικά", ή με στόχο την κατασκευή από αντικείμενα που
παριστάνονται γραφικά (που θα έχουν ως υποδοχές τα κατασκευαστικά τους στοιχεία, θέση,
συνδέσεις με άλλα, εξαρτήσεις από άλλα, κλπ.).
401

Παράδειγμα
Να κατασκευαστεί οικογένεια κύκλων.
Βήμα 1ο: Δημιουργία παράθυρου γραφικών, με όνομα bmwin , ως στιγμιότυπο της κλάσης
window , τύπου bitmap (βλέπε "graphics index" της Allegro CL v.5.0.1, που δίνεται στη
διεύθυνση: http://www.franz.com/):
(setq graf (make-window :graf :device 'bitmap-window))

Βήμα 2ο: Ορισμός συνάρτησης κατασκευής οικογένειας n κύκλων επαναληπτικά, αρχικού
κέντρου (x,y) , όπου σε κάθε επανάληψη ο επόμενος κύκλος μετατοπίζεται και μεγαλώνει κατά
την τάξη επανάληψης, μοιρασμένοι σε ομάδες και χρωματισμένοι, όπου να μεσολαβεί διακοπή m
δευτερολέπτων από κύκλο σε κύκλο
(defun circs (n x y k m)
(progn (clear-page graf)
(let ( (lis (list red cyan yellow black green blue)) )
(dotimes (i n)
(let ((color (nth (mod i k) lis))
(center (make-position
(+ (* x i) (* i (truncate (/ i k))))
(+ y 40 (* i (mod i k))))))
(setf (foreground-color graf) color)
(fill-circle graf center i) (sleep m) )))))

Εφαρμογή:
(circs 48 8 8 6 0.1)

402

8.3.4 Αξιολόγηση της χρήσης αντικειμένων στον ΣΠ
Πλεονεκτήματα
Η ευχέρεια και η αποτελεσματικότητα κτισίματος από πολυσύνθετες κλάσεις, ιεραρχικές ή μη,
και η δημιουργία μεθόδων σχετικών με συγκεκριμένους κόμβους, είναι σε πολύ μεγάλο βαθμό
απλούστερη από αυτό που θα απαιτούσε η απόδοση των εννοιών με συνήθεις συνθέσεις
συναρτήσεων∙ το ίδιο και η αναζήτηση / καταχώρηση πληροφορίας μέσα σ' αυτές και η εφαρμογή
μεθόδων πάνω σε στιγμιότυπα.
Η διάκριση επιπέδων γενικότητας που επιτυγχάνεται με τις κλάσεις είναι άμεση και απορρέει
από τον ίδιο τον ορισμό των κλάσεων και υποκλάσεών τους, χωρίς καμμία προγραμματιστική
προσπάθεια, όπως θα χρειαζόταν να γίνει αν χρησιμοποιούσαμε λίστες και λ-εκφράσεις.

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

Η προκαθορισμένη και σταθερή μορφή της κάθε δομής και των στιγμιότυπων της, καθιστά το
χώρο σαφή, με αποτελέσματα όχι μόνο βέβαια και προβλεπόμενα, αλλά και "ευθέως ορατά": η
περιγραφή μεθόδου έχει την απλότητα της περιγραφής τού "τί θα γίνει στη συγκεκριμένη
περίπτωση" και η εφαρμογή της σε κατάλληλα στιγμιότυπα αποκτά πολύ εύκολα τη βεβαιότητα
εφαρμογής ενός συνεπούς διαδικαστικού αλγόριθμου, ενώ η ευκολία για εκ των υστέρων
επέκταση της μεθόδου σε άλλες περιπτώσεις, είναι αναντικατάστατη.

Κοιτάζοντας από τη σκοπιά της συναρτησιακής οργάνωσης προγράμματος, η χρήση κλάσεων,
στιγμιοτύπων και μεθόδων δεν μας απαγορεύει τη χρήση συναρτήσεων που αναφέρονται στις
οντότητες αυτές.
Η δυνατότητα οργάνωσης παραθύρων και γραφικών, είναι ευχερής, πολύ πλούσια, και επιτρέπει
αλληλεπίδραση συνυφασμένη με το συγκεκριμένο παράθυρο ή/και το γραφικό αντικείμενο.

Η αυστηρότητα της προδιαγραμμένης μορφής των κλάσεων και της δομής που δημιουργούν,
χαλαρώνει αρκετά με τη δυνατότητα που μας δίνει το CLOS για μεταφορά αντικειμένου από την
κλάση του σε άλλη.

Σε γενικές γραμμές, οι δομές που ορίζονται μέσω της DEFSTRUCT απαιτούν κάπως πιο
χειρωνακτικό τρόπο ανάπτυξης των επιθυμητών συνδέσεων και λειτουργιών, ενώ οι κλάσεις που
ορίζονται μέσω της DEFCLASS είναι πιο ευέλικτες. Οι πρώτες είναι σε μεγάλο βαθμό στατικές
ενώ οι δεύτερες είναι πολύ πιο δυναμικές.
Κατά κάποιο τρόπο, χρησιμοποιώντας κλάσεις, αντικείμενα και μεθόδους, εμείς περιγράφουμε
μόνο τί θέλουμε να γίνει, και το περιβάλλον προγραμματισμού το αναπτύσσει.

403

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

Παράλληλα με τις δυνατότητες, διαπιστώνει κανείς κάποια δυσχέρεια στην "κίνηση" μέσα στο
χώρο των κλάσεων και των στιγμιότυπων τους. Ειδικά για τα στιγμιότυπα που εντάσσονται στην
ίδια οικογένεια, χρειάζεται προσπάθεια για τον εντοπισμό της μεταξύ τους συγγένειας. Το
ενωτικό είναι χρήσιμο για την κατασκευή ειδικότερων αντικειμένων αλλά όχι και για την
αναγνώριση των συσχετισμών εξειδίκευσης.

Για την αναζήτηση "ποιος ακριβώς κληρονομεί ποιόν και με ποια σειρά" απαιτείται εφαρμογή
συναρτήσεων χαμηλού επιπέδου, που είναι πολύ δύσχρηστες σε σχέση με το "νοηματικά
εύπλαστο" των αντικειμένων, κλάσεων και μεθόδων.

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

Η εφαρμογή συναρτήσεων που έρχονται "έξω από το χώρο των αντικειμένων" άλλοτε είναι
αναγκαία για να πετύχουμε τη ζητούμενη λειτουργικότητα (όπως για να μεταβάλλουμε τιμή
υποδοχής με την SETF) και άλλοτε γίνεται για ευκολότερη εξυπηρέτηση του στόχου μας (όπως
για την ομαδοποίηση αντικειμένων βάσει του context αναφοράς). Όμως αυτή η ανάμειξη
συνδυάζει ετερόκλητες έννοιες: μοιάζει περισσότερο με "χειρουργική επέμβαση" παρά με φυσική
διαμόρφωση ων αντικειμένων, και ανοίγει δρόμους πρόκλησης σφαλμάτων.
Για παράδειγμα, η ονομασία αντικειμένου με την SETQ
(setq Κώστας (make-φοιτητής ...))

δεν είναι πραγματικά ονομασία, αλλά σύνδεση που ξεκινά από το όνομα και πηγαίνει στο
περιεχόμενο: μπορούμε να αλλάξουμε το περιεχόμενο του συμβόλου αλλά για να αλλάξουμε το
όνομα του αντικειμένου πρέπει να κατέβουμε σε χαμηλά επίπεδα, "φυσικών θέσεων" . Αν
ξανακαλέσουμε:
(setq Κώστας (make-φοιτητής ...))

το όνομα "Κώστας" θα ονομάζει το νέο αντικείμενο - φοιτητή, ενώ το προηγούμενο αντικείμενο
θα μείνει ανώνυμο και, ενώ θα εξακολουθεί να υπάρχει, θα είναι απροσπέλαστο.
Η δυνατότητα χρήσης λ-εκφράσεων στα πλαίσια του λογισμού αντικειμένων περιορίζεται πολύ:
αν και δεν απαγορεύεται, η δέσμευση λ-μεταβλητής σε οντότητες από το χώρο αυτό (κλάσεις,
αυτά καθ' εαυτά τα φυσικά στιγμιότυπα, μέθοδοι...) είναι έννοια "εκτός του μοντέλου" και εύκολα
θα προκαλέσει προβλήματα.

Αυτός ο περιορισμός είναι ένα πρόβλημα εγγενές των macro συναρτήσεων: ο κατασκευαστής
της γλώσσας ή ο χρήστης οργανώνουν σε μορφή συνάρτησης macro, μια λειτουργικότητα "πιο
εντοπισμένη" εννοιολογικά προς το σκοπό χρήσης∙ κτίζει έτσι ένα κόσμο που έχει τη δική του
σημασιολογία, πολύ ευέλικτο αν κινηθεί ο χρήστης στα πλαίσιά της, αλλά εξαιρετικά δυσκίνητο
αν έχει κατά νου μια άλλη σημασιολογία.
Ο εγκλεισμός (encapsulation) δεν γίνεται με τρόπο σύμφωνο προς το θεωρητικό μοντέλο των
αντικειμένων, αλλά με τρόπο εξαρτημένο από τους τύπους, και συγκεκριμένα από τον εξειδικευτή
404

τύπου εισόδου. Έτσι, οι μέθοδοι δεν ανήκουν σε κλάσεις, αλλά σχετίζονται με κλάσεις, και η
συσχέτιση είναι πάντα χαλαρή: μπορούμε να επαναπροσδιορίσουμε τη διαδοχή ταιριάγματος ή
απ' ευθείας κλήσης μεθόδων. Αυτό κατ' άλλους είναι πλεονέκτημα, διότι ανοίγει ευρύτερους
δρόμους υπολογισμού, και κατ' άλλους μειονέκτημα, διότι παραβιάζει τον προσανατολισμό στα
αντικείμενα και χάνονται οι σχετικές δυνατότητες.

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

α'.
Κάτω από το πρίσμα της επέκτασης των συναρτησιακών δυνατοτήτων, ο
προσανατολισμός που παίρνει η Lisp με τη χρήση της DEFSTRUCT είναι μια πρόσθετη και
ουσιαστική ευκολία ανάπτυξης κάτω από το συναρτησιακό μοντέλο. Συνδυάζει
αποτελεσματικότητα με ευκολία έκφρασης και οργάνωσης, χωρίς να επιβάλλει ουσιαστικούς
περιορισμούς στη συναρτησιακή σύνθεση.
Η χρήση της DEFCLASS ανοίγει δρόμους έκφρασης που απομακρύνονται από τη βασική
συναρτησιακή δόμηση, και δυσχεραίνει σε μεγάλο βαθμό την διασύνδεση συναρτησιακών
εννοιών με έννοιες αντικειμένων. Αν πραγματικά είναι αναγκαίο να γίνει συναρτησιακή
επεξεργασία, όπως διαχείριση της λίστας προτεραιότητας κλάσης, χρειάζεται να κατεβεί ο
προγραμματιστής χαμηλότερα στον προγραμματισμό, δυσανάλογα χαμηλά σε σχέση με τα
νοήματα που εκφράζονται.

β'.
Κάτω από το πρίσμα της εξυπηρέτησης του μοντέλου των αντικειμένων, οι δυνατότητες
της DEFSTRUCT είναι ανεπαρκείς για να καλύψουν όλες τις απαιτήσεις του προσανατολισμού
προς τα αντικείμενα, αλλά βελτιώνεται πολύ με τη χρήση της DEFCLASS και των συναφών με
αυτή συναρτήσεων (CLOS). Όμως κάτω από το πρίσμα αυτό, πρέπει να κριθεί και η σκοπιμότητα
της αντίστοιχης επέκτασης, και να ληφθεί απόφαση επιλογής, ανάμεσα από πολλές διαβαθμίσεις,
μεταξύ ακραίων καταστάσεων:
i) να μείνει ο προγραμματισμός σε αυστηρά συναρτησιακά πλαίσια, αποφεύγοντας το χώρο
του CLOS
ii) να θεωρηθεί ως βασική προγραμματιστική προσέγγιση το συναρτησιακό μοντέλο και κάτω
από αυτό το CLOS, επεκτείνοντας τις βασικές δυνατότητες, με τις δυσχέρειες που προαναφέραμε
iii)
απλώς να συνυπάρχουν και να εκμεταλλεύεται ο χρήστης κατά περίπτωση ότι αποδίδει
καλύτερα (με όλους τους κινδύνους που συνεπάγεται αυτή η συνύπαρξη).
iv)
να αποτελεί τη βασική προσέγγιση το CLOS, και το συναρτησιακό να θεωρείται ως
τρόπος επαύξησης των δυνατοτήτων του αντικειμενοστρεφούς τρόπου προγραμματισμού (πχ.
ευελιξία μεταβολών με χρήση της SETF), με το ενδεχόμενο πρόκλησης καταστροφών από
επεμβάσεις σε εσωτερικά δομικά στοιχεία που από την επιφάνεια δεν φαίνονται να έχουν σχέση
(πχ. ιδιότητες συμβόλου και υποδοχές κλάσης)
405

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

406

8.5. Ασκήσεις

1. α)
Να δημιουργήσετε: μια κλάση παπούτσια με υποδοχές μέγεθος , τύπος , όπου τύπος
να έχει τιμή από το σύνολο {σπορ, πέδιλα, κλασικά, μοντέρνα}, τιμή , ιδιοκτήτης με αρχική
τιμή κατάστημα . Επίσης, τις υποδομές παιδικά , ανδρικά , γυναικεία .
β) Να δημιουργήσετε: μια κλάση άνθρωπος με υποδοχές τις συνήθεις μετρήσεις του
σώματος (όπως: ύψος, πέλμα, μέση, λαιμός, πλάτη, σε cm) και άλλα χαρακτηριστικά των
ατόμων (όπως: συμπεριφορά) με υπο-δομές παιδί, άνδρας, γυναίκα.
γ)

Να δημιουργήσετε μια λίστα οικογένεια στιγμιότυπων ανθρώπων.

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

Να σχηματίσετε συνάρτηση ψώνια που να δέχεται είσοδο τις λίστες οικογένεια και
βιτρίνα_παπουτσιών, και να υπολογίζει, με ταίριαγμα, τη λίστα των ζευγών "ποιος ψώνισε
ποιά παπούτσια" με βάση την προηγούμενη FITS , δεδομένου οτι ο καθένας θα ψωνίσει το
πολύ ένα ζεύγος παπούτσια, αν βρει κατάλληλο.

2. Να δημιουργήσετε κλάση σχήμα με ειδικές περιπτώσεις: τρίγωνο, με υποδοχές μεταξύ άλλων,
βάση και ύψος∙ τετράγωνο με υποδοχή πλευρά∙ εξάγωνο με υποδοχή πλευρά∙ κύκλος με
υποδοχή ακτίνα. Να προσδιορίσετε για κάθε περίπτωση μέθοδο εμβαδόν που να υπολογίζει το
εμβαδόν συγκεκριμένων σχημάτων (για δεδομένες τιμές των αντιστοίχων υποδοχών) και
ειδικότερα:
– αν ο χρήστης ζητήσει εμβαδόν τέτοιων σχημάτων αλλά με άλλα δεδομένα, να τυπώσει
μήνυμα "δεν προβλέφθηκαν οι υποδοχές …, πρέπει να προσδιορίσεις αντίστοιχη υπο-δομή,
και να δώσεις μέθοδο υπολογισμού εμβαδού"

– αν ζητηθεί εμβαδόν σχήματος το οποίο δεν έχει προσδιοριστεί, να τυπώσει μήνυμα "δεν
προβλέφθηκε το σχήμα ..., προσδιόρισε την υπο-δομή ... και δώσε τη μέθοδο ...".
3. α)

Να

δημιουργήσετε κλάση υπάλληλος με υποδοχές μηνιαίες_αποδοχές,
κρατήσεις_ταμείων, κράτηση_φόρου, βαθμός, και υποκλάσεις δημόσιος_υπάλληλος και
ιδιωτικός_υπάλληλος.

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

4. Να δημιουργήσετε κλάση ταξείδι με υποδοχές μέσον, τόπος_εκκίνησης, χρόνος εκκίνησης,
τόπος_προορισμού , χρόνος άφιξης , χρέωση , εξώφληση . Υποκλάση ταξείδι_με_επιιστροφή
407

, με κατάλληλες επί πλέον υποδοχές. Επίσης, υποκλάση ταξείδι_τουρισμού με επί πλέον
υποδοχές που να αφορούν το ξενοδοχείο (όπως κράτηση, προκαταβολή, εξώφληση).
α) Να προσδιορίσετε μέθοδο που να υπολογίζει το κόστος συνδυασμένου ταξειδιού που κάνει
κάποιος, ο οποίος ταξιδεύει ξεκινώντας από τον τόπο Α , περνά από τους τόπους Β και Γ , και
τελικά επιστρέφει στο Α , και να προσδιορίσετε συνάρτηση που να υπολογίζει το μικρότερο
κόστος ενός τέτοιου ταξειδιού, συγκρίνοντας διάφορες δεδομένες περιπτώσεις.
β) Να προσδιορίσετε τρόπο περιγραφής ενός τουριστικού ταξειδιού με ΙΧ, τεσσάρων
συγκεκριμένων ατόμων (δύο ζευγάρια), από την Αθήνα στη Βενετία, και μεθόδους που να
προσδιορίζουν τις συνήθεις σε ένα τέτοιο ταξείδι πληροφορίες όπως πρόβλεψη κόστους,
κράτηση θέσεων / δωματίων, πραγματοποιημένα κοινά και ατομικά έξοδα.

408

9 Τεχνικές Συναρτησιακής Δόμησης
Στο κεφάλαιο αυτό θα περιγράψουμε διάφορες τεχνικές αξιοποίησης της συναρτησιακής
προσέγγισης και θα αναλύσουμε τη σημασία τους. Θα δούμε τρόπους υπολογιστικής
αντιμετώπισης ζητημάτων που προσδιορίζονται σημασιολογικά, άλλων που παρουσιάζουν
γενικότητα, και άλλων που εκφράζονται μέσω εννοιών σύνθεσης και αφαίρεσης.
Τέτοια θέματα έχουμε συναντήσει σε προηγούμενα κεφάλαια, σαν παραδείγματα χρήσης
συγκεκριμένων συναρτήσεων. Σ' αυτό το κεφάλαιο επικεντρώνουμε στις προγραμματιστικές
τεχνικές που εφαρμόσαμε, και στους ευρύτερους στόχους που εξυπηρετούνται.

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

Τρόποι δημιουργίας ενός σ.δ.
Είδαμε περιπτώσεις σημασιολογικών δικτύων, ανεπτυγμένων κάτω από διαφορετικές
προσεγγίσεις αναπαράστασης, υλοποίησης και επεξεργασίας. Τέτοιες αναπαραστάσεις είναι:

α'. Με λίστα συμβόλων τα οποία έχουν τιμή λίστα συμβόλων κοκ, δενδροειδώς μέχρι φύλλων
(τα οποία είναι σύμβολα με περιεχόμενο την κενή λίστα), όπου το "ανήκει στη λίστα" δηλώνει το
"ανήκει στην κατηγορία".

Αυτός ο τρόπος υποστηρίζει δυναμικές μεταβολές, διότι η ονομασία των κόμβων με σύμβολα
επιτρέπει: i) την ανεξάρτητη επεξεργασία τους (ανάγνωση / διαμόρφωση περιεχομένου, ακόμα
και ως ολόκληρου κλάδου του δένδρου), ii) την είσοδο στο δίκτυο απ' ευθείας από τον
επιθυμητό κόμβο, είτε για αναζήτηση πληροφορίας στον ειδικό κόμβο είτε για σάρωση από κει
και κάτω, iii) τη διασύνδεση συμβόλων με σχέση γονέα - παιδιού και την εγκατάσταση
κληρονομικότητας σε ολόκληρη τη δομή, όπου η κληρονομικότητα παρακολουθεί λόγω της
409

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

Με εκμετάλλευση της λίστας ιδιοτήτων συμβόλου, μπορούμε να συνδέσουμε πολλαπλά ένα
κόμβο, πχ. σύμβολο με ιδιότητες "έχει-τα", "είναι", "αποτελείται-από", "συνεπάγεται-τα",
"αντιφάσκει-με-τα", κλπ, και καθεμία με τιμή τη λίστα των αντιστοίχων συμβόλων.
Η πολύπλευρη συσχέτιση οντότητας, μέσω ιδιοτήτων, είναι ευρύτερη από την αποκλειστική
συσχέτιση μέσω τιμής, διότι μας επιτρέπει να θεωρούμε πολλαπλούς κανόνες μεταβατικότητας
χαρακτηριστικών, ανεξάρτητους μεταξύ τους. Επιτρέπει ακόμα, και με πολύ απλό τρόπο, την
επιπρόσθετη απόδοση ειδικών (μη "κληρονομούμενων") χαρακτηριστικών σε κάθε κόμβο.
β'.
Ο προσδιορισμός δικτύου με κόμβους κλάσεις και υποκλάσεις τους, είτε ιεραρχικό είτε
μη, εκμεταλλεύεται τις πλούσιες δυνατότητες του CLOS για αναπαράσταση εννοιών και για
δυναμικές επεκτάσεις και -σε κάποιο μέτρο- μεταβολές. Ταιριάζει πολύ καλύτερα από κάθε άλλη
προγραμματιστική προσέγγιση στην περίπτωση δικτύου όπου δεν έχουμε συχνές μεταβολές
συσχετισμών, αλλά είναι αρκετά δυσκίνητο όταν θεωρούμε μεταβολές που αλλάζουν ριζικά τη
δομή ή θεωρούμε πως υφίστανται πολλαπλές δομές πάνω στις ίδιες οντότητες, με διαφορετικές
εξαρτήσεις και διαφορετικές ανάγκες μεταβολών∙ τότε πλεονεκτεί η πρώτη προσέγγιση, που
επιτρέπει ταυτόχρονη θεώρηση περισσοτέρων, ανεξαρτήτων δομών σχηματισμένων από τις ίδιες
οντότητες.
Σε όλες τις περιπτώσεις μπορούμε να ονομάσουμε το δίκτυο με ένα σύμβολο, και αν πρόκειται
για ιεραρχική δομή μιας σχέσης, το όνομα του δικτύου αποτελεί και τη ρίζα του αντίστοιχου
δένδρου. Αν δεν πρόκειται για ιεραρχική δομή, το όνομα μπορεί να προσδιορίζει γενικά τον τόπο
όπου προσδιορίζεται το δίκτυο, ή τη ρίζα εκκίνησης της αναζήτησης.

Ιδιόμορφες περιπτώσεις σ.δ.
Οι ακόλουθες περιπτώσεις δικτύων ενδιαφέρουν ιδιαίτερα, όπου οι συσχετισμοί αντιμετωπίζονται
μέσω συναρτήσεων οι οποίες "ψάχνουν μέσα στο περιεχόμενο των συμβόλων":
α'.
Όταν η χαρακτηριστική σχέση που συσχετίζει κόμβους δεν δημιουργεί κάποια ιεραρχία, ή
έστω δεν είναι άμεσα ορατή (πχ. "a contradics-to b").

Τέτοιοι συσχετισμοί είναι προτιμότερο να δίνονται ως σύνολα ζευγών (πχ. ως λίστες
συσχέτισης), απ' όπου ειδικές συναρτήσεις θα συνάγουν παραγωγικά τις υφιστάμενες στο δίκτυο
σχέσεις, όπου και όταν υφίστανται. Ένας απλός τρόπος ενημέρωσης ολόκληρου του δικτύου μετά
από κάθε εγγραφή, είναι με σάρωση που θα εφαρμόσει σε κάθε δυνατή περίπτωση ζεύγους τις
συνεπαγόμενες σχέσεις∙ άλλος τρόπος είναι να δημιουργούμε δυναμικά τις σχέσεις όταν κληθούν
οι αντίστοιχες οντότητες.
β'. Όταν αναφέρονται περισσότερες της μιας χαρακτηριστικές σχέσεις, τέτοιες που η καθεμιά να
δημιουργεί μια ιδιαίτερη ιεραρχική δομή, διαφορετική των άλλων.

Μια λύση είναι να αναπαραστήσουμε κάθε οντότητα ως αυτόνομο σύμβολο με τις
υφιστάμενες ιδιότητές του, και να δώσουμε συναρτήσεις οι οποίες θα οργανώνουν το αντίστοιχο
410

δίκτυο για κάθε περίπτωση σχέσης.
Πρόβλημα γεννάται όταν οι πολλαπλές σχέσεις δεν είναι ανεξάρτητες μεταξύ τους, και η θέση
ή άρση μιας συγκεκριμένης σχέσης μεταξύ δύο οντοτήτων επηρεάζει κάποιες άλλες σχέσεις. Η
καλύτερη αντιμετώπιση από αυτό το πρόβλημα είναι η εγκατάσταση καθυστερημένων
εφαρμογών, συνδεδεμένων με τα σύμβολα - κόμβους, που θα ενεργοποιούνται μετά από κάθε
σχετική κλήση, και θα ενημερώνουν τις εξαρτημένες σχέσεις.
γ'. Όταν το δίκτυο στο οποίο αναφερόμαστε εξελίσσεται δυναμικά.
Διακρίνουμε διάφορα επίπεδα εξέλιξης, όπως: εγγραφή / διαγραφή συγκεκριμένων κόμβων
(απαιτεί στη συνέχεια σάρωση του δικτύου για διόρθωση)∙ εγγραφή / διαγραφή συσχετισμών
κατά συμπέρασμα, βάσει των υφισταμένων κανόνων∙ εγγραφή / διαγραφή συσχετισμών βάσει
νέων σχέσεων που επισυνάπτονται.
Τον πιο σημαντικό ρόλο στην εξέλιξη δικτύου παίζει το αν οι συσχετισμοί συνδέονται με
άλλους συσχετισμούς ή όχι, διότι στην πρώτη περίπτωση απαιτείται μηχανισμός διόρθωσης του
δικτύου, ή εναλλακτικά, αμέσου ενημέρωσης των "ενδιαφερομένων" μερών, όπως έχουμε
αναφέρει.
Απλά παραδείγματα δικτύων είδαμε στις λίστες και στις κλάσεις.

9.2 Από τη σύνθεση στην αφαίρεση
Στην ενότητα αυτή θα δούμε τρόπους προγραμματισμού που συνδυάζουν σύνθεση με αφαίρεση
πολλαπλών επιπέδων, συνδυάζοντας από το ένα μέρος τις λ-εκφράσεις και από το άλλο μέρος
τους νοητικούς συσχετισμούς διαφόρων επιπέδων που αντιλαμβάνεται ο χρήστης.
Η σύνθεση και η αφαίρεση είναι τεχνικές συναρτησιακού προγραμματισμού που αφορούν τόσο
τον αρχικό προγραμματιστή όσο και τον τελικό χρήστη.

9.2.1 Δέσμευση λ-μεταβλητής σε συναρτησιακή έκφραση
Συναντήσαμε σε προηγούμενα την τεχνική δέσμευσης λ-μεταβλητής σε συναρτησιακή έκφραση
(συνάρτηση ή λ-έκφραση). Θα δούμε εδώ μια σύνθεση που αξιοποιεί αυτή τη δυνατότητα
παραμετροποιώντας τις χρησιμοποιούμενες συναρτήσεις με τρόπο που να επιτρέπει αφαίρεση
προς συγκεκριμένη κατεύθυνση, και συγκεκριμένα:
– αφαίρεση του γενικού όρου μιας ακολουθίας (αντί να δίνεται συγκεκριμένη ακολουθία),
και
– αφαίρεση του τρόπου μετάβασης από όρο σε όρο (αντί να είναι απλώς η επόμενη τιμή του
δείκτη).

411

Παράδειγμα
Υπολογισμός αθροίσματος που δίνεται ως σειρά με γενικό όρο δοσμένο συναρτήσει της τάξης του,
από μια ακέραιη αρχική τιμή του δείκτη μέχρι μια άλλη, μεγαλύτερη, με κάποιο βήμα μεταβολής
του δείκτη καθοριζόμενο από μια σχέση.
Ορίζουμε συνάρτηση SERIES υπολογισμού αθροίσματος σειράς, με τα εξής ορίσματα:
– πρώτο όρισμα, n-term, είναι η συνάρτηση που προσδιορίζει το ν-οστό όρο της σειράς
– δεύτερο όρισμα, next, η συνάρτηση μετάβασης στον επόμενο δείκτη
– τρίτο όρισμα, a, είναι η αρχική τιμή του δείκτη (κάτω άκρο)
– τέταρτο όρισμα, b, είναι η τελική τιμή του δείκτη (άνω άκρο).
Προσδιορίζουμε τον υπολογισμό αναδρομικά, όπου μεταβάλλουμε το κάτω άκρο, θέτοντάς το
να είναι και ο τρέχων δείκτης στην αναδρομή:
(defun series (n-term next a b)
(cond
((> a b) 0)
(t (+ (funcall n-term a)
(series n-term next (funcall next a) b) ) ) ) )

Ερμηνεία:
"αν a > b τότε η SERIES έχει τιμή 0 , αλλιώς είναι το άθροισμα του τρέχοντα όρου, με τη
series εφαρμοσμένη στις ίδιες συναρτήσεις n-term και next, για τον επόμενο δείκτη, και με
τελικό δείκτη τον ίδιο"
Εφαρμογή:
(series #'square #'1+ 2 5)
→ 54

9.2.2 Τα πολυώνυμα ως αφαιρετικές υπολογιστικές οντότητες
Τα πολυώνυμα αποτελούν αφαιρετικές υπολογιστικές οντότητες διότι μπορούν να
συγκεκριμενοποιηθούν σε διάφορες τιμές ανάλογα με την τιμή της μεταβλητής τους (ή κάποιων
από τις μεταβλητές τους, αν έχουν περισσότερες της μιας). Αν δοθούν ως συναρτήσεις της Lisp,
δεν διαφέρουν σε τίποτα στη συμπεριφορά τους από οποιεσδήποτε άλλες μαθηματικές
συναρτήσεις. Έχουν όμως την ιδιομορφία οτι υπακούουν σε συγκεκριμένους νόμους μαθηματικές ιδιότητες, που καθορίζουν τις πράξεις μεταξύ πολυωνύμων, πράξεις με σταθερές,
εφαρμογή ειδικών τελεστών, απ' όπου προκύπτουν πάλι πολυώνυμα δηλαδή καθορίζουν τους
αλγόριθμους εύρεσης του αποτελέσματος σε κάθε περίπτωση.
Η περιγραφή τέτοιων αλγόριθμων είναι ευκολότερη και πιο γενική αν χρησιμοποιήσουμε
αναπαραστάσεις των πολυωνύμων που να καλύπτουν τόσο την υπολογιστική τους υπόσταση
(δηλ. αναγνωρίσιμες από τη Lisp ως συναρτησιακές εκφράσεις) όσο και τη Μαθηματική.
Η επεξεργασία πολυωνύμων μιας μεταβλητής περνά από συσχετισμούς ομοίων όρων (αναγωγές,
πράξεις μονώνυμων). Η γραφή σε μορφή λ-έκφρασης δεν εξυπηρετεί, διότι πρέπει να
412

συσχετίσουμε όρους του πολυωνύμου μεταξύ τους και με όρους άλλου πολυώνυμου σε κάθε
είδους πράξη, διακρίνοντας κατά περίπτωση μια μεταβλητή (μεταξύ των λ-μεταβλητών) προς την
οποία θεωρούμε συνταγμένο το πολυώνυμο. Είναι απλούστερο να δώσουμε ένα τρόπο
αναπαράστασης πολυώνυμου που να παρέχει άμεσα τα εξής στοιχεία: ποια είναι η μεταβλητή του
πολυώνυμου, ποιά τάξη έχει κάθε μονώνυμό του, και ποιό συντελεστή έχει. Η αναπαράσταση
αυτή πρέπει να είναι άμεσα αναγνωρίσιμη από τη γλώσσα και εύκολα μετατρέψιμη στη συνήθη
μαθηματική μορφή.
Για το σκοπό αυτό ακολουθούμε αναπαράσταση με ζεύγος, όπου πρώτο μέλος είναι η
μεταβλητή του πολυωνύμου (το σύμβολό της) και δεύτερο μέλος μια λίστα συσχέτισης, που
περιέχει ζεύγη αποτελούμενα από την τάξη του όρου και τον αντίστοιχο συντελεστή. Μια
παραλλαγή της αναπαράστασης, είναι να θέσουμε απλή λίστα ως δεύτερο μέλος, τη λίστα των
συντελεστών κατ' αύξουσα τάξη (προσέχοντας να μη παραλείψουμε τους μηδενικούς
συντελεστές).
Ακολουθώντας την πρώτη πορεία, η γενική μορφή του πολυώνυμου θα είναι:
(<όνομα μεταβλητής> . <λίστα συσχέτισης>)
όπου οι όροι της λίστας διασύνδεσης είναι:
(<εκθέτης τής μεταβλητής> . <συντελεστής του όρου>)
Δηλαδή ένα πολυώνυμο θα παρίσταται ως:
(var . ((n . an) ((– n 1) . an-1) ((– n 2) . an-2) … (1 . a1) (0 . a0)))
Κατά την αναπαράσταση αυτή παραλείπουμε τους όρους με μηδενικό συντελεστή.
Μονώνυμο μιας μεταβλητής είναι προφανώς ένα πολυώνυμο ενός μόνο όρου.
Έτσι, το πολυώνυμο 4x5 – 2x3 + 3x – 1 θα παρασταθεί με:
( x . ( (5 . 4) (3 . 2) (1 . 3) (0 . -1) ) )

Αλγεβρική πρόσθεση πολυωνύμων της αυτής μεταβλητής:
Πρώτο βήμα για την πρόσθεση είναι ο έλεγχος αν τα πολυώνυμα είναι της ίδιας μεταβλητής.
Δεύτερο, η αλγεβρική πρόσθεση των αντιστοίχων συντελεστών. Τρίτο, η κατασκευή της λίστας
συσχέτισης του αποτελέσματος, με διαδοχική επισύναψη των όρων που προκύπτουν. Η λίστα που
προκύπτει θα είναι διατεταγμένη και δεν χρειάζεται επεξεργασία.
Πολλαπλασιασμός μονώνυμων της αυτής μεταβλητής:
Πρώτο βήμα είναι ο έλεγχος αν τα μονώνυμα είναι της ίδιας μεταβλητής, Συντελεστής του
γινομένου είναι το γινόμενο των συντελεστών των μονώνυμων. Εκθέτης της μεταβλητής είναι το
άθροισμα των εκθετών των μεταβλητών των μονώνυμων.
Πολλαπλασιασμός πολυωνύμων της αυτής μεταβλητής:
Πρώτο βήμα είναι ο έλεγχος αν τα πολυώνυμα είναι της ίδιας μεταβλητής, Δεύτερο, ο
πολλαπλασιασμός "όλοι οι όροι με όλους" (διπλή αναδρομή). Το γινόμενο των όρων είναι
γινόμενο μονώνυμων μίας μεταβλητής, της ίδιας. Η λίστα συσχέτισης προκύπτει με επισύναψη
των ζευγών που προκύπτουν και με διάταξή της ως προς την τάξη.
413

Το θέμα αυτό είναι απλό αλλά αρκετά εκτενές, και θα το δούμε αναλυτικά, με αν΄λογη ανάπτυξη
κώδικα, στο επόμενο τεύχος.

9.2.3 Ανάμειξη συναρτησιακών εκφράσεων και εφαρμογών
Ένας υπολογισμός στα πλαίσια του ΣΠ είναι πάντα μια εφαρμογή συναρτησιακής έκφρασης
(σ.ε.) πάνω σε ορίσματα. Σε μια εφαρμογή, είναι δυνατό να προσδιορίζεται / να χρησιμοποιείται
κάποια άλλης σ.ε.. Και αντίθετα, μια σ.ε. είναι δυνατό να προσδιορίζεται με τρόπο που στο σώμα
της να αναφέρεται υπολογισμός.
Τέτοιες αναμείξεις, όπως είδαμε, ανοίγουν πλατείς δρόμους στον ΣΠ διότι η ενσωμάτωση
εκτέλεσης αλγόριθμου μέσα σε ορισμό μιας σ.ε. σημαίνει ευελιξία, ανάλογα με την οπτική γωνία
που το βλέπουμε, για: i) την έκφραση διαδικασίας, ii) την πρόκληση καθυστέρησης, iii) τη
διαχείριση υπολογισμού από ένα ανώτερο επίπεδο, κα.
Αλλά και από την άλλη πλευρά, η θέση ορισμού σ.ε. μέσα σε μια εφαρμογή, παρέχει ευελιξία
για: i) την ενσωμάτωση νέων αλγόριθμων κατά τη διάρκεια της εκτέλεσης, ii) απόδοση
συναρτησιακού περιεχομένου σε μεταβλητή, iii) εφαρμογή που διαβάζει συναρτήσεις, κά.
Αυτή η αντιμετώπιση και χρήση των συναρτήσεων υπερβαίνει την έννοια των καθαρά
Μαθηματικών συναρτήσεων, και αποτελεί ένα από τα ουσιαστικά στοιχεία της θεωρητικής
διαφοροποίησης Μαθηματικών και Πληροφορικής.
Κατά κανόνα, οι φόρμες που συνθέτουμε στη Lisp είναι τέτοιοι σύνθετοι υπολογισμοί, ακόμα και
σε πολύ απλές περιπτώσεις. Ως τεχνική, η ανάμειξη σ.ε. και εφαρμογής προσφέρει μεγάλη
εκφραστική δύναμη για την περιγραφή αλγορίθμων με τοπικές γενικεύσεις, με το νόημα
προσδιορισμού συναρτησιακών εκφράσεων στη θέση οντοτήτων που συνήθως σε άλλες γλώσσες
δεν είναι παρά μεταβλητές με δυνατό περιεχόμενο κάποια απλή σταθερά τιμή, ή εξειδικεύσεις, με
το νόημα μερικής ή ολικής εφαρμογής συναρτησιακής έκφρασης. Θα δούμε μερικές ενδεικτικές
περιπτώσεις.

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

Παράδειγμα
Υπολογισμός του π :
H παρακάτω συνάρτηση THIS-PI υπολογίζει το άθροισμα μιας σειράς που συγκλίνει στον αριθμό
π . Έχει γενικό όρο ο οποίος προσδιορίζεται εσωτερικά από τη συνάρτηση THIS-TERM
συναρτήσει του δείκτη, και η μετάβαση από όρο σε όρο γίνεται με μεταβολή του δείκτη σύμφωνα
με την επίσης εσωτερικά ορισμένη συνάρτηση THIS-ΝΕΧΤ . Η συνάρτηση THIS-PI χρησιμοποιεί
414

τη συνάρτηση SERIES που ορίσαμε σε προηγούμενη ενότητα:
(defun this-pi (a b &aux this-term this-next)
(defun this-term (n) (/ 1 (* n (+ n 2))))
(defun this-next (m) (+ m 4))
(* 8 (series #'this-term #'this-next a b)) )

Η κλήση πρέπει να γίνει με πρώτο όρισμα 1 και δεύτερο όσο μεγάλο απαιτείται για ικανοποιητική
προσέγγιση:
(double-float (this-pi 1 500)) → 3.137…
(double-float (this-pi 1 1000)) → 3.139…
(double-float (this-pi 1 1500)) → 3.140…

Η τάξη του υπολογισμού είναι τετραγωνική, και δύσκολα οδηγεί σε μεγάλες ακρίβειες.

Υπολογισμοί με εσωτερικά χρησιμοποιούμενες λ-εκφράσεις

Συνάρτηση δεν είναι παρά μια ονομασμένη λ-έκφραση. Άρα μπορούμε να αποδώσουμε το
προηγούμενο νόημα χωρίς να ορίσουμε εσωτερικά συναρτήσεις, προσδιορίζοντας πρώτα την
σ.ε. και αμέσως μετά την εφαρμογή της.

Η απ' ευθείας αναγραφή των υπολογισμών που απαιτούνται, σε μορφή λ-εκφράσεων, έχει κάποια
πλεονεκτήματα, όπως: αυτάρκεια του κώδικα της συνάρτησης (δεν καλεί άλλα σύμβολα) και
κάπως καλύτερη ταχύτητα εκτέλεσης. Έχει όμως και κάποια μειονεκτήματα, όπως: μια έκφραση
που ξαναχρησιμοποιείται μέσα στον υπολογισμό πρέπει να ξαναγραφεί, και οι εκφράσεις που
προκύπτουν είναι δυσνόητες (πχ. αν αντικαταστήσουμε και τη συνάρτηση SERIES με την
αντίστοιχη λ-έκφραση, η έκφραση που προκύπτει είναι ακατανόητη).
Παράδειγμα
Η συνάρτηση THIS-PI να εκφραστεί χωρίς εσωτερικό ορισμό συναρτήσεων:
Αντί για κλήση συναρτήσεων στην τελευταία γραμμή της προηγούμενης,
… (series #'this-term #'this-next a b) …

μπορούμε να προσδιορίσουμε απ' ευθείας τις μεθόδους υπολογισμού ως λ-εκφράσεις:
(defun another-pi (a b)
(series (lambda (n) (/ 1 (* n (+ n 2))))
(lambda (m) (+ m 4))
a b))

Υπολογισμοί που "διαπλάθουν" τα ορίσματά τους

Είδαμε macro συναρτήσεις που επιδρούν μόνιμα σε όρισμά τους. Όμως, πολλές φορές
χρειαζόμαστε τη μονιμοποίηση του αποτελέσματος όχι σαν "standard" λειτουργικότητα της
συνάρτησης αλλά ευκαιριακά. Ένας τρόπος είναι να ορίσουμε παράλληλα δύο συναρτήσεις, η μία
κοινή και η άλλη macro, όπως οι "built-in" INTERSECTION και NINTERSECTION (η πρώτη
415

επιστρέφει την τομή των δύο ορισμάτων της, που πρέπει να είναι σύνολα, και η δεύτερη το ίδιο
αλλά θέτοντας το αποτέλεσμα στη θέση του πρώτου ορίσματος).
Ένας άλλος τρόπος, είναι να προκαλέσουμε τη μονιμοποίηση του αποτελέσματος μετά τον
υπολογισμό, σε επόμενο βήμα:
(setf liist '(1 3 5 1 3 7))
(remove 3 liist) → (1 5 1 7)
liist
→ (1 3 5 1 3 7)

Η δράση τής REMOVE ήταν προσωρινή. Αλλά:
(setf lst (remove 3 liist)) → (1 5 1 7)
liist
→ (1 5 1 7)
Η setf προκάλεσε μόνιμη αντικατάσταση, διότι εφαρμόστηκε σε φυσικά εντοπισμένο
αντικείμενο.
Το βήμα μονιμοποίησης είναι αυτόνομο, αλλά μπορούμε να εντάξουμε και τα δύο στα πλαίσια
μιας ενιαίας έκφρασης.

9.2.4 Επαγωγική οργάνωση προγράμματος
Επαγωγή είναι μια μέθοδος συλλογισμού, όπου από το ειδικό συνάγουμε ή παράγουμε το γενικό.
Βεβαιότητα σε ένα επαγωγικό συμπέρασμα, έχουμε μόνο στην περίπτωση όπου έχουμε ελέγξει
όλες τις ειδικές περιπτώσεις που καλύπτει ή ενδέχεται να καλύψει μελλοντικά, το γενικό, και
αυτό δεν είναι πάντα εφικτό. Επομένως, είναι μια μέθοδος που, σε πρώτο βήμα, μας δίνει
περισσότερο υποθέσεις, ως "υποψήφιες αλήθειες", παρά βέβαια γεγονότα, και γενικά νόμους.
Είναι όμως η ισχυρότερη μέθοδος για αναζήτηση ή διεύρυνση νόμων.
Διακρίνουμε δύο βασικές περιπτώσεις επαγωγής:
α'. Την αφαίρεση, όπου από ένα σύνολο οντοτήτων για τις οποίες αληθεύει κάτι κοινό, πχ. να
έχουν ένα κοινό χαρακτηριστικό, δημιουργούμε την αφηρημένη οντότητα που αντιπροσωπεύει
κάθε οντότητα για την οποία αληθεύει το ίδιο. Με τον τρόπο αυτό δημιουργούμε ιεραρχικούς
κόμβους "προς τα πάνω".
Στην περίπτωση αυτή έχουμε λογική βεβαιότητα των συμπερασμάτων, σε ότι αφορά
συγκεκριμενοποιήσεις της αφηρημένης οντότητας πάνω στις αρχικές οντότητες, αλλά όχι και
βεβαιότητα αν η συγκεκριμενοποίηση αφορά νέες οντότητες, που ιεραρχικά θεωρούμε οτι
εντάσσονται κάτω από τον αφηρημένο κόμβο αλλά δεν έχουμε διαπιστώσει την ισχύ του
χαρακτηριστικού.
β'. Την επαγωγική γενίκευση, όπου από την παρατήρηση πάνω στο "ειδικό" συνάγουμε σχέση
για τη γενικότερη οντότητα. Μόνο η επαλήθευση σε κάθε ειδική περίπτωση μπορεί να
επιβεβαιώσει απόλυτα τέτοιους νόμους, αλλά συχνά αρκούμαστε είτε σε πρακτικές διαπιστώσεις
(πιθανοθεωρητικής μορφής, με κάποιο δείγμα επαρκούς εμπιστοσύνης), είτε σε υποθετικές
παραδοχές, για λόγους μεγαλύτερης βαρύτητας του αληθούς από το ψευδές (όπως "οι
κατηγορούμενοι είναι αθώοι μέχρις αποδείξεως του εναντίου")
416

Στον ΣΠ ενδιαφέρει ιδιαίτερα η επαγωγή, διότι είναι ο τρόπος με τον οποίο εκμεταλλευόμαστε
ένα πρόγραμμα για να κατασκευάσουμε ένα άλλο, γενικότερο. Πρόκειται για ένα τρόπο
ανάμειξης της συλλογιστικής του χρήστη στα στοιχεία που περιέχει το πρόγραμμα.
Θα δούμε μερικές εφαρμογές αυτού του τρόπου σκέψης στον προγραμματισμό.

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

Μια συνάρτηση που είδαμε να παίζει τόσο αφαιρετικό ρόλο, αντιπροσωπεύοντας ποικίλες ειδικές
περιπτώσεις ειδικής εφαρμογής, όσο και συνθετικό ρόλο, διευκολύνοντας τη σύνθεση άλλων
συναρτήσεων, είναι η SERIES . Εδώ θα δούμε μια περίπτωση χρήσης τής SERIES ως συνθετική
συνιστώσα υπολογισμού.
Παράδειγμα

Να δοθεί συνάρτηση υπολογισμού INTEGRAL-F του ορισμένου ολοκληρώματος της συνάρτησης F
από a μέχρι b ως προς dx , όπου F , a , b , dx να είναι λ-μεταβλητές της INTEGRAL-F :
Προσέγγιση ορισμένου ολοκληρώματος συνάρτησης f(x) από a έως b με τη σειρά:
[ f(a + dx/2) + f(a + dx + dx/2) + f(a + 2∙dx + dx/2) + …] ∙ dx
Θα κάνουμε χρήση εσωτερικά οριζόμενης συνάρτησης:
(defun integral-f (f a b dx)
; ολοκλήρωμα της συνάρτησης f από a έως b με λεπτότητα διαμέρισης dx
(defun add-dx (x) (+ x dx))
(* (series
f
#'add-dx
(+ a (/ dx 2)) b )
dx ) )

Χρήση:
Να βρεθεί το ολοκλήρωμα της f(x)=x 3 από 0 έως 1
(float (integral-f #'cube 0 1 0.1))
→ 0.24875
(float (integral-f #'cube 0 1 0.01)) → 0.2499875
(float (integral-f #'cube 0 1 0.0001)) → 0.2499999
417

Η μεταβολή του αποτελέσματος έφθασε, για διαμέριση dx ίση με 10 -4 , πέραν του τέταρτου
δεκαδικού (η πραγματική τιμή είναι 0.25)

Επαγωγική επέκταση συνάρτησης

Ένα πολύ σημαντικό πλεονέκτημα του ΣΠ είναι η ευκολία ανάπτυξης συναρτήσεων με εφαρμογή
επαγωγικής συλλογιστικής, δηλαδή παρατηρώντας οτι ήδη υλοποιημένες συναρτήσεις αποτελούν
ειδική περίπτωση από κάτι γενικότερο, που μπορούμε να υλοποιήσουμε ευκολότερα βάσει των
υπαρχόντων.
Παράδειγμα
Παρατηρούμε οτι η γνωστή μας SERIES, με την πρόσθεση όρων ακολουθίας που κάνει,
ουσιαστικά προκαλεί την εφαρμογή της πρόσθεσης ως δυαδικής πράξης πάνω στο μέχρι στιγμής
μερικό άθροισμα με τον επόμενο όρο ακολουθίας, αρχίζοντας από το 0, δηλ. την ουδέτερη τιμή
της πρόσθεσης.

Επομένως, αν αντικαταστήσουμε την πράξη "+" με οποιαδήποτε δυαδική πράξη F , και την
αρχική τιμή του αποτελέσματος με την ουδέτερη τιμή της F (που πλέον πρέπει να τεθεί ως νέα
παράμετρος, έστω init ), θα έχουμε μια αφαιρετικά γενικότερη συνάρτηση.
Παίρνουμε τον προηγούμενο ορισμό της συνάρτησης SERIES και κάνουμε τις αλλαγές αυτές:
(defun operation-on-sequence (f n-term next a b init)
(cond
( (> a b) init)
;; συνθήκη τερματισμού
( t (funcall f
;; αν δεν έχει τερματίσει, εφάρμοσε τη συνάρτηση f με ορίσματα:
(funcall n-term a)

;; που υπολογίζεται ως ο επόμενος όρος του ήδη υφισταμένου, και
; την ακολουθιακή πράξη:
(operation-on-sequence

;; εφαρμοσμένη στις ίδιες συναρτήσεις f , n-term , next , με
; επόμενο δείκτη, τελικό δείκτη και τιμή τερματισμού τα ίδια:
f n-term next (funcall next a) b init)))))

Αν θέσουμε ως F την πρόσθεση και init το 0 , θα πάρουμε την προηγούμενη SERIES. Αν
θέσουμε ως F τον πολλαπλασιασμό και init το 1 , θα πάρουμε συνάρτηση που δίνει το γινόμενο
των όρων ακολουθίας.

Επαγωγική επέκταση λειτουργικότητας προγράμματος
Συχνά, αφού προσδιορίσουμε μια συνάρτηση, στη συνέχεια διαπιστώνουμε πως χρειαζόμαστε
κάτι πλατύτερο, που βασίζεται μεν στην ίδια "φιλοσοφία" αλγόριθμου, αλλά ζητάμε να καλύψει
ένα ευρύτερο θέμα.
418

Συμβαίνει επίσης να γνωρίζουμε εξ αρχής το ευρύ πρόβλημα αλλά να διαπιστώνουμε πως είναι
πολύ ευκολότερο να το λύσουμε σε ένα ειδικό πλαίσιο, και να αντιμετωπίσουμε στη συνέχεια το
πρόβλημα σαν γενίκευση από το ειδικό.
Παράδειγμα
Ένας από τους αρχαιότερους αλγόριθμους που έχουν περιγραφεί, είναι ο αλγόριθμος του
Ευκλείδη εύρεσης του ΜΚΔ δύο αριθμών. Εδώ, βλέπουμε στον πρώτο αλγόριθμο την
εκφραστική απλότητα και αμεσότητα της συναρτησιακής σύνθεσης, και στο δεύτερο την
επεκτασιμότητα αυτής της σύνθεσης έτσι ώστε να επιλύει, με το ίδιο σκεπτικό και αξιοποιώντας
τον αρχικό αλγόριθμο, ένα ευρύτερο πρόβλημα.
1. Να βρεθεί ο ΜΚΔ δύο ακεραίων αριθμών με τον αλγόριθμο του Ευκλείδη.
(defun gcd (a b)

;; ο μ.κ.δ. δύο ακεραίων με |a|≥|b|
(cond
((= a b) a)

;; αν a=b επίστρεψε a, αλλιώς βρες τον μ.κ.δ.
; του b με το υπόλοιπο του a δια b :
(gcd b (remainder a b))))

2. Να υπολογιστεί ο ΜΚΔ n αριθμών (δοσμένων σε λίστα) με μέθοδο – επέκταση του αλγόριθμου
του Ευκλείδη.
Χρειαζόμαστε μια συνάρτηση GCD-OF που να σαρώνει τη λίστα: πρώτο με δεύτερο,
αποτέλεσμα με τρίτο, κοκ. μέχρι τέλους. Χρήσιμη είναι και μια βοηθητική συνάρτηση, έστω
GCDS, που να ταξινομεί τα δύο μέλη του ζεύγους ακεραίων πριν βρει τον μ.κ.δ. (υποθέτουμε
οτι οι τιμές εισόδου καλύπτουν τις απαιτούμενες συνθήκες):
(defun gcds (a b)
(cond
( (>= (abs a) (abs b)) (gcd a b) )
( t (gcd b a) )))
(defun gcd-of (lis)
(gcd-aux (a lis)))
(defun gcd-aux (a lis)
(setq lis (sort lis #'<))
(cond
( (= 1 (len lis) 1 )
(gcds a (car lis)) )
(t
(gcd-aux (gcds (car lis) (cadr lis)) (cddr lis)) )))

419

Προσδιορισμός συνάρτησης πολυεπίπεδης εφαρμογής
Ένα από τα πολύ "δυνατά" σημεία του ΣΠ είναι η υπολογιστική παραγωγή συναρτήσεων. Τέτοια
παραγωγή συναντήσαμε συχνά: η ίδια η DEFUN επιτελεί ακριβώς αυτό το έργο∙ σε συναρτήσεις
όπου κατά την εφαρμογή έχουμε δέσμευση λ-μεταβλητής σε λ-έκφραση έχουμε ως αποτέλεσμα
πάλι συνάρτηση.
Μια πολύ ενδιαφέρουσα περίπτωση είναι να προσδιορίζουμε γενικής μορφής συνάρτηση η οποία
με την εφαρμογή της να παράγει ειδικότερη συνάρτηση.
Παράδειγμα
1. Να οριστεί συνάρτηση που όταν εφαρμοστεί σε αριθμητικό όρισμα Ν να επιστρέφει συνάρτηση
ενός αριθμητικού ορίσματος Μ , η οποία να προσθέτει Ν στο όρισμα Μ όταν κληθεί.
Προφανώς πρέπει να δώσουμε μια λ-μεταβλητή στην οριζόμενη συνάρτηση (που θα δεσμευτεί
κατά την πρώτη εφαρμογή), και σώμα μια λ-έκφραση, με δική της λ-μεταβλητή (που θα
δεσμευτεί κατά την δεύτερη εφαρμογή), όπου σώμα του σώματος θα είναι απλώς η πράξη
πρόσθεσης των δύο λ-μεταβλητών:
(defun add-to (x)
#'(lambda (y) (+ y x)))

Η ADD-TO είναι συνάρτηση μιας μεταβλητής, μέσα στην οποία ενυπάρχει το νόημα και μιας
άλλης μεταβλητής, αλλά η δεύτερη είναι ένα επίπεδο εφαρμογής παραπάνω.
Επαναλαμβάνουμε πως το να θέσουμε πρόθεμα #' σε σώμα που είναι λ-έκφραση, απαιτείται από
ορισμένους μόνον compilers. Η προηγούμενη συνάρτηση ADD-TO μπορεί να δοθεί ανώνυμα:
(lambda (x) #'(lambda (y) (+ y x)))

Εφαρμογή:

(add-to 4)
→ <function …>

(δίνει μια συμβολική έκφραση που δηλώνει οτι πρόκειται για συνάρτηση της Lisp)
Αυτή η έκφραση είναι η παράσταση "x+4", δηλ. ουσιαστικά είναι η λ-έκφραση:
(lambda (x) (+ x 4) )

και μπορεί να εφαρμοστεί σε νέο όρισμα:
(funcall (add-to 4) 5) → 9
2. Να οριστεί συνάρτηση ενός αριθμητικού ορίσματος, που όταν εφαρμοστεί σε αριθμό Χ να
επιστρέφει συνάρτηση ενός αριθμητικού ορίσματος, η οποία όταν εφαρμοστεί σε αριθμό Y να
επιστρέφει το αποτέλεσμα του υπολογισμού X Y
Παρόμοια με το προηγούμενο:
(defun power-of (x)
#'(lambda (y) (expt x y)))
(power-of 3)
→ < συμβολική έκφραση, που παριστά το 3 y >
(funcall (power-of 3) 2) → 9
420

Συναρτήσεις ανωτέρας τάξης

Συνάρτηση ανωτέρας τάξης είναι αυτή που δέχεται ως όρισμα συνάρτηση. Ως τάξη ορίζεται το
πλήθος των δυνατών βημάτων εφαρμογής της συνάρτησης (δίνοντας ως αποτέλεσμα συνάρτηση
που ξανά εφαρμόζεται σε συνάρτηση, κοκ.).
Στον λ-λογισμό και τη Lisp, έχουμε αυτή τη δυνατότητα, και την εκμεταλλευόμαστε για να
διαχειριστούμε συναρτήσεις, δίνοντας έννοια συνάρτησης στη διαχείριση. Θα αναφερθούμε ξανά
στο ζήτημα της τάξης.
Παράδειγμα

Έστω οτι θέλουμε να προσδιορίσουμε συνάρτηση που να εφαρμόζει σε όλα τα στοιχεία λίστας
μια συγκεκριμένη πράξη μετασχηματισμού, πχ. τετραγωνισμό των στοιχείων αριθμητικής λίστας.
Αντί να γράψουμε όλες τις αντίστοιχες συναρτήσεις ανεξάρτητα, μπορούμε να σχηματίσουμε μια
και μοναδική συνάρτηση, πχ. με όνομα APPLY-TO-EACH , δύο ορισμάτων, όπου το πρώτο θα
είναι η εφαρμοζόμενη συνάρτηση και δεύτερο η λίστα των αντικειμένων στα οποία αυτή θα
εφαρμόζεται , με έξοδο τη λίστα των αποτελεσμάτων:
(defun apply-to-each (f list)
(cond
((null list) nil)
(t (cons (funcall f (car list))
(apply-to-each f (cdr list))))))

Εφαρμογή:
(apply-to-each #'square '(1 2 3 4 5))
→ (1 4 9 16 25)

(μπορούμε βέβαια να κάνουμε χρήση των σχετικών "built-in" συναρτήσεων, όπως η MAPCAR)

9.3 Η έννοια της πολλαπλότητας

Στην ενότητα αυτή θα δούμε μερικές εφαρμογές που βασίζονται στην έννοια της πολλαπλότητας.
Ειδικότερα θα δούμε τα εξής ζητήματα:
– πώς από συνάρτηση δύο μεταβλητών η οποία είναι διμελής πράξη, μπορούμε να
σχηματίσουμε με εύκολο τρόπο συνάρτηση-πράξη οσωνδήποτε μεταβλητών
– τί είναι και πώς κατασκευάζουμε φίλτρα∙ πώς χειριζόμαστε την μερική εξειδίκευση
(συγκεκριμενοποίηση)
– πώς εκμεταλλευόμαστε τις λίστες-ρεύματα και πώς κατασκευάζουμε απεριόριστες δομές
– πώς κινούμαστε ανάγοντας λογικά την πολλαπλότητα σε μια γενικότερη έννοια.

421

9.3.1 Συναρτήσεις αναγωγής
Ονομάζουμε συνάρτηση αναγωγής (reduction function) μια συνάρτηση η οποία πετυχαίνει
επαναλαμβανόμενη εφαρμογή μιας συνάρτησης που είναι διμελής σχέση.
Προσδιορίζουμε μια συνάρτηση αναγωγής ως συνάρτηση τριών ορισμάτων με πρώτο όρισμα
συνάρτηση δύο ορισμάτων, δεύτερο λίστα και τρίτο κάποια οντότητα που παίζει ρόλο "πώς
τελειώνει η αναγωγή".
Η συνάρτηση αυτή λειτουργεί σαρώνοντας τη λίστα με κατεύθυνση προς τα δεξιά, προκαλώντας
μερικό αποτέλεσμα σε κάθε βήμα, όπου το τρίτο όρισμα παίζει ρόλο οριακής τιμής για την
επανάληψη:
(reduce-right f list a)
→ (f (car list) (f (cadr list) (f (caddr list) … (f (last list) a)…))))
Δηλαδή, για list τη λίστα (x1 x2 x3 … xk) , με μαθηματικό συμβολισμό η εφαρμογή θα έχει τη

μορφή:
reduce-right(f , list, a) = f(x 1 , f(x2 , f(x3 ,…, f(xk , a) …)))
Η REDUCE-RIGHT ορίζεται ως εξής:
(defun reduce-right (f list a)
(cond
((null list ) a)
(t (funcall f (car list ) (reduce-right f (cdr list) a) ))))

Με τον τρόπο αυτό μπορούμε να ορίσουμε συνάρτηση απεριορίστων ορισμάτων, όχι μέσω του
κλειδιού &rest αλλά μέσω αναγωγής πάνω σε συνάρτηση - διμελή πράξη.
Αντίστοιχα με την REDUCE-RIGHT μπορούμε να ορίσουμε την REDUCE-LEFT , που σαρώνει
τη λίστα προς τα αριστερά.
Παραδείγματα
1. Έστω ο πολλαπλασιασμός MULT ακριβώς δύο ορισμάτων:
(defun mult (x y)
(* x y))

Αν εφαρμόσουμε την reduce-right με συνάρτηση τη MULT , λίστα την (1 2 3 4 5) και a το 1 , θα
πάρουμε το 5! (παραγοντικό):
(reduce-right #'mult '(1 2 3 4 5) 1)
→ 120

Αν εφαρμόσουμε την reduce-right με συνάρτηση την CONS και ορίσματα δύο λίστες,
παίρνουμε :
(reduce-right #'cons '(1 2 3 4 5) '(6 7 8 9 0))

(1 2 3 4 5 6 7 8 9 0)

δηλαδή, δημιουργήσαμε τη λειτουργικότητα της APPEND :
(defun myappend (l1 l2)
422

(reduce-right #'cons l1 l2)

2. Έστω η συνάρτηση ΜΑΧ-2 δύο ορισμάτων:
(defun max-2 (x y)
(cond
((> x y) x)
(t y) ))

Αν εφαρμόσουμε την reduce-right με συνάρτηση την ΜΑΧ-2 , επόμενο όρισμα μια λίστα
αριθμών και τρίτο όρισμα ένα αριθμό σίγουρα μικρότερο όλων, δημιουργούμε τη
λειτουργικότητα της MAX :
(reduce-right #max-2 '(1 2 3 4 5 6 7 8 9 0) -1) → 9
Έτσι μπορούμε να σχηματίσουμε, από τη διμελή πράξη MAX-2 , την πράξη με ελεύθερο πλήθος

ορισμάτων:
(defun mymax (lis rel)
(reduce-right #max-2 lis rel))

3. Μπορούμε να πάρουμε το άθροισμα των αριθμών που περιέχονται σε λίστα από λίστες
αριθμών, εφαρμόζοντας σε δύο βήματα την άθροιση στοιχείων λίστας:
(defun add-all (obj-list
(reduce-right #'+
(mapcar #'(lambda (list) (reduce-right #'+ list 0)) obj-list)

Εφαρμογή:
→ 36
Από τα παραπάνω βλέπουμε τον αφαιρετικό ρόλο της REDUCE-RIGHT , που οδηγεί σε
επίπεδα γενικότητας που δεν είχαν προβλεφθεί κατά τον ορισμό της !!!
(add-all '((1 2 3) (4 5) (6 7 8)) )

9.3.2 Φίλτρα
Αποκαλούμε φίλτρο μια συνάρτηση που κρίνει το στοιχείο εισόδου της και το αφήνει / δεν το
αφήνει να περάσει στην έξοδο. Συνήθως ένα φίλτρο τροφοδοτείται κατ' επανάληψη με στοιχεία
εισόδου, από ένα ρεύμα. Το ρεύμα μπορεί να είναι μια προκατασκευασμένη λίστα, αρχείο, το
ρεύμα εισόδου του compiler, ή μια λίστα παραγωγής.
Μια παραλλαγή από αυτό το νόημα του φίλτρου είναι μια συνάρτηση που προκαλεί συσσώρευση
των στοιχείων που περνούν, σε μια λίστα. Μια άλλη παραλλαγή είναι, να προκαλεί και κάποιο
μετασχηματισμό των στοιχείων που φιλτράρει.
Τα φίλτρα αξιοποιούνται ιδιαίτερα στην περίπτωση όπου τα στοιχεία εισόδου είναι απεριόριστου
ή πολύ μεγάλου πλήθους. Συνδυάζονται πολύ εύκολα και αποτελεσματικά τόσο με μηχανισμούς
ενεργοποίησης της παραγωγής στοιχείου που θα δοθεί ως είσοδος στο φίλτρο (ενεργοποίηση από
το ίδιο το φίλτρο ή από περιβάλλουσα συνάρτηση του φίλτρου και του μηχανισμού παραγωγής),
όσο και με μηχανισμούς διαχείρισης των στοιχείων που δίνουν ως έξοδο.
423

Παράδειγμα:
Φιλτράρισμα της λίστας που δίνεται ως είσοδος στο φίλτρο, με κριτήριο συνάρτηση που επίσης
δίνεται ως είσοδος.
(defun filter (f list)
; f είναι κατηγόρημα - κριτήριο
(cond
((null list) nil)

; συνθήκη διακοπής
; εφάρμοσε το κριτήριο φίλτρου πάνω στο πρώτο στοιχείο:

(funcall f (car list))

; και αν πετυχαίνει, συνένωσε το στοιχείο με όσα προκύψουν:
(cons (car list) (filter f (cdr list))))

; αλλιώς προχώρα στο επόμενο:
(t (filter f (cdr list)))))

Εφαρμογή:
(filter #'listp '(1 (2 3) 4 (5) 6 (7 8)))

→ ( (2 3) (5) (7 8) )

Το φιλτράρισμα χρησιμεύει πολύ περισσότερο σε συνδυασμό δύο συναρτήσεων όπου η μια γεννά
υποψήφιες τιμές και μετά η άλλη συνάρτηση ελέγχει ποιες από αυτές είναι αποδεκτές και "τις
αφήνει να περάσουν", όπως θα δούμε παρακάτω

Πολλαπλό φιλτράρισμα
Με πολλαπλά φίλτρα ελέγχουμε μια πολυσύνθετη απαίτηση, διασπώντας την σε διαδοχικά απλά
φίλτρα, που, όταν περάσει απ' όλα το ελεγχόμενο στοιχείο, μόνο τότε να πετυχαίνει.
Γενικότερα, μπορούμε να σχηματίσουμε ένα δίκτυο από "and - or - not" συσχετίσεις φίλτρων.
Μια μορφή γενικής συνάρτησης πολλαπλών διαδοχικών φίλτρων, δοσμένων σε λίστα είναι η
ακόλουθη PASS-MULTI . Το πρώτο όρισμα είναι μια λίστα φίλτρων (συναρτησιακές εκφράσεις)
και το δεύτερο όρισμα είναι το στοιχείο που κρίνεται.

Με κατάλληλα επαναλαμβανόμενη εφαρμογή της PASS-MULTI προκαλούμε φιλτράρισμα ενός
ολόκληρου χώρου οντοτήτων, ο οποίος είτε προϋπάρχει είτε δημιουργείται δυναμικά.
(defun pass-multi (filter-list elem)
(cond
( (null sieves) t)
( (funcall (car filter-list) elem)
(pass-multi (cdr filter-list) elem))
(t nil)))

Τώρα, μπορούμε να ορίσουμε μια συνάρτηση που να περνά κάθε στοιχείο λίστας (λίστα - δεύτερο
όρισμα) από όλα τα φίλτρα (λίστα - πρώτο όρισμα):
(defun multi-filter (filter-list obj-list)
(cond
424

((null obj-list) nil)
((pass-multi filter-list (car obj-list))
(cons (car obj-list)
(multi-filter filter-list (cdr obj-list))))))

Εφαρμογή των παραπάνω μπορούμε να δούμε στο ακόλουθο:
Παράδειγμα
Να υπολογιστούν πλήθους n πρώτοι αριθμοί, ξεκινώντας από το 1 .
Ορίζουμε αναδρομική συνάρτηση PRIMES-AUX που πρώτο όρισμά της είναι το πλήθος των
ζητουμένων πρώτων αριθμών, δεύτερο η λίστα συσσώρευσης των δημιουργημένων πρώτων
αριθμών μέχρι τον τρέχοντα κύκλο, τρίτο η λίστα των φίλτρων, και τέταρτο ο επόμενος ακέραιος
αριθμός που θα ελεγχθεί∙ έξοδος είναι η τελική μορφή που έχει η λίστα συσσώρευσης.
Βάσει της PRIMES-AUX ορίζουμε συνάρτηση ευκολίας PRIMES , μιας παραμέτρου, το πλήθος
n που ζητάμε, διότι οι λοιπές δεν απευθύνονται στον τελικό χρήστη.
Η συνάρτηση GEN-NOT-DIV-P είναι ανωτέρου επιπέδου και χρησιμοποιείται για να γεννήσει
τον επόμενο ακέραιο μη-διαιρέτη του ορίσματός της.
Στο παρακάτω πρόγραμμα γίνεται εκμετάλλευση της έννοιας του "σπόρου" (sieve) ως οντότητας
από την οποία "γεννάται κάτι".
(defun primes-aux (n primes sieves num)
(cond
((= n 0) primes)
(t (let
((prime (next-prime sieves num)))
(primes-aux
(1- n)
(nconc primes (list prime))
(nconc sieves
(list
(gen-not-div-p prime)))
(+ prime 2))))))
(defun gen-not-div-p (p)
#'(lambda (n)
(not (zerop (rem n p)))))
(defun next-prime (sieves num)
(cond
( (pass-multi-p sieves num) num)
(t (next-prime sieves (+ num 2)))))
(defun primes (n)
(primes-aux (1- n) (list 2) (list (gen-not-div-p 2)) 3))

Εφαρμογή:
(primes 12)

→ (2 3 5 7 11 13 17 19 23 29 31 37)
425

(primes 120) → (2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 ...)

Ο χρόνος εκτέλεσης της PRIMES είναι τάξης Ο(n2) . Παρ' όλο που υπάρχουν καλύτεροι
αλγόριθμοι, ο παραπάνω συνδυάζει πολλές τεχνικές και είναι "πυκνό". Οι συναρτήσεις
PRIMES-AUX και PRIMES είναι αρκετά πολύπλοκες, διότι χρησιμοποιούν κλήσεις
συναρτήσεων στα ορίσματα∙ συνιστάται να μελετηθούν με χρήση της TRACE .

9.4 Ρεύματα και απεριόριστες δoμές
Είδαμε σε προηγούμενα, το ρεύμα εισόδου ως πηγή πληροφορίας. Αυτή η πηγή έχει το
χαρακτηριστικό οτι "κάποιος άλλος, εξωτερικά της εφαρμογής, παρέχει τις τιμές που δίνονται ως
είσοδος". Εκτός από την ανάγνωση από το ρεύμα εισόδου, έχουμε και τη δυνατότητα να
δημιουργήσουμε τέτοιες πηγές που να παρέχουν τιμές εισόδου σε άλλες εφαρμογές, όπου οι τιμές
αυτές να παράγονται με υπολογιστικό τρόπο. Αν οι παραγόμενες τιμές είναι απεριόριστου
πλήθους, δημιουργούμε έτσι μια πηγή τιμών.

9.4.1 Υπολογισμός υπό καθυστέρηση και ρεύματα
α'. Τί είναι ρεύμα
Ρεύματα (streams) είναι δομές που συνθέτουν ένα τρόπο αναπαράστασης απεριορίστου πλήθους
δεδομένων, τα οποία παράγονται από κάποια μέθοδο υπολογισμού.
Λειτουργικά, καλύπτουν την "ιδεατή υπόθεση" οτι υπάρχει αποθηκευμένη η απεριόριστη
πληροφορία, που όμως παράγουν και παρέχουν δυναμικά, τοποθετώντας την στο βαθμό που
χρειάζεται σε ένα αποθηκευτικό μέσον απ' όπου τη διαβάζουν οι "ενδιαφερόμενες" συναρτήσεις.
Με αυτή την υπόθεση, η συνάρτηση που διαβάζει την πληροφορία την αντιμετωπίζει ως στατική
πηγή (δηλ. οτι προϋπάρχουν όλες οι τιμές, όπως σε μια δοσμένη λίστα), ενώ στην
πραγματικότητα αυτό που συμβαίνει είναι να δημιουργεί το ρεύμα την "επόμενη τιμή", μία και
μόνο, μόλις ζητηθεί ή πριν ζητηθεί.

Για το σκοπό αυτό εκμεταλλευόμαστε την τεχνική του καθυστερημένου υπολογισμού (delayed
evaluation), ενσωματώνοντας τη μέθοδο παραγωγής στο αποθηκευτικό μέσον (συνήθως μια
λίστα) και την ενεργοποίησή της στη συνάρτηση ανάγνωσης.
Τυπικά, ρεύμα είναι μια δομή δεδομένων που περιέχει, με τρόπο "κρυμμένο" από την ανάγνωση,
υπολογισμό (δηλαδή, με τρόπο όπου διαφοροποιούμε την εφαρμογή παραγωγής τιμής από την
εφαρμογή ανάγνωσης της τιμής). Με τον τρόπο αυτό, η συνάρτηση αναζήτησης δεδομένων δεν
χρειάζεται να είναι ενήμερη του γεγονότος οτι αυτά υπολογίζονται με την κλήση τους, ή έχουν
υπολογιστεί μόνο τα απαραίτητα για την επόμενη κλήση: τα "αντιμετωπίζει σαν να υπάρχουν
ήδη". Έτσι, μπορούμε να αναφερόμαστε σε απεριόριστου μεγέθους δομές χωρίς να τις έχουμε
δημιουργήσει. Είναι λοιπόν ρεύμα ένας παροχέας δεδομένων, που μοιάζει με:
426

"ένα χαλί που ξετυλίγεται μπροστά στα πόδια μας όσο περπατάμε"
Ο συνδυασμός ρευμάτων με φίλτρα αποτελεί μια πολύ δυνατή υπολογιστική τεχνική διότι το
ρεύμα μπορεί να παράγει απεριόριστα τιμές γενικής μορφής ενώ το φίλτρο να κρίνει τις
κατάλληλες και να τις αφήνει να περάσουν. Πχ, έστω οτι αντιμετωπίζουμε το πρόβλημα "να
βρεθούν οι πρώτοι διαιρέτες του n" με τη διαδοχή υπολογισμών:
(ρεύμα) πρώτοι αριθμοί → (φίλτρο) διαιρέτες του n → οι πρώτοι διαιρέτες του n
Η αντιμετώπιση αυτή παρέχει μεγάλη ευχέρεια για εφαρμογή της τεχνικής "διαίρει και βασίλευε",
δηλ. τμηματοποίηση του συνολικού προβλήματος σε μερικότερα προβλήματα. Η βασική τεχνική
εφαρμογής φίλτρων, είναι:
απαρίθμησε → φιλτράρισε → υπολόγισε → συσσώρευσε → επανάλαβε
όπου η απαρίθμηση παρέχει μια νέα τιμή μόλις χρησιμοποιηθεί η προηγούμενη. Το φίλτρο κρίνει
αν η υφιστάμενη τιμή ταιριάζει και, αν ναι τροφοδοτεί τον υπολογισμό, αν όχι την απορρίπτει
και κρίνει την επόμενη. Ο υπολογισμός χρησιμοποιεί την τιμή που του χορηγείται και παράγει
αποτέλεσμα, και τα αποτελέσματα συσσωρεύονται με την επανάληψη του κύκλου. Ο
υπολογισμός μπορεί να διακλαδίζεται εσωτερικά σε "and-or" δένδρο τέτοιων διαδοχών.
Αν το φίλτρο κρίνει τα αποτελέσματα του υπολογισμού και όχι τις τιμές εισόδου του, τότε
προφανώς τοποθετείται μετά τον υπολογισμό:
απαρίθμησε → υπολόγισε → φιλτράρισε → συσσώρευσε → επανάλαβε

Η τεχνική των φίλτρων παρέχει επίσης την ευχέρεια καθυστέρησης του υπολογισμού μέχρι να
χρειαστούν τα δεδομένα, όπου η καθυστέρηση εμπεριέχεται στην απαρίθμηση.

Η χρήση ρευμάτων είναι εξαιρετικά αποτελεσματική, για πολλούς λόγους:
– κάνει μόνο τους απαραίτητους υπολογισμούς, και όταν χρειαστούν
– επιτρέπει οργάνωση του συνολικού υπολογισμού σε πολλά επίπεδα γενικότητας και με
ανεξάρτητους επί μέρους υπολογισμούς
– ο υπολογισμός είναι ταχύτατος, διότι υπολογίζει μόνο μια τιμή κάθε φορά
– είναι δυνατό να τοποθετηθεί ο υπολογισμός τιμής σε μέρος της συνολικής εκτέλεσης όπου
υπάρχει ευχέρεια χρόνου, πριν χρειαστεί η νέα τιμή
– αν και η αρχική οργάνωση είναι αρκετά σύνθετη και απαιτεί συναρτήσεις ανωτέρου επιπέδου,
από κει και πέρα, οι επεξεργασίες που βασίζονται σε ρεύματα ακολουθούν απλές μεθοδεύσεις
σύνθεσης, πολύ εύκολα κατανοητές από τον ανθρώπινο νου, επιτρέποντας ανάπτυξη
προγραμμάτων πολύ μεγάλου βάθους και συνθετότητας
– οι παραγόμενες τιμές μπορούν να είναι σύνθετοι όροι∙ οποιοσδήποτε τύπος ή φόρμα είναι
δυνατό να παραχθεί ως έξοδος του κατάλληλου φίλτρου.

β'. Υπολογιστική αναπαράσταση ρεύματος
Η ιδέα που περιγράφηκε μπορεί να αναπαρασταθεί υπολογιστικά με πολλούς τρόπους. Ένας
τρόπος είναι η αναπαράσταση με ζεύγος, όπου πρώτο μέλος είναι η τρέχουσα τιμή που παράγει η
ροή και δεύτερο μέλος η μέθοδος παραγωγής.
– Το πρώτο μέλος μπορεί να είναι απλή τιμή ή μια λίστα όπου συσσωρεύονται οι τιμές που
427

έχουν εξαχθεί μέχρι στιγμής.
– Το δεύτερο μέλος είναι μια συνάρτηση (ή λ-έκφραση) που "θυμάται" πού έχει μείνει η
παραγωγή και είναι σε θέση να παράγει την επόμενη:
(τιμή . δοχείο-που-παράγει)
Η μορφή αυτή ονομάζεται κανονική μορφή του ρεύματος (normal form).

Χρειαζόμαστε και ένα τρόπο "εξαγωγής τιμής από το δοχείο", δηλ. ενεργοποίησης του
υπολογισμού που αυτό περιέχει. Μια αντιμετώπιση είναι, να δούμε την κανονική μορφή σαν μια
"άλλου είδους λίστα", όπου με κατάλληλες συναρτήσεις, εξωτερικά πανομοιότυπης λειτουργίας
με τις γνωστές CAR, CDR, APPEND κλπ, να χειριζόμαστε την πληροφορία που υποτίθεται πως
περιέχεται στη "λίστα" αυτή. Αυτό σημαίνει, να εκτελείται η αντίστοιχη πράξη με επί τόπου
ενεργοποίηση της διαδικασίας παραγωγής της ή των τιμών που χρειάζονται για να
χρησιμοποιηθούν από την αντίστοιχη συνάρτηση.
Εδώ ίσως γεννάται στον μελετητή ένα ερώτημα: "αφού είναι τόσο χρήσιμο, γιατί δεν είναι
ενσωματωμένο στη Lisp;". Υπάρχουν διάφοροι λόγοι γι' αυτό. Σημαντικότεροι είναι:

– Αυτό το "νέο είδος λίστας", που ουσιαστικά είναι μια ειδική χρήση του ζεύγους, μπορεί
εύκολα να προκαλέσει σύγχυση∙ πχ, έχει μεν ο χρήστης τη δυνατότητα να ζητήσει το πρώτο
στοιχείο της λίστας, και αυτό συμπίπτει με τις κοινές λίστες, αλλά τί θα γίνει αν ζητήσει το
δεύτερο, που δεν υπάρχει ακόμα; πόσα πρέπει να προβλέψουμε να υπάρχουν; Έτσι, ανάλογα με
το στόχο, κτίζουμε τη δομή που μας εξυπηρετεί καλύτερα, θέτοντας και τους ανάλογους
περιορισμούς χρήσης.

– Η μεθόδευση υλοποίησης μπορεί να είναι διαφορετική, ακόμα και για τον ίδιο στόχο∙
εξαρτάται στενά από τη γενικότερη προσέγγιση που ακολουθεί ο προγραμματιστής (χαμηλά ή
ψηλά συναρτησιακή, με χρήση macros, προσανατολισμένη στα αντικείμενα, λογικού
προγραμματισμού…). και ο κάθε μελετητής μπορεί να οργανώσει τη δική του μεθόδευση,
ανάλογα με την περίπτωση υπολογισμού αλλά και τη νοοτροπία συγγραφής.

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

α'. Με διαδοχικές αντικαταστάσεις της παρεχόμενης τιμής
Καταχωρούμε στο ζεύγος - ρεύμα πρώτο μέλος τον όρο που θα προσφέρεται από το ρεύμα προς
ανάγνωση, και δεύτερο μέλος τη μέθοδο υπολογισμού του επόμενου όρου.
428

Η διαχείριση της παραγωγής χρειάζεται δύο επί πλέον έννοιες, της εφαρμογής καθυστέρησης
και της ενεργοποίησης υπολογισμού, δηλαδή, δυο ακόμα συναρτήσεις, έστω DELAY και
FORCE . Η πρώτη έχει σκοπό την καθυστέρηση του υπολογισμού μέχρι ένα συγκεκριμένο
γεγονός, ενώ η δεύτερη έχει σκοπό την ενεργοποίηση της εκτέλεσης που εκκρεμεί.
Η τοποθέτηση της νέας τιμής ως πρώτο στοιχείο του ζεύγους χρειάζεται μια συνάρτηση
αντίστοιχη της CONS , έστω CONS-STREAM , που θα αντικαθιστά την κεφαλή με τον νέο όρο ή
θα τον επισυνάπτει στη λίστα των όρων που έχουν ήδη παραχθεί, και θα "ειδοποιεί" το δεύτερο
μέλος "να είναι έτοιμο για νέο υπολογισμό".
Οι συναρτήσεις προσπέλασης του ζεύγους θα είναι αντίστοιχες των CAR και CDR.
Μπορούμε να ακολουθήσουμε διάφορους δρόμους συσχέτισης της "ανάγνωσης" και
"παραγωγής", πχ. να ενεργοποιείται η παραγωγή νέου όρου με την ανάγνωση του παλαιού και να
χάνεται ο παλαιός, ή να μπορούμε να διαβάζουμε τον παλαιό κατ' επανάληψη μέχρι να
χρειαστούμε νέο οπότε θα ενεργοποιείται η παραγωγή εξωτερικά της ανάγνωσης, ή να φυλάμε
όλους τους παραγόμενους όρους και να παράγεται νέος με ειδική ζήτηση, κλπ.
Θεωρούμε εδώ τα εξής:
i) η νέα τιμή θα αντικαθιστά την παλαιά, και

ii) η ανάγνωση δεν θα ενεργοποιήσει τίποτα, και το έργο της τοποθέτησης νέου όρου στην
κεφαλή του ζεύγους, θα το αναλάβει ιδιαίτερη συνάρτηση, έστω η MEMORIZE . Η προσέγγιση
αυτή έχει το πλεονέκτημα της ελεύθερης επαναχρησιμοποίησης της τιμής της κεφαλής [Abe+89].

Θεωρούμε οτι η συνάρτηση που επιτελεί την ενεργοποίηση είναι ενσωματωμένη στην αντίστοιχη
της CDR , την TAIL-STREAM , ενώ η αντίστοιχη της CAR , έστω η HEAD-STREAM , κάνει
ακριβώς την ίδια δουλειά με την CAR (την ορίζουμε μόνο για λόγους ομοιομορφίας ονομάτων
συμβόλων∙ όλες οι συναρτήσεις ορίζονται πολύ απλά, εκτός της DELAY που είναι συνθετότερη).
(defun
(defun
(defun
(defun

head-stream (stream) (car stream))
tail-stream (stream) (force (cdr stream)))
force (expr) (funcall expr))
cons-stream (a expr) (cons a (delay expr))

Tο ζεύγος - ρεύμα θα έχει τη μορφή:
( a . (delay expr) )

όπου η expr είναι συναρτησιακή φόρμα (άρα θα κληθεί με πρόθεμα #' )
Η "απομνημόνευση" γίνεται με τη συνάρτηση MEMORISE :
(defun memorize (expr)
(let
((is-running nil) (result nil))
(funcall #'(lambda ( )
(cond ( (not is-running)
(setf result (funcall expr))
(setf is-running t)
429

result )
( t result )) ) ) ) )

Η MEMORISE , όταν κληθεί με όρισμα λ-έκφραση, η οποία να είναι ακριβώς η φόρμα
παραγωγής της νέας τιμής, θα δώσει έξοδο λ-έκφραση κατά τα γνωστά. Άρα δεν θα γίνει ο
υπολογισμός παρά με επεξηγηματική εκτέλεση αυτού του αποτελέσματος, με την FORCE .
Η DELAY ορίζεται ως εξής:
(defun delay (expr)
(memorise (funcall #'(lambda ( ) expr)))

β'. Με συσσώρευση των παραγομένων τιμών σε λίστα
Η δεύτερη μεθόδευση, πολύ απλή, είναι η παρακάτω, όπου για την καθυστέρηση και την
ενεργοποίηση χρησιμοποιούμε τις QUOTE και EVAL. Η τεχνική αυτή προσφέρεται ιδιαίτερα για
τη συσσώρευση των αποτελεσμάτων στο α' μέλος, αντί για αντικατάσταση της τιμής. Το ρεύμα
θα δίνεται και πάλι από ζεύγος:
(a . b)
όπου:
– a είναι η λίστα των τιμών που έχουν παραχθεί εξ αρχής μέχρι την τελευταία κλήση
– b είναι η "quoted" λ-έκφραση που παράγει το επόμενο στοιχείο.
Η συνάρτηση επεξεργασίας τέτοιου ζεύγους, που επιστρέφει ολόκληρη τη σειρά των τιμών που
έχουν παραχθεί, είναι η παρακάτω UP-TO-NOW-VALUES, που επιτελεί καί την ανάγνωση της
λίστας των τιμών που έχουν παραχθεί μέχρι τώρα, καί την επισύναψη στην αρχή μιας νέας τιμής,
κάθε φορά που καλείται:
(defun up-to-now-values (x)
(setq d (funcall (eval (cdr x))))
; δώσε μια ακόμα τιμή
(setf (car x) (cons d (car x))) ; τοποθέτησέ την στη λίστα τιμών
(car x))
; επίστρεψε τη λίστα τιμών

Αν θέλουμε μόνο την τελευταία τιμή, θα ζητήσουμε ως επιστροφή το CAAR, αλλά είναι
προτιμότερο να "αποσυνδέσουμε νοηματικά" την κλήση αυτή από τις λίστες, διότι ο χρήστης
"περιμένει ανάγνωση τύπου CAR και όχι CAAR . Γι' αυτό ορίζουμε :
(defun new-value (x)
(setq d (funcall (eval (cdr x))))
(setf (car x) (cons d (car x)))
(caar x) )
; επίστρεψε την κεφαλή της λίστας τιμών

Η μεθόδευση αυτή, με κάποιες επεκτάσεις, μπορεί να καλύψει και ευρύτερα ζητήματα, όπως: i)
το δεύτερο μέλος να έχει τιμή μια "quoted" λίστα, με στοιχεία λ-εκφράσεις παραγωγής, και το
πρώτο να είναι λίστα από λίστες των παραγομένων αντίστοιχα στοιχείων, ii) η ενεργοποίηση μιας
από τις λ-εκφράσεις να επισυνάπτει το επόμενο στοιχείο στην αντίστοιχη λίστα - όρο του α'
μέλους.
Στην περίπτωση αυτή, μπορούμε να ορίσουμε την συνάρτηση APPEND-S που να συνενώνει
τέτοια ζεύγη, που να επισυνάπτει τις (διαφορετικές) μεθόδους παραγωγής στη λίστα μεθόδων του
430

αποτελέσματος, επισυνάπτοντας και τις (ελεγχόμενες ως διαφορετικές αν πρόκειται για την αυτή
μέθοδο) αντίστοιχες τιμές στην αντίστοιχη λίστα τιμών.
Παράδειγμα
Έστω οτι ζητάμε ρεύμα που να παράγει διαδοχικούς ακέραιους. Ορίζουμε ως αρχική κατάσταση,
δηλ. τη λίστα τιμών, την (0) , και ως μέθοδο παραγωγής την λ-έκφραση "συν 1 στην τελευταία
τιμή που παρήχθη":
(setq a '(0))
(setq b '(lambda ( ) (+ 1 (caar c))))
(setq c '(a . b) )

Το ρεύμα επομένως θα είναι το c , όπου είναι προτιμότερο να γράψουμε απ' ευθείας τις τιμές των
μελών του ζεύγους (a . b) , ώστε να αποφύγουμε τη χρήση μεταβλητών:
(setq c '( (0) . '(lambda ( ) (+ 1 (caar c))) )

Παραγωγή:

(up-to-now-values
(up-to-now-values
(up-to-now-values
(new-value c)
(new-value c)
(up-to-now-values

→ (1 0)
→ (2 1 0)
→ (3 2 1 0)
→ 4
→ 5
c) → (6 5 4 3 2 1 0)
c)
c)
c)

Απεριόριστες δομές

Τα ρεύματα, με τη δυνατότητά τους να παράγουν σε κάθε ζήτηση νέα τιμή, ουσιαστικά
αποτελούν απεριόριστες δομές, με την έννοια συνόλου που "δεν τελειώνει ποτέ", ή λίστας που
μας δίνει τη δυνατότητα να διαβάζουμε διαρκώς νέους όρους της. Θέσαμε ως δεύτερο μέλος του
ζεύγους μια απλή παραγωγή διαδοχικών φυσικών αριθμών. Μπορούμε στη θέση της να δούμε
οποιαδήποτε μέθοδο επανειλημμένης παραγωγής στοιχείων.
Είδαμε ρεύματα που κρατούν μόνο μια τιμή, την τελευταία που παρήχθη, και ρεύματα που
κρατούν όλες τις τιμές που παράγονται. Στην πρώτη περίπτωση, η λειτουργία είναι απεριόριστη,
αλλά στη δεύτερη, η συσσώρευση κάποια στιγμή θα προκαλέσει αναπόφευκτα υπερχείλιση, έσω
και αν τα περιθώριά μας είναι πολύ μεγάλα.
Περιορίζουμε αυτό το ενδεχόμενο με ένα μηχανισμό απλοποίησης που απαλείφει όρους που δεν
χρειαζόμαστε πλέον. Μπορούμε ακόμα να εντάξουμε το μηχανισμό αυτό μέσα στη μέθοδο
παραγωγής (δηλ. στο β' μέλος του ζεύγους) ή να τον εντάξουμε σε μια συνάρτηση που θα ασκεί
το γενικό έλεγχο.
Ένα τέτοιο ρεύμα θα έχει τη μορφή δύο σταδίων υπολογισμού: στο πρώτο θα γίνεται η
συσσώρευση (βάσει της σύνθεσης δύο αλγορίθμων, παραγωγής και φιλτραρίσματος) και στο
δεύτερο η απλοποίηση της λίστας όταν υπερβαίνει κάποια όρια (με "νοήμονα" αλγόριθμο που θα
διαγράφει τα λιγότερο σημαντικά στοιχεία):
431

[παραγωγή → αρχικό φιλτράρισμα → συσσώρευση] →
→ [φιλτράρισμα απλοποίησης]
Η μεθόδευση που τελικά θα εφαρμόσει κανείς, δεν είναι δυνατό να προκαθοριστεί διότι
κατευθύνεται από τον επιδιωκόμενο στόχο. Πχ, αν στόχος είναι να συσσωρεύονται όσο το δυνατό
περισσότερες τιμές και το αποθηκευτικό μέσον είναι περιορισμένο, πρέπει να οργανώσουμε
αυτόματη απλοποίηση της λίστας αποτελεσμάτων όταν ξεπερνά κάποιο μέγεθος, και με κριτήριο
"τί είναι συγκριτικά πιο σημαντικό, απορρίπτοντας σε αναλογία ποσοστών τους πιο ασήμαντους
όρους".
Στα ρεύματα επιδιώκουμε, να είναι η μέθοδος υπολογισμού γραμμικής πολυπλοκότητας, διότι δεν
έχει νόημα "ρεύματος" μια δομή όπου ο χρόνος ανάγνωσης του επόμενου στοιχείου αυξάνει
σημαντικά με την τάξη του παραγόμενου όρου.

9.5 Πηγαίνοντας βαθύτερα στον υπολογισμό
Εδώ θα δούμε μερικά ζητήματα που υπεισέρχονται στην ουσία του υπολογισμού, με σκοπό την
υπολογιστική εκμετάλλευση της ίδιας της έννοιας "υπολογισμός". Θα εξετάσουμε αυτή την
έννοια από τρεις απόψεις:
– τον υπολογιστικό μηχανισμό της Lisp,
– το υπολογιστικό μοντέλο, και
– το λογικό μοντέλο.
Αν και η εξέταση αυτή είναι απλώς ενδεικτική, δείχνει τις δυνατότητες για υπολογιστική
υλοποίηση που προσφέρει του μοντέλο του λ-λογισμού.

9.5.1 "Lisp μέσα στη Lisp": κατασκευάζουμε μια νέα γλώσσα;

Η λειτουργία του compiler της Lisp βασίζεται σε μια ατέρμονα επανάληψη ανάγνωσης,
υπολογισμού και εκτύπωσης, το λεγόμενο "read-eval-print loop". Μπορούμε πολύ εύκολα να
εγκαταστήσουμε το δικό μας κύκλο "read-eval-print", προσδιορίζοντας διαδικαστικά αυτή την
επανάληψη.
Το πρόγραμμα είναι όλο κι όλο το ακόλουθο:
(defun new-lisp ( )
(prog ( )
label1
(princ " lisp1-> ")
(print (eval (read))) (terpri)
(go label1) ))

Εκτέλεση:
(new-lisp)
432

→ lisp1->
Αυτό είναι η έξοδος της NEW-LISP αλλά ταυτόχρονα αποτελεί και ένα "prompt" που περιμένει
να δώσουμε τιμή (είσοδο στο read). Πχ, αν δώσουμε (setq a 2) επιστρέφει την τιμή αυτής της
φόρμας, δηλ. 2 , και επαναλαμβάνει, ξαναδίνοντας το prompt.
Οτιδήποτε δοθεί ως είσοδος τώρα, σε μορφή συνήθων συναρτήσεων της Lisp, εκτελείται και
εκτελείται πανομοιότυπα όπως αν δινόταν στο prompt της Lisp:
lisp1-> (setq a 2)
→ 2
lisp1-> a
→ 2

Η συνάρτηση NEW-LISP δείχνει πώς μπορούμε να προσδιορίσουμε τον κύκλο "read-eval-print"
μέσα στη Lisp, αλλά βέβαια δεν προσφέρει τίποτα νέο. Ο τρόπος όμως αυτός είναι εξαιρετικά
ευέλικτος, διότι μπορούμε να διαμορφώσουμε κατάλληλα τη φόρμα (print (eval (read)) , όπως
πχ. "να βγάλουμε έξω από την παρένθεση" τη συνάρτηση, δηλαδή να προσδιορίσουμε σύνταξη με
prefix μορφή συναρτήσεων.
Η ακόλουθη συνάρτηση PREFIX-LISP δίνει prompt και περιμένει δύο ορίσματα, συνάρτηση και
λίστα, της οποίας τα στοιχεία θα αποτελέσουν τα ορίσματα της συνάρτησης:
(defun prefix-lisp ( )
(prog ( )
label1
(princ " prefix-> ")
(print (apply (read) (read)))
(terpri)
(go label1) ))

Εκτέλεση:
(prefix-lisp)
→ prefix->

(αυτή είναι η έξοδος∙ περιμένει να δώσουμε είσοδο δύο όρων, συνάρτηση και λίστα∙ μετά την
είσοδο των στοιχείων αυτών, ανακυκλώνει λόγω του "go" και ξαναδίνει την έξοδο)
prefix-> cons (a (b c))
→ (a b c)
prefix-> car ((a b c))
→ a

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

Στην επόμενη ενότητα θα δούμε γιατί χαρακτηρίσαμε τη "Lisp μέσα στη Lisp" μια νέα γλώσσα.
Εν τω μεταξύ, θα αναλύσουμε την έννοια της αλληλεπίδρασης κάτω από το πρίσμα της ιδέας που
οδήγησε στη "Lisp μέσα στη Lisp".

433

Επίπεδα αλληλεπίδρασης

Παρατηρούμε πως, στο prompt εισόδου μπορεί να δώσει ο χρήστης όποια εφαρμογή θέλει.
Ορισμοί και λ-εκφράσεις δίνονται σε μορφή εφαρμογής συνάρτησης ( defun, lambda, κλπ.). Αυτό
είναι το επίπεδο αλληλεπίδρασης του χρήστη με τη γλώσσα.
Μια εφαρμογή που δέχεται είσοδο με προκαθορισμένο τύπο τιμής εισόδου, μπορεί να δεχτεί ως
τιμή σταθερές αυτού του τύπου. Αυτό είναι το καθαρά διαδικαστικό επίπεδο, και δεν έχει τίποτα
ιδιαίτερο απέναντι στο συνήθη τρόπο που αυτό γίνεται σε κάθε διαδικαστική γλώσσα.

Μια εφαρμογή που δέχεται ως είσοδο συναρτησιακή έκφραση (πχ. με αναφορά της FUNCALL ή
της APPLY στο σώμα της) ανεβαίνει ένα σκαλοπάτι ψηλότερα: ο αλγόριθμος που θα εκτελεστεί
δεν είναι προκαθορισμένος, αλλά καθορίζεται από στοιχεία εισόδου. Αυτό φέρνει μια τέτοια
συνάρτηση σε επίπεδο αφηρημένης μηχανής Turing: Αφηρημένη Μηχανή Turing είναι μια
υποθετική μηχανή η οποία δέχεται είσοδο οποιαδήποτε συγκεκριμένη μηχανή Turing (ο όρος
"Μηχανή Turing" είναι συνώνυμος του "αλγόριθμου") και παράγει μια μηχανή Turing η οποία
όταν διαβάσει μια λέξη εισόδου, θα λειτουργήσει ("λειτουργία μηχανής Turing" είναι συνώνυμο
της "εκτέλεσης αλγόριθμου"). Η είσοδος της συναρτησιακής έκφρασης η οποία (δηλ. η σ.ε.) θα
δοθεί ως είσοδος στην αρχική συνάρτηση, μπορεί να είναι προκαθορισμένη ή να διαβάζεται
επίσης από την είσοδο.
Επί πλέον, μια συνάρτηση που "διαβάζεται", μπορεί επίσης να "διαβάζει" συνάρτηση, και αυτό
να επαναλαμβάνεται απεριόριστα. Με τον τρόπο αυτό ανεβάζουμε κατά μία μονάδα την τάξη της
αρχικής συνάρτησης κάθε φορά που δίνουμε είσοδο συνάρτηση που μπορεί να δεχτεί είσοδο
συνάρτηση, άρα η αρχική συνάρτηση έχει εν δυνάμει άπειρη τάξη.

Ουσιαστικό στοιχείο που διαφοροποιεί την είσοδο "ανάγνωσης" από την είσοδο "απ' ευθείας στη
γλώσσα" είναι η δυνατότητα διασύνδεσης της τιμής εισόδου με κάτι που ήδη τρέχει, όπως θα
δούμε στο επόμενο.

9.5.2 Ενεργοί κανόνες, γεγονότα και αλληλεπίδραση διεργασιών
Ενεργοί κανόνες: "σκανδάλη"
Έχουμε ένα πολύ σημαντικό λόγο να προσδιορίσουμε μια "Lisp μέσα στη Lisp", και δεν είναι
απλώς ένα "υπολογιστικό παιχνίδι":

Οπως είδαμε, ένας Lisp compiler δεν είναι μόνον ένας compiler μεταγλώττισης του πηγαίου
κώδικα σε κώδικα εκτελέσιμο: είναι και ένα περιβάλλον έτοιμο να ανταποκριθεί. Στο
περιβάλλον του "toploop" της Lisp, δεν είναι εφικτό να ενεργοποιηθεί κάτι αυτόματα, δηλαδή
μόλις πληρωθούν οι απαιτούμενες συνθήκες: απαιτείται οπωσδήποτε να προηγηθεί κλήση.

Μπορούμε όμως να πετύχουμε την κλήση αυτή, δημιουργώντας μια εφαρμογή που σε ανώτερο
επίπεδο να ασκεί τον κατάλληλο έλεγχο με ατέρμονα ανακύκλωση και να προβαίνει στις
ανάλογες ενεργοποιήσεις, και σε χαμηλότερο επίπεδο να λειτουργεί σε σχέση με τη -θεωρούμενη
ως κύρια- εφαρμογή. Τότε οι εφαρμογές που προκαλούνται από το ανώτερο επίπεδο, παίρνουν
434

έννοια αυτόματης ενεργοποίησης διεργασιών. Ταυτόσημη έννοια είναι η αφύπνιση διεργασίας.
Η γενικότερη μορφή από μια τέτοια ανακύκλωση, είναι μια συνάρτηση που επιπρόσθετα θα
επιτρέπει σε κάθε κύκλο ελεύθερη είσοδο (όπως η παραπάνω NEW-LISP), περιμένοντας να δοθεί
στοιχείο εισόδου ή αναγνωρίζοντας αν έχει δοθεί.

Προφανώς το "αυτόματο" της εκτέλεσης δεν είναι παρά η αντίληψη που σχηματίζουμε για το πώς
λειτουργούν κάποια μέρη της γενικότερης εφαρμογής μας, σε σχέση με άλλα. Ένα τέτοιο
"αυτόματο" μέρος, αν συντίθεται από λογικό έλεγχο και αντίστοιχο συμπέρασμα, θα εκτελέσει το
συμπερασματικό μέρος μόνο -και οπωσδήποτε- κάτω από την ικανοποίηση των απαιτουμένων
προϋποθέσεων. Έτσι συνθέτουμε ένα κανόνα που εφαρμόζεται διαρκώς (ή τουλάχιστον αυτή την
εντύπωση μας δίνει), και γι' αυτό αποκαλείται ενεργός κανόνας (active rule) ή σκανδάλη
(trigger).
Αν διακρίνεται αλληλουχία περισσοτέρων τέτοιων ενεργοποιήσεων σε αλυσιδωτή εξάρτηση,
το αίτιο μιας ενεργοποίησης αποτελεί τη σκανδάλη για την επόμενη∙ γενικότερα, αν η
αλληλουχία είναι σε δενδροειδή μορφή, κάθε κόμβος αποτελεί τη σκανδάλη για όλους του
κλάδους που κρέμονται από αυτόν. Στην περίπτωση αλληλουχίας, από κάθε κρίκο της αλυσίδας
και πέρα (αντίστοιχα από κάθε κόμβο του δένδρου και κάτω) το τμήμα αυτό έχει έννοια ενεργού
κανόνα, ενώ η έννοια της σκανδάλης συγκεκριμενοποιείται στους κρίκους (αντίστοιχα κόμβους).
Δεδομένου όμως οτι ένας κόμβος μπορεί να περιλαμβάνει οτιδήποτε, η διάκριση ενεργού κανόνα
και σκανδάλης είναι σχετική με το context αναφοράς.
Επομένως, ενεργός κανόνας είναι μια εφαρμογή σε μορφή κανόνα, ο οποίος προκαλεί την
εκτέλεση (ενεργοποίηση) μιας συγκεκριμένης διεργασίας
(cond (<προϋποθέσεις> <διεργασία>))

και είναι τοποθετημένος στο σώμα μιας ανακυκλούμενης εφαρμογής η οποία "ταυτόχρονα"
επιδρά στις <προϋποθέσεις> και καλεί τον κανόνα για εκτέλεση. Για παράδειγμα:
(cond ((and (= a b) (> c a)) (beep)))

σ' αυτό, ρόλο σκανδάλης παίζει η συνθήκη
διεργασίας η (beep) .

(and (= a b) (> c a)) και ρόλο ενεργοποιούμενης

Ο κανόνας θα εκτελεστεί ως εφαρμογή μόλις "περάσει" από αυτόν η αλγοριθμική διαδοχή της
διεργασίας ανωτέρου επιπέδου, και η αντίστοιχη <διεργασία> θα εκτελεστεί μόνο, και
οπωσδήποτε, αν τη στιγμή αυτή ικανοποιούνται οι <προϋποθέσεις> .
Αν χρειαζόμαστε πολλαπλή χρήση του κανόνα, μπορούμε να τον ονομάσουμε ως συνάρτηση:
(defun trigger1 ( ) (cond (<προϋποθέσεις> <διεργασία>)))
Μια ενδεικτική μορφή της ανακυκλούμενης εφαρμογής ανωτέρου επιπέδου είναι η παρακάτω,
όπου <trigg1> <trigg2>…<triggk> είναι ενεργοί κανόνες:
(defun trigg-lisp ( )
(prog ( )
label1
<trigg1> <trigg2> … <triggk>
(terpri) (princ " trig-lisp--> ")
435

(print (eval (read))) (terpri)
(go label1) ))

Η κλήση εφαρμογής τής TRIGG-LISP εκτελεί, ατέρμονα ανακυκλούμενη, όσους από τους
ενεργούς κανόνες πληρούν τις αναγκαίες προϋποθέσεις και μετά (ξανα-)δίνει pronpt εισόδου. Η
αναγνώριση συνθηκών και αντίστοιχη ενεργοποίηση κανόνων σταματά όταν φθάσει στην
εκτέλεση της "read" , αναμένοντας είσοδο, και επαναδραστηριοποιείται μετά το πέρας της
εκτέλεσης της εισόδου που θα δώσουμε. Παρακάτω θα δούμε και ανακύκλωση που επίσης
διαβάζει αλλά δεν περιμένει.

Χρειάζεται προσοχή στο οτι, με τη σειρά που γράφηκαν οι φόρμες στο σώμα της TRIGG-LISP , η
αφύπνιση διεργασίας αφορά τον επόμενο κύκλο από αυτόν όπου ίσχυσαν οι κατάλληλες
συνθήκες, και κατά συνέπεια δεν μπορούμε να χρησιμοποιήσουμε επί μέρους αποτελέσματά της
μέσα στην εκτέλεση της φόρμας εισόδου που θα δώσουμε. Μπορούμε αντίστοιχα να θέσουμε
πρώτα την είσοδο και μετά τις σκανδάλες ή ανάμεικτα, και γενικότερα να δημιουργήσουμε
περισσότερα επίπεδα ανακύκλωσης, ως κόμβους σε μορφή δένδρου, ή να δώσουμε prompt
εισόδου σε χαμηλότερο επίπεδο.

Γεγονότα
Χαρακτηρίζουμε ως γεγονός (event) κάθε κατάσταση που μπορεί να έχει την έννοια οτι
συμβαίνει, εφ' όσον βέβαια έχουμε δημιουργήσει τρόπο να εξετάσουμε αν συμβαίνει ή αν
συνέβηκε. Ένα γεγονός μπορεί να αφορά καταστάσεις που συμβαίνουν κατά τη διάρκεια μιας
εκτέλεσης, κάποια ενέργεια του χρήστη, ή να συσχετίζει εσωτερικές καταστάσεις με καταστάσεις
του εξωτερικού κόσμου. Ένα γεγονός προκαλείται: από το χρήστη, από κανόνα - σκανδάλη, ή
απλώς ως βήμα κάποιας εκτέλεσης, και αποτελεί κομβικό σημείο αναφοράς για την εκτέλεση.
Αναφερόμαστε σε ένα γεγονός ως προς: i) τον ορισμό του και τη σύνδεσή του με τη σκανδάλη
που το προκαλεί, ii) το αν έχει γίνει ή όχι, iii) τον τρόπο συσχετισμού του με άλλα γεγονότα, iv)
την ευρύτερη ακολουθία αιτιών που το προκαλούν, v) τα αποτελέσματα που συνεπάγεται, άμεσα
ή δευτερογενώς. Γενικότερα, αναφερόμαστε σε ένα γεγονός ως προς το ρόλο που παίζει στα
πλαίσια της λειτουργίας όπου εντάσσεται.

Παρ' όλο που σε κάθε περίπτωση πρόκειται για εκτέλεση εφαρμογής, αντιδιαστέλλουμε την
έννοια της κλήσης εφαρμογής από της ενεργοποίησης δράσης ή διεργασίας, διότι στην πρώτη
περίπτωση θεωρούμε αυτόνομη την εφαρμογή ενώ στη δεύτερη θεωρούμε την εκτέλεση σε
συνάρτηση με το αίτιο που την προκάλεσε, και γενικότερα το αλυσιδωτό ή δενδροειδές πλαίσιο
εκτελέσεων όπου εντάσσεται, όπου διακρίνουμε σχέσεις αιτίου - αιτιατού.

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

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

Κρίνουμε ένα γεγονός από τις εξής απόψεις:
– αν έχει συμβεί
– αν είναι δυνατό να συμβεί
– αν είναι υποχρεωτικό στα πλαίσια μιας ευρύτερης λειτουργίας να συμβεί
– αν, στα πλαίσια μιας ευρύτερης λειτουργίας, είναι επιθυμητό να συμβεί
Η πραγματοποίηση γεγονότος μπορεί να είναι πρωτογενής ή εξαρτημένη από άλλα γεγονότα.
Αίτια πραγματοποίησης γεγονότος είναι ένα σύνολο συνθηκών που όταν επαληθευτούν θα
πραγματοποιηθεί το γεγονός, στα πλαίσια της ευρύτερης λειτουργίας που εκτελείται.
Συσχετίζουμε γεγονότα μεταξύ τους:

– από κοινά σημεία ελέγχου της πραγματοποίησής τους, από κοινά αίτια, και από κοινά
αποτελέσματα∙ ως προς τα αίτια, έχουμε γεγονότα ανεξάρτητα ή παράλληλα, και εξαρτημένα,
όπου η εξάρτηση μπορεί να είναι υποχρεωτικής διαδοχής ή επίδρασης του προγενέστερου σε
κάποια από τα αίτια του επόμενου.
– από τη σχετική θέση πραγματοποίησής τους στον άξονα του χρόνου (χρονική διαδοχή σε
πραγματικό ή προσομοιωμένο χρόνο)
Τα γεγονότα στη Lisp δεν αποτελούν ειδικές οντότητες αλλά προσδιορίζονται σε σημασιολογικό
επίπεδο, κρίνοντας κάποιες καταστάσεις σε σχέση με άλλες. Το να συμβεί ένα γεγονός είναι
ταυτόσημο με την επίτευξη (ή την μη επίτευξη) μιας κατάστασης. Μπορεί να συσχετιστεί, στα
πλαίσια του τρέχοντος ανακυκλούμενου υπολογισμού, με στιγμιαίες καταστάσεις (πχ. "τώρα
έγινε κλικ δεξιού πλήκτρου ποντικιού"), διαρκείς καταστάσεις (πχ. "τώρα είμαστε μέσα στη
διάρκεια πατημένου αριστερού πλήκτρου ποντικιού") ή καταστάσεις συσχετισμού ("πριν" "μετά" άλλο γεγονός, "κατά τη διάρκεια" εκτέλεσης). Μπορεί να συσχετιστεί μα απόλυτες
καταστάσεις, στατικά αναφερόμενες στο πρόγραμμα (πχ. ορισμένες μεταβλητές να έχουν πάρει
κάποιες τιμές), ή δυναμικά δημιουργούμενες (πχ. όσο μια διεργασία εκτελείται). Το ουσιαστικό
σημείο που χαρακτηρίζει μια εφαρμογή ως γεγονός, είναι αν διαθέτουμε τρόπο ελέγχου της
πραγματοποίησής του.
Μπορούμε να έχουμε εξάρτηση διεργασιών από γεγονότα τα οποία είτε ελέγχονται μέσω
κάποιου αποτελέσματός τους στο υποθετικό μέρος λογικού ελέγχου, είτε αυτά καθ' εαυτά
ενεργοποιούνται κατά τον έλεγχο (διότι έχουμε τη δυνατότητα εκτέλεσης εφαρμογής στο
υποθετικό μέρος).

Κάθε σύγχρονος compiler διαθέτει ένα -πλουσιότερο ή φτωχότερο- σύνολο από primitive
γεγονότα, σύμφωνα με το πρότυπο της ANSI CL ή ειδικότερα της συγκεκριμένης έκδοσης.
Διακρίνουμε τα primitive γεγονότα σε συναρτήσεις (σύμβολα) και σε κλειδιά (με πρόθεμα ":")
που ελέγχουν συγκεκριμένες καταστάσεις. Τα πρώτα είναι γενικής χρήσης (ως κοινές
συναρτήσεις) ενώ τα δεύτερα εξειδικεύουν τον έλεγχο που κάνουν άλλες, ευρύτερης σημασίας
συναρτήσεις (πχ. :on-mouse-in ). Το σύνολο αυτό είναι στενά εξαρτημένο από το εκάστοτε
437

πακέτο (package) που χρησιμοποιείται.
Η πραγματοποίηση γεγονότος έχει έννοια είτε προσωρινή, στα πλαίσια της εκτελούμενης
διεργασίας, είτε μόνιμη, ως ίχνος που έμεινε πάνω στο πρόγραμμα από κάποια εκτέλεση που
προηγήθηκε. Επομένως, συσχετισμός γεγονότων μπορεί να γίνει στα πλαίσια της αυτής
γενικότερης διεργασίας, ή γενικότερα στα πλαίσια του προγράμματος, συνδέοντας ανεξάρτητες
μεταξύ τους κλήσεις.

Διαμόρφωση νέας σκανδάλης μέσα από την αλληλεπίδραση
Ο προσδιορισμός σκανδάλης δεν προϋποθέτει ελεύθερη αλληλεπίδραση, και μπορεί να γίνει και
στα πλαίσια κοινής συνάρτησης. Παρ' όλα αυτά, το να διαθέτουμε είσοδο ελεύθερης πρόσβασης
στη γλώσσα είναι σημαντικό. Όμως, ο μηχανισμός που περιγράψαμε, δέχεται αναδιαμόρφωση
των διαδικασιών σκανδάλης μόνο με "editing" της βασικής συνάρτησης, κάτι που αποτελεί
αρκετά χονδροειδή παρέμβαση.
Λεπτότερο, και απλούστερο, είναι να δώσουμε το σύνολο των triggered μηχανισμών σε
ονομασμένη λίστα, και κάθε μεταβολή να γίνεται σ' αυτήν, ενώ η ενεργοποίηση να γίνεται από
μια αναδρομική σάρωση εκτέλεσης της λίστας:
(setq triggers '(tr1 tr2 … trk))
; trX να είναι συναρτήσεις χωρίς ορίσματα
(defun triggers (trig-list)
(cond
((endp trig-list) nil)
(t (funcall (car triggers)) (triggers (cdr trig-list)))))

και η βασική συνάρτηση να διαμορφωθεί σε:
(defun in-trigg-lisp ( )
(prog ( )
label1
(triggers trig-list)
(terpri) (princ "in-trig-lisp--> ")
(print (eval (read))) (terpri)
(go label1) ))

οπότε, κατά την εκτέλεση, μπορούμε να δώσουμε επαναπροσδιορισμό της λίστας trig-list .
Ουσιαστικό για την αλληλεπίδραση είναι, να δώσουμε και συναρτήσεις επεξεργασίας της λίστας
trig-list, όπως προσθήκη_στο_τέλος , προσθήκη_στη_θέσηΝ , προσθήκη_μετά_τη σκανδάλη
(είναι απλό να οριστούν).

Λειτουργία στο background
Στο παραπάνω, η εκτέλεση της κεντρικής διεργασίας περιμένει την είσοδο σε κάθε κύκλο. Μια
διαφορετική εκδοχή είναι, αν δεν έχει δοθεί είσοδος να επαναλάβει τον κύκλο.
438

Χρειαζόμαστε γι' αυτό έναν έλεγχο του γεγονότος "έχει δοθεί είσοδος". Ο απλούστερος τρόπος
είναι να ελέγχει αν έχει δοθεί ένας (οποιοσδήποτε) χαρακτήρας (μέσω της συνάρτησης READCHAR-NO-HANG), και αν ναι, τότε να δίνει μήνυμα οτι δέχεται είσοδο φόρμας, να προχωρά στην
ανάγνωση και εκτέλεσή της.
Στην παρακάτω εκδοχή κεντρικής συνάρτησης, εκφράζουμε την είσοδο φόρμας προς εκτέλεση
μέσα σε μια "triggered" διαδικασία η οποία προηγουμένως έχει ελέγξει αν έχει πατηθεί κάποιο
πλήκτρο.
Προφανώς ο χαρακτήρας που θα πληκτρολογήσει ο χρήστης για να μεταστρέψει τη λειτουργία
του τρέχοντος κύκλου, δεν ανήκει στη φόρμα προς εκτέλεση. O χρήστης πρέπει να είναι ενήμερος
αυτής της δυνατότητας, ή να δίνεται μια εκτύπωση μηνύματος πριν την "(read-char-no-hang)",
όπως: "πάτα ένα πλήκτρο για να έρθει σε κατάσταση εισόδου", που βέβαια θα είναι ενοχλητικό αν
υπάρχει και άλλο επίπεδο αλληλεπίδρασης (που να προκύπτει από εκτελούμενο trigger).
(defun no-hang-trigg-lisp ( )
(prog ( )
label1
<trigg1> <trigg2> … <triggk>
(cond

; αν έχει πατηθεί ένα πλήκτρο, γυρίζει σε κατάσταση εισόδου:

((read-char-no-hang)

; και δίνει prompt εισόδου φόρμας

(terpri) (princ " no-hang-lisp--> ")

; διαβάζει τη φόρμα εισόδου και την εκτελεί
(print (eval (read))) (terpri)

; και με το πέρας της εκτέλεσης επαναλαμβάνει τον κύκλο
(go label1) )

; αλλιώς επαναλαμβάνει απ' ευθείας τον κύκλο
(t (go label1) ) ) )

Το πλεονέκτημα της μεθόδευσης αυτής είναι οτι δεν περιμένει να δοθεί είσοδος διακόπτοντας
κάθε λειτουργία, όπως στην προηγούμενη TRIGG-LISP , αλλά απλώς "κοιτά αν ζητείται από το
χρήστη το δικαίωμα εισόδου". Έτσι μπορεί να εκτελεί τον επαναλαμβανόμενο κύκλο ανεξάρτητα
του αν δίνουμε ή όχι φόρμα προς εκτέλεση, άρα είναι δυνατό να εξελίσσεται κάποια
λειτουργία, με βήματα που ακολουθούν τον κύκλο. Με τον τρόπο αυτό δημιουργούμε τη
δυνατότητα λειτουργίας στο background.
Παράδειγμα
Εξάρτηση γεγονότος "πριν ή μετά" από τον πραγματικό χρόνο.
(defun now ( ) (mod (get-universal-time) 10000) )
(defun πριν-ή-μετά-από-τώρα (στιγμή)

; γεγονός που αφορά μια διαπίστωση

(cond ((>= (now) στιγμή) 'πριν )
((< (now) στιγμή) 'μετά)))
(setq event1 0)
439

(defun event1 ( )

; γεγονός - "συνέβηκε η ανάγνωση τιμής του α"

(setq a (read)) (setq event1 (now)) )

; event1 είναι μια "σημαία"

Εφαρμογή σε σταθερά:
(πριν-ή-μετά-από-τώρα 2000)
(πριν-ή-μετά-από-τώρα 6000)

→ πριν
→ μετά

Εφαρμογή σε γεγονός:
(event1)
(πριν-ή-μετά-από-τώρα event1)

→ πριν

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

Αλληλεξάρτηση γεγονότων, σε επίπεδο ελέγχου / σε επίπεδο δράσης
Η διαδοχή πρόκλησης γεγονότων μπορεί να οφείλεται σε μια απλή, απ' ευθείας παράθεση
ανεξαρτήτων γεγονότων, ή να προκύπτει έμμεσα από την γενικότερη εκτελούμενη φόρμα, ή
ακόμα και να υπάρχει άμεση σχέση αιτίου - αποτελέσματος μεταξύ γεγονότων: "αν συμβεί το Α
τότε υποχρεωτικά ακολουθεί και το Β".

Η αναφορά εξαρτημένων γεγονότων κανονικά γίνεται στο μέρος δράσης ενός conditional, και η
αντίστοιχη πρόκληση να προκύπτει στα πλαίσια της εκτέλεσης του κλάδου. Αν το conditional
είναι στο βασικό, επαναλαμβανόμενο επίπεδο εκτέλεσης (όπως στη συνάρτηση TRIGG-LISP)
τότε το υποθετικό μέρος παίζει ρόλο σκανδάλης.
Είναι όμως δυνατό να προκύψει ενεργοποίηση και στα πλαίσια λογικού ελέγχου: όπως είχαμε
περιγράψει στο conditional, τα λογικά συνδετικά είναι συναρτήσεις με ορίσματα οποιεσδήποτε
εφαρμογές, άρα μπορούν να συμπεριλαμβάνουν δράση, άρα μπορούν να προκαλούν γεγονότα.
Συμπεραίνουμε οτι η πρόκληση γεγονότων στα πλαίσια αυτού καθ' εαυτού του λογικού
ελέγχου είναι δυνατή.

Αλληλεπίδραση διεργασιών μέσω γεγονότων
Ένα πολύ σημαντικό θέμα, με ευρύτατες εφαρμογές, είναι η αλληλεπίδραση διεργασιών. Στα
προηγούμενα κεφάλαια, αντιμετωπίσαμε το ενδεχόμενο αυτό με τους δύο ακόλουθους τρόπους α'
και β'. Από τα παραπάνω γίνεται εμφανές οτι διαθέτουμε και ένα τρίτο τρόπο (γ'):
α'. Της αποθήκευσης μιας κατάστασης που προκαλεί η διεργασία Α σε μια ή περισσότερες
καθολικές μεταβλητές, τις οποίες στη συνέχεια θα μπορεί να δει οποιαδήποτε "ενδιαφερόμενη"
διεργασία (είδαμε πως είναι δυνατό αυτό και με τοπικές μεταβλητές, αρκεί οι διεργασίες να
εντάσσονται στον ίδιο τόπο ή να έχουν τρόπο να δουν η μια τον τόπο της άλλης).
Με τον τρόπο αυτό έχουμε τη δυνατότητα να προσδιορίσουμε αλληλεπίδραση διεργασιών κατά
440

τη σειρά εκτέλεσής τους. Αν προσδιορίσουμε επαναλαμβανόμενη εκτέλεση, μπορούν να βλέπουν
εναλλάξ η μια τις μεταβολές που προκαλεί η άλλη.
β'.
Της εγκατάστασης μόνιμης συναρτησιακής σύνδεσης μεταξύ οντοτήτων (με την τεχνική
είτε της θέσης συμβόλου ως τιμή συμβόλου, είτε της απόδοσης ιδιότητας σε σύμβολο, είτε της
σύνδεσης συμβόλου με ένα πίνακα κατακερματισμού, είτε της δημιουργίας οντότητας - ζεύγους,
είτε της δημιουργίας σύνθετης οντότητας όπως είναι μια κλάση, κλπ). Στην περίπτωση αυτή
έχουμε αυτόματη ενημέρωση των συνδεδεμένων οντοτήτων για μεταβολές που προκαλεί κάποια
δράση.
γ'.
Στα πλαίσια του επαναπροσδιορισμού της Lisp με τον τρόπο που προαναφέραμε (πχ. με
την TRIGG-LISP), αποκτάμε τη δυνατότητα να εκμεταλλευτούμε και τις δύο περιπτώσεις με
τρόπο που να εντάσσονται στα πλαίσια μιας (φαινομενικά) μόνιμης ενδοεπικοινωνίας
διεργασιών: σε κάθε κύκλο η μία (από τις <triggX> εφαρμογές) μπορεί να βλέπει αυτά που
προκάλεσε η άλλη, και συγκεκριμένα, αν η μια ακολουθεί την άλλη, τότε τα βλέπει στον ίδιο
κύκλο, ενώ αν προηγείται, στον επόμενο κύκλο.
Επομένως, πρόκειται για μια δυνατότητα να εγκαταστήσουμε επικοινωνία όχι μόνο έμμεσα,
στα πλαίσια συναρτήσεων που όταν κληθούν για εφαρμογή θα ενημερώσουν ένα "μαυροπίνακα",
αλλά άμεσα, τουλάχιστον σε ότι αφορά τη φόρμα που θα δώσει ο χρήστης (στην πραγματικότητα,
πρόκειται για κατέβασμα αυτής της επικοινωνίας χαμηλότερα, και γι΄ αυτό χαρακτηρίσαμε
φαινομενική την ενδοεπικοινωνία των διεργασιών).
Η χρήση κλάσεων ανοίγει νέους ορίζοντες στη δημιουργία γεγονότων και τη συσχέτισής τους με
διεργασίες, διότι εκμεταλλευόμαστε την δυνατότητα κατηγοριοποίησης γεγονότων σε συσχετισμό
με την κατηγοριοποίηση διεργασιών, πχ. στη μορφή:
(defstruct event
time trigger resulting-process)
(defstruct process
name input_events duration function output_event)
(setq e1 (make-event
:time 0 :trigger p2 :resulting-process p1 ))
(setq p1 (make-process
:name 'p1 :input_events e2 :duration 12
:function '<φόρμα εκτέλεσης> :output_event e3 ))

; κοκ

(defmetthod fire-process (proc)

<ενεργοποίηση της διεργασίας που περιλαμβάνει η αντίστοιχη "φόρμα εκτέλεσης"> )

Στη διασύνδεση γεγονότων με διεργασίες, χρειάζεται προσοχή για την περίπτωση ατέρμονος
επανάληψης λόγω σύνδεσης κλήσεων "από-σε".
Η σύνδεση πραγματοποίησης γεγονότων με την ενεργοποίηση διεργασιών, και μέσω τέτοιας
σύνδεσης η αλληλεπίδραση διεργασιών, είναι ο κεντρικός άξονας ανάπτυξης προγραμμάτων
προσομοίωσης.

441

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

Χωρίς να είναι αναγκαίο να διατίθεται ελεύθερη είσοδος στον χρήστη, αν υπάρχει αυξάνει πολύ
τις δυνατότητες. Θεωρώντας οτι η εφαρμογή μιας βασικής συνάρτησης όπως η REP-LISP
αποτελεί την κεντρική διεργασία στα πλαίσια της οποίας συμβαίνει οτιδήποτε, είναι απλό να
δούμε ως συνυπάρχουσες διεργασίες αυτές που συντίθενται από μέρη, τα οποία είναι αυτά που
καλεί σε κάθε κύκλο της η βασική συνάρτηση:
(defun rep-lisp ( )
(prog ( )
label1
<proc-1> <proc-2> … <proc-k>
(cond
((read-char-no-hang)
(terpri) (princ " rep-lisp--> ")
(print (eval (read))) (terpri)
(go label1) )
(t (go label1) ) ) )

Θεωρούμε ως μια διεργασία αυτή που συντίθεται από τις επαναλαμβανόμενες κλήσεις της proc1 , άλλη αυτή που συντίθεται από τις κλήσεις της proc-2 , κοκ. Τα παρακάτω σχήματα δείχνουν
την "καθετότητα" μεταξύ διεργασιών και βημάτων:

442

Σημειώνοντας την ανακύκλωση ως διαδοχικά βήματα, παίρνουμε το ακόλουθο σχήμα, που
δείχνει τη διαδοχή:

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

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

– Μια διεργασία Ζ να συντίθεται από προκαθορισμένα βήματα, δοσμένα σε λίστα - τιμή
συμβόλου, και το αντίστοιχο τμήμα proc-Ζ να καλεί προς εκτέλεση τον αντίστοιχο με το βήμα
όρο της λίστας.

– Μια διεργασία W να συντίθεται από δυναμικά παραγόμενα βήματα, που σε κάθε κύκλο
δημιουργούνται από ένα ρεύμα παραγωγής, δοσμένο εξωτερικά της κεντρικής συνάτησης σε
μορφή λίστας παραγωγής, και το αντίστοιχο τμήμα proc-W να καλεί προς εκτέλεση τον
"επόμενο όρο" του ρεύματος.
443

"Τα πάντα" είναι δυνατό να επιτευχθούν με την τεχνική αυτή, όπως: πολυνηματικές εξαρτήσεις,
αλληλεπιδράσεις αντικειμένων, επικοινωνία συστημάτων. Το πεδίο έρευνας είναι ανεξάντλητο,
και εύκολα προσβάσιμο για το μελετητή.

Επικοινωνία διεργασιών μέσω γεγονότων

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

Η χρησιμότητα της επικοινωνίας μέσω γεγονότων, απέναντι σε απλές μεταβλητές που παίρνουν
τιμή από μια διεργασία και διαβάζει άλλη, έγκειται στο οτι συνδέεται εύκολα η μεταφερόμενη
τιμή με δύο ουσιαστικά χαρακτηριστικά: το ένα είναι η πληροφορία οτι "υπάρχει νέο μήνυμα"
(σαν τα αμερικάνικα γραμματοκιβώτια με το σημαιάκι)∙ το δεύτερο είναι οτι το μήνυμα μπορεί να
"συνδέεται με τον αποστολέα του ή/και τον/τους παραλήπτες" (πχ. σύμβολα με ιδιότητες
"αποστολέας", "παραλήπτες").
Η τεχνική αυτή μοιάζει με μια εγγράψιμη και αναγνώσιμη ταινία που κινείται κυκλικά, και
όποιος έχει να δώσει μήνυμα σε κάποιους, το εγγράφει στην ταινία μεταφοράς, υπογράφοντάς 'το,
και οι ενδιαφερόμενοι το διαβάζουν (προφανώς, δυνατό να υπογράφεται και η ανάγνωση). Αν η
"ζωή" του μηνύματος δεν χρειάζεται από κάποιο σημείο και πέρα (πχ. όταν επανέλθει στον
αποστολέα μετά τον κύκλο) μπορεί να το διαγράψει, για εξοικονόμηση πόρων.
Η τεχνική του "κύκλου της ταινίας" έχει διάφορα πλεονεκτήματα, όπως:
i) οι επί μέρους διεργασίες έχουν ένα τρόπο να επικοινωνήσουν, διατηρώντας την αυτονομία
τους
ii) υπάρχει ευχέρεια να δέχονται και να επεξεργάζονται το μήνυμα περισσότεροι του ενός
αποδέκτες, και να το μεταβάλλουν ή να το αφήνουν ανέπαφο.

Επικοινωνία μέσω συνδέσεων σε "μοιραζόμενες οντότητες"
Η προηγούμενη σύνδεση διεργασιών έχει τα εξής μειονεκτήματα:
i) εξαρτάται από τις παρεμβολές εκτέλεσης άλλων διεργασιών στον κύκλο, όπου οι παρεμβολές
αυτές παίζουν ρόλο χρονικά αλλά και διαμορφωτικά πάνω στα "μηνύματα"
ii) η προτεραιότητα εκτέλεσης είναι αυτή που επιβάλλει η διαδοχή βημάτων της κεντρικής
διεργασίας
iii) η διαδοχή στον κύκλο είναι προκαθορισμένη, και μεταβάλλεται μόνο με ριζική επέμβαση
στη βασική συνάρτηση.
Αμεσότερος τρόπος επικοινωνίας, που απαλλάσσεται ευκολότερα από τα μειονεκτήματα αυτά,
444

είναι μέσω ευθέως συνδεδεμένων οντοτήτων, που είναι δυνατό να μεταφέρουν ολόκληρη
διεργασία αν απαιτηθεί, "επί τόπου" στην καλούσα διεργασία. Με τον τρόπο αυτό δημιουργούμε
ένα "κανάλι" που παρακάμπτει τον κύκλο, που βέβαια δεν είναι τίποτα άλλο από το οτι η
καλούσα διεργασία "βλέπει" ως οντότητα μια άλλη διεργασία, όπου η οντότητα μπορεί να είναι
εκτελέσιμη υπό καθυστέρηση εφαρμογή.
Μια οντότητα είναι δυνατό να αποτελεί αντικείμενο σύνδεσης περισσοτέρων οντοτήτων∙ στην
περίπτωση αυτή αποτελεί μια διαμοιραζόμενη (shared) οντότητα.
Για παράδειγμα, κάποια από τις διεργασίες του κύκλου, έστω αυτή που προσδιορίζεται από το
βήμα (proc1) , να περιλαμβάνει, στο σώμα του βήματός της, ένα σύμβολο - μεταβλητή με τιμή
την υπό καθυστέρηση εκτέλεση βήματος άλλης διεργασίας δηλ. εφαρμογής κάποιας συνάρτησης
PROC2 χωρίς λ-μεταβλητές, ανεξάρτητα των εκτελέσεων που θα ακολουθήσουν.
Με τέτοιο τρόπο μπορούμε να επέμβουμε στη διαδοχή εκτέλεσης των βημάτων των διεργασιών,
άρα και να θέσουμε τα γεγονότα κάτω από συνθήκες που αναδιαμορφώνονται δυναμικά.
Αποτέλεσμα τέτοιας επικοινωνίας είναι δυνατό να είναι ακόμα και η μεταβολή βήματος
διεργασίας που θα ακολουθήσει αργότερα, μέσα από το βήμα διεργασίας που προηγείται, άρα να
"περάσουμε τη διαχείριση" σε κάποια διεργασία. Δεδομένου οτι πρόκειται για κύκλο βημάτων
των διεργασιών, οποιαδήποτε διεργασία είναι δυνατό να αναλαμβάνει, μόνιμα ή προσωρινά, αυτό
τον έλεγχο.

445

Η αρχική κλήση της κεντρικής διεργασίας, όπως κάθε κλήση εφαρμογής, τοποθετείται στα
πλαίσια του κόσμου που έχει ήδη δημιουργηθεί (ενδεχομένως κενός). Ο κόσμος αυτός
διαμορφώνεται εξελικτικά από τα βήματα <δ-ΧΧ> , άρα κατά την εκτέλεση της φόρμας (eval
(read)) σε κάποιο κύκλο, ο χρήστης βρίσκεται μπροστά στον κόσμο όπως αυτός διαμορφώθηκε
από τα βήματα που προηγήθηκαν.
Τα βήματα <δ-ΧΧ> μπορούν να είναι: προκαθορισμένα χωρίς συνθήκες, προκαθορισμένα αλλά
εξαρτημένα από συνθήκες, διαμορφωνόμενα από προγενέστερα βήματα, να διαμορφώνονται από
το χρήστη στο αμέσως προηγούμενο βήμα εισόδου, κλπ..

Κάθε οριζόντια γραμμή παίζει έναν ιδιαίτερο ρόλο, διότι αποτελεί το βήμα μεταβολής του
κόσμου, σε ότι αφορά τις δυνατότητες παρέμβασης του χρήστη (δηλ. αυτό που μεσολαβεί
λειτουργικά, από τη μία παρέμβαση του χρήστη μέχρι να δοθεί δυνατότητα επόμενης). Γι' αυτό
την χαρακτηρίζουμε ως φάση του κόσμου.

Η κάθετη διασύνδεση των βημάτων των διεργασιών είναι κατά κύριο λόγο νοηματική: εμείς
βλέπουμε διαφοροποιημένα την κάθετη εξάρτηση και την αυτονόμηση κάθε μιάς στήλης. Για να
είναι σαφέστερη η εξάρτηση αυτή, τα βήματα <δ-ΧΧ> οφείλουν να αποτελούν ολοκληρωμένες,
κατά κάποιο νοηματικό τρόπο, επί μέρους διεργασίες. Με χρήση γεγονότων καθολικά ορατών,
αποκτάμε τη δυνατότητα να θέσουμε σε αλληλεξάρτηση τις κάθετες διεργασίες.

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

Με τη θέση κάποιων βημάτων <δ-ΧΧ> (ή μέρος τους) κάτω από καθυστέρηση, μπορούμε να
ελέγξουμε (πχ. από το τελευταίο βήμα της οριζόντιας γραμμής) τη διαδοχή εκτέλεσης των
βημάτων αυτών. Επομένως η διαδοχή που προσδιορίζεται από τα παραπάνω βέλη είναι απλώς μια
αρχική ενεργοποίηση που παρέχει η κεντρική διεργασία, στην οποία δεν είναι υποχρεωτικό να
υπακούουν τα βήματα αυτά (αλλά οφείλουν να την αναμένουν).

Οι παρεμβάσεις του χρήστη μπορούν να είναι υποχρεωτικές, δηλ. με αναμονή εισόδου από το
χρήστη, ή δυνητικές, δηλ. χωρίς αναμονή εισόδου (πχ. με χρήση τής READ-CHAR-NO-HANG )
οπότε μόνο αν ο χρήστης ζητήσει είσοδο, να κατευθυνθεί ο υπολογισμός στο βήμα (eval (read))
.

Επικοινωνία αντικειμένων μέσω μεθόδων
Μια κατηγορία συνθέτων οντοτήτων είναι και τα αντικείμενα - στιγμιότυπα κλάσεων. Όπως
είδαμε, μπορούν να περιλαμβάνουν, εκτός από απλή πληροφορία (χαρακτηριστικά), και
υπολογισμό ως τιμή υποδοχής (πχ. με κλειδί :reader ), εκτελούμενο στη φάση αναζήτησής της.
Η επικοινωνία τέτοιων οντοτήτων επιτυγχάνεται απλά,
446

μέσω μεθόδων που δέχονται ως

παραμέτρους τα αντικείμενα αυτά: μια μέθοδος μπορεί να βλέπει συνδυασμένα, και να
επεξεργάζεται ανάλογα, υποδοχές περισσοτέρων στιγμιοτύπων.
Τέτοια επικοινωνία προσφέρει πολύ μεγάλη ευελιξία, διότι μπορούμε εύκολα να διαχωρίσουμε τα
αντικείμενα που διαμορφώνονται, από τον τρόπο διαμόρφωσής τους, και να θέσουμε αυτόν τον
τρόπο (δηλ, τη μέθοδο) ως καλούμενο βήμα στον κεντρικό κύκλο, ή ως βήμα εισόδου, ή ακόμα
να αποτελεί έμμεσα καλούμενο βήμα, από την αναζήτηση τιμής μιας υποδοχής (όπως στην
περίπτωση που έχουμε εξαρτήσει την τιμή υποδοχής από μέθοδο, με κλειδί :reader ).
Με τον τρόπο αυτό, η μέθοδος μπορεί να καλείται κυκλικά, για να σαρώσει σύνολα αντικειμένων
επιφέροντας μεταβολή, ή αντίθετα για να ενημερώσει συγκεκριμένα αντικείμενα για το "τί
συνέβηκε" σε άλλα αντικείμενα κατά τον κύκλο που προηγήθηκε.
Γενικότερα, ακόμα και η ίδια η μέθοδος μπορεί να αναδιαμορφωθεί σε κάποια φάση, είτε με
εξειδικευτικό τρόπο (διαφορετική συγκεκριμενοποίηση παραμέτρων της σε κάθε κύκλο) είτε
εξελικτικά (με επανορισμό της σε κάθε κύκλο, ως αποτέλεσμα εκτέλεσης που προηγήθηκε, ή από
το χρήστη).

Αλληλεπιδραστική επικοινωνία περισσοτέρων του ενός συστημάτων Lisp

Μια διαφορετική, πλατύτερη μορφή επικοινωνίας είναι αυτή όπου δύο Lisp - συστήματα
επικοινωνούν. Τέτοια επικοινωνία μπορούμε να εγκαταστήσουμε σε περιβάλλοντα όπου
περισσότεροι του ενός δικτυακά συνδεδεμένοι υπολογιστές, βλέπουν όλοι όλους τους δίσκους (ή,
έστω, ένα κοινό). "Τρέχοντας" τη Lisp σε έναν υπολογιστή του δικτύου, έχουμε τη δυνατότητα
εγγραφής σε αρχείο οποιουδήποτε δίσκου, και ανάγνωσης από αρχείο πάλι οποιουδήποτε
δίσκου. Συνεπώς, τρέχοντας τη Lisp σε περισσότερους υπολογιστές, μπορούμε να θέσουμε σε
επικοινωνία τα συστήματα Lisp, μέσω ανάγνωσης / εγγραφής σε αρχεία.
Επειδή η ανάγνωση / εγγραφή εξαρτάται από την τοπική εκτέλεση, για να εγκαταστήσουμε
διαρκή επικοινωνία πρέπει να θέσουμε ένα κυκλικό μηχανισμό εγγραφής - ανάγνωσης. Ο
μηχανισμός αυτός πρέπει να θέτει προτεραιότητες, δικαιώματα και υποχρεώσεις, και να
διευκολύνει την ενημέρωση ώστε να μη γίνονται άσκοπες κινήσεις εξερεύνησης "μήπως υπάρχει
κάτι νέο", αν είναι δυνατό να μη καθυστερεί η κυκλοφορία από συμφόρηση, κλπ.

Τεχνικά, πρέπει να λάβουμε υπ' όψη πως οι περισσότεροι compilers διαθέτουν κάποιους ειδικούς
μηχανισμούς ελέγχου αρχείων, εξαρτημένους από το σύστημα, που διευκολύνουν και
επιταχύνουν αυτή τη λειτουργία. Για το σκοπό αυτό εξυπηρετεί η χρήση ενός επί πλέον αρχείου
επικοινωνίας, με σύντομα μηνύματα για γρήγορη ενημέρωση των ενδιαφερομένων σχετικά με
την τρέχουσα κατάσταση.
Με τον τρόπο αυτό έχουμε τη δυνατότητα να φέρουμε σε αλληλεπιδραστική επικοινωνία
διεργασίες που έχουν φυσικά και νοηματικά κάποια τοπικότητα διότι αλληλεπιδρούν με τον
τοπικό χρήστη. Ουσιαστικά, πετυχαίνουμε την επικοινωίας διαφορετικών κόσμων.
Παρ' όλο που η CL δεν είναι προς το παρόν ιδιαίτερα εξελιγμένη στον τομέα αυτό, οι δυνατότητες
που ανοίγονται είναι ήδη πολλές: ελάφρυνση του φόρτου υπολογισμού του "ενός συστήματος",
επικοινωνία χρηστών κάτω από νοήμονα διαχείριση της μεταφερόμενης πληροφορίας,
447

επεξεργασία τοπικών δεδομένων σε δύο ή περισσότερα στάδια (τοπική αναζήτηση στον εκπομπό,
μεταφορά του αποτελέσματος και επεξεργασία στον αποδέκτη), εξέλιξη τοπικών μεθόδων /
συναρτήσεων υπολογισμού, κά.
Σημαντικό ρόλο στη διάσταση της αλληλεπίδρασης συστημάτων παίζουν οι πρόσθετες
δυνατότητες των ειδικών εκδόσεων, όπως της Allegro CL v.5.0.1 που δέχεται ενσωματωμένες
εγγραφές σε σύνταξη HTML και επιστρέφει κώδικα αυτούσιο HTML, επιτρέποντας έτσι την
εμφάνιση πληροφορίας που προέρχεται από ένα σύστημα, σε web σελίδα που εμφανίζεται μέσω
ενός "browser" που τρέχει σε άλλο σύστημα∙ και αντίθετα, ανάγνωση εγγραφών που κάνει ο
χρήστης πάνω σε web σελίδα και επιστρέφεται στην πηγή της σελίδας (σχετικές πληροφορίες και
"downloadable" κώδικα διαθέτει η σελίδα της Franz Lisp, που αναφέραμε).

9.5.4 Ο αλγόριθμος της επιλογής
Είδαμε στο πρώτο κεφάλαιο, στο λ-λογισμό, την προσέγγιση που προσδιορίζει τη λογική επιλογή,
που καταλήγει στο να ορίσει τη συνάρτηση cond ως την έκφραση:
λe1.λe2.λc.((c e1) e2)
[α]
όπου c είναι μια από τις ακόλουθες δύο εκφράσεις, όπου η πρώτη παριστά την "επιλογή του
πρώτου", και την ονομάσαμε "true", και η δεύτερη την "επιλογή του δεύτερου", και την
ονομάσαμε "false":
λx.λy.x
[β]
λx.λy.y
[γ]
Οι αντίστοιχες λ-εκφράσεις σε σύνταξη Lisp είναι:
((lambda (x) (lambda (y) x))
[β1]
((lambda (x) (lambda (y) y))
[γ1]

Επειδή στην Common Lisp καλούμε λ-έκφραση μέσω της FUNCALL, οι παραπάνω εκφράσεις
πρέπει να κληθούν για εφαρμογή ως εξής:
(funcall
(funcall
#'(lambda (x) (lambda (y) x))
<λx-arg>)
<λy-arg>)
(funcall
(funcall
#'(lambda (x) (lambda (y) y))
<λx-arg>)
<λy-arg>)
H πρώτη επιστρέφει <λx-arg> και η δεύτερη <λy-arg>

[β2]

[γ2]

Και πράγματι, αν τις καλέσουμε εφαρμοσμένες στα ορίσματα 3 και 4 λαβαίνουμε αντίστοιχα:
(funcall
(funcall #'(lambda (x) (lambda (y) x))

3)
448

4)



→ 3

(funcall
(funcall
#'(lambda (x) (lambda (y) y))
3)
4)

→ 4



όπου στη θέση των ορισμάτων 3 και 4 μπορούμε να θέσουμε οποιαδήποτε έκφραση προς
υπολογισμό:
(funcall
(funcall
#'(lambda (x) (lambda (y) x))
(+ 4 8))
(* 3 5) )

→ 12
(είναι το αποτέλεσμα του (+ 4 8) )
(funcall
(funcall
#'(lambda (x) (lambda (y) y))
(+ 4 8))
(* 3 5) ) →
→ 15
(είναι το αποτέλεσμα του (* 3 5) )

Αυτή είναι η βάση του συλλογισμού που οδηγεί στην επιλογή.
Το να δώσουμε την αντίστοιχη συνάρτηση με το παραπάνω "conditional" (σχέση [α]) σε
πρόγραμμα Lisp δεν είναι τόσο απλό, διότι, όπως παρατηρούμε, η "συνθήκη" c δεν είναι μόνο
"τιμής αληθές ή ψευδές", οφείλει να είναι και συνάρτηση, που να μπορεί να εφαρμοστεί στην
έκφραση e1 και το αποτέλεσμα αυτής της εφαρμογής να είναι πάλι συνάρτηση, η οποία να μπορεί
να εφαρμοστεί στην έκφραση e2.
Η πορεία υλοποίησης είναι η εξής:
Βήμα 1ο: Ορίζουμε συνάρτηση δύο επιπέδων εφαρμογής, αντίστοιχη της έκφρασης [β2]:
(defun first-appli (x)
#'(lambda (y) x) )

(αναγράφουμε μόνο το δεύτερο "λ" διότι το πρώτο εννοείται μέσα στη defun). Η κλήση της,
επιστρέφει την τιμή της πρώτης εφαρμογής, ανεξάρτητα της δεύτερης εφαρμογής:
(funcall (first-appli 3) 4)
→ 3
Βήμα 2ο: Ορίζουμε συνάρτηση δύο επιπέδων εφαρμογής, αντίστοιχη της έκφρασης [γ2]:
(defun second-appli (x)
#'(lambda (y) y) )

Η κλήση της, επιστρέφει την τιμή της δεύτερης εφαρμογής, ανεξάρτητα της πρώτης εφαρμογής:
449

(funcall (second-appli 3) 4) → 4

Βήμα 3ο: Τώρα, χρειαζόμαστε για ευκολία, να "ανεβάσουμε" τις διαδοχικές εφαρμογές στο
πρώτο επίπεδο. Με τον τρόπο αυτό, θα δημιουργήσουμε συναρτήσεις επιλογής "πρώτου" και
δεύτερου" ορίσματος:
(defun first-of-two (a b) (funcall (first-appli a) b))
(defun second-of-two (a b) (funcall (second-appli a) b))

Καλώντας 'τες αντίστοιχα, παίρνουμε:
(first-of-two 3 4)
→ 3
(second-of-two 3 4) → 4
Οι συναρτήσεις αυτές θα χρησιμεύσουν ως τιμές αλήθειας, γι' αυτό ας τους δώσουμε "πιο
κατάλληλα" ονόματα:
(defun true (a b) (first-of-two a b) )
(defun false (a b) (second-of-two a b) )

Εφαρμογή:
(true 3 4)
(false 3 4)

→ 3
→ 4

Τώρα είμαστε έτοιμοι να ορίσουμε το "conditional" :
(defun conditional (c a b)
(funcall c a b))
; και αυτό είναι όλο

Εφαρμογή:
(conditional
(conditional
(conditional
(conditional

#'true 3 4)
→ 3
#'false 3 4) → 4
#'true (square 3) (cube 2)) → 9
#'false (square 3) (cube 2)) → 8

Παρατηρούμε οτι αυτή η συνάρτηση CONDITIONAL λειτουργεί μόνο με τις τιμές αλήθειας true
και false , που στην πραγματικότητα είναι συναρτήσεις δύο ορισμάτων, που είναι ακριβώς οι δύο
κλάδοι υπολογισμού του conditional.
Τώρα, το ερώτημα είναι: Πώς θα συνδεθεί αυτό το conditional με το COND της Lisp ;
Έχουμε δύο δρόμους: ή θα συνεχίσουμε την πορεία που αρχίσαμε, προσδιορίζοντας νέα λογικά
συνδετικά μέσω των "true", "false", "conditional", ή θα σταματήσουμε εδώ και θα
ενσωματώσουμε το conditional και τις τιμές αυτές στην υπάρχουσα λογική της Lisp.

Ο δεύτερος είναι απλούστερος, διότι για να χρησιμοποιηθεί η CONDITIONAL με οποιοδήποτε
όρο της Lisp ως λογικό όρο, αρκεί να προσδιοριστεί ένας υπολογισμός που για όρο λογικής τιμής
Τ να επιστρέφει την TRUE και για όρο λογικής τιμής ΝΙL να επιστρέφει την FALSE .
Ξεκινάμε από τη "λογική τής Lisp", χρησιμοποιώντας την COND :
(defun evalu (expr)

; εκτίμηση λογικής τιμής της expr στο πλαίσιο αυτό
(cond ((eql expr nil) #'false)
((not (eql expr nil)) #'true)
450

( t 'error)))

Χρειαζόμαστε και μια παραλλαγή της conditional που να δέχεται ως πρώτο όρισμα οποιοδήποτε
όρο της Lisp, που εκτιμάται λογικά βάσει της EVALU :
(defun condi (c a b)
(conditional (evalu c) a b))

Εφαρμογές:
→ 10
διότι το (1 2 3) οδηγεί σε λογική τιμή "true" άρα υπολογίζεται ο πρώτος κλάδος, ενώ:
(condi (cdddr '(1 2 3)) 10 20) → 20
διότι το (cdddr (1 2 3)) είναι nil , άρα οδηγεί σε "false", και υπολογίζεται ο δεύτερος κλάδος.
(condi '(1 2 3) 10 20)

9.5.5 Υλοποίηση αναδρομής μέσω μη αναδρομικών συναρτήσεων
Είδαμε στο πρώτο κεφάλαιο τρόπους πρόκλησης αναδρομής με χρήση μη αναδρομικών
συναρτήσεων, μέσω αφαίρεσης. Ας θυμηθούμε τον πρώτο τρόπο, όπου ορίσαμε τη συνάρτηση
ADD-NUMBERS με τη χρήση βοηθητικής συνάρτησης IT-ADD , όπου χρησιμοποιήσαμε μια
αφαιρετική λ-μεταβλητή που παίζει ρόλο "να δεσμευτεί σε συνάρτηση η οποία να εφαρμοστεί
στα πλαίσια του ίδιου υπολογισμού" (γράφουμε εδώ την ίδια συνάρτηση σε σύνταξη Lisp):
(defun it-add (f x y)
(cond ((= 0 y) x)
(t (funcall f f (+ x 1) (- y 1)) )))
(defun add-numbers (x y)
(it-add #'it-add x y))

Εφαρμογή:

→ 9
Το οτι "λειτουργεί" δεν σημαίνει βέβαια πως "είναι και αποτελεσματική": για μεγάλους αριθμούς
έχουμε σημαντικό χρόνο εκτέλεσης, και από κάποιο μέγεθος και πέρα, υπερχείλιση της μνήμης
και πρόκληση σφάλματος
Με την ίδια τεχνική, σε μια ολοκληρωμένη συνάρτηση, ορίζουμε το παραγοντικό:
(add-numbers 4 5)

(defun parag (n)
(funcall
(defun fafa (fa)
(funcall fa fa 1 1))
(defun fifipc (fi p c)
(cond ((> c n) p)
(t (funcall fi fi (* c p ) (+ c 1)) )) ) ))

Εφαρμογή:
(parag 4)
(parag 6)
(parag 10)

→ 24
→ 720
→ 3628800
451

Μπορούμε να μη χρησιμοποιήσουμε την DEFUN , αναγράφοντας απ' ευθείας την αντίστοιχη λέκφραση που προσδιορίζει τον αλγόριθμο του παραγοντικού (έστω εφαρμογή του, πάνω στον
αριθμό 8):
(funcall #'(lambda (n)
(funcall
#'(lambda (fa)
(funcall fa fa 1 1))
#'(lambda (fi p c)
(cond ((> c n) p)
(t (funcall fi fi (* c p ) (+ c 1 )) )) ) ) )
8)
→ 40320

9.5.6 Η έννοια του τόπου ως έννοια εγκλεισμού υπολογισμών
Στο κεφάλαιο 5 είδαμε τρόπους προσδιορισμού τόπου και χρήσεις τους. Το κεντρικό όμως
στοιχείο στη θεωρητική υπόσταση των λ-εκφράσεων είναι οτι ουσιαστικά, αυτό που κάνουν είναι
να εκφράζουν τόπο: εξετάζοντας μια λ-μεταβλητή απο την άποψη της τοπικότητας, δεν είναι
τίποτα άλλο από μια τοπική μεταβλητή, και τόποι δεν είναι τίποτα άλλο από υπολογισμούς
εσωτερικά εγκλεισμένους σε άλλους
Χρησιμοποιώντας την LET για να προσδιορίσουμε μια τοπική χρήση μεταβλητών, πχ:
(let ((x 3) (y 4)) (+ x y))

δεν κάνουμε τίποτα άλλο από το να χρησιμοποιούμε έναν απλούστερο τρόπο γραφής της
ισοδύναμης έκφρασης:
(funcall #'(lambda (x y) (+ x y)) 3 4)

Παρ' όλα αυτά, στη δεύτερη έκφραση φαίνεται πιο καθαρά ο ρόλος των μερών: η λ-έκφραση δύο
λ-μεταβλητών καλείται προς εφαρμογή πάνω σε δύο αριθμούς, και ο υπολογισμός του σώματος
θα γίνει ως υπολογισμός της παράστασης με τις τιμές αυτές. Στην πρώτη έκφραση, γνωρίζουμε
πως "θα αντικατασταθούν οι τιμές των τοπικών μεταβλητών στο σώμα και θα επιστραφεί το
αποτέλεσμα της τελευταίας έκφρασης που υπολογίζεται". Δηλαδή, μετατρέπουμε τη συναρτησιακή
έκφραση σε διαδικαστική, τόσο συντακτικά όσο και νοηματικά.

Επομένως, δεν υπάρχει καμμία απολύτως θεωρητική ανάγκη για επεξηγηματικό προσδιορισμό
τόπων: μπορούμε πάντοτε να εκφράσουμε την απαιτούμενη τοπικότητα μέσω κατάλληλης λέκφρασης, και τον κιβωτισμό τόπων μέσω εγκλεισμένων λ-εκφράσεων. Η διαφοροποίηση είναι
καθαρά εννοιολογική: η LET έκφραση είναι "με μια ματιά" κατανοητή, ενώ η λ-έκφραση είναι
αρκετά πιο περίπλοκη.
Τα "χρονικά πλαίσια ζωής συμβόλου" δεν είναι τίποτα άλλο από το πλαίσιο που καθορίζει η
διάρκεια εφαρμογής της αντίστοιχης λ-έκφρασης: με το πέρας του υπολογισμού της, παύουν να
υφίστανται οι λ-μεταβλητές.
452

Ένα ζήτημα που χρειάζεται προσοχή στη χρήση λ-εκφράσεων είναι αυτό του ορισμού καθολικών
συμβόλων μέσα σε λ-έκφραση: είδαμε στην LET πως η κλήση καθολικού συμβόλου που στο
περιεχόμενό του (συναρτησιακό ή τιμής) αναφέρονται τοπικά σύμβολα, κατά τον υπολογισμό
του, επαναδημιουργεί τον τόπο. Αυτό οφείλεται στο οτι, ο ορισμός καθολικού συμβόλου μέσα σε
λ-έκφραση με τρόπο που να χρησιμοποιεί τοπικά σύμβολα, παρ' όλο που φαινομενικά είναι μια
δράση που συμβαίνει εσωτερικά του τόπου, ουσιαστικά εμπεριέχει τον ορισμό του τόπου.
Έστω οι "διασταυρούμενοι" ορισμοί:
(defun a1 (v1)
(funcall
#'(lambda (x)
(defun act1 (q1) (princ x) (princ q1) (act2 q1)))
v1) )
(defun a2 (v2)
(funcall
#'(lambda (x)
(defun act2 (q2) (princ x) (princ q2) (act1 q2)))
v2) )

Εφαρμογή:
(a1 3)
(a2 5)
(act1 1)
(act2 2)






act1
act2
31513513513513513531531535…
523252325232523252325232…

Πρόκειται για κλήσεις καθολικών συναρτήσεων. Το αποτέλεσμα των (act1 1) και (act2 2) είναι
διαδοχική εκτύπωση αριθμών, όπου ένας είναι το όρισμα εφαρμογής και άλλοι δύο είναι οι τιμές
που δόθηκαν ως ορίσματα εφαρμογής προηγούμενων, "ανεξαρτήτων" κλήσεων.

9.5.7 Σχέση Λογικού και Συναρτησιακού Προγραμματισμού
Συγκριτική τοποθέτηση Λογικού και Συναρτησιακού Προγραμματισμού,
εκφρασμένων με τις γλώσσες Prolog και Lisp
Ένας τόπος ισχυρών επιστημονικών αντιπαραθέσεων, ιδιαίτερα κατά τη δεκαετία του '80, ήταν η
σύγκριση στόχων και επιτεύξεων ανάμεσα στις γλώσσες Lisp και Prolog. Το ζήτημα είναι
βαθύτερο από μια σύγκριση γλωσσών προγραμματισμού: είναι σύγκριση δύο μοντέλων
υπολογισμού, του λ-λογισμού και της κατηγορηματικής συμπερασματικής λογικής απέναντι στα
πραγματικά προβλήματα.
Τα μοντέλα αυτά είναι θεωρητικά ισοδύναμα, αλλά και πρακτικά, αν λάβουμε υπ' όψη οτι το
κατηγορηματικό μοντέλο υλοποιείται πάνω σε ένα ήδη κτισμένο αλγόριθμο - μηχανισμό λογικής
παραγωγής (inference engine) που πετυχαίνει την ανάλυση (resolution) των ερωτήσεων μέσω
ταιριάγματος μεταβλητών (matching), συνένωσης κανόνων (unification), και επανάκαμψης
453

(backtracking) [Clo+86], και είναι δυνατό να αναπτύξουμε τέτοιον αλγόριθμο πάνω στο μοντέλο
του λ-λογισμού (και οφείλουμε να αναπτύξουμε, για να πετύχουμε την πρακτική ισοδυναμία).
Συγκρίνοντας τη Lisp με την Prolog διαπιστώνουμε μια εκφραστική συνάφεια: μια συνάρτηση
της Lisp μπορεί να αποδοθεί στην Prolog, απλώς εξειδικεύοντας τον τρόπο γραφής κανόνα, πχ:
[Lisp] ορισμός:
(defun f (x y) (* x (+ x y)))
κλήση:
(f 3 4)
απάντηση: 21
(επιστροφή)
[Prolog] ορισμός:
f(x y r) :- r is x * (x + y).
κλήση:
?- f(3 4 x).
απάντηση: x = 21 (εκτύπωση)
Αντίθετα όμως, δεν έχουμε άμεσα τη δυνατότητα έκφρασης πεπλεγμένου κανόνα στη Lisp, λόγω
της ανάγκης να είναι οι σχέσεις που πρόκειται να υλοποιηθούν ως συναρτήσεις, επιλυμένες ως
προς το αποτέλεσμα που επιστρέφει η συνάρτηση, και γενικότερα της ανάγκης να έχουν
κατεύθυνση "από-προς".
Η έννοια της επιστροφής τιμής στη Lisp είναι θεμελιακή, διότι είναι ο κρίκος που συνδέει
διαδοχικούς υπολογισμούς, και μπορεί να την διαχειρίζεται επεξηγηματικά ο προγραμματιστής ή
ο χρήστης σε συνθέσεις στα πλαίσια της ίδιας εφαρμογής ή άλλης ανεξαρτητοποιημένης. Στην
Prolog, η δέσμευση μεταβλητής σε τιμή είναι ορατή από τα μέρη (που ακολουθούν ως εκτέλεση)
τον τρέχοντα κανόνα, αλλά λόγω των (δυνητικά) περισσοτέρων λύσεων, αυτές οι τιμές δέσμευσης
δεν είναι διαχειρίσιμες από τον προγραμματιστή ή το χρήστη: αν χρειαστεί επεξηγηματική
αναφορά τους σε άλλες συνθέσεις, απαιτείται χρήση κατηγορημάτων ανωτέρου επιπέδου λογικής,
όπως το "setof".
Εξετάζοντας το νόημα του κατηγορήματος, παρατηρούμε πως είναι συναφές με το νόημα της
κλάσης, διότι και τα δύο είναι σύνθετες οντότητες με υποδοχές, με τη διαφορά οτι μια κλάση
είναι μια υλοποιημένη αυτόνομη οντότητα, ενώ ένα κατηγόρημα είναι απλώς δομικό στοιχείο.
Αντίστοιχα, το νόημα του λογικού γεγονότος είναι συναφές με το νόημα του στιγμιότυπου:
[Prolog] κατηγόρημα:
πατέρας(<όνομα-πατέρα>, <όνομα-παιδιού>)
λογικό γεγονός: πατέρας( 'Γιάννης' , 'Κώστας' ).
[Lisp] κλάση:
(defstruct πατέρας όνομα-πατέρα όνομα-παιδιού)
[Lisp] στιγμιότυπο: (make-πατέρας
:όνομα-πατέρα "Γιάννης"
:όνομα-παιδιού "Κώστας")

Οι κατηγορηματικές μεταβλητές έχουν τα εξής χαρακτηριστικά: i) αναφέρονται πάντοτε σε
κάποια αντίστοιχη υποδοχή κατηγορήματος, ii) ονοματίζουν την (ενδεχόμενη) τιμή που θα λάβει
αυτή η θέση, διατηρώντας έτσι τη δυνατότητα να αναφέρεται αυτό το όνομα στα πλαίσια του
ευρύτερου κατηγορήματος αναφοράς, το οποίο παίζει ρόλο ρίζας - τόπου (κατά περίπτωση, είναι
το όνομα του κατηγορήματος για τα λογικά γεγονότα, το κατηγόρημα - σύμβολο του κανόνα ":-",
το κατηγόρημα - σύμβολο της ερώτησης "?-" ).
Βλέποντας αυτά τα χαρακτηριστικά από τη συναρτησιακή πλευρά, παρατηρούμε πως το πρώτο
454

εκφράζεται άμεσα από τις υποδοχές κλάσης, αλλά η "μεταβλητότητα" της τιμής μιας υποδοχής
υποστηρίζεται με πιό δυσκίνητο τρόπο (εκτέλεση μεθόδου), και δεν διατίθεται "έτοιμος" ο
μηχανισμός αυτόματου ταιριάγματος..

Επίσης, η Prolog έννοια "κανόνας" είναι συναφής με μια κλάση, έστω με όνομα ":-" όπου κάθε
κανόνας θα αποτελεί στιγμιότυπο αυτής της κλάσης, με μια υποδοχή με τιμή την κλάση που
αντιστοιχεί στο συμπερασματικό μέρος του κανόνα, και μια υποδοχή με τιμή την λίστα των
κλάσεων που αντιστοιχούν στις υποθέσεις του κανόνα.

Ακόμα, η Prolog έννοια "ερώτηση" είναι συναφής με την έννοια "εφαρμογή", όπου η
συνάρτηση της εφαρμογής πρέπει να είναι ο μηχανισμός ανάλυσης και οι είσοδος της εφαρμογής
να είναι οι συνθήκες προς εκπλήρωση της ερώτησης.
Θα δούμε το συσχετισμό κατηγορηματικού - συναρτησιακού μοντέλου σε ότι αφορά την
υλοποίηση γνώσης, στο επόμενο κεφάλαιο.
Ο μελετητής μπορεί να βρει τεχνικές υλοποίησης Prolog σε Lisp, στη βιβλιογραφία:

Προσεγγίσεις υλοποίησης "Prolog μέσα σε Lisp"

Αναφέρονται διάφορες προσεγγίσεις υλοποίησης, όπως οι εξής:
– Οι Winston και Horn περιγράφουν μια συναρτησιακή τεχνική "matching" και "unification",
επικεντρωμένη στον αυτοματοποίηση των δύο βασικών τρόπων συλλογιστικής, "προς τα εμπρός"
και "προς τα πίσω". Κάνουν χρήση ειδικών μεταβλητών και εν μέρει κλάσεων ([Win+89]: Κεφ.
24, 25, 26 και 27). Ενδιαφέρει ιδιαίτερα τον αναγνώστη που μελετά τρόπους υλοποίησης
διαφόρων μεθόδων συλλογιστικής.
– Οι Abelson και Sussman περιγράφουν μια καθαρά συναρτησιακή τεχνική καθοδηγούμενη
από το στόχο εξυπηρέτησης ερωτήσεων (queries) πάνω σε βάσεις δεδομένων (εκφρασμένων σε
μορφή facts) με χρήση κανόνων (rules). Εκθέτουν το θέμα λιγότερο αναλυτικά από το
προηγούμενο αλλά αποτελεσματικά, και σε επίπεδο μοντέλου για το "πώς πρέπει να συνεχίσει ο
αναγνώστης" είναι συνοπτικό αλλά καθοδηγητικό ([Abe+89], §§ 4.4 και 4.5). Ενδιαφέρει
ιδιαίτερα τον μελετητή που ασχολείται με τη Γνωσιολογία.
– Ο Sangal αφιερώνει στο ζήτημα αυτό ολόκληρο το δεύτερο μέρος του βιβλίου του ([Sang91],
κεφ. 4, 5, 6 και 7), ακολουθώντας μια μεικτή πορεία: συναρτησιακή σε ότι αφορά την περιγραφή
γεγονότων και κανόνων, και χρήση αντικειμένων για την ανάπτυξη του μηχανισμού ανάλυσης.
– Η Tatar περιγράφει το λογικό μηχανισμό ενσωματωμένο σε ένα μικρό expert system,
περισσότερο "σαν παιχνίδι, που κτίζει βήμα προς βήμα" παρά σαν μοντέλο υπολογισμού
([Tata87], κεφ. 12).
Τα δύο τελευταία ενδιαφέρουν περισσότερο τον προγραμματιστή που μελετά τεχνικές
συναρτησιακού προγραμματισμού.

455

9.6 Ειδικά θέματα συναρτησιακής οργάνωσης
Στον τομέα αυτό θα κάνουμε μια σύντομη επίσκεψη σε ορισμένα θέματα ερευνητικής σημασίας
για τον ΣΠ. Θα δούμε διάφορες όψεις της έρευνας:
– εκμετάλλευση του συναρτησιακού προγραμματισμού για τη δημιουργία από περιβάλλοντα
πιο ευέλικτα των προσανατολισμένων στα αντικείμενα.
– διασύνδεση περιβάλλοντος ΣΠ με περιβάλλοντος OOP ώστε να εκμεταλλεύεται ο χρήστης τις
δυνατότητες και των δύο
– επεκτάσεις του ΣΠ προς την κατεύθυνση της ευελιξίας, αντιμετωπίζοντας είτε σε επίπεδο εκ
των υστέρων προσαρμογής του προγράμματος στις απαιτήσεις χρήσης, είτε σε επίπεδο εκ των
προτέρων ανάπτυξης επαρκώς γενικών μορφών, ικανών να προσαρμόζονται κατά περίπτωση
– υποστήριξη εξελιξιμότητας βάσει της άποψης επεξεργασίας του θέματος
– αξιοποίηση του πλαίσιου του λ-λογισμού για επεκτάσεις της λογικής σε πλειότιμα μοντέλα.
Μια συνοπτική παρουσίαση των δυνατοτήτων της Lisp, όπως εξελίχθηκε με τους σύγχρονους
compilers, συναντάμε στην εργασία των Sinclair & Moone [Sin+91], που έχει μορφή "από τις
θεωρητικές δυνατότητες προς την πρακτική εφαρμογή τους" και των Baker και Wichmaann
[Bak+98] που έχει περισσότερο μορφή "από την πρακτική εφαρμογή προς τις δυνατότητες που
διαφαίνονται".

9.6.1 Υβριδικά Περιβάλλοντα Προγραμματισμού
Υβριδικό περιβάλλον προγραμματισμού αποκαλείται ένα περιβάλλον που φέρνει σε συνάφεια δύο
ή περισσότερες γλώσσες προγραμματισμού, με τρόπο που να συνυπάρχουν έτσι ώστε να μπορεί ο
χρήστης να βλέπει τη μία ως επέκταση της άλλης. Σκοπός είναι, να εκμεταλλεύεται τις
δυνατότητες της καθεμιάς, και ιδιαίτερα να εφαρμόζει τεχνικές προγραμματισμού για τις οποίες
είναι καταλληλότερη είτε η μία είτε η άλλη γλώσσα. Σε ότι αφορά τον ΣΠ, κεντρική επιδίωξη
είναι να επεκταθεί η συναρτησιακή γλώσσα με τρόπο που να καλύπτει και τις πρακτικές
δυνατότητες που προσφέρουν άλλες γλώσσες προγραμματισμού.
Σε πρόσφατες σχετικές έρευνες όπως οι αναφερόμενες παρακάτω, ιδιαίτερη έμφαση έχει δοθεί
στο να επιτευχθεί συνάφεια της Lisp με τη Java, με διπλό στόχο: ο χρήστης του υβριδικού
προγραμματιστικού περιβάλλοντος θεωρεί οτι αναπτύσσει συναρτησιακά το πρόγραμμά του,
χρησιμοποιώντας κατά βάση τη "μεριά Lisp", να είναι δυνατό να βλέπει τις πρόσθετες
δυνατότητες που του παρέχει η Java ως επέκταση των δυνατοτήτων του ΣΠ (χρησιμοποιώντας
κατανεμημένα συστήματα, applets στο web, χρήση βιβλιοθηκών, εύχρηστο GUI, κλπ.)∙ αντίθετα,
αν χρησιμοποιεί κατά βάση τη "μεριά Java", να είναι δυνατό να βλέπει τη Lisp ως τρόπο για
ανάπτυξη αντικειμένων της Java με ευρύτερες δυνατότητες (scripting, εισαγωγή από
πολυεπίπεδες συναρτησιακές εκφράσεις, λ-εκφράσεις ως τιμές εισόδου, κλπ.).
456

Η υβριδική προσέγγιση έχει ως στόχο την συνεκμετάλλευση των εξελίξεων τόσο αυτών καθ'
εαυτών των γλωσσών προγραμματισμού όσο και των τεχνικών προγραμματισμού που
αναπτύσσονται σε καθεμιά. Αυτό πετυχαίνει το επιθυμητό αποτέλεσμα - επέκταση με τρόπο πολύ
πιο άμεσο από τη δημιουργία μιας νέας γλώσσας, διότι επεκτείνει την μια μεριά
εκμεταλλευόμενη τις δυνατότητες της άλλης.
Δύο βασικές προσεγγίσεις ακολουθούνται: i) της ανάπτυξης ενός Lisp interpreter σε μια άλλη
γλώσσα, όπως η Java, και ii) της ανάπτυξης ενός προγράμματος σε Lisp με τρόπο που να
επικοινωνεί με τη Java "σαν η Lisp να ήταν ένα component της Java" (Java bean).

i) Κατά την πρώτη προσέγγιση, επιλέγεται κατ' αρχήν η κατάλληλη "γλώσσα περιβάλλοντος"
που να μπορεί να υποστηρίξει τα απαιτούμενα λειτουργικά χαρακτηριστικά. Στη συνέχεια
αναπτύσσεται στη γλώσσα αυτή ένας "Lisp like" πυρήνας, δηλ. που να συμπεριφέρεται ως Lisp
interpreter, με τρόπο που να επικοινωνεί με τον λοιπό κώδικα της γλώσσας περιβάλλοντος.
Αρχικά οι προσπάθειες είχαν βασιστεί στη C++, αλλά τελευταία προτιμήθηκε η Java, για την
αμεσότητα που παρέχει για ανάπτυξη από δικτυακές εφαρμογές.
ii)Κατά τη δεύτερη προσέγγιση, αναπτύσσεται στην ίδια τη Lisp ένας πυρήνας ικανός να
επικοινωνήσει με άλλες γλώσσες, αναγνωρίζοντας τις απαιτήσεις τους σε μορφή και τύπο
δεδομένων εισόδου - εξόδου. Στη συνέχεια επιλέγεται η γλώσσα περιβάλλοντος, στην οποία
αναπτύσσονται τμήματα τα οποία προσφέρουν αυτά που είναι δύσκολο ή αδύνατο να επιτευχθούν
με τη Lisp.

α'. KAWA: ξεκινώντας από την "γλώσσα περιβάλλοντος" (Java) προς την
"εσωτερική" γλώσσα (Lisp)
Χαρακτηριστικό παράδειγμα της πρώτης προσέγγισης είναι η KAWA, που αποτελεί ένα υβριδικό
περιβάλλον που συνδυάζει τα πλεονεκτήματα της Lisp (Scheme) με τα πλεονεκτήματα της Java
[Both98]. H KAWA είναι ένα σύνολο από Java κλάσεις που υποστηρίζουν την ανάπτυξη μιας
δυναμικής γλώσσας, και βάσει αυτών αναπτύσσεται ένας Scheme compiler. Αυτό που πετυχαίνει
η KAWA είναι να λειτουργεί ως compiler που μεταγλωττίζει εκφράσεις δοσμένες σε Scheme, σε
"bytecode instructions" τής "Java Virtual Machine".
Τα πλεονεκτήματα που προσφέρει η KAWA είναι:
i) Περισσότερη "δυναμική" στον προγραμματισμό, διότι υλοποιεί τον ατέρμονα
αλληλεπιδρώντα κύκλο "διάβασε-υπολόγισε-τύπωσε" (read-eval-print loop) της Lisp, οπότε
δέχεται ανώτερης τάξης οντότητες εισόδου στον κύκλο αυτό, και γενικά, συναρτησιακή
σύνθεση.
ii) Ολοκλήρωση Java και Lisp (Scheme) με τρόπο που να μπορεί ο προγραμματιστής να καλεί
μέσα από Scheme κώδικα, Java μεθόδους, και αντίθετα, μέσα από Java μεθόδους να καλεί
Scheme διαδικασίες.
iii) Ο χρήστης που αντιμετωπίζει το περιβάλλον ως συναρτησιακό, βλέπει ένα Lisp-like
compiler (Scheme) που διαθέτει ως επέκταση το πλεονέκτημα χρήσης των βιβλιοθηκών των
κλάσεων της Java, καθώς και γενικά των δυνατοτήτων που προσφέρει η Java για κατανομή
457

και επικοινωνία.
iv) Ο χρήστης που αντιμετωπίζει το περιβάλλον ως πλαίσιο αντικειμένων, βλέπει μια
επέκταση της Java που έχει δυνατότητες "scripting".

β'. D-OMAR: αντίθετα, από τη Lisp προς τη γλώσσα περιβάλλοντος
Χαρακτηριστικό παράδειγμα της δεύτερης προσέγγισης αποτελεί το D-OMAR [Cram98], το
οποίο αποτελεί ένα υβριδικό σύστημα που συνενώνει τις δυνατότητες της Lisp με τις δυνατότητες
της Java. Κατευθύνεται προς την υποστήριξη του χρήστη, με κατάλληλο γραφικό διάμεσο (GUI)
με σκοπό να καλύψει συγκεκριμένες λειτουργικές αδυναμίες της Lisp όπως η δικτυακή
επικοινωνία και κατανομή κώδικα, λειτουργώντας ως επέκτασή της.
Ο πυρήνας του D-OMAR είναι γραμμένος σε CL, και διατηρεί την πλήρη λειτουργικότητα της
Lisp, ενσωματώνοντας τις δυνατότητες της "γλώσσας επαφής με το περιβάλλον", και ως τέτοια
έχει επιλεγεί η Java. Άλλα τμήματα του κώδικα, όπως το GUI, είναι γραμμένα σε Java.

Αυτό που πετυχαίνει το D-OMAR είναι οτι παρουσιάζει τον εαυτό του στην εξωτερική γλώσσα,
ως μια συνιστώσα γραμμένη στη γλώσσα αυτή, προσομοιάζοντας τη λειτουργικότητά της σε ότι
αφορά αυτά που επιτελεί. Έτσι ο χρήστης της Java, μπορεί να βλέπει το D-OMAR απλώς ως ένα
"Java bean" που προσφέρει "scripting". Ο χρήστης της Lisp μπορεί να το βλέπει ως επέκταση της
Lisp.
Τα πλεονεκτήματα που προσφέρει είναι:
i) Η ανάπτυξη του πυρήνα είναι συμβατή με μεγάλο εύρος εφαρμογών βασισμένων σε Lisp,
οπότε μπορεί να αξιοποιηθεί για επέκτασή τους.
ii) Αποτελεί ένα "διαφανές" περιβάλλον, αξιοποιήσιμο σε χαμηλό επίπεδο για πολλές γλώσσες
προγραμματισμού που θα χρησιμοποιηθούν εξωτερικά, ώστε να επιτευχθεί η διασύνδεση της
Lisp με περιβάλλοντα που υποστηρίζουν περισσότερα και ειδικότερα χαρακτηριστικά.
iii) Πετυχαίνει "κατανομή", τόσο με την έννοια οτι είναι εμφυτεύσιμο σε ένα κατανεμημένο
σύστημα, όσο και με την έννοια οτι τα τμήματά του μπορούν να είναι φυσικώς κατανεμημένα.
Επιτρέπει μεταφερτότητα κώδικα σε μεγάλο εύρος συστημάτων, επικοινωνώντας με την
αντίστοιχη εξωτερική γλώσσα μέσω του κατάλληλου πρωτοκόλλου, και για διάφορες
πλατφόρμες, και επαρκώς ευέλικτο και σταθερό απέναντι σε αλλαγές του συστήματος.

9.6.2 Ειδικότερα "στυλ" προγραμματισμού
α'. Προσαρμοστικός Προγραμματισμός
Προσαρμοστικός Προγραμματισμός (Adaptive Programming) είναι ένα στυλ προγραμματισμού
που στοχεύει στη συντηρισιμότητα και εξελιξιμότητα του λογισμικού, μέσω της επίτευξης
μεγάλου βαθμού ευελιξίας προσαρμογής του στις εκάστοτε πραγματικές ανάγκες χρήσης. Αν και
μελετάται ιδιαίτερα στα πλαίσια του OOP, εν τούτοις έχει έννοια και η μελέτη του στα πλαίσια
458

του ΣΠ, διότι η εξελιξιμότητα και ευελιξία προσαρμογής είναι από τα κύρια χαρακτηριστικά του
ΣΠ, και οι αναφερόμενες έννοιες είναι κοινές.
Στα προηγούμενα είδαμε οτι η Lisp αποτελεί ένα περιβάλλον επιδεκτό προσαρμογών, με
επιδεκτικότητα συγκρίσιμη μόνο με της Prolog. Παρ' όλα αυτά, οι προσαρμογές που είδαμε
αντιμετωπίστηκαν με τρόπο λίγο-πολύ "χειρωνακτικό", δηλ. με επεμβάσεις στον κώδικα, έστω
και σε πολύ συγκεκριμένα τμήματα. Όμως είδαμε την ευκολία προσαρμογής που παρέχουν οι
αφαιρετικές συναρτήσεις ή ακόμα και η αφαιρετική χρήση κοινών συναρτήσεων.
Η κεντρική ιδέα του ΠΠ είναι, να αναπτυχθούν τεχνικές που να εκμεταλλεύονται από την
αφαίρεση με τρόπο ώστε εξειδικεύοντας κατάλληλα ορισμένες αφαιρετικά γενικευμένες
συναρτήσεις, μικρού πλήθους και εύκολης διαχείρισής τους, να παράγεται ένας χώρος πρόγραμμα που να καλύπτει τις εκάστοτε συγκεκριμένες ανάγκες. Σκοπός είναι, η εξειδίκευση
αυτή να μπορεί να ελέγχεται σε επίπεδο χρήσης, είτε από τον τελικό χρήστη είτε από ένα νοήμον
πρόγραμμα που θα προκαλεί τις κατάλληλες εξειδικεύσεις βάσει των υφισταμένων συνθηκών και
απαιτήσεων.
Η παραπάνω γενική ιδέα συγκεκριμενοποιείται στην κατεύθυνση του ΠΠ με ένα συγκεκριμένο
τρόπο:

Ο Προσαρμοστικός Προγραμματισμός βασίζεται στη θεμελιώδη έννοια της διάσχισης (traversal),
που τυπικά συντίθεται από την τριάδα:
(πηγή, στόχος, συνθήκες)
Αν δούμε το χώρο σαν ένα γράφημα, μια διάσχιση προσδιορίζει εν δυνάμει ένα σύνολο από
μονοπάτια που ικανοποιούν την τριάδα αυτή πάνω στο γράφημα.
Ως τέτοιο γράφημα μπορούμε να δούμε ένα συναρτησιακό πρόγραμμα, όπου κόμβοι του
γραφήματος είναι κατ' αρχήν ένα σύνολο στοιχειωδών διαδικασιών (συναρτήσεων), και ακμές οι
δυνατότητες συναρτησιακής σύνδεσής τους (τί ταιριάζει με τί), και στη συνέχεια η επέκταση του
γραφήματος με παραγόμενους κόμβους, τις συνθέσεις των αρχικών (ενδεχομένως και με
επαναλήψεις). Μονοπάτι στο γράφημα αυτό είναι κάθε συγκεκριμένος αλγόριθμος που μπορεί να
συντεθεί με χρήση αυτών των συναρτήσεων. Διάσχιση, είναι μια διαδικασία (συνάρτηση,
μέθοδος) που εντοπίζει κατάλληλο μονοπάτι βάσει των υφισταμένων τη στιγμή της κλήσης
συνθηκών και των απαιτήσεων της χρήσης.
Κάθε υπολογισμός ουσιαστικά συνθέτει αυτά τα τρία μέλη της τριάδας, αλλά συνήθως αυτό
γίνεται σε επίπεδο σχεδιασμού / υλοποίησης και όχι εκτέλεσης∙ σε "κλασικά" συστήματα, η
υλοποίηση αναφέρεται σε ένα ήδη προσδιορισμένο μονοπάτι (βήματα αλγόριθμου) που επιλύει το
πρόβλημα.. Το όφελος από την "προσαρμοστική" προσέγγιση έγκειται στο οτι αντί για τέτοιο
μονοπάτι, προσδιορίζουμε μια μέθοδο (αλγόριθμο) που να βρίσκει δυναμικά (κατά περίπτωση
τρεχουσών συνθηκών, διαθεσίμων και απαιτήσεων) ένα κατάλληλο μονοπάτι.

Με τον τρόπο αυτό, ανάγουμε την εκτέλεση προγράμματος σε αναζήτηση κατ' αρχήν ενός
κατάλληλου μονοπατιού και στη συνέχεια εκτέλεσης των βημάτων που αυτό προσδιορίζει .
Εφ' όσον οι συνθήκες "ισχύος" του μονοπατιού παραμένουν αμετάβλητες, δεν υπάρχει λόγος
αναζήτησης νέου μονοπατιού, και η επανεκτέλεση μπορεί να εκμεταλλεύεται τον εντοπισμό που
459

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

Η χρήση της διάσχισης ανάγει τον προγραμματισμό σε ανώτερο επίπεδο γενικότητας, διότι
αντιμετωπίζεται η ανάπτυξη ως ένας αλγόριθμος - κλάση αλγορίθμων όπου ειδικές μέθοδοι
εντοπίζουν την ειδική περίπτωση εισόδων και ζητουμένων και κατευθύνουν ανάλογα τον
εντοπισμό του κατάλληλου ειδικού αλγόριθμου που θα εκτελεστεί.
Οι κυριότερες δυσκολίες για αποτελεσματική υλοποίηση είναι: i) το δυναμικά επεκτάσιμο ενός
τέτοιου γραφήματος, οπότε η συνάρτηση προσαρμογής πρέπει να είναι σε θέση να αντιμετωπίζει
αυτή τη δυναμική διάσταση (σε κάποιο μέτρο, επαρκές για τον πραγματικό χώρο χρήσης), και ii)
η επίτευξη μιας αυτοματοποιημένης από το στόχο κατευθυνόμενης ανάλυσης, ώστε η συνάρτηση
προσαρμογής να επιλέγει το κατάλληλο μονοπάτι.
Η συντηρισιμότητα των προσαρμοστικών προγραμμάτων σχετίζεται άμεσα με τη γενικότητα των
διασχίσεων, διότι ακριβώς αυτή η γενικότητα έχει ακριβώς σκοπό να πετύχει τη συντηρισιμότητα
μέσω της προσαρμοστικότητας, όπου αντί γα μεταβολή ολόκληρου τμήματος κώδικα αρκεί να
γίνουν απλές προσαρμογές του πεδίου των συνθηκών, επαρκείς για να προκαλέσουν την
κατάλληλη εξειδίκευση.
Το πρόγραμμα AP/S++ βασίζεται στην ιδέα του Προσαρμοστικού Προγραμματισμού. Είναι
αποτέλεσμα έρευνας πάνω στην εκμετάλλευση των αντικειμένων στα πλαίσια του ΣΠ, με σκοπό
την δημιουργία προγράμματος που να προσαρμόζεται ευέλικτα πάνω στις διαμορφούμενες
απαιτήσεις της πραγματικότητας. Το πετυχαίνει ανεβαίνοντας σε ανώτερο επίπεδο γενικότητας,
μέσω των Metaobject Protocols (MOP), που εξειδικεύεται κατά περίπτωση περιβάλλοντος
χρήσης. Το πρόγραμμα αυτό αντιμετωπίζει το πρόβλημα που παρουσιάζουν οι κλάσεις στον OOP
να είναι πολύ δύσκολα συντηρήσιμες [Lop+96].

β'.

Μεταπρογραμματισμός

Μεταπρογραμματισμός (metaprogramming) είναι η ανάπτυξη προγραμμάτων που λειτουργούν σε
ανώτερο επίπεδο γενικότητας απέναντι στο "αντικείμενο εισόδου" τους. Η ανάπτυξη
μεταπρογραμματισμού είναι στενά δεμένη από το ένα μέρος με τη γλώσσα προγραμματισμού και
από το άλλο μέρος με το λειτουργικό σκοπό του προγράμματος, λειτουργώντας αφαιρετικά ως
προς το δεύτερο με εκμετάλλευση των δυνατοτήτων του πρώτου.
Τυπικά, κάθε συνάρτηση που μπορεί να χρησιμοποιηθεί αφαιρετικά αποτελεί στοιχείο
μεταπρογραμματισμού. Σημασία όμως έχουν τα συστήματα συναρτήσεων που εξυπηρετούν τις
εξελισσόμενες ανάγκες (απαιτήσεις, δυνατότητες…) σε πραγματικούς χώρους χρήσης.
460

Η έννοια του μεταπρογραμματισμού, συνδυασμένη με του "ανοικτού" οδηγεί σε ανοικτές
εφαρμογές ικανές να δεχθούν μεταβολές, όπου με κατάλληλη συγκεκριμενοποίηση των
οντοτήτων μετα-επιπέδου παράγονται αντίστοιχα προγράμματα συγκεκριμένης λειτουργικότητας.
Ένα παράδειγμα ανάπτυξης προς αυτή την κατεύθυνση είναι το πρόγραμμα "Capabilities"
[Broο98] που δέχεται ως είσοδο κώδικα και παράγει αυτόματα και με ελεγχόμενο τρόπο κώδικα
προσαρμοσμένο στις απαιτήσεις. Στην ουσία, το πρόγραμμα Capabilities είναι ένα σύνολο
αφηρημένων συναρτήσεων CL/CLOS. Σε ότι αφορά τις κλάσεις αντικειμένων, προσφέρει μια
εξαιρετικά πλατειά δυναμική διάσταση, επιτρέποντας εκ των υστέρων τη δημιουργία
υπερκλάσεων, την εισαγωγή νέων υποδοχών σε κλάσεις / αντικείμενα, προσωρινά ή μόνιμα.

γ'.

Προγραμματισμός "κατευθυνόμενος από την άποψη"

Ο κατευθυνόμενος από την άποψη προγραμματισμός (aspect oriented programming, ΑΟΡ) είναι
ένα στυλ προγραμματισμού κατά το οποίο οργανώνονται υπολογιστικές μέθοδοι που εξυπηρετούν
μια συγκεκριμένη άποψη, με τρόπο ώστε, όταν εφαρμόζονται αυτές οι μέθοδοι πάνω σε
οντότητες, να προκαλούν αποτέλεσμα - μετασχηματισμό της οντότητας ανάλογο με τα ζητούμενα
κάτω από το πρίσμα του ενδιαφέροντος από την εν λόγω άποψη. Αν και αποτελεί κατά κύριο
λόγο κλάδο του OOP, όπου έχουν θεμελιωθεί οι αρχές του, αποτελεί ένα πολύ ενδιαφέροντα χώρο
για τον ΣΠ διότι συνδέεται άμεσα με τον OOP με τη χρήση του CLOS, αλλά και ως γενική ιδέα
είναι πολύ χρήσιμη και εφαρμόσιμη.
Για παράδειγμα: μετασχηματισμός μιας εικόνας έτσι ώστε να μειωθεί ο απαιτούμενος χώρος
καταχώρησης χωρίς να χαθούν τα κύρια χαρακτηριστικά της (μια άποψη), ή έτσι ώστε να δώσει
την απαιτούμενη ευκρίνεια σε συγκεκριμένο πεδίο του φάσματος (άλλη άποψη), ή μεγιστοποίηση
της ευκρίνειας γύρω από ένα κεντρικό σημείο με ελάττωση της λεπτομέρειας όσο απομακρύνεται
η παρατήρηση από το κέντρο (τρίτη άποψη), κλπ.
Η κεντρική ιδέα του ΑΟΡ συντίθεται από τα εξής δύο χαρακτηριστικά:
α'. Οι υπολογιστικές οντότητες σε ένα συναρτησιακό περιβάλλον (και ανάλογα σε ένα πλαίσιο
αντικειμένων) φέρουν σύνθετη πληροφορία που κατά κανόνα αφορά περισσότερες της μιας
απόψεις κάτω από τις οποίες είναι δυνατό να θέλουμε να τις εξετάσουμε ή να τις επεξεργαστούμε.
Κατά συνέπεια, αν οργανώσουμε ένα τρόπο αξιοποίησης του μέρους της πληροφορίας που
ενδιαφέρει τη συγκεκριμένη άποψη και για κάθε άποψη που είναι δυνατό να θεωρήσουμε, θα
έχουμε τη δυνατότητα πολύπλευρης χρήσης των οντοτήτων.

β'. Η πληροφορία που φέρουν οι υπολογιστικές οντότητες, αρχικά είναι προσδιορισμένη με τρόπο
που να καλύπτει τις ανάγκες συγκεκριμένων απόψεων, αλλά ενδέχεται να είναι μερική ως προς
αυτά που απαιτεί από μια νέα άποψη, στοιχεία που δεν είχαν προβλεφθεί ή δεν ήταν σκόπιμο να
αναζητηθούν / καταχωρηθούν όλα για λόγους κόστους, διόγκωσης της καταχωρημένης
πληροφορίας, μη επίγνωσης εκ των προτέρων της ειδικής πληροφορίας που θα απαιτηθεί, κλπ.
Επομένως, χρειαζόμαστε τρόπους, τόσο για δυναμική προσθήκη πληροφορίας όταν και όπου
χρειαστεί κατά την εξέταση του θέματος κάτω από μια νέα άποψη, έτσι ώστε να κρατά την τοπική
σημασία της χωρίς να επιβαρύνει τις άλλες απόψεις, όσο και για εξειδικευμένη επεξεργασία της
πληροφορίας κατά άποψη, έτσι ώστε να εκμεταλλεύεται κοινούς τόπους των διαφόρων απόψεων.
461

Για παράδειγμα, ένα κομμάτι ασημί χαρτόνι σε τρίγωνο σχήμα, από την άποψη της Γεωμετρίας
έχει πλευρές, εμβαδόν κλπ, από την άποψη της Φυσικής είναι υλικό όπου ασκούνται δυνάμεις,
από τη άποψη της αισθητικής έχει ωραίο χρώμα που ταιριάζει σε ένα μπλε φόντο, κλπ. κλπ. Αν
προσδιοριστεί ως στιγμιότυπο μιας κλάσης "τρίγωνο", υποκλάσης της "σχήμα", με υποδοχές τα
γεωμετρικά χαρακτηριστικά του, ανταποκρίνεται στην άποψη "Γεωμετρία" αλλά για την άποψη
"Φυσική" χρειάζεται να τεθούν κάποια πρόσθετα χαρακτηριστικά, χωρίς να χρειάζονται όλα τα
γεωμετρικά.
Άρα, οι απαιτήσεις που πρέπει να καλύψει η προσέγγιση αυτή, είναι οι εξής:
α'. πώς θα πετύχουμε τη δυναμική αντιμετώπιση νέων απόψεων, αποφεύγοντας τη διόγκωση
της πληροφορίας.
β'. πώς θα αυτονομηθούν οι διάφορες απόψεις, ώστε να μπορούμε να αναφερόμαστε σ' αυτήν ή
σ' αυτές που θέλουμε και μόνον.
Οι παρακάτω μεθοδεύσεις αποτελούν μερικές λύσεις του α' :
i) Να είναι οργανωμένα τα αντικείμενα σε κλάσεις όπου να έχουμε προβλέψει μια υποδοχή
για κάθε άποψη, και τιμή μια λίστα συσχέτισης χαρακτηριστικών και αντιστοίχων τιμών. Το
ανεπίλυτο εδώ είναι οτι πρέπει να έχουμε προβλέψει κάθε άποψη. Κάποια μικρή ευελιξία μπορεί
να προσφέρει η πρόβλεψη από ένα αριθμό από "ελεύθερες" υποδοχές. Δυσχέρειες θα προκαλέσει
και το οτι απαιτείται πρόσθετη οργάνωση μηχανισμών παραγωγής και επεξεργασίας αντικειμένων
σε αντιστοιχία με τη φόρμα αυτή που να επιτελούν την κατάλληλη επέμβαση στις λίστες αυτές (η
επεξεργασία μπαίνει σε λεπτομέρειες που δεν αντιμετωπίζονται από τις συναρτήσεις του CLOS)
και αυτό καθιστά δυσκίνητα τόσο τη συγγραφή όσο και την εκτέλεση κώδικα. Άλλο πρόβλημα
είναι η εξ αρχής διόγκωση των οντοτήτων, που φέρουν σε κάθε άποψη όλη την πληροφορία χωρίς
λόγο.
ii) Κάπως καλύτερη λύση είναι να δώσουμε μια ειδική υποδοχή, με τιμή λίστα συσχέτισης
(πρώτη διάσταση) με πρώτα μέλη των όρων της τέτοια που να παριστούν απόψεις και δεύτερα
(δεύτερη διάσταση) λίστες συσχέτισης χαρακτηριστικών και αντιστοίχων τιμών που εντάσσονται
στις απόψεις αυτές. Αυτό είναι οργανωτικά απλούστερο και πολύ ευέλικτο σε μεταβολές, διότι
τώρα δεν χρειαζόμαστε πρόβλεψη από τίποτα (οι λίστες είναι επεκτάσιμες απεριόριστα, και στις
δύο διαστάσεις) αλλά παραμένει η ανάγκη της πρόσθετης οργάνωσης των μηχανισμών
παραγωγής και επεξεργασίας αντικειμένων, και της συνεπαγόμενης επιβάρυνσης της συγγραφής
και της εκτέλεσης. Αν αυτό δικαιολογείται από την "εφ' άπαξ" εργασία, μένει αδικαιολόγητη η
σταδιακή διόγκωση των αντικειμένων σε κάθε νέα άποψη. Ένα πρόσθετο ζήτημα που γεννά η
σταδιακή διόγκωση της πληροφορίας, είναι η καθυστέρηση εύρεσης κάποιων στοιχείων που είναι
βαθύτερα στις λίστες, ή ακόμα χειρότερα, της διασταύρωσης στοιχείων, που απαιτεί πολλαπλή
σάρωση των λιστών.
iii)
Μια πολύ πιο αποτελεσματική λύση είναι να δώσουμε στα αντικείμενα (τουλάχιστον
μια) υποδοχή που θα έχει ως τιμή της ένα δυναμικό vector του οποίου κάθε στοιχείο να "δείχνει"
σε ένα πίνακα κατακερματισμού, ο οποίος να συσχετίζει χαρακτηριστικά σχετικά με την
αντίστοιχη άποψη, συνδέοντάς τα με αντίστοιχες τιμές. Έτσι, η πληροφορία ενυπάρχει και απλώς
"δείχνονται" από το αντικείμενο χωρίς να το διογκώνει, είναι επεκτάσιμη, αναπλάσιμη, και
462

περισσότερα του ενός αντικείμενα μπορούν να δείχνουν στον ίδιο πίνακα.
Η χρήση από τέτοιους δείκτες δεν είναι απαλλαγμένη προβλημάτων, διότι εκτός των
ζητημάτων σύγκρουσης μοντέλων υλοποίησης, εισάγει και ζητήματα σημασιολογικής
σύγκρουσης: μπορεί να συμβεί να έχουμε επικάλυψη χαρακτηριστικών διαφόρων απόψεων, όπου
οι διαφορετικές εκτιμήσεις ενδέχεται να δίνουν διαφορετική τιμή στο ίδιο όνομα
χαρακτηριστικού (πολύ πιθανή, αν προσφέρεται η δυνατότητα αυτή στον τελικό χρήστη), και
βέβαια θα κρατηθεί μόνο η τελευταία καταχώρηση.
Το σημαντικότερο πρόβλημα γεννάται από τη δεύτερη απαίτηση: η συσχέτιση αντικειμένων με
μεθόδους γίνεται βάσει του τύπου των στοιχείων εισόδου της μεθόδου, αλλά ο τύπος
αντικειμένου αναφέρεται μπορούμε να το αντιμετωπίσουμε με πολλούς τρόπους, όπως να
οργανώσουμε ένα "περίβλημα κλήσης μεθόδων", δηλαδή συνάρτηση που να αναλαμβάνει να
κατευθύνει την κλήση κατά περίπτωση άποψης, αναγνωρίζοντάς την από τη δέσμευση μιας
πρόσθετης λ-μεταβλητής.
Εργασίες του Gregor Kiczales και συνεργατών του, αναπτύσσουν σε μεγάλο βάθος το θέμα των
απόψεων στα ειδικά πλαίσια του ΟΟΡ, συνδέοντας 'το και με άλλες τεχνικές προγραμματισμού,
όπως: Υποκειμενικός Προγραμματισμός (Subjective Programming), Προγραμματισμός
Προσανατολισμένος στα Αντικείμενα και Συνιστώσες (components), Ανάκλαση (reflection)
[Men+93], Πρωτόκολλα Μεταντικειμένων (metaobject protocols) [Kic+93], [Kic+97], [Kic+99].
H Lisp συγκεντρώνει το ενδιαφέρον των ερευνητών ως πλαίσιο ανάπτυξης εφαρμογών για τις
κατευθύνσεις αυτές.

9.6.3 Υπολογισμός σε πλειότιμο λογικό πλαίσιο

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

Το θέμα πάντως δεν εξαντλείται με το αν θα δεχθούμε ή όχι το σφάλμα ως λογική τιμή, διότι οι
τρεις περιπτώσεις "αληθές" - "ψευδές" - "σφάλμα" δεν επαρκούν για να αντιμετωπίσουν μια
διαφορετική κατάσταση, της μη ύπαρξης λογικής τιμής λόγω ανεξαρτησίας του
χαρακτηριζόμενου αντικείμενου, από το λογικό χώρο.
Η σημασία ενός τέτοιου πλαίσιου διαφαίνεται
συμπερασματικούς μηχανισμούς (inference engine).

όταν

επιχειρούμε

να

αναπτύξουμε

Σημασιολογικά πλαίσια ανάπτυξης πλειότιμης λογικής
Έχουμε περισσότερα του ενός διαφορετικά σημασιολογικά πλαίσια όπου είναι δυνατό να
παρουσιαστεί κατάσταση μη ύπαρξης λογικής τιμής, όπως:
– Η περίπτωση σχέσης που θεωρούμε οτι θα ήταν δυνατό να αποτιμηθεί λογικά σε κάποιο
λογικό πλαίσιο, αλλά "τώρα" είναι έξω από το τοπικά θεωρούμενο λογικό σύστημα (για
παράδειγμα, το "αξίωμα της επιλογής" σε σχέση με τον αξιωματικό χώρο της Άλγεβρας
Συνόλων)∙ η αντιμετώπιση τέτοιων σχέσεων είναι χρήσιμη στην περιγραφή και μελέτη από
εξελισσόμενους χώρους.
– Η περίπτωση σχέσης που θεωρούμε οτι είναι δυνατό να αποτιμηθεί λογικά στο δίτιμο
πλαίσιο "αληθές" - "ψευδές", αλλά υποκειμενικά δεν είμαστε σε θέση, ή δεν ενδιαφερόμαστε, να
της αποδώσουμε τέτοια τιμή, και απλώς θέλουμε να χαρακτηρίσουμε το γεγονός οτι η ελεγχόμενη
σχέση ανήκει στο λογικό χώρο (δηλ. είναι αληθής ή ψευδής και η τιμή θα μπορούσε να προκύψει
αν εφαρμόσουμε κατάλληλο συμπερασμό)∙ η αντιμετώπιση τέτοιων σχέσεων είναι χρήσιμη στην
αποτύπωση της τρέχουσας κατάστασης μέσα σε ένα συνθετότερο συμπερασμό ή μέσα στη
εξέλιξη μιας διεργασίας.
Η επέκταση του συνόλου των λογικών τιμών μπορεί να γίνει είτε θεμελιακά,
επαναπροσδιορίζοντας συνολικά το υπολογιστικό μοντέλο (κάτι που θα απαιτούσε ανάπτυξη νέας
γλώσσας), ή ως επέκταση της υφιστάμενης γλώσσας, αξιοποιώντας το υφιστάμενο λογικό
μοντέλο σε ένα ανώτερο επίπεδο (πχ. "υπάρχει" - "δεν υπάρχει") και εντάσσοντας το πλειότιμο
πλαίσιο ως εξειδικεύσεις κάτω από τη λογική τιμή "υπάρχει" (που στη Lisp είναι το Τ,
ταυτοποιώντας επεξηγηματικά τη νέα τιμή "ψευδές" με το NIL).

Μοντέλα πλειότιμης λογικής
Διάφορα πλειότιμα μοντέλα λογικής έχουν κατά καιρούς προταθεί, με σκοπό την υποστήριξη
έκφρασης και χρήσης εννοιών που δεν είναι δυνατό ή δεν είναι εύκολο να αποδοθούν σε δίτιμο
λογικό πλαίσιο. Κοινό χαρακτηριστικό τους είναι η παρείσφυση σημασιολογικών
χαρακτηριστικών, που κατά κανόνα δεν αποτελούν τυπικά εκφράσιμα στοιχεία, αλλά έχει νόημα
η υλοποίησή τους, κάτω από παραδοχές που δεν βλάπτουν την πραγματικότητα.
Πάνω σε ένα λογικό μοντέλο οργανώνουμε μια συμπερασματική συλλογιστική. Χαρακτηρίζεται
ένα μοντέλο συλλογιστικής μονότονο αν αποκλείεται από τα αυτά δεδομένα να καταλήξουμε σε
συγκρουόμενα αποτελέσματα, και μη μονότονο αλλιώς.
Διακρίνουμε διάφορες κατηγορίες από μοντέλα που πηγαίνουν "πιο πέρα" από τη Μαθηματική
464

Λογική, όπως: α) επέκτασης της δίτιμης λογικής με πρόσθετες λογικές τιμές, β) μεταβολής της
σημασίας της "λογικής τιμής", όπως είναι η "λογική των περιπτώσεων" και η "λογική με
βαρύτητα των τιμών" γ) πιθανοθεωρητικής λογικής, και γενικότερα της ασαφούς λογικής.
α'.

Μοντέλα επέκτασης της δίτιμης λογικής. Διακρίνονται σε:

i) Αυτά που αποσκοπούν να αποδώσουν έννοιες υποκειμενικής εκτίμησης της "αλήθειας", με
απώτερο σκοπό την περιγραφή και επεξεργασία εξελισσομένων λογικών καταστάσεων που
προκύπτουν από την επικοινωνία μεταξύ περισσοτέρων "λογικά δρώντων παραγόντων" ή από τη
βαθύτερη μελέτη του χώρου από το "υποκείμενο". Δεν είναι μονότονα, διότι τα συμπεράσματα
είναι δυνατό να μεταβάλλουν τις θεωρούμενες λογικές τιμές.

ii) Αυτά που αποσκοπούν να αποδώσουν έννοιες αντικειμενικής εκτίμησης, και πάλι με
απώτερο σκοπό την περιγραφή και επεξεργασία εξελισσομένων λογικών καταστάσεων, αλλά
τώρα η εξέλιξη νοείται ως μετάβαση σε άλλο λογικό χώρο, οφειλόμενη σε προσθήκη ή διαγραφή
λογικού στοιχείου, όπου χρειαζόμαστε να μελετήσουμε το πώς συνδέονται οι χώροι αυτοί. Η
μονοτονία εξασφαλίζεται κάτω από προϋποθέσεις χρήσης συμπερασμού (διότι η αντίφαση, ως
δεδομένο, μπορεί να οδηγήσει σε συγκρουόμενες καταστάσεις).
Κεντρικό χαρακτηριστικό της πρώτης κατηγορίας είναι η τιμή "δεν γνωρίζω" που αφορά τη
γνώση που έχει το υποκείμενο ως προς το αν κάτι είναι αληθές ή ψευδές, με την προϋπόθεση οτι
αυτό το "κάτι" είναι, σε κάποιον υποτιθέμενο αντικειμενικό χώρο οπωσδήποτε και αποκλειστικά
αληθές ή ψευδές. Κυριότερα μοντέλα της πρώτης κατηγορίας είναι:

1.1. Το τρίτιμο {αληθές , ψευδές , δεν γνωρίζω} . Τυπικά καθορίζεται από τη διάταξη που
καθορίζει την or-απορροφητικότητα (δηλ. ποια τιμή υπερισχύει στη διμελή πράξη OR):
αληθές >OR δεν γνωρίζω >OR ψευδές
ή την αντίθετης φοράς and-απορροφητικότητα (από την απορροφητικότητα προκύπτουν και οι
πίνακες αλήθειας των πράξεων).
1.2. Το τετράτιμο {αληθές , ψευδές , δεν γνωρίζω , ασύμβατο} , που τυπικά καθορίζεται από
τη διάταξη που καθορίζει την or-απορροφητικότητα:
αληθές >OR δεν γνωρίζω >OR ψευδές >OR ασύμβατο
ή την αντίθετης φοράς and-απορροφητικότητα.
Η άρνηση αντιστοιχίζει τις τιμές "δεν ξέρω" και "ασύμβατο" στον εαυτό τους.
Τα μοντέλα αυτά είναι χρήσιμα στην υποστήριξη της έννοιας του δρώντος παράγοντα, όπως ο
μελετητής του λογικού χώρου.
Τα κυριότερα μοντέλα της δεύτερης κατηγορίας είναι:
2.1. Το τρίτιμο {αληθές , ψευδές , ανεξάρτητο} , που τυπικά καθορίζεται από τη διάταξη που
καθορίζει την or-απορροφητικότητα:
αληθές >OR ανεξάρτητο >OR ψευδές
ή την αντίθετης φοράς and-απορροφητικότητα. Η άρνηση αντιστοιχίζει την τρίτη τιμή στον
εαυτό της.
2.2. Το τρίτιμο {αληθές , ψευδές , ασύμβατο}, που τυπικά καθορίζεται από τη διάταξη που
καθορίζει την or-απορροφητικότητα:
465

αληθές >OR ψευδές >OR ασύμβατο
ή την αντίθετης φοράς and-απορροφητικότητα. Η άρνηση αντιστοιχίζει την τρίτη τιμή στον
εαυτό της.
2.3. Το τετράτιμο {αληθές , ψευδές , ανεξάρτητο , ασύμβατο} , που τυπικά καθορίζεται από τη
διάταξη που καθορίζει την or-απορροφητικότητα:
αληθές >OR ανεξάρτητο >OR ψευδές >OR ασύμβατο
ή την αντίθετης φοράς and-απορροφητικότητα. Η άρνηση αντιστοιχίζει τις νέες τιμές στον
εαυτό τους.
2.4. Το μοντέλο έξι τιμών {αληθές, ψευδές, ανεξάρτητο, ασύμβατο, συσχετιζόμενο, συμβατό},
που τυπικά καθορίζεται από τη διάταξη που καθορίζει την or-απορροφητικότητα:
αληθές >OR συσχετιζόμενο >OR ψευδές >OR ανεξάρτητο >OR
>OR συμβατό >OR
ασύμβατο
ή την αντίθετης φοράς and-απορροφητικότητα. Έχουμε δύο ειδών αρνήσεις, όπως θα
περιγράψουμε παρακάτω.
Τα μοντέλα (1.1), (1.2), (2.1), (2.2), (2.3) αποδίδουν μια και μόνο μια λογική τιμή σε κάθε λογική
οντότητα, ενώ το (2.4) μπορεί να αποδώσει περισσότερες, όπως θα δούμε πιο αναλυτικά.
Το προκύπτον λογικό σύστημα των περιπτώσεων (1.1), (2.1), (2.2), δεν είναι ούτε πλήρες (δεν
καλύπτει κάθε περίπτωση λογικών νόμων, όπως τον νόμο de Morgan) ούτε πραγματικά επαρκές
(δεν καλύπτει κάθε πραγματική ανάγκη, όπως τη συνύπαρξη αγνώστου τιμής και αντιφατικών
εκφράσεων). Κατά τι πληρέστερα είναι τα (1.2) και (2.3) αλλά και πάλι παρουσιάζουν
ανεπάρκειες με την εφαρμογή του νόμου de Morgan, την άρνηση και τον συμπερασμό. Το (2.4)
δεν παρουσιάζει ανεπάρκεια έκφρασης καταστάσεων.
Όλα τα μοντέλα αυτά είναι συμβατά με τη δίτιμη λογική, βέβαια κάτω από προϋποθέσεις
περιορισμού του χώρου των λογικών αντικειμένων.
β'. Μοντέλα με διαφορετική σημασία της "λογικής τιμής":
Η "λογική των περιπτώσεων" μοντελοποιεί λογικά τον συσχετισμό περιπτώσεων. Ας
φανταστούμε έναν "επισκέπτη" που κινείται πάνω σε ένα γράφημα, και ο επισκέπτης αυτός να
αποτελεί το λογικό αντικείμενο, με τιμή τον κόμβο στον οποίο βρίσκεται. Όταν το γράφημα είναι
κατευθυνόμενο, μπορούμε να ορίσουμε την πράξη "and" μεταξύ δύο μελών - κόμβων ως τον
κοινό κατώτερο κόμβο, και την πράξη "or" ως τον κοινό κατώτερο κόμβο. Οι λογικές πράξεις
"and" - "or" είναι παντού ορισμένες όταν το γράφημα είναι δικτυωτό (άκυκλο κατευθυνόμενο και
έχει προς τα πάνω και προς τα κάτω ακραίους κόμβους).

Για τον προσδιορισμό της άρνησης είναι δυνατό να ακολουθήσουμε διάφορες προσεγγίσεις, σε
σχέση με ειδικότερες ιδιότητες του γραφήματος (πχ. αν το γράφημα είναι μια γενικότερη άλγεβρα
Boole, άρνηση για κάποιο στοιχείο να σημαίνει πως "ο επισκέπτης είναι στο συζυγές του"∙ σε
γενική περίπτωση, να θεωρήσουμε πως άρνηση για κάποιο στοιχείο σημαίνει "δεν είναι εκεί ο
επισκέπτης").
Η κατεύθυνση της λογικής των περιπτώσεων χρησιμεύει σε προβλήματα όπου το γράφημα των
περιπτώσεων είναι αναλλοίωτο και οι συλλογισμοί εξελίσσονται σε σχέση με την κίνηση στο
γράφημα. Ο συμπερασμός διαμορφώνεται σε σχέση με το "πού βρίσκεται / μπορεί να βρίσκεται /
466

αποκλείεται να βρίσκεται ο επισκέπτης". Δεν είναι συμβατή με τη δίτιμη λογική, εκτός από ειδικές
περιπτώσεις. Πρόκειται για πλαίσιο μονότονης συλλογιστικής.

Με διαφορετική σημασία είναι η λογική όπου δίνουμε βαρύτητα στις τιμές αλήθειας. Συνήθως
οργανώνουμε ένα δίτιμο μοντέλο, με το πρόσθετο χαρακτηριστικό οτι δεν είναι ισοδύναμες οι
λογικές τιμές σε σχέση με το context αναφοράς, όπως: "ρίχνω ένα νόμισμα και αν έρθει κορώνα
κερδίζω 1 δρχ, αν έρθει γράμματα χάνω 1000 δρχ". Οι λογικές πράξεις και ο συμπερασμός
διαμορφώνονται λιγότερο ή περισσότερο εξαρτημένα από το context αναφοράς, όπως με κριτήρια
κέρδους ή/και με πιθανοθεωρητική πρόβλεψη. Η μονοτονία ή μη εξαρτάται στενά από το είδος
συμπερασμού που θα εφαρμόσουμε.
γ'. Πιθανοθεωρητική λογική, ασαφής λογική:
Η πιθανοθεωρητική λογική συνενώνει τη δίτιμη λογική με τη θεωρία πιθανοτήτων, εκφράζοντας
το "αληθές" ως "πιθανότητα να συμβεί κάτι" (και αντίστοιχα το ψευδές). Χρησιμεύει για
αντιμετώπιση θεμάτων πρόβλεψης. Μπορούμε να δούμε τα ενδεχόμενα ως λογικές οντότητες με
τιμή την πιθανότητά τους, οπότε στην περίπτωση περισσοτέρων των δύο ενδεχομένων έχουμε ένα
πλειότιμο μοντέλο.
Η ασαφής λογική (fuzzy logic) είναι γενικότερη της πιθανοθεωρητικής διότι δεν υποχρεώνει τα
ενδεχόμενα να έχουν άθροισμα τιμών ίσο με τη μονάδα. Οι τιμές νοούνται είτε σε συνεχές
διάστημα τιμών όπως το [0 , 1] , είτε σε πεπερασμένο σύνολο, όπως το:
{αδύνατο, απίθανο, μάλλον απίθανο, ανάμεσα, μάλλον πιθανό, πιθανό, βέβαιο}
είτε και συσχετισμένα, θέτοντας μια πύλη διάβασης (threshold): πχ, για x στο διάστημα [0, 1] αν
0≤x<0.3 τότε x ψευδές, και αν 0.7≤x≤1 τότε x αληθές, κοκ.
Η ασαφής λογική είναι συμβατή με τη δίτιμη με την έννοια οτι είναι δυνατό να καταλήξουμε
συμπερασματικά σε ένα δίτιμο πλαίσιο, καθώς και με τα αντικειμενικά πλειότιμα μοντέλα
επέκτασης του δίτιμου πλαίσιου, με την έννοια οτι το διάστημα των ασαφών τιμών τοποθετείται
ανάμεσα στις τιμές "αληθές" - "ψευδές" . Βλέποντας αυστηρά το μοντέλο αυτό, δεν είναι
μονότονο, διότι είναι δυνατό ένας συλλογισμός να συγκεκριμενοποιεί περισσότερο μια τιμή (με
την έννοια, να μετακινείται προς το πλησιέστερο άκρο).

Τεχνικές υλοποίησης πλειότιμης λογικής
Για την επέκταση της δίτιμης λογικής, για τη λογική των περιπτώσεων ή για την διακριτές τιμές
της ασαφούς λογικής, η φορμαλιστική θεμελίωση μπορεί να γίνει σε θεωρητικό επίπεδο
(συμβολισμός λ-λογισμού) με τρόπο ανάλογο της θεμελίωσης των δύο λογικών τιμών. Πχ, για
τρεις τιμές:
i) λx.λy.λz.x για την τιμή "πρώτη κατάσταση από τρεις δυνατές επιλογές":
η τιμή της πρώτης εφαρμογής, καθορίζει οριστικά την τελική τιμή της έκφρασης, ανεξάρτητα
των επομένων επιπέδων, δηλαδή:
(((λx.λy.λz.x a) b) c) → ((λy.λz.a b) c) → (λz.a c) → a

467

ii) λx.λy.λz.y για την τιμή "δεύτερη κατάσταση":
η τιμή της δεύτερης εφαρμογής αποτελεί την οριστική τιμή για την έκφραση:
(((λx.λy.λz.y a) b) c) → ((λy.λz.y b) c) → (λz.b c) → b
iii)

λx.λy.λz.z για την τιμή "τρίτη κατάσταση" :
(((λx.λy.λz.z a) b) c) → ((λy.λz.z b) c) → (λz.z c) → c

η τιμή της τρίτης εφαρμογής αποτελεί την τελική τιμή της έκφρασης.
Μπορούμε να ορίσουμε συναρτήσεις που να εκφράζουν τα λογικά συνδετικά περιπτωσιολογικά,
βάσει του πίνακα τιμών αλήθειάς τους.
Αν θεωρήσουμε τις περιπτώσεις αυτές ως λογικές τιμές, πχ:
def true
::= λx.λy.λz.x
def false
::= λx.λy.λz.y
def contradictory ::= λx.λy.λz.z
αυτό που χρειαζόμαστε, είναι ο ορισμός των λογικών πράξεων (βάσει των πινάκων αλήθειας, που
προσδιορίζονται βάσει της απορροφητικότητας των πράξεων, που θα δούμε παρακάτω) και της
αντίστοιχης συνάρτησης επιλογής.
Από πρακτική πλευρά, είναι πολύ απλούστερο και πλεονεκτικότερο να προσδιορίσουμε το
πλειότιμο μοντέλο σε ψηλότερο επίπεδο, καθορίζοντας απ' ευθείας τις περιπτώσεις ισχύος των
τιμών αυτών στα πλαίσια ενός μοντέλου ανεπτυγμένου στη Lisp. Για το σκοπό αυτό προσφέρεται
ιδιαίτερα η λίστα ιδιοτήτων συμβόλου, όπου μπορεί να καταχωρηθεί, για κάθε σύμβολο επιδεκτό
λογικής αποτίμησης, ιδιότητα λογική-τιμή με τιμή την ισχύουσα από το σύνολο των λογικών
τιμών. Τα λογικά συνδετικά προσδιορίζονται και αυτά περιπτωσιολογικά, ως συναρτήσεις με
κλάδους κατά περίπτωση τιμών εισόδου.

Ένα πλαίσιο έξι λογικών τιμών
Για λόγους πληρότητας (ισχύς νόμου de Morgan, και λογικών ιδιοτήτων), για να εκφράσουμε τις
περιπτώσεις "ασύμβατο" και "ανεξάρτητο" χρειαζόμαστε ένα πλαίσιο έξι λογικών τιμών:
{αληθές, ψευδές, συμβατό, ασύμβατο, συσχετιζόμενο, ανεξάρτητο}
τις οποίες τιμές θα διαχειρίζεται ένα σύνολο συναρτήσεων – κανόνων επαναπροσδιορισμού των
λογικών πράξεων με τρόπο που να αποτελεί επέκταση των συνήθων της δίτιμης λογικής
[Γυφ95α], [Γυφ+95].
Καθορίζουμε μια διάταξη των τιμών αυτών σύμφωνα με την αντίληψη που έχουμε για τις
λογικές έννοιες, ώστε να προκύψουν οι πίνακες αλήθειας των λογικών συνδετικών με εφαρμογή
του νόμου της απορροφητικότητας. Η "and-απορροφητικότητα" κατά τη διάταξη των λογικών
τιμών, σημαίνει: η μεγαλύτερη τιμή υπερισχύει σε οποιαδήποτε "and-σχέση":
αληθές <AND συσχετιζόμενο <AND ψευδές <AND ανεξάρτητο <AND συμβατό <AND ασύμβατο
Από τη διάταξη αυτή απορρέει ακριβώς η αντίθετης φοράς σχέση για την "or468

απορροφητικότητα", δηλαδή η μεγαλύτερη τιμή υπερισχύει σε οποιαδήποτε "and-σχέση":
αληθές >OR συσχετιζόμενο >OR ψευδές >OR ανεξάρτητο >OR συμβατό >OR ασύμβατο
Προφανώς οι τέσσερις από τις τιμές αυτές είναι θεμελιακές (αληθές, ψευδές, ανεξάρτητο,
ασύμβατο) και οι άλλες παράγωγες.
Μία από τις παραπάνω σχέσεις διάταξης επαρκεί για τον προσδιορισμό του πίνακα τιμών των
λογικών συνδετικών. Ερώτημα ανοικτό είναι, ποιά ακριβώς θα είναι τα λογικά συνδετικά στο
πλαίσιο αυτό∙ διότι, στη Λογική Boole έχουμε (2 2) 2=16 διαφορετικές διμελείς σχέσεις, με
σαφή σημασιολογία (μεταξύ των οποίων τις and, or, xor, imp, eqv, nand, nor) και μια μονομελή
(not) με επάρκεια ως αρχικές, ως γνωστό, ενός των συνόλων {nand}, {nor}, {and, or, not} ενώ
στο τετράτιμο έχουμε (44) 4=65536 (που περιέχουν μεν τις παραπάνω αλλά και άλλες,
ανεξερεύνητες). Θεωρούμε οτι οι θεμελιακές λογικές πράξεις είναι οι ίδιες (and or, not), με την
προσθήκη μιας ακόμα μονομελούς (inv).
Σημασία των έξι τιμών:

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

Ψευδές (F) σημαίνει οτι για το συγκεκριμένο context αναφοράς, η άρνηση "not" της
αναφερόμενης σχέσης θεωρείται ή αποδεικνύεται αληθής. Ο χώρος των ψευδών σχέσεων, έστω
F, είναι 1:1 αντίστοιχος του χώρου Τ (για κάθε αληθή σχέση υπάρχει μία και μόνο μία ψευδής,
και το αντίθετο).

Συσχετισμένο ή εξαρτημένο (D) σημαίνει οτι είναι ένα από τα δύο, αληθές ή ψευδές (αλλά
δεν ξέρουμε ποιο ή δεν μας ενδιαφέρει). Το σύνολο των σχέσεων της τιμής αυτής αποτελεί το
δίτιμο χώρο D αληθών ή ψευδών σχέσεων, που αν είναι σταθεροποιημένος, είναι ένας
μπουλιανός (Βoolean) χώρος.


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

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

ένα νέο δίτιμο σύστημα (από το D, απ' όπου αναιρείται η Α και όλες οι σχέσεις που προϋποθέτουν
την Α). Αυτή η πορεία επιτρέπει τη θεώρηση εξελίξεων σε ένα δίτιμο λογικό σύστημα.

Συμβατό (R) σημαίνει οτι αν θεωρήσουμε οτι το στοιχείο αυτό είναι μέσα στο λογικό
σύστημα D , δεν προκαλούμε αντίφαση. Αν ήδη περιέχεται, είναι αληθές ή ψευδές και
οπωσδήποτε εξαρτημένο∙ αν δεν περιέχεται, είναι ανεξάρτητο.

Η τιμή αυτή προσφέρει τη δυνατότητα να μη γνωρίζουμε ακριβώς την περίπτωση αλλά να
έχουμε την βεβαιότητα συνέπειας του χώρου. Θα σημειώνουμε με R το χώρο των συμβατών προς
ένα χώρο D σχέσεων ( R επίσης δεν είναι ένα καθοριζόμενο σύνολο).


Ασύμβατο (C) σημαίνει είτε οτι πρόκειται για αυτό-αντιφατική σχέση, είτε για σχέση
συγκρουόμενη με το λογικό σύστημα των αληθών - ψευδών σχέσεων. Αυτό είναι ισοδύναμο με το
οτι αν η ασύμβατη σχέση επισυναφθεί στο σύνολο Τ (αντίστοιχα στο F) τότε το σύστημα που
προκύπτει είναι ασυνεπές. Θα σημειώνουμε με C το χώρο των ασύμβατων προς ένα χώρο D
σχέσεων ( C επίσης δεν είναι ένα καθοριζόμενο σύνολο).
Οι δύο τελευταίες τιμές χρησιμεύουν για τον έλεγχο συνέπειας συστήματος.
Στο πλαίσιο αυτό διακρίνουμε δύο είδη άρνησης:

Την εξωτερική άρνηση (inv) , που αναφέρεται στη λογική τιμή μιας σχέσης και προκαλεί
μετατροπή της: από T σε F , B σε I , και από R σε C , και αντίθετα (το νόημα αυτής της άρνησης
είναι "οδήγηση στη συζυγή κατάσταση", όπως "το Α δεν είναι ασύμβατο με το χώρο Β, σημαίνει
οτι είναι συμβατό προς αυτόν").
Η έννοια της άρνησης αυτής είναι οτι αναφέρεται στη λογική τιμή της οντότητας-σχέσης πάνω
στην οποία εφαρμόζεται (και όχι σ' αυτή καθ' εαυτή την οντότητα). Πχ έχουμε:
"ο Γιάννης είναι γιατρός, αληθές" 
"ο Γιάννης δεν είναι γιατρός, ψευδές"
" ο Μίμης είναι γιατρός, εξαρτημένο" 
"ο Μίμης δεν είναι γιατρός, εξαρτημένο"
en'v οι φράσεις:
"Α αντιφατικό ως προς το χώρο Μ" και "A συμβατό με το χώρο Μ"
είναι "inv" η μια της άλλης
– Την εσωτερική άρνηση (not) , που αναφέρεται στην ίδια τη λογική σχέση, και δηλώνει απλώς
μια άλλη σχέση, που συνδέεται με την πρώτη, κατ' αρχήν υπαρξιακά (αν υπάρχει η Α τότε
υπάρχει και η not A), και στη συνέχεια ως προς την τιμή που λαβαίνει: αν η Α έχει τιμή Τ , τότε
η not(A) έχει τιμή F και το αντίθετο, ενώ παραμένουν οι λοιπές τιμές αναλλοίωτες: αν η σχέση Α
είναι αντιφατική ως προς το χώρο D, τότε και η not A είναι επίσης αντιφατική ως προς αυτόν, και
αν η σχέση Α είναι ανεξάρτητη του χώρου D, η not(A) είναι επίσης ανεξάρτητη∙ αν η σχέση Α
είναι συσχετιζόμενη με το χώρο D (τιμή D), τότε και η not(A) είναι συσχετιζόμενη (έχει τιμή D).
Η έννοια της άρνησης αυτής, είναι οτι αντιστοιχεί μια λογική οντότητα Α σε μια άλλη, την
not(A), με τιμή όπως ορίστηκε παραπάνω (δηλ. εφαρμόζεται σ' αυτή καθ' εαυτή την οντότητα και
όχι στην τιμή της). Πχ: "ΑΑ , αντιφατικό"  "not(ΑΑ) , αντιφατικό"
470

Τα δύο είδη άρνησης συμπίπτουν από πλευράς πίνακα τιμών αλήθειας, όταν περιοριστούμε στο
δίτιμο πλαίσιο {T , F} (γι' αυτό και δεν προσδιορίζουμε δύο είδη άρνησης στο δίτιμο πλαίσιο).
Οι τιμές {αληθές , ψευδές} επαναπροσδιορίζονται, ομοιόμορφα με τις υπόλοιπες, ώστε να
μείνουν οι {Τ , NIL} για χρήση σε επίπεδο "υπάρχει - δεν υπάρχει", δηλ. πιο ψηλά από το λογικό
σύστημα, και να "κρεμαστούν" οι έξι τιμές κάτω από το Τ .
Το σύνολο των λογικών τιμών και των αντιστοίχων χώρων, παριστάνεται με το παρακάτω.
δένδρο. Κάθε λογική οντότητα έχει μια τιμή στο δένδρο αυτό, αλλά με την επιπλέον προσθήκη,
οτι αν έχει μια τιμή, έχει και κάθε ανώτερή της στο δένδρο.

Η απόδοση λογικής τιμής μπορεί να γίνει με σχέσεις της μορφής:
(setf (get <οντότητα επιδεκτή λογικής τιμής>) 'logic-value)
<λογική τιμή>)

Ο προσδιορισμός των λογικών συνδετικών γίνεται με κάλυψη των περιπτώσεων:
(defun multi-and (x y &aux xl yl)
(setq xl (get x 'logic-value) yl (get x 'logic-value))
(cond
((or (eql xl 'c) (eql yl 'c)) 'c)
((or (eql xl 'r) (eql yl 'r)) 'r)
471

((or (eql xl 'i) (eql yl 'i)) 'i)
((or (eql xl 'f) (eql yl 'f)) 'f)
((or (eql xl 'd) (eql yl 'd)) 'd)
((and (eql xl 't) (eql yl 't)) 't)
(t 'error)))
(defun multi-or … )
(defun multi-inv (x &aux xl)
(setq xl (get x 'logic-value))
(cond
((eql xl 'c) 'r)
…))
(defun multi-not (x &aux xl)
(setq xl (get x 'logic-value))
(cond
((eql x1 'c) 'c)
…))

Στο εξάτιμο πλαίσιο μπορούμε να χαρακτηρίσουμε την ασυμβατότητα ή την ανεξαρτησία προς το
θεωρούμενο λογικό χώρο και να την επεξεργαστούμε βάσει κανόνων, εντάσσοντας έτσι στο
συνήθη υπολογισμό και καταστάσεις που θα προκαλούσαν σφάλμα. Διαθέτουμε έτσι ένα τρόπο
να "ανεβάσουμε" τον λογικό έλεγχο που μπορούν να ασκήσουν οι υποθέσεις, και από αυτόν τη
λειτουργικότητα του προγράμματος, σε επίπεδα που να καλύπτουν και περιπτώσεις που, αν
έμεναν περιορισμένες στο δίτιμο λογικό πλαίσιο, θα προκαλούσαν διακοπή της εκτέλεσης.
Από την άποψη της αυστηρής Μαθηματικής Λογικής, στην προσέγγιση αυτή τοποθετούμε το
λογικό μοντέλο κάτω από ορισμένες σημασιολογικές παραδοχές, οι οποίες όμως στην
υπολογιστική πρακτική ούτως ή άλλως γίνονται.

9.7 Απαντήσεις στα αρχικά ερωτήματα
Στην ενότητα αυτή θα συγκεκριμενοποιήσουμε τα πλεονεκτήματα του ΣΠ, απαντώντας
απαντήσεις στα ερωτήματα που θέσαμε στην αρχή του πρώτου κεφαλαίου:
α'. Τιμή μεταβλητής, που να αναφέρεται σε άλλες μεταβλητές και να μπορεί να παρακολουθεί τις
μεταβολές τιμών τους, προσδιορίζεται απλά με αναφορά των μεταβλητών αυτών στην αρχική
μεταβλητή, με την προϋπόθεση οτι καλούνται κατά τον υπολογισμό της αρχικής μεταβλητής και
όχι κατά τον ορισμό της. Αυτό σημαίνει απλά, οτι αρκεί να αναφέρονται μέσα σε "quoted"
έκφραση.
Αλυσίδα αναφορών δημιουργείται αυτόματα με τη διαδοχική κλήση από μεταβλητή σε
μεταβλητή (πάντα σε "quoted" έκφραση), διότι η αναγραφή τους δεν αναφέρεται στην τρέχουσα
τιμή μεταβλητής, όπως σε άλλες γλώσσες, αλλά στο ίδιο το σύμβολο, το οποίο μπορεί πολύ καλά
να επαναπροσδιορίζεται σε επόμενη φάση, πριν την κλήση που θα προκαλέσει υπολογισμό του:
472

(setq b 1 c 2)
(setq a '(+ b c))
(eval a) → 3
(setq b 3 c 4)
(eval a) → 7

β'. Συναντήσαμε συχνά την κλήση μεταβλητής συνάρτησης μέσω του ορισμού της ως λμεταβλητής, και προσδιορισμού οτι πρόκειται για κλήση συνάρτησης μέσω της FUNCALL. Η
κλήση μεταβλητής συνάρτησης μπορεί να συγκεκριμενοποιείται σε φόρμα που καλεί άλλη
μεταβλητή συνάρτηση, κοκ, σε οποιοδήποτε βάθος θέλουμε, είτε προσδιοριζόμενο κατά την
κλήση είτε προκαθορισμένο.
γ'. Είδαμε το θέμα του αυτόματου ταιριάγματος της κατάλληλης κατά περίπτωση συνάρτησης
στις αρχέτυπες συναρτήσεις, και τη δυναμική της απεριόριστης επεκτασιμότητας τέτοιων
συναρτήσεων χωρίς μείωση της αποτελεσματικότητάς τους. Ο τρόπος αυτός
"αυτοματοποιημένης προσαρμογή συμπεριφοράς" δεν αποκλείει την ανάπτυξη συναρτήσεων με
περιπτωσιολογικό τρόπο απέναντι στα στοιχεία εισόδου, όπου η αφαίρεση μπορεί να παίξει ρόλο
καθορισμού οντότητας αρκετά γενικής ώστε να εξειδικεύεται κατά την εφαρμογή.

δ'. Είδαμε τη δυνατότητα να δεσμεύουμε λ-μεταβλητή μιας συνάρτησης πάνω σε λ-έκφραση
αρκεί η να καλείται κατάλληλα η λ-μεταβλητή στο χώμα της συνάρτησης (πχ. με την FUNCALL).
H έξοδος τέτοιου υπολογισμού μπορεί να είναι πάλι συνάρτηση.
Αν θέλουμε να δεσμεύσουμε λ-μεταβλητή που περιμένει συναρτησιακή έκφραση, πάνω σε
σταθερά, μπορούμε να προσδιορίσουμε μια εναλλακτική επιλογή στο σώμα της αρχικής
συνάρτησης, βάσει τύπου εισόδου.

Η συγκεκριμενοποίηση λ-μεταβλητής σε συνάρτηση ή λ-έκφραση, με τις κατάλληλες
προϋποθέσεις στο σώμα, μπορεί να "ανοίξει" την εφαρμογή προς σύνθεση έκφρασης με
περισσότερες λ-μεταβλητές, άρα έχουμε ένα δυναμικό άνοιγμα του δένδρου κλήσεων που
προσδιορίζει η συνάρτηση. Αυτό σημαίνει οτι έχουμε τη δυνατότητα για απεριόριστες σε πλάτος
και βάθος νέες συγκεκριμενοποιήσεις. Τέτοια συγκεκριμενοποίηση μπορεί να γίνεται κατά την
κλήση συνάρτησης από το χρήστη.
ε'. Το "τώρα" ή "μετά" του υπολογισμού ελέγχεται με πολλούς τρόπους, όπου απλούστερος είναι
ο συνδυασμός χρήσης "quote" και "eval":
(setq later '(+ a b)) → (+ a b)

(setq a 4 b 6)
(eval later) → 10

Στα ρεύματα χρησιμοποιήσαμε ένα συνθετότερο τρόπο, με χρήση της DELAY , όπου μαζί με την
καθυστέρηση προσδιορίζουμε και την εκμετάλλευση του αποτελέσματος
ς'. Συνάρτηση που να επαναπροσδιορίζεται σε κάθε κλήση της, είναι δυνατό να δοθεί ευθέως:
(defun a (…) … (defun a (…) …))

Είναι δυνατό ο επανορισμός να περάσει και σε επίπεδο χρήστη:
473

(defun a (…) … (defun a (read))

ζ'. Συνάρτηση που δίνει έξοδο συνάρτηση συναντήσαμε πολλές φορές. Μια πολύ απλή
περίπτωση, που είναι εξαιρετικά δύσκολο να υλοποιηθεί σε διαδικαστική γλώσσα, είναι η
ακόλουθη συνάρτηση, που καλούμενη με ένα αριθμητικό όρισμα, δίνει συνάρτηση η οποία, όταν
κληθεί με ένα νέο όρισμα, υψώνει το προηγούμενο στο νέο :
(defun expos (x) #'(lambda (n) (expt x n)))
(funcall (expos 2)) → #<closure…>

Η απάντηση που πήραμε, ουσιαστικά είναι η συνάρτηση 2 x .
Εφαρμογές:
(funcall (expos 2) 3) → 8
(defun power-of-two (x) (funcall (expos 2) x) )
(power-of-two 3)
→ 8

Αντίστοιχο αποτέλεσμα μπορούμε να λάβουμε με τη δέσμευση λ-μεταβλητής μιας συνάρτησης
πάνω σε μια λ-έκφραση. Διαφέρει από το προηγούμενο στο οτι μπορούμε να καθυστερήσουμε
τον ορισμό συνάρτησης, τοποθετώντας τον κάτω από υπολογισμό.
η'. Αν δώσουμε "quoted" φόρμα υπολογισμού ως τιμή σε μεταβλητή, με απλή κλήση της
μεταβλητής θα πάρουμε τη φόρμα ενώ με την eval – κλήση της θα πάρουμε την τιμήαποτέλεσμα:
(setq vvq '(> 3 4))
vvq
→ (> 3 4)
(eval vvq)
→ NIL

Αν δώσουμε "quoted" λ-έκφραση ως τιμή σε μεταβλητή, με την απλή κλήση της θα πάρουμε την
έκφραση, ενώ εφαρμογή της θα προκαλέσουμε μέσω της FUNCALL :
(setq vvl '(lambda (x y) (> x y)))
vvl
→ (lambda (x y) (> x y))
(funcall vvl 3 4) → NIL
(funcall vvl 4 3) → T

Αντίστοιχα, μπορούμε να δώσουμε τιμή σε μεταβλητή μια "quoted" λίστα συναρτήσεων ή λεκφράσεων και να προσδιορίσουμε την προς εκτέλεση φόρμα ως στοιχείο της λίστας, εξαρτώντας
τον υπολογισμό από τα ισχύοντα στο περιβάλλον του, τη στιγμή της εκτέλεσης:
(setq
(setq
(eval
(eval
(setq
(eval

fun-lis '((+ a b) (= c 1)))
a 2 b 3 c 6)
(car fun-lis))
→ 5
(cadr fun-lis)) → NIL
c 1)
(cadr fun-lis)) → T

θ'. Είδαμε τη διπλή χρήση συμβόλου, ως μεταβλητή και ως συνάρτηση. Στο μέρος
περιεχομένου δίνουμε τιμή τον πηγαίο κώδικα "quoted", και στο συναρτησιακό μέρος το ίδιο
474

σώμα, που θα μεταγλωττιστεί αυτόματα.

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

9.8 Ασκήσεις
1. α) Να γράψετε συνάρτηση που να πετυχαίνει αναπαράσταση πολυώνυμου μιας μεταβλητής,
παίρνοντας ως ορίσματα το όνομα της μεταβλητής (ως string), το βαθμό του πολυώνυμου (ως
φυσικό αριθμό) και τους συντελεστές από το μεγαλύτερη τάξη μέχρι τη μηδενική (ως φυσικούς
αριθμούς, μη εξαιρουμένων των μηδενικών). Η αναπαράσταση να γίνει με τρόπο που να
εξυπηρετεί τον αναλυτικό λογισμό, δηλ. να διευκολύνει την ανάπτυξη συναρτήσεων
επεξεργασίας πολυωνύμων, όπως οι παρακάτω.
β) Να γραφεί συνάρτηση που να προσθέτει όσα τέτοια πολυώνυμα δώσουμε, μιας μεταβλητής
ίδιου ονόματος, με έξοδο σε ίδια μορφή.
γ) Αντίστοιχα, συνάρτηση που να πολλαπλασιάζει όσα τέτοια πολυώνυμα δώσουμε, μιας
μεταβλητής ίδιου ονόματος, με έξοδο σε ίδια μορφή.
δ) Αντίστοιχα, συνάρτηση που να υψώνει τέτοιο πολυώνυμο μιας μεταβλητής σε δύναμη
φυσικό αριθμό, με έξοδο σε ίδια μορφή.
ε) Με βάση τα παραπάνω, να αναπαρασταθούν σε φόρμες υπολογιστικά αναγνωρίσιμες τα
πολυώνυμα:
3x3-2x2+5x-1 και 5x 6-3x2+4
και να υπολογιστεί το άθροισμά τους, το γινόμενό τους και ο κύβος του καθενός.

2. α) Να προσδιορίσετε τρόπο υπολογιστικής αναπαράστασης μονώνυμου οσωνδήποτε
μεταβλητών (καθορισμένου πεπερασμένου πλήθους), σε μορφή παρόμοια της λίστας
πολυωνύμου.
β) Να προσδιορίσετε συνάρτηση αθροίσματος οσωνδήποτε ομοίων μονώνυμων, δοσμένων στη
μορφή που ορίσατε στο α'.
γ) Να προσδιορίσετε συνάρτηση γινομένου οσωνδήποτε μονώνυμων, δοσμένων στη μορφή
που ορίσατε στο α'.

3. α) Να δώσετε συνάρτηση που να απλοποιεί ομοίους όρους σε πολυωνυμική παράσταση.
475

β) Να επεκτείνετε τις συναρτήσεις 1.β' και 1.γ' έτσι ώστε να δέχονται τυχαία πολυώνυμα
(ενδεχομένως διαφορετικών μεταβλητών ή ακόμα και περισσοτέρων της μιας μεταβλητής).

4. α) Να σχηματίσετε συνάρτηση AGAIN-PI παρόμοια της ANOTHER-PI , που, αντί για κλήση
της SERIES να έχει στη θέση της την κατάλληλη αντίστοιχη λ-έκφραση.
β) Να σχηματίσετε συνάρτηση PRODUCT που να υπολογίζει το γινόμενο των χιλίων πρώτων
όρων ακολουθίας που δίνεται με γενικό όρο α ν , με κριτήριο διακοπής την απόσταση
διαδοχικών υπολογισμών μικρότερο δοθέντος αριθμού ε .

5. Να υπολογίσετε τα παρακάτω ολοκληρώματα, αξιοποιώντας είτε την INTEGRAL είτε την
integral-f που ορίσαμε, με ακρίβεια δοθέντα αριθμό ε :



π /2

cos(2 x)dx

0



7.21

1

∫∫

2

cos(2 x )dx

0.17

0

π /3

y cos(2 x )dxdy

0

6. Να δοθεί συνάρτηση έκπτωσης για υπολογισμό του ΜΚΔ λίστας ακέραιων, με χρήση της
συνάρτησης – αλγόριθμου του Ευκλείδη για δύο αριθμούς, και της REDUCE-RIGHT .

7. α) Να κατασκευαστεί ρεύμα που να παράγει αριθμούς διαιρετούς δια 2, 3 και 5 (ταυτόχρονα),
και συναρτήσεις ανάγνωσης / ενεργοποίησής του.
β) Να κατασκευαστεί φίλτρο που να επιτρέπει το πέρασμα αριθμών που είναι διαιρετοί δια 7
γ) Να κατασκευαστεί συνάρτηση που να συνδυάζει τα παραπάνω ρεύμα και φίλτρο,
παράγοντας αριθμούς διαιρετούς δια 2, 3, 5 και 7 , καθυστερώντας την παραγωγή του
ρεύματος μέχρι την αίτηση για επόμενο, ενεργοποιούμενη από είσοδο "yes" του χρήστη, και να
αποθηκεύει τα αποτελέσματα σε λίστα διαφορετική της λίστας του ρεύματος.

8. α) Να κατασκευάσετε ρεύμα που να παράγει πρώτους αριθμούς απεριόριστα, με βάση τον
γνωστό αλγόριθμο "τρίγωνο του Ερατοσθένους".
β) Να κατασκευάσετε φίλτρο που να ελέγχει αν ένας αριθμός είναι διαιρέτης του Ν, για
οποιοδήποτε ακέραιο Ν.
γ) Χρησιμοποιώντας τα παραπάνω, να κατασκευάσετε συνάρτηση που να παράγει τους
πρώτους διαιρέτες του Ν.
δ) Με βάση τα προηγούμενα, να κατασκευάσετε μηχανισμό (συνάρτηση) που να δέχεται
είσοδο ακέραιο αριθμό και να τον αναλύει σε γινόμενο πρώτων παραγόντων.

9. α) Nα σχηματίσετε συνάρτηση COMPRESS η οποία να δέχεται είσοδο λίστα, η οποία
υποτίθεται πως έχει μεγάλο αριθμό πολλαπλών στοιχείων, και να επιστρέφει λίστα όπου τα
περιττής τάξης στοιχεία να δηλώνουν το πλήθος των ίδιων διαδοχικών όρων, και τα άρτιας
τάξης, ποιοι είναι οι όροι αυτοί. Πχ, η λίστα:
(1 1 1 1 1 1 2 2 2 2 0 0 0 0 0 0 0 0 0 0 1 1 3 3 3 3)

να συμπιεστεί στην:
(6 1 4 2 10 0 2 1 4 3)

η οποία προφανώς περιγράφει την πρώτη ως: "έξι άσσοι, τέσσερα δυάρια, δέκα μηδενικά, δύο
476

άσσοι, τέσσερα τριάρια".
β)
Nα σχηματίσετε συνάρτηση UNCOMPRESS η οποία να δέχεται είσοδο μια
συμπιεσμένη από την COMPRESS λίστα, και να την αποσυμπιέζει στην αρχική της μορφή.
γ) Να σχηματίσετε συνάρτηση ELEMENTS που να ανιχνεύει τα διαφορετικά στοιχεία σε μια
λίστα, που υποτίθεται οτι έχει πολλές επαναλήψεις και σε διαδοχικές θέσεις, και να επιστρέφει
λίστα δύο όρων: ο πρώτος όρος να είναι η λίστα των διαφορετικών στοιχείων, και ο δεύτερος,
λίστα που να περιγράφει τη θέση των στοιχείων, ως εξής: "το πρώτο στοιχείο είναι στην
αρχική λίστα στις θέσεις από α έως β, από γ έως δ, κοκ., το δεύτερο στις θέσεις... κοκ.". Πχ, αν
η λίστα που επιστρέφει η ELEMENTS η εξής:
(( α β γ ) (((1 17) (23) (39 46) (72 100)) ((18)) ((19 22) (47 71))

σημαίνει οτι: το α εμφανίζεται στις θέσεις: από 1 ως 17, 23, 39 έως 46, και από 72 έως 100 ∙
το β στη θέση 18 ∙ το γ στις θέσεις από 19 έως 22 και από 47 έως 71 .
δ)
Να σχηματίσετε συνάρτηση UNFOLDE που να παίρνει είσοδο μια λίστα της μορφής
εξόδου της ELEMENTS και να επανασχηματίζει την αρχική λίστα.

10. Να ορίσετε συνάρτηση OPERATING-ON-SEQUENCE η οποία να εφαρμόζεται σε
πεπερασμένο πλήθος m αρχικών όρων ακολουθίας (παράμετρο της συνάρτησης) όπου η
ακολουθία να έχει γενικό όρο δύο μεταβλητών (δείκτες του όρου), εφαρμόζοντας διαδοχικά
δύο διμελείς πράξεις p1 και p2 (παραμέτρους της συνάρτησης), αντίστοιχες των δεικτών.
Να προνοήσετε για την καταλληλότητα των διμελών πράξεων ως προς τον τύπο των όρων. Για
παράδειγμα, για αριθμητική ακολουθία aij , 1<i<m , 1<j<m , οι πράξεις "πολλαπλασιασμός"
και "πρόσθεση" είναι δεκτές ενώ η "cons" δεν είναι.
Να γίνει εφαρμογή για τις πράξεις "πολλαπλασιασμό" και "πρόσθεση" με αριθμητική
ακολουθία γενικού όρου aij (δώστε συγκεκριμένο γενικό όρο) Η συνάρτηση θα δίνει ως
αποτέλεσμα το "άθροισμα των γινομένων" Σi(Πjaij) .
Η ίδια συνάρτηση, για τις πράξεις "πρόσθεση" και "πρόσθεση" θα δίνει ως αποτέλεσμα το
"άθροισμα των αθροισμάτων" Σi(Σjaij) .
11. α) Να κατασκευάσετε συνάρτηση που να διαβάζει από την είσοδο τη συνάρτηση που θα δίνει
ο χρήστης, και να την εφαρμόζει σε ορίσματα που επίσης θα δίνει ο χρήστης από την είσοδο.
β) Να κατασκευάσετε συνάρτηση που να διαβάζει συνάρτηση από την είσοδο, η οποία πάλι
να διαβάζει συνάρτηση από την είσοδο, και οι δύο να εφαρμόζονται σε ορίσματα που θα δώσει
ο χρήστης.

477

Συμπεράσματα

Αξιολόγηση της συναρτησιακής προσέγγισης
Στην ενότητα αυτή θα συνοψίσουμε τα πλεονεκτήματα και μειονεκτήματα της συναρτησιακής
προσέγγισης, και ειδικότερα της ανάπτυξης συναρτησιακού προγράμματος με χρήση της Lisp.
Τέλος θα κάνουμε μια συγκριτική αναφορά αναφέρουμε στις διάφορες προσεγγίσεις διδακτικής
που συναντάμε στη σχετική με ΣΠ βιβλιογραφία.

α) Πλεονεκτήματα
Τα πλεονεκτήματα συντίθενται από τις δυνατότητες έκφρασης που διαθέτει το συναρτησιακό
μοντέλο και τη συγκεκριμένη μορφή που παίρνουν αυτές οι δυνατότητες με τη γλώσσα Lisp, που
όπως είδαμε, είναι πάρα πολλά. Συγκεντρώνουμε εδώ τα σημαντικότερα:
– πρόγραμμα σε μορφή δάσους, με ελεύθερη κίνηση μέσα σ' αυτό, για χρήση δένδρου,
επέκταση δένδρου, συνένωση δένδρων, προσθήκη δένδρων, μεταβολή δένδρων
– ομοιόμορφη και απλή σύνταξη συγγραφής, σε κάθε περίπτωση
– χρήση δομημένου προγραμματισμού, με χρήση αφηρημένων τύπων δεδομένων
– αλληλεπίδραση με δυνατότητα διαμόρφωσής της σε κάθε επίπεδο και σε κάθε βαθμό
συνθετότητας οντοτήτων
– αποσφαλμάτωση, με "tracing" της εκτέλεσης ή με χρήση του "debugger"∙ διάκριση
σφαλμάτων σε καταστροφικά (fatal error) και συνεχιζόμενα (continuable error)
– κατάτμηση του προβλήματος σε απλούστερα και συνένωση των επί μέρους λύσεων, για
απλούστευση της σύνθεσης όσο και για μείωση της πολυπλοκότητας
– σύνθεση αλγορίθμων επίλυσης για επίλυση διαφορετικών προβλημάτων από αυτά για τα
οποία συντέθηκαν αρχικά
– υπολογιστική έκφραση που "δένεται" σημασιολογικά με τη δομή των πραγματικών εννοιών
στις οποίες αντιστοιχεί, και υπολογισμός που αναπαριστά ευθέως και βήμα προς βήμα την
αντίστοιχη πραγματική λειτουργία
– σύνθεση αλγόριθμων εφαρμόζοντας προς τα εμπρός ή προς τα πίσω συλλογιστική (δόμηση
από πάνω προς τα κάτω ή αντίθετα) ή και ανάμεικτα∙ η από το στόχο κατευθυνόμενη ανάλυση
του προβλήματος, οδηγεί σε αντίστοιχη υλοποίηση προγράμματος
– διαφορική σύνταξη συμβόλων, σημειώνοντας μόνο σε τί διαφέρει το νέο σύμβολο από ένα
προηγούμενο, για ανάπτυξη προς τα πλάγια, προς τα κάτω (εξειδίκευση), προς τα πάνω
(επαγωγική γενίκευση), μόνιμα ή προς δοκιμή
– σειρά γραφής κώδικα απαλλαγμένη φορμαλισμού άσχετου προς το νόημα που υλοποιείται,
και επί πλέον ανάλογη της σειράς που ακολουθεί η φυσική σκέψη
478

– η μεταβολή στοιχείου του προγράμματος δεν απαιτεί παρά συμβατότητα τύπων και πλήθους
εισόδων - εξόδων του στοιχείου ως προς τ άλλα στοιχεία του προγράμματος
– δυναμική έννοια προγράμματος, που συμπεριλαμβάνει όλες τις δυνατές συνθέσεις οντοτήτων
– διάκριση επιπέδων των χρηστών
– μεταφερσιμότητα κώδικα οντοτήτων, σε άλλο πρόγραμμα, σε άλλο compiler σε άλλη
πλατφόρμα
– "typeless" ή "strongly typed", κατά την επιθυμία μας, και από αυτό χρήση συναρτήσεων με
μεταβλητές επαρκώς γενικευμένου τύπου ή ειδικά συγκεκριμενοποιημένου για διαμόρφωση
συμπεριφοράς
– φυσική / εννοιολογική κατακερματιστικότητα, για διάκριση συνεκτικού πλαίσιου ως προς το
"τί καλεί τί" ή ως προς το "τί θα μπορούσε να καλέσει τί" στα πλαίσια ενός context χρήσης
– δυναμικά διαμορφωνόμενες και χρησιμοποιούμενες βιβλιοθήκες συναρτήσεων, σε μορφή
αρχείων ή packages∙ διάκριση στόχων χρήσης βιβλιοθηκών (υποπρογράμματα, εργαλεία,
δομικά υλικά, προσδιορισμός context
– διάκριση επιπέδων για το context αναφοράς (τυπικό, σημασιολογικό, πραγματολογικό)
– συντηρησιμότητα, προσαρμοστικότητα προγράμματος (ελαχιστοποίηση κόστους κατασκευής,
ευελιξία χρήσης)
– ταχύτητα εκτέλεσης, εντυπωσιακά αυξανόμενη με την κατάλληλη δόμηση ή/και τη διαχείριση
της μεταγλώττισης (παίζει ρόλο το τί πρώτα και τί μετά)
– συμπαγότητα κώδικα, μέσω πολλαπλών διασυνδέσεων στην ίδια φόρμα, ή αντίθετα,
αυτονόμηση οντοτήτων για διαφάνεια και ευχέρεια επίδρασης
– διαφάνεια και ευκρίνεια των εκφράσεων (στο βαθμό που δεν κρύβουν τη λειτουργικότητα)
– λογική ευελιξία (διαμόρφωση λογικού και συμπερασματικού πλαίσιου)
– διαμορφούμενο περιβάλλον προγραμματισμού (απεθυνόμενο στον προγραμματιστή), μέχρι
του βαθμού να προσδιορίζουμε μια νέα γλώσσα
– ευελιξία εφαρμογής άλλων στυλ προγραμματισμού
– δόμηση μικρόκοσμων, δένδρων μικρόκοσμων, ανοικτών μικρόκοσμων
– προσδιορισμός γενικευμένων οντοτήτων, για κίνηση στον άξονα αφαίρεσης συγκεκριμενοποίησης
– συμβολοποίηση δράσεων
– αλληλεπιδρών προγραμματισμός
– μεθοδολογία ανάπτυξης "με δοκιμή και λάθος" (trial and error)
– μεθοδολογία ανάπτυξης "τί-αν" (what if)
– με αρχικό καθορισμό του "τί" και στη συνέχεια αναζήτηση των προϋποθέσεων που το
παράγουν / επιτρέπουν / ικανοποιούν, δηλ. συμπερασματικό προσδιορισμό του "αν"
– με αρχικό καθορισμό του "αν", ως συνόλου συνθηκών, και στη συνέχεια διερεύνηση
συμπερασματικά του "τί", που θα αξιοποιηθεί κάτω από την εκτίμηση αν είναι επιθυμητό ή
ανεπιθύμητο, αναπόφευκτο ή ελεγχόμενο ενδεχόμενο.
– εξέλιξη προγράμματος βάσει ιδεών
– αυτοεξέλιξη προγράμματος
– υλοποίηση μεθόδων "πώς" (how) και "γιατί" (why)
– διάφορα επίπεδα προγραμματισμού, ψηλότερα προς τον άνθρωπο ή χαμηλότερα προς τη
479

μηχανή
Από τα παραπάνω, άλλα είναι εγγενή του μοντέλου και της γλώσσας, άλλα απαιτούν
συγκεκριμένη τακτική στη χρήση, και άλλα απαιτούν ειδική ανάπτυξη κώδικα. Ειδικά πακέτα εργαλεία υλοποιούν αυτά που δεν είναι απλό να αναπτύξει ο χρήστης της γλώσσας, όπως το ΚΕΕ
που υλοποιεί τους μηχανισμούς εξήγησης / αναζήτησης αιτίων "how - why".

β) Μειονεκτήματα
Τα μειονεκτήματα διακρίνονται στα οφειλόμενα στην επιλεγμένη γλώσσα (εδώ η Lisp), στα
οφειλόμενα στo θεωρητικό μοντέλο (λ-λογισμός) και στα οφειλόμενα στη μεθοδολογία
ανάπτυξης και τους γενικούς στόχους του ΣΠ. Είναι εν γένει συνυφασμένα, διότι δεν μπορούμε
να ανεξαρτητοποιήσουμε τον ΣΠ από τον λ-λογισμό και τη γλώσσα ανάπτυξης προγράμματος,
αλλά οπωσδήποτε παίζει διαφορετικό ρόλο και αντιμετωπίζεται αλλιώς η κάθε κατηγορία
μειονεκτημάτων. Αυτό που μπορούμε γενικά να πούμε, είναι οτι αν και συγκεκριμένα
μειονεκτήματα μπορούν να παρακαμφθούν με άλλες επιλογές, δεν είναι εύκολο να βρεθεί ενιαίος
γενικός χώρος, θεωρητικός και πρακτικής σημασίας για την ανάπτυξη προγραμμάτων, τέτοιος
που να υπερκαλύπτει τις δυνατότητες του ΣΠ. Επίσης, διακρίνονται σε μειονεκτήματα
συμβολικής ή τεχνικής υφής (τί μπορεί να αποδώσει κανείς), και μειονεκτήματα έκφρασης
(προσαρμογή της διαμόρφωσης εννοιών) που μελετήσαμε διεξοδικά.

Μειονεκτήματα της Lisp
α'.
Ένα από τα σημαντικά μειονεκτήματα της Lisp ήταν μέχρι πρόσφατα η παλαιότητα των
εκδόσεων, που δεν ανταποκρίνονταν στις σύγχρονες απαιτήσεις και τις υπολογιστικές
δυνατότητες, όπως: εκμετάλλευση παραθυρικών συστημάτων, χρήση υψηλών γραφικών με
κίνηση, επεξεργασία multimedia πληροφορίας, δικτυακή επικοινωνία, εκμετάλλευση των
διαθεσίμων του συστήματος, κά. Μετά από μεγάλο διάστημα αδράνειας των εταιριών παραγωγής
λογισμικού (συγκριτικά με άλλες γλώσσες προγραμματισμού) νεότερες εκδόσεις έρχονται τα
τελευταία χρόνια με ταχύ ρυθμό (πχ. Allegro Common Lisp, Golden Common Lisp και τα
σχετικά με αυτές πακέτα) καλύπτοντας σταδιακά τα κενά. Διαπιστώνεται οτι υπάρχουν πολλά να
γίνουν ακόμα, ιδίως σε ότι αφορά τη διαχείριση multimedia πληροφορίας και την επικοινωνία,
αλλά η σημερινή ταχύτητα ανάπτυξης νέων Lisp compilers είναι στα επίπεδα των προηγμένων
γλωσσών και διαθέτουν τις "visual" ευκολίες που συναντάμε σε άλλους compilers "ευρείας
κατανάλωσης".
β'.
Aν και κυκλοφορούν στην αγορά πολλές ειδικές βιβλιοθήκες (μαθηματικές, συστήματα
συγγραφής εμπείρων συστημάτων, συστήματα διαχείρισης βάσεων δεδομένων, κά.) που
διευκολύνουν την ανάπτυξη συναρτησιακών εφαρμογών (όπως το εκπληκτικό πακέτο
MUMATH, υποστήριξης Μαθηματικού υπολογισμού, ανεπτυγμένο σε MuLisp και δυστυχώς
ασύμβατο προς την CL) είναι εν γένει εξαρτημένες από την ειδική έκδοση στην οποία
αναφέρονται και δεν είναι μεταφέρσιμες σε άλλη πλατφόρμα ή άλλη έκδοση, οπότε χάνεται το
πλεονέκτημα της μεταφερσιμότητας, και βέβαια της διασυνδεσιμότητας των διαφορετικών
480

βιβλιοθηκών μεταξύ τους.
γ'.
Η υποστήριξη του μοντέλου των αντικειμένων με το CLOS πετυχαίνει μεν σε κάποιο
βαθμό την απαιτούμενη αντικειμενοστρεφή λειτουργικότητα, αλλά δεν είναι απαλλαγμένη
προβλημάτων. Δεν πρέπει να παραβλεφθεί οτι η χρήση αυτού μοντέλου αποτελεί απλώς ένα
πρόγραμμα - πακέτο συναρτήσεων, το οποίο δεν παρεμποδίζει τις λοιπές δυνατές λειτουργίες της
Lisp, και κατά συνέπεια είναι δυνατό να αναπτυχθεί επιπρόσθετα και κάποιο άλλο τμήμα του
προγράμματος σχεδιασμένο σε άλλο μοντέλο, που ακολουθεί άλλες αρχές από το μοντέλο των
αντικειμένων, και αυτό ανοίγει δρόμους για δυσλειτουργίες.

δ'.
Η "κίνηση προς τα κάτω" σε αλληλεξαρτημένες μέσω κλήσης οντότητες της Lisp είναι μεν
απλή στην έκφραση και υποστηρίζεται σε χαμηλό επίπεδο, αλλά οπωσδήποτε είναι χρονοβόρα
για την εκτέλεση. Πολύ σημαντικότερο πρόβλημα γεννάται στην "κίνηση προς τα πάνω" (πχ. από
ιδιότητα συμβόλου στο σύμβολο, από στοιχείο συνόλου στο σύνολο, από όρο λίστας σε
προηγούμενό του, από κλάση σε υπερκλάση της, από στιγμιότυπο στην κλάση του) διότι δεν
υποστηρίζεται άμεσα και πρέπει να εκφραστεί με κάποιο τρόπο. Αν ο τρόπος αυτός είναι μια
διαδικασία, από το ένα μέρος η εκτέλεσή της προκαλεί πρόσθετες καθυστερήσεις και από το
άλλο μέρος η ανάπτυξη του κώδικα γίνεται πολύ πιο σύνθετη. Αν είναι η εγκατάσταση μιας
σύνδεσης, δημιουργείται κίνδυνος από ατέρμονα επανάληψη.
Αποτέλεσμα είναι, να μην είναι ισοδύναμες εκφραστικά και λειτουργικά οι κινήσεις αυτές:
έτσι, το αν και πόσο γρήγορα (και αν) θα απαντήσει η Lisp σε ερωτήματα όπως "ποιος είναι ο
συγγραφέας του τάδε βιβλίου" εξαρτάται απόλυτα από τον τρόπο προσδιορισμού και σύνδεσης
των χαρακτηριστικών της οντότητας-βιβλίο, αλλά και της μεθόδου αναζήτησης.
ε'.
Σοβαρό μειονέκτημα είναι η σχετική αδυναμία υποστήριξης δικτυακής επικοινωνίας, στο
επίπεδο των σημερινών απαιτήσεων και δυνατοτήτων, που αποτελεί μια διάσταση με ολοένα
αυξανόμενη σημασία. Προς το παρόν έχουν αναφερθεί προσπάθειες, που είναι μάλλον
ερευνητικής σημασίας παρά πρακτικής. Ουσιαστικότερη λύση προσφέρουν τα υβριδικά
περιβάλλοντα, όπως Lisp-Java.
ς'.
Aναφέρεται συχνά στη βιβλιογραφία η συγκριτικά υπερβολική ανάγκη της Lisp σε
κεντρική μνήμη∙ και πραγματικά, προ δεκαετίας οι απαιτήσεις μνήμης ήταν σχεδόν
απαγορευτικές, διότι η ελάχιστη μνήμη για ένα "σοβαρό" πρόγραμμα εκτιμούνταν στα 6 ΜΒ.
Σήμερα που η κεντρική μνήμη είναι πολύ πιο φθηνή και τεχνικά προσπελάσιμη σε πολύ μεγάλα
μεγέθη, το πρόβλημα δεν είναι τόσο κρίσιμο, αλλά παραμένει συσχετισμένο με την ανάγκη
εγκατάστασης του προγράμματος στη μνήμη. Αυτή η ανάγκη μειώνεται πολύ με τεχνικές που
"φορτώνουν" μόνο τα σύμβολα που θα χρησιμοποιήσει το πακέτο των συναρτήσεων στις οποίες
θα κινηθεί ο χρήστης∙ όμως, σε γενικές γραμμές μπορούμε να πούμε οτι ο τρόπος
"πακεταρίσματος" συμβόλων παραμένει ακόμα "χειρωνακτικός", και περιμένει κανείς να
εμφανιστούν αυτοματοποιημένες και πιο ευέλικτες τεχνικές.
ζ'.
Ο χρήστης της γλώσσας αντιμετωπίζει πολλά προβλήματα όταν θέλει να κινηθεί σε
θεωρητικά επίπεδα του λ-λογισμού, για τους λόγους που έχουμε αναφέρει, και οφείλει να
υποτάσσει τις συναρτησιακές συνθέσεις στους κανόνες: i) οι λ-εκφράσεις προσδιορίζονται ως
κλήσεις της συνάρτησης LAMBDA και όχι ως συναρτησιακές οντότητες, και ii) η μεταβλητή
481

κλήση συνάρτησης απαιτεί κατάλληλη πρόβλεψη στο σώμα της καλούσας συνάρτησης. Το
σημαντικότερο είναι, η θεωρητική δυνατότητα απεριόριστης ανάπτυξης δένδρου συναρτήσεων
μέσω εξειδίκευσης είναι συγκριτικά περιορισμένη στη Lisp, και μόνο μέσω κλάσεων μπορεί ο
χρήστης να κινηθεί κάπως πιο άνετα.
η'.
Όπως έχουμε αναφέρει, η Lisp δεν διαθέτει τρόπο υλοποίησης σχέσεων απ' ευθείας, και
πρέπει να γίνει μεταφορά τους στις διαθέσιμες τεχνικές, με όλα τα μειονεκτήματα δυσκολίας στην
επιλυσιμότητα, αλλαγής μοντέλου αναπαράστασης και μοντέλου επεξεργασίας.

Μειονεκτήματα του λ-λογισμού

α'.
Κυριότερο μειονέκτημα του μοντέλου του λ-λογισμού είναι η νοηματική πολυπλοκότητα
των εκφράσεων, δοσμένη με μια σημειολογία ξένη προς τη συνήθη για το χρήστη, είτε αυτός
έρχεται με εμπειρία από άλλες γλώσσες προγραμματισμού, είτε με γνώσεις Μαθηματικών, είτε
Λογικής. Ένας τρόπος παράκαμψης της εκφραστικής πολυπλοκότητας είναι, να γίνεται
συνδυασμένη χρήση ορισμών που εκφράζουν συνοπτικά "με ένα όνομα" πολυσύνθετες σχέσεις,
και μετατροπής του τρόπου έκφρασης ώστε να γίνεται "πιο περιεκτικός". Συχνά μετατρέπεται ο
τρόπος έκφρασης σε πιο προσιτή για τον αναγνώστη μορφή (πχ. με εκφραστική προσαρμογή σε
μοντέλα που έχει συνηθίσει ο χρήστης, όπως γραφή "if…then…else…" αντί της "cond…"). Αυτό
όμως οδηγεί στην ανάγκη επέκτασης (expansion) των εκφράσεων κάθε φορά που θέλουμε να
ελέγξουμε την πραγματική λειτουργία τους, μέχρι τη βασική μορφή τους, έργο πρόσθετο και
επίπονο. Επί πλέον, η υπολογιστική υλοποίηση του μοντέλου (γλώσσα προγραμματισμού) γίνεται
σε ένα από τα επίπεδα "σύνοψης" μέσω ορισμών και σημειολογίας, οπότε η μεταφορά από μια
γλώσσα σε άλλη είναι εξαιρετικά δυσχερής (πχ. από ML σε Lisp και αντίστροφα).
β'.
Συγκριτικά, η Κατηγορηματική Λογική, υπολογιστικά υλοποιημένη με την Prolog, είναι
πολύ πιο κοντά στην ανθρώπινη νόηση, χωρίς αυτό να σημαίνει οτι η Prolog δεν παρουσιάζει τα
δικά της μειονεκτήματα στον υπολογισμό. Ο λ-λογισμός αποδίδει μεν το νόημα του υπολογισμού
με τον γενικότερο δυνατό συμβολικό τρόπο, αλλά οφείλει ο χρήστης να εντάξει τη σκέψη του σ'
αυτό το μοντέλο, που είναι οπωσδήποτε μια κρίσιμη απόφαση διότι επηρεάζει γενικότερα τις
προσεγγίσεις που θα ακολουθήσει στους συλλογισμούς του, και κυρίως διότι συνήθως
χρησιμοποιεί και άλλα περιβάλλοντα ανάπτυξης, με άλλο συμβολικό τρόπο έκφρασης και
απόδοσης εννοιών υπολογισμού, πχ. που ακολουθούν το επιτακτικά διαδικαστικό μοντέλο.
γ'.
Αν και το μοντέλο του λ-λογισμού είναι θεωρητικά επαρκές για να καλύψει οποιοδήποτε
υπολογιστικό ζήτημα, στην πράξη υπάρχουν ειδικότερα μοντέλα που αποδίδουν πιο άμεσα και
απλά το ζητούμενο όταν αυτό μπορεί να ενταχθεί στα ειδικά πλαίσια (πχ. η Σχεσιακή Άλγεβρα
για ανάπτυξη βάσης δεδομένων). Επίσης, θα ήταν δυσχερές να επαναπροσδιορίσει κανείς βάσει
του μοντέλου του λ-λογισμού τη λειτουργία αλγορίθμων που είναι ήδη ανεπτυγμένοι σε άλλη
βάση και αποτελούν τον κορμό προγραμμάτων που κυκλοφορούν.

Μειονεκτήματα του Συναρτησιακού Προγραμματισμού
α'.
Ένα μειονέκτημα που ανακύπτει στην πράξη, είναι οτι στον προγραμματισμό δεν ξεκινάμε
εκ του μηδενός, ούτε στη μοντελοποίηση ούτε στην υλοποίηση, διότι έχουν διανυθεί ήδη μεγάλες
482

"αποστάσεις", στους περισσότερους τομείς, και τα ήδη επιτευχθέντα πρέπει να αποτελούν βοήθεια
και οδηγό για τις νέες προσπάθειες. Άρα το προσδοκώμενο όφελος από τον ΣΠ πρέπει να
εκτιμηθεί "διαφορικά", και εδώ ο ΣΠ χάνει το προβάδισμα απέναντι στις διαδικαστικές γλώσσες.
β'.
Η ανάμειξη υπολογισμών στους ορισμούς συναρτησιακών εκφράσεων, άλλων με
καθυστέρηση και άλλων χωρίς, φέρνει το μοντέλο αυτό σε μια περίεργη σχέση απέναντι στο
Μαθηματικό μοντέλο σύνθεσης συναρτήσεων, στο οποίο δεν υπεισέρχεται το νόημα της "φάσης
της εκτέλεσης του υπολογισμού". Η Μαθηματική έκφραση πρέπει να προσαρμοστεί στα
καλούπια του ΣΠ, που είναι μεν επαρκώς πλατειά αλλά παραβαίνουν σε πολλά σημεία το τυπικό
των Μαθηματικών.
Η ανάμειξη της αμιγώς συναρτησιακής όψης των συνθέσεων με τη λειτουργική τους υπόσταση
(παράπλευρες δράσεις) ενδέχεται να προκαλέσει δυσχέρειες στη σύλληψη και έκφραση ενός
υπολογισμού: η παράπλευρη λειτουργικότητα μπορεί να αποτελεί επιθυμητό λειτουργικό στοιχείο
του προγράμματος, και μερικές φορές να είναι ο κύριος σκοπός που χρησιμοποιούμε μια
συνάρτηση, αλλά μπορεί να είναι ανεπιθύμητη, και να γίνει αιτία καταστροφών αν δεν ληφθεί υπ'
όψη.

Γενικά, η πεπλεγμένη ανάμειξη στο πρόγραμμα συναρτησιακών συσχετισμών και δράσεων,
μπορεί να προκαλέσει δυσκολίες απόδοσης των εννοιών όταν βλέπουμε "μονόπλευρα" τις
οντότητες της Lisp (σαν μαθηματικές συναρτήσεις ή σαν διαδικαστικές δράσεις): αν δούμε τα
σύμβολα μόνο ως τρόπο συσχετισμού εννοιών, θα εμπλακούμε με τα παράπλευρα αποτελέσματα
δράσεων, προσπαθώντας να τις κατευθύνουμε∙ αν τα δούμε μόνο ως τρόπο έκφρασης
διαδικασιών, θα βρεθούμε μπροστά στην ανάγκη να διαχειριστούμε ένα πλήθος εισόδων –
εξόδων συναρτήσεων που είτε δεν χρειάζονται είτε θα προτιμούσαμε να διαθέτουμε ένα
επιτακτικό τρόπο να τις κατευθύνουμε.

γ'.
Ένα ζήτημα, από μια άποψη μειονέκτημα και από άλλη πλεονέκτημα, είναι η πυκνότητα
γραφής των συναρτησιακών νοημάτων, τα οποία γράφονται μεν σειριακά αλλά δεν εκφράζουν
ενέργειες στην ίδια διαδοχή. Έτσι, μια συναρτησιακή φόρμα της Lisp μπορεί να αποδώσει σε
λίγες γραμμές το αντίστοιχο από ευμεγέθη προγράμματα σε άλλες γλώσσες, αλλά η ανάγνωσή
της απαιτεί πολύ μεγάλη εμπειρία. Ο συγγραφέας "τοποθετεί τον εαυτό του στη θέση της μηχανής
και εκτελεί νοητικά τα βήματα" και έτσι αντιλαμβάνεται τη λειτουργία του προγράμματος που
καταγράφει στη συνέχεια, αλλά ο "επόμενος αναγνώστης" δύσκολα παρακολουθεί αυτά τα
βήματα: πρέπει να κατανοήσει τη λειτουργικότητα που συντίθεται κατά ένα "hyper" τρόπο, μέσα
από τη διαδοχική ανάγνωση χαρακτήρων, με βοηθό τη σημασία των ονομάτων των συμβόλων, τα
σχόλια που συνοδεύουν τον κώδικα, τις παρενθέσεις, και τους ορισμούς των συμβόλων.
Με δεδομένο οτι ένα "καλό" συναρτησιακό πρόγραμμα εκμεταλλεύεται στο έπακρο τους
αλληλοσυσχετισμούς και μένει "ανοικτό" για συγκεκριμενοποιήσεις, τα αλγοριθμικά βήματα
παρουσιάζουν πολυπλοκότητα που ξεφεύγει ταχύτατα από την εποπτεία της ανθρώπινης
αντίληψης: μεγάλο βάθος και πλάτος δομών∙ συνδέσεις προς στοιχεία που αναφέρονται αλλού
στη σειριακή γραφή του κειμένου του κώδικα∙ επανασυνδέσεις κλάδων δένδρου∙ αφαιρετικά
βήματα των οποίων η συγκεκριμενοποίηση άλλοτε είναι μέρος του αλγόριθμου και άλλοτε
εναπόκειται στην κλήση∙ σε κάποια βήματα προσδιορίζεται υπολογισμός που όμως εκτελείται σε
483

άλλη φάση, λόγω εφαρμογής καθυστέρησης η οποία ελέγχεται από αλλού, με αποτέλεσμα να "μη
διαβάζεται" ο πηγαίος κώδικας με τη διαδοχή γραφής των χαρακτήρων (όπου "διάβασμα"
σημαίνει "νοητική εκτέλεση").
Όμως, παρ' όλη την απλότητα της επιτακτικά διαδικαστικής έκφρασης, αυτή δεν μπορεί να
καλύψει τη συναρτησιακή, διότι η δυναμική της διαδοχής συνθέσεων - εφαρμογών είναι
εξαιρετικά δύσκολο να αποδοθεί διαδικαστικά. Η λύση επομένως στο πρόβλημα αυτό δεν μπορεί
να έλθει από την αποφυγή συναρτησιακών συνθέσεων, αλλά από την ίδια τη βηματική υφή των
συνθέσεων, και συγκεκριμένα:
– οι συσχετισμοί πάντοτε ακολουθούν μια συγκεκριμένη "φορά ροής", που δεν διαβάζεται στο
κείμενο του κώδικα αλλά ο αναγνώστης του, μπορεί να την ανακαλύψει αν έχει κάποιες
οδηγίες.
– η σύνθεση φορμών μπορεί πολύ εύκολα να οργανωθεί με τρόπο που να ακολουθεί τη φυσική
σύνθεση των προτύπων νοημάτων
– είναι απλό να παρέχεται η "κατά βήμα" προσδιορισμού του υπολογισμού υποστήριξη (σχόλια):
η ερμηνεία κάθε βήματος αποτελεί ουσιαστικά μια παράλληλη γραφή του ίδιου του κώδικα σε
φυσική γλώσσα, δίπλα στη φορμαλιστική.
δ'.
Αναφέραμε σε προηγούμενα, τις αντίθετες μεταξύ τους συνέπειες της χρήσης macro
συναρτήσεων:
– Λόγω της σημασιολογικής ευκρίνειάς τους, παρέχουν ευχέρεια για συνοπτική περιγραφή και
χρήση πολυσυνθέτων νοημάτων, και λόγω της για απόσπασης της υπολογιζόμενης οντότητας
από την οντότητα δέσμευσης της λ-μεταβλητής, παρέχουν ένα τρόπο για "φυσική μεταφορά
δράσης" πάνω στο αντικείμενο εφαρμογής (βλέπε συνάρτηση PUSH).
– Λόγω υποκρυπτόμενων συνδέσεων και υπολογισμών σε χαμηλότερο επίπεδο που δεν είναι
ορατό στο χρήστη, προκαλούν αποδυνάμωση της συναρτησιακής δόμησης και οδηγούν
σταδιακά τη συναρτησιακή πορεία έκφρασης και χρήσης σε διαδικαστική (πχ. δεν μπορούμε
εύκολα να "πλέξουμε" συναρτησιακά μεθόδους με συναρτήσεις ή λ-εκφράσεις).
ε'.
Η εκφραστική πολυπλοκότητα αυξάνεται "εκθετικά" με τη συνθετότητα των εννοιών, και
πολύ γρήγορα καθιστά το περιβάλλον ανάπτυξης αναποτελεσματικό ως γλώσσα έκφρασης, ιδίως
για τον "επόμενο χρήστη". Όμως αυτό πρέπει να κριθεί με κριτήριο το "τί μπορούμε να πετύχουμε
συγκριτικά με άλλες γλώσσες" και όχι με το "τί θα θέλαμε να κάνουμε και μας εμποδίζει η
πολυπλοκότητα της έκφρασης". Η υπολογιστική πολυπλοκότητα εύκολα ξεφεύγει από τη
διαχείριση, αφ' ενός λόγω του "αφανούς" της χαμηλής υπολογιστικής δράσης των περισσοτέρων
συναρτήσεων, και αφ' ετέρου λόγω της συνθεσιμότητας που μπορεί να εκμεταλλεύεται ο τελικός
χρήστης.
Έγιναν προσπάθειες για κατασκευή ολοκληρωμένων "συναρτησιακών μηχανών" (Lisp machines)
που θα ενοποιούσαν λειτουργικό σύστημα, γλώσσα προγραμματισμού και εφαρμογές, οι οποίες
όμως δεν ευοδώθηκαν.

484

Διδακτικές προσεγγίσεις του ΣΠ
Βασική βιβλιογραφία
Είναι γεγονός οτι ο ΣΠ παρουσιάζει σημαντικά ψηλότερο βαθμό συνθετότητας από το
διαδικαστικό προγραμματισμό τόσο διότι βασίζεται σε ένα διαφορετικό από τα συνηθισμένα
θεωρητικό μοντέλο, όσο και διότι επιχειρεί την κατασκευή υπολογισμών με συνθετικά πολύ
βαθύ τρόπο. Τα στενά χρονικά πλαίσια που συνήθως διατίθενται για τη διδασκαλία του ΣΠ
πρέπει να καλύψουν το θέμα εκ του μηδενός και μέχρι τέλους, και αυτό επιβάλλει τομές στην
ύλη, θεωρητικά και πρακτικά. Για το λόγο αυτό, στη βιβλιογραφία συναντάμε διάφορες
προσεγγίσεις για την εισαγωγή στον ΣΠ που δίνουν έμφαση σε μια άποψη. Οι κυριότερες είναι οι
εξής:
α'. Να θεμελιωθεί θεωρητικά ο λ-λογισμός και να στηριχθεί εξ ολοκλήρου και βήμα προς βήμα
η ανάπτυξη των συναρτησιακών εννοιών σε λ-λογισμό.
Την προσέγγιση αυτή ακολουθεί ο Michaelson [Mich88] θεμελιώνοντας αυστηρά τον λλογισμό, ξεκινώντας κυριολεκτικά από το μηδέν, και πάνω στη θεμελίωση αυτή θέτει τις αρχές
των γλωσσών ML και Lisp. Κινείται σε επίπεδο αρχών και δεν φθάνει μέχρι του σημείου να
δώσει στον αναγνώστη δυνατότητες ανάπτυξης πραγματικού προγράμματος.
Το σύγγραμμα αυτό είναι εξαιρετικά χρήσιμο σε όποιον αναζητά περισσότερα για τον λλογισμό, και γενικότερα στον μελετητή που αναζητά τα βαθύτερα θεμέλια του ΣΠ. Μπορεί να
μελετηθεί είτε σαν αρχικό βιβλίο για μια καλή θεμελίωση πριν τη μελέτη άλλου βιβλίου για τη
Lisp ή/και τον ΣΠ, είτε ως συμπληρωματικό, για να καλυφθούν θεωρητικά κενά που συνήθως σε
άλλα συγγράμματα αντιμετωπίζονται πρακτικά.
β'. Να θεμελιωθεί ο Συναρτησιακός Προγραμματισμός κτίζοντας σταδιακά τις στοιχειώδεις
συναρτησιακές δομές. Στόχος είναι η μοντελοποίηση μιας συναρτησιακής γλώσσας όπως η Lisp,
κατασκευάζοντας και χρησιμοποιώντας ένα συγκεκριμένο θεμελιώδες κατασκευαστικό εργαλείο,
τον δομητή (constructor).
Την προσέγγιση αυτή ακολουθεί ο Henderson [Hend80]. Πλεονέκτημα αυτής της προσέγγισης
είναι οτι οργανώνεται σταδιακά το μοντέλο του ΣΠ και η με φυσικό τρόπο υλοποίηση αυτού του
μοντέλου στη γλώσσα Lisp. Μειονέκτημα είναι οτι, η πρακτική δυνατότητα για οποιαδήποτε,
έστω και απλή, υπολογιστική υλοποίηση, αργεί πολύ: μόνο προς το τέλος αυτού του
συγγράμματος αναφέρονται κάποιες πολύ απλές υλοποιήσεις συναρτησιακών δομών.
Το κυριότερο ίσως μειονέκτημά του, είναι οτι χρησιμοποιεί Pascal-like ψευδοκώδικα, που δεν
είναι επαρκώς θεμελιωμένος για κτίσιμο ενός τόσο βασικού μοντέλου υπολογισμού, ούτε
αξιοποιεί ένα από τα κύρια χαρακτηριστικά της γλώσσας Lisp, το οτι μπορεί να χρησιμοποιηθεί
απ' ευθείας ως γλώσσα έκφρασης. Αποτέλεσμα είναι, να στηρίζεται σε ένα επιτακτικό τρόπο
έκφρασης, μάλλον παραπλανητικό για την κατανόηση των συναρτησιακών νοημάτων.
γ'. Να αναπτυχθεί απ' ευθείας η Lisp, και πάνω στα περιγραφόμενα στοιχεία της γλώσσας να
εξηγηθούν οι συναρτησιακές δυνατότητες και η θεωρία που "κρύβεται".
485

Κατά την προσέγγιση αυτή, γίνεται μια κατηγοριοποιημένη έκθεση των συναρτήσεων της Lisp,
όπου οι κατηγορίες διαφοροποιούνται είτε βάσει τυπικών ομοιοτήτων και διαφορών τους, είτε σε
συνακολουθία με τις συνήθεις από άλλες γλώσσες τυποποιήσεις. Μια τέτοια περιγραφή μοιάζει
λίγο – πολύ με manual, αλλά διαθέτει και κάποια οργάνωση στηριγμένη στο συσχετισμό και τη
χρησιμότητα των αναφερομένων, τα οποία αναπτύσσονται με τρόπο αυτόνομο ώστε να μην
απαιτούν πολλαπλές διασταυρώσεις (cross-references).
Την προσέγγιση αυτή ακολουθεί η Milner [Miln88]. Πλεονέκτημα αυτής της προσέγγισης είναι
οτι ο αναγνώστης μπορεί –μετά από κάποια αρχική εξοικείωση με το θέμα– να βρίσκει γρήγορα
την ή τις συναρτήσεις που χρειάζεται για κάποιο σκοπό, για να τις μελετήσει αμέσως και εύκολα.
Ένα μειονέκτημα, εμφανές στο σύγγραμμα της Milner, είναι οτι ο αναγνώστης οφείλει να
ανακαλύψει μόνος του τη σημασία-ρόλο των αναφερομένων συναρτήσεων: οφείλει να εντοπίσει
τη χρησιμότητά τους για την έκφραση και μοντελοποίηση πραγματικών καταστάσεων. Σε κάθε
περίπτωση, όντας ανεπτυγμένα τα πάντα σε ένα πρώτο επίπεδο, "δεν πάνε βαθειά".
δ'. Να γίνει παράθεση σειράς μαθημάτων της γλώσσας Lisp, εσωτερικά οργανωμένων έτσι ώστε
έμμεσα να καλύπτουν τους μαθησιακούς στόχους που αφορούν τον ΣΠ.
Την προσέγγιση αυτή ακολουθούν οι Winston και Horn [Win+89]. Πλεονέκτημα αυτής της
προσέγγισης είναι οτι, ο αναγνώστης που ακολουθεί και ολοκληρώνει με συνέπεια τη μελέτη του
συγγράμματος, θα φθάσει να έχει τις αναφερόμενες γνώσεις. Μειονέκτημα είναι οτι οι
συναρτήσεις, τοποθετημένες κάτω από συγκεκριμένους γνωσιακούς στόχους, και έχοντας υψηλό
βαθμό διασταυρώσεων και ιεραρχία οργάνωσης εξαρτημένη από ειδικούς στόχους, δεν είναι
κάθετα προσβάσιμες για μια γρήγορη αναζήτηση και μελέτη τους, ούτε καν σε επίπεδο
υπόμνησης. Στο σύγγραμμα αυτό, τα θεωρητικά θεμέλια του ΣΠ (λ-εκφράσεις) μόνο έμμεσα
προσεγγίζονται ως γνώση, από τον τρόπο χρήσης τους.

ε'.
Διαφορετική είναι η προσέγγιση όπου η οργάνωση και έκθεση της ύλης βασίζεται στη
σημασιολογία του υπολογισμού και των μεθόδων προγραμματισμού, και όπου οι στόχοι είναι
καθαρά στο επίπεδο της αναζήτησης και οργάνωσης μιας πιο αφηρημένης σύλληψης των
οντοτήτων.
Την προσέγγιση αυτή ακολουθούν οι Abelson και Sussmann [Abe+89]. Πλεονέκτημα αυτής
της προσέγγισης είναι, η υποστήριξη των ευρύτερων δυνατοτήτων του ΣΠ και της αναφερόμενης
προγραμματιστικής γλώσσας (η Scheme στο σύγγραμμα αυτό). Κεντρική προσπάθεια είναι, να
οδηγείται η έκφραση και σύνθεση υπολογιστικών οντοτήτων σε ανώτερα επίπεδα γενικότητας και
αφαίρεσης, ενοποιώντας την υλοποίηση με τη συλλογιστική τού χρήστη.
Στο σύγγραμμα αυτό, το βάθος των στόχων είναι εξαιρετικά μεγαλύτερο των προηγουμένων.
Μειονέκτημα από την άποψη της εισαγωγής στον ΣΠ είναι οτι αναφέρεται σποραδικά τόσο στη
θεωρητική δομή του λ-λογισμού όσο και στα διαθέσιμα της γλώσσας προγραμματισμού, ακόμα
και στις τεχνικές του ΣΠ. Αυτό οφείλεται στο οτι οι δομές που εκτίθενται και οι τεχνικές που
χρησιμοποιούνται αποσκοπούν στην επίτευξη συγκεκριμένων στόχων αφαίρεσης, της μορφής "να
τί μπορούμε να πετύχουμε, και να το πώς γίνεται".
Η αναφορά παραδειγμάτων σε Scheme δεν είναι σημαντικό μειονέκτημα για το μελετητή που
χρησιμοποιεί Lisp, διότι η Scheme χρησιμοποιείται μόνο σε επίπεδα που έχουν τυπική
486

αντιστοίχηση στο φορμαλισμό της Lisp.

στ'. O Paul Graham στο σύγγραμμά του [Grah96] ακολουθεί την προσέγγιση της έκθεσης των
κεντρικών σημείων της Lisp και του ΣΠ, με σκοπό να κινήσει το ενδιαφέρον του αναγνώστη.
Κάνει μια πολύ συνοπτική παρουσίαση της Lisp, κάτω από το νεότερο πρότυπο της ANSI
Common Lisp.
Το σύγγραμμα αυτό, μικρό σε μέγεθος, επικεντρώνει με διαφορικό τρόπο απέναντι στις άλλες
προσεγγίσεις στις δυνατότητες της Lisp, και δίνει λεπτομερή μεθοδολογία για εφαρμογή
ορισμένων τεχνικών, ιδίως στα πλαίσια του ΟΟΡ. Εντοπίζει ταχύτατα και με σαφήνεια τα
πλεονεκτήματα της συναρτησιακής έκφρασης και περιγράφει με σύντομες προγραμματιστικές
υλοποιήσεις κάποιες επιλεγμένες θεμελιώδεις συναρτήσεις, εξηγώντας τον τρόπο ανάπτυξης του
κώδικα και τη σκοπιμότητα των επιλογών του. Περιλαμβάνει λεξιλόγιο αναφοράς του νεότερου
standard, της ANSI Common Lisp (που δεν διαφέρει ουσιαστικά σε όσα αναφέρονται στο παρόν)
μαζί με τις διαφορές μεταξύ Common Lisp και ANSI Common Lisp.
Αν και είναι αύταρκες σύγγραμμα, δεν καλύπτει ούτε την ιδεατή βάση του ΣΠ ούτε τα βασικά
στοιχεία της Lisp∙ απευθύνεται ως συμπλήρωμα σε αναγνώστη που έχει ήδη κάποια βασική
σχετική γνώση. Είναι στενά δεμένο με την Allegro CL v.5.

Bιβλιογραφία για περαιτέρω μελέτη
α'.
Με επίκεντρο το σύστημα αντικειμένων της Lisp (CLOS), το σύγγραμμα της Sonya Keene
[Keen89] που είναι ένας από τους δημιουργούς του CLOS, αντιμετωπίζει με ευκρίνεια και πλήρη
τεκμηρίωση την τεχνική δημιουργίας και επεξεργασίας αντικειμένων. To σύγγραμμα αυτό
αποτελεί βασικό βοήθημα για το χρήστη της Lisp που θέλει να μελετήσει σε μεγαλύτερο βάθος το
CLOS. Κινείται κυρίως σε φορμαλιστικό επίπεδο. Αν και δημιουργεί στον αναγνώστη την
επιθυμία για περισσότερες λεπτομέρειες και παραδείγματα, δίνει επαρκείς αναφορές για
περαιτέρω μελέτη, στα σημεία όπου είναι πολύ περιληπτικό.
β'.
Με ιδιαίτερη έμφαση στις διάφορες προγραμματιστικές προσεγγίσεις οι οποίες μπορούν να
εφαρμοστούν χρησιμοποιώντας τη Lisp, o Rajeev Sangal [Sang91] αν και πολύ συνοπτικά,
οργανώνει την αξιοποίηση της Lisp σε πολλούς βασικούς τομείς προγραμματισμού. Αποτελεί
συμπληρωματικό για τη μελέτη σύγγραμμα, και περιγράφει με απλό τρόπο, δίνοντας και τον
σχετικό κώδικα παραδείγματος, ορισμένες πολύ βαθειές τεχνικές προγραμματισμού. Ιδιαίτερα
σημαντικό είναι το μέρος που αφορά την οργάνωση λογικού προγραμματισμού με Lisp.
Ολόκληρο αποτελεί σημαντικό "επόμενο βήμα μελέτης" γι' αυτόν που ενδιαφέρεται να μελετήσει
προγράμματα οργανωμένα μεθοδικά και σε έκταση ως εφαρμογή συγκεκριμένων τεχνικών.

γ'.
Χρήσιμο, γενικής μορφής εισαγωγικό βιβλίο είναι του Touretzky [Tour90] ακριβώς όπως
λέει και ο τίτλος του, "A Gentle Introduction to Symbolic Computation". Απλό και κατανοητό,
περιγράφει πρακτικά και επεξηγηματικά τους μηχανισμούς της Lisp, χρήσιμο για μια πρώτη
επαφή "άνευ διδασκάλου" με το συμβολικό προγραμματισμό, μελετάται γρήγορα και αφήνει τον
αναγνώστη με την αίσθηση οτι "κατάλαβε".
δ'.

Κάπως συνθετότερο του προηγούμενου, το βιβλίο της Deborah Tatar [Tata87] στοχεύει
487

στο να απλοποιήσει και να βάλει σε μια "συναρτησιακή τάξη" το βιβλίο αναφοράς του Steele, που
είναι περισσότερο σε "αλφαβητική τάξη".
ε'.
Πολύ ενδιαφέροντα παραδείγματα μπορεί να βρει κανείς στο βιβλίο του Christian
Queinnec [Quei84]. Περιορισμένης έκτασης, παραθέτει τον πλήρη κώδικα υλοποίησης των
παραδειγμάτων που αναφέρει. Όμως δεν ακολουθεί το πρότυπο της Common Lisp, και ο
μελετητής οφείλει να κάνει τις αναγκαίες μετατροπές του κώδικα.

ς'.
Σε απλά επίπεδα κινείται το βιβλίο του Robert Wilensky "LispCraft", κατευθυνόμενο από
την εφαρμογή διαφόρων τεχνικών, με αντιπροσωπευτικά παραδείγματα εφαρμογών. Χρήσιμο για
μια πρώτη εξάσκηση στη Lisp.
ζ'.
Το πλήρες εγχειρίδιο αναφοράς (reference manual) για την Common Lisp ήταν και
εξακολουθεί να είναι το βιβλίο του G.L.Steele Jr. περιγράφει σε απόλυτη πληρότητα το πρότυπο
μοντέλο της CL. H πρώτη έκδοση, [Stee86], περιλαμβάνει τα θεμέλια της CL, ενώ η δεύτερη
[Stee90] που είνααι συμβατή με την πρώτη (και σε λίγα σημεία διαφέρει) επεκτείνεται προς τη
νεότερη τυποποίηση (ΑNSI CL, μοντέλο X3J13). Αναγκαία εγχειρίδια του προγραμματιστή μέχρι
πριν από λίγα χρόνια, τα βιβλία αυτά σήμερα δεν είναι τόσο απαραίτητα διότι υπερκαλύπτονται
από τα "on line help" και "hypertext εγχειρίδια" που συνοδεύουν τις εμπορικές εκδόσεις των
compilers της Lisp.

η'.
Το βιβλίο των Kiczales, Rivières και Bobrow "The Art of the Metaobject Protocol"
περιλαμβάνει μια εισαγωγή στο CLOS σε σημασιολογικό επίπεδο, και αναπτύσσει ένα "μικρό
CLOS" βάσει κοινών συναρτήσεων. Η ανάπτυξη είναι απλή, και συμπληρώνει εύστοχα το βιβλίο
της Keene (χωρίς να το υπερκαλύπτει). Αναλύει τις έννοιες των μετακλάσεων και του
πρωτοκόλλου, και αναπτύσσει ένα πρωτόκολλο μετααντικειμένων. Οι έννοιες αυτές αποτελούν
τομέα έρευνας για τον ΣΠ και τα αντικείμενα.
Η ανάπτυξη των συγγραφέων "ενός δικού τους CLOS" ως απλοποιημένου υποσυστήματος του
τυπικά ιδεατού CLOS, σε ένα βαθμό οφείλεται στο οτι έγινε σε παλαιότερες εκδόσεις της Lisp,
που δεν διέθεταν ενσωματωμένο το ολοκληρωμένο CLOS. Σήμερα, σε πολλά σημεία αυτό είναι
περιοριστικό (όπως η μη υποστήριξη κληρονομικότητας μεταβλητών κλάσης). Παρ' όλα αυτά,
αποτελεί μια βατή γέφυρα για τη μελέτη, που ξεκινά από το συναρτησιακό μοντέλο και
καταλήγει στο μοντέλο των αντικειμένων.
θ'.
Σημαντικό, από πλευράς ανάπτυξης συναρτησιακών εφαρμογών, είναι συνολικά το έργο
του Schank και των συνεργατών του, που κινείται στα πλαίσια αξιοποίησης της Lisp για την
ανάπτυξη νοημόνων προγραμμάτων. Ειδικότερα στο βιβλίο του, "Inside Computer
Understanding" [Sch+78], παρ' όλη την τεχνική παλαιότητά του (1978), μυεί με τρόπο
ανεπανάληπτο τον αναγνώστη σε θέματα ανάπτυξης Εμπείρων Συστημάτων (ΕΣ) και
συστημάτων που χρησιμοποιούν φυσική γλώσσα επικοινωνίας με το χρήστη. Παραθέτει τον
πλήρη κώδικα πέντε αντιπροσωπευτικών εφαρμογών ΕΣ.

488

Συμπεράσματα και προοπτικές
Το εύρος των εννοιών Πληροφορικής που καλύπτει το συναρτησιακό μοντέλο υπολογισμού,
ελάχιστα έχει "να ζηλέψει" από άλλα μοντέλα, και αυτό μόνο σε ειδικά ζητήματα. Επιπρόσθετα, η
σε βάθος αλληλεπίδραση που υποστηρίζει, σε συνδυασμό με την εννοιολογική συνάφεια που
παρουσιάζει προς τον φυσικό τρόπο σκέψης, καθιστούν το μοντέλο αυτό προγραμματισμού ένα
χώρο πλήρη και άμεσα χρησιμοποιήσιμο, όπου ο μελετητής μπορεί όχι μόνο να προσεγγίσει
οποιαδήποτε έννοια πληροφορικής, αλλά και να εμβαθύνει αμέσως σε τρέχοντα ερευνητικά
ζητήματα. Πιο συγκεκριμένα, είδαμε οτι:

Ι) Ο Συναρτησιακός Προγραμματισμός είναι μια τεχνική αλλά και μια φιλοσοφία [Sin+91], που
επιτρέπουν:
α'.
την απόδοση της έννοιας υπολογισμός με πολύ ευρύτερο νόημα του συνήθους
διαδικαστικού, τόσο σε ότι αφορά τη συνθεσιμότητα σε πλάτος και βάθος (δηλ. συναρτησιακή
σύνθεση μέσω εφαρμογής συναρτησιακής έκφρασης πάνω σε συναρτησιακή έκφραση), όσο και
την ανασυνθεσιμότητα και επεκτασιμότητα σε οποιοδήποτε επίπεδο (είδαμε ακόμα και "runtime"
επαναπροσδιορισμό συμβόλου ή προσθήκη νέου)

β'.
το "κατέβασμα" και την αξιοποίηση του υπολογισμού ακόμα και σε επίπεδα
προσδιορισμού του ίδιου του λογικού πλαίσιου: μοντελοποίηση του "τί θα πεί αλήθεια" (είδαμε
τον ορισμό των "true" και "false") και του "τί θα πεί συνθέτω υπολογισμό με βάση ελεγχόμενες
αλήθειες" (είδαμε τον ορισμό των "and", "or", "not", "cond")
γ'.

την περιγραφή κόσμων σε λειτουργία, και ακόμα περισσότερο, της εξέλιξης κόσμων

δ'.
την αλληλεπιδραστική επικοινωνία του κόσμου του προγράμματος και του κόσμου του
χρήστη, όχι μόνο σε οποιοδήποτε βάθος αλλά και στα πλαίσια της λειτουργικής εξέλιξης του
καθενός κόσμου.
Τόσο ως τεχνική όσο και ως φιλοσοφία, ο ΣΠ μπορεί να αξιοποιηθεί και να αποδώσει το
αναμενόμενο μόνον όταν ο χρήστης του μοντέλου και της γλώσσας πετύχει να σκέφτεται
συναρτησιακά. Αν η προσπάθειά του εντοπιστεί στο να εκφράσει με συναρτησιακό τρόπο κάποια
επιτακτικά διαδικαστικά νοήματα, τότε μάλλον έχει αποτύχει στη ρίζα του ζητήματος: σε άλλο
περιβάλλον και με άλλο τρόπο προγραμματισμού θα πετύχαινε πολύ αποτελεσματικότερες
αναπτύξεις∙ αν πάλι προσπαθήσει να εκφράσει πεπλεγμένες λογικές σχέσεις, τότε πρέπει να
διαλέξει: ανάμεσα στην εύρεση τρόπων υλοποίησής τους, στην ανάπτυξη μιας γλώσσας Prolog
μέσα στη Lisp, και στη χρησιμοποίηση απ' ευθείας της γλώσσας Prolog.
ΙΙ)
Ο ΣΠ είναι ένας τρόπος βασικής αλλά και πλήρους προσέγγισης των θεμελιωδών εννοιών
της Πληροφορικής:
α'. Είναι ένας πλήρης τρόπος προγραμματισμού:
Δεν υπάρχει τίποτα στο χώρο της Πληροφορικής που να είναι αδύνατο να αποδοθεί
συναρτησιακά, τουλάχιστον σε θεωρητική βάση. Εξετάζοντας το θέμα σε πρακτική βάση, θα
έλεγε κανείς πως από τις γλώσσες που υποστηρίζουν συναρτησιακές εκφράσεις, όπως η CL,
489

λείπουν ακόμα αρκετά, και κυρίως η άμεση υποστήριξη σε θέματα δικτυακής επικοινωνίας,
κατανομής, παραλληλίας, βάσης δεδομένων. Είναι όμως γεγονός οτι τα τελευταία δύο χρόνια
έχουν επιτευχθεί αυτά σε ειδικότερα συναρτησιακά περιβάλλοντα, και δεν θα αργήσει η
ολοκλήρωσή τους σε ενιαίο, φορμαλιστικό και πρακτικό, πλαίσιο. Επομένως ο λ-λογισμός δεν
αποτελεί μια δομή μόνο θεωρητικής σημασίας, αλλά και χώρο πρακτικής εφαρμογής.
Παρά το γεγονός οτι διατίθενται εξειδικευμένα περιβάλλοντα προγραμματισμού, πιο αποδοτικά
και πιο εύχρηστα για ειδικούς σκοπούς, ο ΣΠ παραμένει ένα ισχυρό μέσον υπολογιστικής
έκφρασης διότι καλύπτει τα πάντα με ενιαίο τρόπο. Σύγχρονες εκδόσεις συναρτησιακών
γλωσσών προγραμματισμού παρέχουν εργαλεία ανάπτυξης κώδικα που μπορεί να εκμεταλλεύεται
τα μέσα που παρέχουν οι σημερινές πλατφόρμες.
β'. Είναι ένας εύκολος τρόπος προσέγγισης των εννοιών της Πληροφορικής:
Στον ΣΠ, μπορεί κανείς να ξεκινήσει τη μελέτη από το μηδέν με τη Lisp, ακόμα και
"καθισμένος για πρώτη φορά μπροστά σε υπολογιστή", και να φθάσει σε ειδικά θέματα
Πληροφορικής εντυπωσιακά γρήγορα, σε χρόνο ανέφικτο για άλλες προγραμματιστικές
προσεγγίσεις και περιβάλλοντα ανάπτυξης. Όλα τα βασικά ζητήματα της Πληροφορικής
συναντώνται στον Συναρτησιακό Προγραμματισμό, και αντιμετωπίζονται με τρόπο πραγματικά
αποτελεσματικό με τη Lisp.
Τα σκαλοπάτια μελέτης του ΣΠ μπορούν να ξεκινούν από πολύ απλά βήματα, ακόμα και για
μικρής ηλικίας χρήστες, όπως γίνεται με τη γλώσσα Logo που παρέχει τα θεμελιακά στοιχεία για
ανάπτυξη συναρτησιακού προγράμματος: Είναι χρησιμοποιήσιμη σε κάθε βαθμίδα: στο
νηπιαγωγείο, με χρήση από ρομπότ - χελώνες που εκτελούν ένα μικρό αριθμό από
προγραμματιζόμενα βήματα, με σταδιακά αυξανόμενο πλήθος∙ στο δημοτικό, με αυτόνομα ή
συνδεδεμένα με υπολογιστή ρομπότ - χελώνες που διαθέτουν και αισθητήρες αφής, φωτός κλπ.,
με τη Lego-Logo που κατασκευάζει ο μαθητής προγραμματιζόμενες κινούμενες και
αλληλεπιδρώσες συνθέσεις προσομοίωσης∙ στο γυμνάσιο, με την αξιοποίηση της γλώσσας Logo
για σύνθεση γεωμετρικών σχημάτων, επεξεργασία λιστών, σύνθεση διαδικασιών και απλών
συναρτήσεων∙ στο λύκειο, για μαθηματικές και φυσικές εφαρμογές. Έχουν αναπτυχθεί σε Logo
και ειδικά εκπαιδευτικά πακέτα, όπως το GeomLand , που είναι πακέτο Ευκλειδίου Γεωμετρίας,
ικανό να υποστηρίξει θέματα τόσο σε θεωρητικό επίπεδο όσο και πρακτικό. Η σύνδεση της
κλίμακας αυτής με την κλίμακα μελέτης της Lisp είναι πολύ εύκολη, διότι τα νοήματα και ο
τρόπος έκφρασής τους συνδέονται αμφιμονοσήμαντα.
γ'.

Η αποκτώμενη γνώση προγραμματισμού είναι σταδιακά αυξητική:

Η απόκτηση ικανοτήτων προγραμματισμού με τη Lisp είναι άμεση: από τα πρώτα βήματα ο
χρήστης της γλώσσας μπορεί να συνθέτει προγράμματα που σε άλλες γλώσσες θάπρεπε να
εμβαθύνει γαι πολύ χρόνο. Σε κάθε σκαλοπάτι μελέτης, αποκτά κάποιες ικανότητες έκφρασης
και πετυχαίνει ένα βαθμό αποτελεσματικότητας στη σύνθεση προγραμμάτων. Οι γνώσεις που
αποκτά λειτουργούν προσθετικά και έτσι δεν απαιτείται να έχει μια γερή αρχική γνώση της
γλώσσας για να αρχίσει τη συγγραφή προγράμματος, όπως συμβαίνει με σε άλλες γλώσσες (C++,
Java…). Ακόμα περισσότερο, οι αποκτώμενες γνώσεις σε κάθε βήμα, αυξάνουν εκθετικά τις
δυνατότητες διαμόρφωσης και επεξεργασίας νοημάτων.
490

Ακόμα, ο ΣΠ, συνταγμένος βάσει των αρχών του λ-λογισμού και υλοποιημένος σε περιβάλλοντα
προγραμματισμού όπως η Lisp, αποτελεί έναν άμεσο τρόπο υπολογιστικής αναπαράστασης και
επεξεργασίας φυσικών εννοιών, χωρίς την στενότητα της Basic, όπου είναι αδιανόητη η ελεύθερη
διασύνδεση αλγορίθμων, ή αντίθετα την ευρύτητα της Prolog όπου "τα πάντα επιτρέπονται" αλλά
η σάρωση με την οποία υπολογίζεται το ταίριαγμα εκρήγνυται "με το παραμικρό".
ΙΙΙ) Ο ΣΠ αποτελεί έναν εύχρηστο και πλήρη πυρήνα ανάπτυξης για οποιαδήποτε άλλη
προγραμματιστική προσέγγιση:
i) για προγραμματισμό με προσανατολισμό προς τα αντικείμενα, όπου αρκεί να
χρησιμοποιήσει ο χρήστης τις συναρτήσεις του CLOS (που, παρ' όλες τις επί μέρους
ασυμβατότητες προς το ιδεατό αντικειμενοστρεφές μοντέλο, είναι αποτελεσματικό)
ii) για λογικό προγραμματισμό, όπου βέβαια πρέπει να εγκαταστήσει ένα βασικό σύνολο
συναρτήσεων που υλοποιούν του μηχανισμούς της Prolog
iii) για επιτακτικά διαδικαστικό προγραμματισμό, για τον οποίο διατίθενται ενσωματωμένες
ειδικές συναρτήσεις (που γεφυρώνει τον συναρτησιακό εκφραστικό τρόπο με τον τρόπο που έχει
συνηθίσει ο προγραμματιστής που έχει εμπειρία από διαδικαστικά περιβάλλοντα)
iv) για διαχείριση βάσης δεδομένων, όπου δεν έχει παρά να μοντελοποιήσει τη βάση και να
οργανώσει τις απαραίτητες συναρτήσεις εισαγωγής και επεξεργασίας στοιχείων, που είναι απλές
στη σύλληψη και υλοποίηση (χρήσιμο για εξοικείωση με θέματα διαχείρισης βάσης δεδομένων)
v) για συνεκμετάλλευση άλλων προγραμματιστικών περιβαλλόντων, όπου μπορεί είτε να
οργανώσει επικοινωνία με το άλλο περιβάλλον είτε να οργανώσει σ' εκείνο ένα συναρτησιακό
compiler (όπως είδαμε, σε ερευνητικούς χώρους).
Σε επόμενο τεύχος θα μελετήσουμε μια διαφορετική διάσταση του συναρτησιακού τρόπου
έκφρασης: τη γνωσιολογική διάσταση, που καλύπτει τομείς που συσχετίζουν υπολογισμό,
αναπαράσταση γνώσης, ανάπτυξη και εξέλιξη γνώσης, μάθηση και εξώρυξη γνώσης από ένα
σύστημα που περιλαμβάνει γνώση.

491

Βιβλιογραφία
Α. Βασική βιβλιογραφία
[Abe+89] Ηarold Abelson and Gerald Jay Sussman : Structure and Interpretation of Computer
Programs. Cambridge: MIT Press, 1989.
[Chur41] Allan Church : The Calculi of Lambda Conversion. Princeton, NJ: Princeton University
Press, 1941.
[Grah96] Paul Graham : ANSI Common Lisp. NJ: Prentice Hall, 1996.
[Hend80] Peter Henderson : Functional Programming. Application and Implementation. Eng. Cliffs,
NJ: Prentice Hall. Int. Series in Computer Science, 1980.
[Keen89] Sonya E. Keene : Object-Oriented Programming in Common Lisp. A Programmers Guide to
CLOS. Addison-Wesley, 1989.
[Kic+99] Gregor Kiczales, Jim des Rivières, Daniel G. Bobrow: The Art of the Metaobject Protocol.
MIT Press, Mass: 1999.
[Klee52] S. C. Kleene : Introduction to Metamathematics. Amsterdam: North - Holland, 1952.
[Lore67] P. Lorenzen : Métamathématique. Mathématiques et Sciences de l' Homme, VI, Paris:
Gauthier-Villars, 1967.
[Mend64] E. Mendelson : Introduction to Mathematical Logic. NY: Van Nostrand Reinhold, 1964.
[Clo+86] W. F. Clocksin & C. S. Mellish : Programming in Prolog. NY: Spinger-Verlag, 1987.
[Mich88] G. Michaelson : An Introduction to Functional Programming through Lambda Calculus.
Addison-Wesley, 1988.
[Miln88] Wendy L. Milner : Common Lisp. A Tutorial. NJ: Prentice - Hall 1988.
[Quei84] Christian Queinnec : Lisp. London: Macmillan, 1984.
[Sang91] Rajeev Sangal : Programming Paradigms in Lisp. NY: MacGraw-Hill, 1991.
[Stee86] Guy L. Steele Jr. : Common Lisp: Τhe Language. Digital Press, 1986.
[Stee90] Guy L. Steele Jr. : Common Lisp: Τhe Language, 2nd Edition. Digital Press, 1990.
[Tata87] Deborah G. Tatar : A Programmers Guide to Common Lisp. Digital Press 1987.
[Tour90] David S. Touretzky : Common Lisp. A Gentle Introduction to Symbolic Computation.
Benjamin/Cummings Publ. Co, 1990.
[Win+89] Patrick Henry Winston and Berthold Klaus Paul Horn : Lisp, 3rd Edition. Reading: Addison
Wesley, 1989.
[Wile84] Robert Wilensky : LISPcraft. NY: W.W.Norton & Co, 1984.

B. Ειδικές αναφορές
[Bake98] P. L. Baker & K. Wichmann (1998): Future Directions - An Outside View. 40th Ann. Conf. of
Lisp Users: "Lisp in the Mainstream". Berkeley, California Nov.98
[Both98] Per Bothner (1998) : Kawa: Compiling Scheme to Java. 40th Ann. 40th Ann. Conf. of Lisp
Users: "Lisp in the Mainstream". Berkeley, California Nov.98. In:
492

(1999).
[Bob+89] D. G. Bobrow et als: Common Lisp Object System Specification X3J13 Document. In: Lisp and
Symbolic Computation, 1, ¾, Jan1989, pp. 245-394.
[Broo98] Martin Brooks (1998) : Open Implementation using Capabilities. 40th Ann. Conf. of Lisp
Users: "Lisp in the Mainstream". Berkeley, California Nov.98
[McCa60] John McCarthy (1960) : Recursive Functions of Symbolic Expressions and their Computation
by Machine (Part I). CACM, April 1960.
[McCa80] John McCarthy (1980) : Lisp-Notes on Its Past and Future - 1980. In: Procs of 1980 Lisp
Conf. & Procs of 1988 Lisp Conf. Also in: John's McCarthy personal web page, 1999.
[Cram98] Nichael Cramer (1998) : Distributed-OMAR: Reconfiguring a Lisp System as a Hybrid
Lisp/Java Component. In: 40th Ann. Conf. of Lisp Users: "Lisp in the Mainstream". Berkeley,
California Nov.98.
[Woo+87] D. Woods & E. Holnagel (1987) : Mapping Cognitive Demands in Complex Problem Solving Worlds. Int.J. on Man - Machine Studies, v. 26, pp. 257-275.
[Kic+93] G. Kiczales, J. Lamping A. Mendehekar (1993) : What a Metaobject Protocol Based
Computer Can Do for Lisp. Xerox PARC, Internal report, Dec93.
[Kic+97] G. Kiczales, J. Lamping A. Mendehekar, C. Maeda, C.V. Lopes and J.M. Loingtier (1997) :
Aspect - Oriented Programming. In: Procs of ECOOP '97, Finland, June '97, ed. SpringerVerlag (23 p.)
[Lop+96] Crisina Videira Lopes & Karl J. Lieberherr (1996) : AP/S++: Case-study of a MOP for
Purposes of Software Evolution. In: Procs of "Reflection '96", San Francisco, USA, April '96
(18 p.)
[Men+93] Anurag Mendhekar & Daniel P. Friedman (1993) : Towards a Theory of Reflective
Programming Languages. In: Procs of OOPSLA '93, (11 p.)
[Sin+91] K. Sinclair and D. Moon (1991) : The Philosophy of Lisp. CACM, v.34, n.9, pp. 41-47.
[Sch+81] R.C. Schank and C.K. Riesbeck : Inside Computer Understanding. Five Programs plus
Miniatures. Hildale, NJ: Lawrence Erlbaum Assoc. Publ., 1981.
[Wegn97] P. Wegner (1997) : Why Interaction Is More Powerful Than Algorithms. CACM, May '97,
v40, n.5, pp. 80-91.
[Αναγ+92] Δ. Αναγνωστόπουλος, Π. Γεωργιάδης, Γ. Γυφτοδήμος, Γ. Δουκίδης : Ερευνητικές
Κατευθύνσεις προς ένα Σύγχρονο Περιβάλλον Προσομοίωσης μέσω Τεχνητής Νοημοσύνης.
Πρακτ. 4ου Παν. Συν. Πληροφορικής ΕΠΥ, σελ. 287-299
[Γυφ+91] Γ. Γυφτοδήμος & Μ. Σπηλιοπούλου (1991) : Ο Μίτος της Αριάδνης σαν Εργαλείο
Επαναπροσανατολισμού για την Εξερεύνηση ενός Hypertext Δικτύου. Πρακτ. 3ου Πανελλ. Συν.
Πληροφορικής, ΕΠΥ, Αθήνα, σ. 298-312.
[Γυφ+95] Γ. Γυφτοδήμος & Γ. Φιλοκύπρου (1995) : Μια Σημασιολογική Προσέγγιση για την Οργάνωση
ενός Ανοικτού Υπολογιστικού Μαθησιακού Μικρόκοσμου, με βάση Έξι Λογικούς
Χαρακτηρισμούς. Πρ. Β' Πανελλ. Συν. "Διδακτική των Μαθηματικών και Πληροφορική στην
Εκπαίδευση", Λευκωσία, σ. 655-668.
[Γυφ95α] Γιώργος Γυφτοδήμος : Σημασιολογικές Επεκτάσεις του Δίτιμου Λογικού Πλαίσιου για την
υποστήριξη της Μαθησιακής Διαδικασίας με χρήση Υπολογιστή. Διδακτορική Διατριβή, Τμ.
Πληροφορικής EKΠΑ, Αθήνα 1995.
http://www.gnu.org/software/kawa/

493

Γιώργος Ε. Γυφτοδήμος
[email protected]

τηλ (01) 727 52 18

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

3

c

a
list1

d

b

nil

558

nil

list2

a

b

list2

559

nil

b

list2

b

560

nil

nil

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

συνάρτηση που διαχειρίζεται τη διασωλήνωση
x

συνάρτηση3

διεργασία3
διεργασία6


x

συνάρτηση2

διεργασία1
διαργασία4


x

συνάρτηση1

διεργασία2
διεργασία5


Η διαδοχή εκτέλεσης διεργασιών μπορεί να είναι διαφορετική
από τη διαδοχή σύνθεσης των συναρτήσεων που τις περιέχουν∙
όλες χρησιμοποιούν την ίδια πληροφορία εισόδου, που
μεταφέρεται μέσω εκτέλεσης

583

x

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

list1

a
b

c

d

c

599

d

nil

nil

a

600

b

nil

z1


x

601

x
z2

nil
a
nil
x

602

Sponsor Documents

Or use your account on DocShare.tips

Hide

Forgot your password?

Or register your new account on DocShare.tips

Hide

Lost your password? Please enter your email address. You will receive a link to create a new password.

Back to log-in

Close