Download as pdf or txt
Download as pdf or txt
You are on page 1of 475

Σχολή Ηλεκτρολόγων Μηχανικών

και Μηχανικών Υπολογιστών Γλώσσες Προγραμματισμού Ι


Τομέας Τεχνολογίας Πληροφορικής
και Υπολογιστών
Διδάσκοντες:
Εθνικό Μετσόβιο Πολυτεχνείο Νικόλαος Παπασπύρου,
Κωστής Σαγώνας

Εισαγωγή στις Γλώσσες


Προγραμματισμού
Προτεινόμενα βιβλία

Εισαγωγή στις Γλώσσες Προγραμματισμού 6

Προτεινόμενα βιβλία για ML

Εισαγωγή στις Γλώσσες Προγραμματισμού 7


Προτεινόμενα βιβλία για Java

Εισαγωγή στις Γλώσσες Προγραμματισμού 8

Προτεινόμενα βιβλία για Prolog

Εισαγωγή στις Γλώσσες Προγραμματισμού 9


Γιατί είναι ενδιαφέρουσες οι γλώσσες;

• Λόγω της ποικιλίας τους και των χαρακτηριστικών τους


• Λόγω των αμφιλεγόμενων στοιχείων τους
• Λόγω της ενδιαφέρουσας εξέλιξής τους
• Λόγω της στενής τους σχέσης με τον προγραμματισμό
και την ανάπτυξη λογισμικού
• Λόγω του θεωρητικού τους υπόβαθρου και της στενής
τους σχέσης με την επιστήμη των υπολογιστών

Εισαγωγή στις Γλώσσες Προγραμματισμού 10

Φοβερή ποικιλία γλωσσών προγραμματισμού

• Υπάρχουν πάρα πολλές και αρκετά διαφορετικές μεταξύ


τους γλώσσες
• Το 1995, μια συλλογή που εμφανιζόταν συχνά στη λίστα
comp.lang.misc περιλάμβανε πάνω από 2300 γλώσσες
• Οι γλώσσες συχνά κατατάσσονται στις εξής οικογένειες:
– Προστακτικού προγραμματισμού (Pascal, C, Ada)
– Συναρτησιακού προγραμματισμού (Lisp, ML, Haskell, Erlang)
– Λογικού προγραμματισμού (Prolog, Mercury)
– Αντικειμενοστρεφούς προγραμματισμού (Smalltalk, C++, Java, C#)
– Γλώσσες σεναρίων (Perl, Javascript, PHP, Python, Ruby)

Εισαγωγή στις Γλώσσες Προγραμματισμού 11


Γλώσσες προστακτικού προγραμματισμού

Παράδειγμα: η συνάρτηση παραγοντικό στη C


int fact(int n) {
int f = 1;
while (n > 0) f *= n--;
return f;
}

• Κύρια χαρακτηριστικά:
– Ανάθεση μεταβλητών (πολλαπλή)
– Επανάληψη
– Η σειρά εκτέλεσης παίζει σημαντικό ρόλο

Εισαγωγή στις Γλώσσες Προγραμματισμού 12

Γλώσσες συναρτησιακού προγραμματισμού (1)

Παράδειγμα: η συνάρτηση παραγοντικό στην ML


fun fact x =
if x <= 0 then 1 else x * fact(x-1);

• Κύρια χαρακτηριστικά:
– Μεταβλητές μιας τιμής
– Η επανάληψη εκφράζεται με χρήση αναδρομής

Εισαγωγή στις Γλώσσες Προγραμματισμού 13


Γλώσσες συναρτησιακού προγραμματισμού (2)

Παράδειγμα: η συνάρτηση παραγοντικό στη Lisp


(defun fact (x)
(if (= x 0) 1 (* x (fact (- x 1)))))

• Συντακτικά, η συνάρτηση δείχνει αρκετά διαφορετική


από ό,τι στην ML
• Αλλά η ML και η Lisp είναι συγγενείς γλώσσες

Εισαγωγή στις Γλώσσες Προγραμματισμού 14

Γλώσσες λογικού προγραμματισμού

Παράδειγμα: η συνάρτηση παραγοντικό στην Prolog


fact(X, F) :-
( X =:= 1 -> F = 1
; X > 1,
NewX is X - 1,
fact(NewX, NF),
F is X * NF
).
• Κύρια χαρακτηριστικά:
– Λογικές μεταβλητές και χρήση ενοποίησης
– Το πρόγραμμα γράφεται με χρήση κανόνων λογικής
– (Τα παραπάνω δε φαίνονται πολύ καθαρά στο συγκεκριμένο κώδικα)

Εισαγωγή στις Γλώσσες Προγραμματισμού 15


Γλώσσες αντικειμενοστρεφούς προγραμματισμού

Παράδειγμα: ορισμός στη Java ενός αντικειμένου που


μπορεί να αποθηκεύσει έναν ακέραιο και να υπολογίσει
το παραγοντικό του
public class MyInt {
private int value;
public MyInt(int value) {
this.value = value; Κύρια χαρακτηριστικά:
}
public int getValue() {
– Ανάθεση
return value; – Χρήση αντικειμένων:
} δεδομένων που έχουν
public MyInt getFact() {
return new MyInt(fact(value)); κατάσταση και ξέρουν πως
} – να τη μεταβάλλουν
private int fact(int n) {
int f = 1; – να την γνωστοποιήσουν
while (n > 1) f *= n--; σε άλλα αντικείμενα
return f;
}
}

Εισαγωγή στις Γλώσσες Προγραμματισμού 16

Πλεονεκτήματα και μειονεκτήματα

• Συνήθως, διαφορετικές γλώσσες δείχνουν τα


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

Εισαγωγή στις Γλώσσες Προγραμματισμού 17


Οικογένειες δε θίγουμε...

• Υπάρχουν πολλές οικογένειες γλωσσών


(η λίστα είναι μη εξαντλητική και έχει επικαλύψεις)
– Applicative, concurrent, constraint, declarative, definitional,
procedural, scripting, single-assignment, …

• Κάποιες γλώσσες ανήκουν σε πολλές οικογένειες


• Κάποιες άλλες είναι τόσο ιδιάζουσες που η κατάταξή
τους σε κάποια οικογένεια δεν έχει μεγάλο νόημα

Εισαγωγή στις Γλώσσες Προγραμματισμού 18

Παράδειγμα: Παραγοντικό σε Forth

• Γλώσσα βασισμένη σε στοίβα (stack-oriented)


: FACTORIAL
1 SWAP BEGIN ?DUP WHILE TUCK * SWAP 1- REPEAT ;

• Θα μπορούσε να χαρακτηριστεί προστακτική γλώσσα,


αλλά έχει λίγα κοινά στοιχεία με τις περισσότερες
προστακτικές γλώσσες

(Η γλώσσα Postscript είναι επίσης stack-oriented)


Εισαγωγή στις Γλώσσες Προγραμματισμού 19
Παράδειγμα: Παραγοντικό σε APL

×/ιX

• Μια έκφραση APL που υπολογίζει το παραγοντικό του X


• Επεκτείνει το X σε ένα διάνυσμα (vector) από ακεραίους
1..X, τους οποίους μετά πολλαπλασιάζει μεταξύ τους
• Θα μπορούσε να θεωρηθεί συναρτησιακή γλώσσα, αλλά
έχει ελάχιστα κοινά στοιχεία με τις περισσότερες
γλώσσες συναρτησιακού προγραμματισμού

(Για την ακρίβεια, δε θα το γράφαμε με αυτό τον τρόπο στην APL, γιατί
η γλώσσα περιλαμβάνει το μοναδιαίο τελεστή παραγοντικό: !X)
Εισαγωγή στις Γλώσσες Προγραμματισμού 20

Αμφιλεγόμενα χαρακτηριστικά και “γλωσσοπόλεμοι”

• Οι γλώσσες πολλές φορές καταλήγουν το αντικείμενο


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

Για προσωπική εμπειρία, παρακολουθείστε τα newsgroups:


comp.lang.*
ή τον ιστοχώρο
http://lambda-the-ultimate.org/
Εισαγωγή στις Γλώσσες Προγραμματισμού 21
Οι διακρίσεις και οι ορισμοί είναι λίγο ασαφείς

• Κάποιοι όροι αναφέρονται σε ασαφείς έννοιες


– Για παράδειγμα, η κατηγοριοποίηση των γλωσσών σε οικογένειες

• Κανένα πρόβλημα, αν θυμάστε ότι κάποιοι όροι είναι


σχετικά ασαφείς
– Λάθος ερώτηση:
• Είναι η γλώσσα X μια πραγματικά αντικειμενοστρεφής γλώσσα;
– Σωστή ερώτηση:
• Ποια χαρακτηριστικά της γλώσσας X υποστηρίζουν τον
αντικειμενοστρεφή προγραμματισμό και πόσο καλά;

Εισαγωγή στις Γλώσσες Προγραμματισμού 22

Η φοβερή εξέλιξη των γλωσσών

• Οι γλώσσες προγραμματισμού εξελίσσονται με πολύ


γρήγορο ρυθμό
– Νέες γλώσσες δημιουργούνται
– Παλιές γλώσσες αποκτούν διαλέκτους ή μεταλλάσσονται

Εισαγωγή στις Γλώσσες Προγραμματισμού 23


Εξέλιξη γλωσσών προγραμματισμού

Εισαγωγή στις Γλώσσες Προγραμματισμού 24

Assembly

Πριν: Αριθμοί Μετά: Σύμβολα


55 gcd: pushl %ebp
89E5 movl %esp, %ebp
8B4508 movl 8(%ebp), %eax
8B550C movl 12(%ebp), %edx
39D0 cmpl %edx, %eax
740D je .L9
39D0 .L7: cmpl %edx, %eax
7E08 jle .L5
29D0 subl %edx, %eax
39D0 .L2: cmpl %edx, %eax
75F6 jne .L7
C9 .L9: leave
C3 ret
29C2 .L5: subl %eax, %edx
EBF6 jmp .L2

Εισαγωγή στις Γλώσσες Προγραμματισμού 25


FORTRAN (FORmula TRANslator)

Πριν: Σύμβολα Μετά: Εκφράσεις, έλεγχος ροής


gcd: pushl %ebp 10 IF (a .EQ. b) GOTO 20
movl %esp, %ebp IF (a .LT. b) THEN
movl 8(%ebp), %eax a = a - b
movl 12(%ebp), %edx
ELSE
cmpl %edx, %eax
b = b - a
je .L9
.L7: cmpl %edx, %eax ENDIF
jle .L5 GOTO 10
subl %edx, %eax 1 END
.L2: cmpl %edx, %eax
jne .L7
.L9: leave
ret
.L5: subl %eax, %edx
jmp .L2

Εισαγωγή στις Γλώσσες Προγραμματισμού 26

COBOL

Δηλώσεις τύπων, εγγραφών, διαχείριση αρχείων


data division.
file section.
* describe the input file
fd employee-file-in
label records standard
block contains 5 records
record contains 31 characters
data record is employee-record-in.
01 employee-record-in.
02 employee-name-in pic x(20).
02 employee-rate-in pic 9(3)v99.
02 employee-hours-in pic 9(3)v99.
02 line-feed-in pic x(1).
Εισαγωγή στις Γλώσσες Προγραμματισμού 27
LISP, Scheme, Common LISP

Συναρτησιακές γλώσσες υψηλού επιπέδου


(defun gnome-doc-insert ()
"Add a documentation header to the current function.
Only C/C++ function types are properly supported currently."
(interactive)
(let (c-insert-here (point))
(save-excursion
(beginning-of-defun)
(let (c-arglist
c-funcname
(c-point (point))
c-comment-point
c-isvoid
c-doinsert)
(search-backward "(")
(forward-line -2)
(while (or (looking-at "ˆ$")
(looking-at "ˆ *}")
(looking-at "ˆ \\*")
(looking-at "ˆ#"))
(forward-line 1))

Εισαγωγή στις Γλώσσες Προγραμματισμού 28

APL

Γλώσσα αλληλεπίδρασης (interactive) με ισχυρούς τελεστές

Εισαγωγή στις Γλώσσες Προγραμματισμού 29


Algol, Pascal, Clu, Modula, Ada
Προστακτικές γλώσσες με τυπικά ορισμένο συντακτικό, χρήση μπλοκ,
δομημένος προγραμματισμός
PROC insert = (INT e, REF TREE t)VOID:
# NB inserts in t as a side effect #
IF TREE(t) IS NIL THEN t := HEAP NODE := (e, TREE(NIL), TREE(NIL))
ELIF e < e OF t THEN insert(e, l OF t)
ELIF e > e OF t THEN insert(e, r OF t)
FI;
PROC trav = (INT switch, TREE t, SCANNER continue, alternative)VOID:
# traverse the root node and right sub-tree of t only. #
IF t IS NIL THEN continue(switch, alternative)
ELIF e OF t <= switch THEN
print(e OF t);
traverse( switch, r OF t, continue, alternative)
ELSE # e OF t > switch #
PROC defer = (INT sw, SCANNER alt)VOID:
trav(sw, t, continue, alt);
alternative(e OF t, defer)
FI;
Εισαγωγή στις Γλώσσες Προγραμματισμού 30

SNOBOL, Icon

Γλώσσες επεξεργασίας συμβολοσειρών


LETTER = ’ABCDEFGHIJKLMNOPQRSTUVWXYZ$#@’
SP.CH = "+-,=.*()’/& "
SCOTA = SP.CH
SCOTA ’&’ =
Q = "’"
QLIT = Q FENCE BREAK(Q) Q
ELEM = QLIT | ’L’ Q | ANY(SCOTA) | BREAK(SCOTA) | REM
F3 = ARBNO(ELEM FENCE)
B = (SPAN(’ ’) | RPOS(0)) FENCE
F1 = BREAK(’ ’) | REM
F2 = F1
CAOP = (’LCL’ | ’SET’) ANY(’ABC’) |
+ ’AIF’ | ’AGO’ | ’ACTR’ | ’ANOP’
ATTR = ANY(’TLSIKN’)
ELEMC = ’(’ FENCE *F3C ’)’ | ATTR Q | ELEM
F3C = ARBNO(ELEMC FENCE)
ASM360 = F1 . NAME B
+ ( CAOP . OPERATION B F3C . OPERAND |
+ F2 . OPERATION B F3 . OPERAND)
+ B REM . COMMENT

Εισαγωγή στις Γλώσσες Προγραμματισμού 31


BASIC

Προγραμματισμός για τις “μάζες”

10 PRINT "GUESS A NUMBER BETWEEN ONE AND TEN"


20 INPUT A$
30 IF A$ = "5" THEN PRINT "GOOD JOB, YOU GUESSED IT"
40 IF A$ = "5" GOTO 100
50 PRINT "YOU ARE WRONG. TRY AGAIN"
60 GOTO 10
100 END

Εισαγωγή στις Γλώσσες Προγραμματισμού 32

Simula, Smalltalk, C++, Java, C#

Γλώσσες φιλοσοφίας αντικειμενοστρεφούς προγραμματισμού

class Shape(x, y); integer x; integer y;


virtual: procedure draw;
begin
comment -- get the x & y coordinates --;
integer procedure getX;
getX := x;
integer procedure getY;
getY := y;
comment -- set the x & y coordinates --;
integer procedure setX(newx); integer newx;
x := newx;
integer procedure setY(newy); integer newy;
y := newy;
end Shape;

Εισαγωγή στις Γλώσσες Προγραμματισμού 33


C

Ικανοποιητική επίδοση για προγραμματισμό συστήματος


int gcd(int a, int b)
{
while (a != b) {
if (a > b) a -= b;
else b -= a;
}
return a;
}

Εισαγωγή στις Γλώσσες Προγραμματισμού 34

ML, Miranda, Haskell, Erlang


structure RevStack = struct
type ’a stack = ’a list
exception Empty
val empty = []
fun isEmpty (s:’a stack):bool =
(case s
of [] => true
| _ => false)
fun top (s:’a stack): =
(case s
of [] => raise Empty
| x::xs => x)
fun pop (s:’a stack):’a stack =
(case s
of [] => raise Empty
| x::xs => xs)
fun push (s:’a stack,x: ’a):’a stack = x::s
fun rev (s:’a stack):’a stack = rev (s)
end
Εισαγωγή στις Γλώσσες Προγραμματισμού 35
sh, awk, perl, tcl, javascript, python, ruby

Γλώσσες σεναρίων (Scripting languages)


class() {
classname=‘echo "$1" | sed -n ’1 s/ *:.*$//p’‘
parent=‘echo "$1" | sed -n ’1 s/ˆ.*: *//p’‘
hppbody=‘echo "$1" | sed -n ’2,$p’‘
forwarddefs="$forwarddefs
class $classname;"
if (echo $hppbody | grep -q "$classname()"); then
defaultconstructor=
else
defaultconstructor="$classname() {}"
fi
}

Εισαγωγή στις Γλώσσες Προγραμματισμού 36

VisiCalc, Lotus 1-2-3, Excel

Γλώσσες προγραμματισμού λογιστικών φύλλων

Β1 * Β2

Εισαγωγή στις Γλώσσες Προγραμματισμού 37


SQL

Γλώσσες βάσεων δεδομένων


CREATE TABLE shirt (
id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
style ENUM(’t-shirt’, ’polo’, ’dress’) NOT NULL,
color ENUM(’red’, ’blue’, ’white’, ’black’) NOT NULL,
owner SMALLINT UNSIGNED NOT NULL
REFERENCES person(id),
PRIMARY KEY (id)
);
INSERT INTO shirt VALUES
(NULL, ’polo’, ’blue’, LAST_INSERT_ID()),
(NULL, ’dress’, ’white’, LAST_INSERT_ID()),
(NULL, ’t-shirt’, ’blue’, LAST_INSERT_ID());
Εισαγωγή στις Γλώσσες Προγραμματισμού 38

Prolog, Mercury

Γλώσσες λογικού προγραμματισμού

/* palindrome(Xs) is true if Xs is a palindrome. */


/* e.g. palindrome([m,a,d,a,m, i,m, a,d,a,m]). */
palindrome([]).
palindrome([_]).
palindrome([X|Xs]) :-
append(Xs1,[X],Xs), palindrome(Xs1).

append([],Ys,Ys).
append([X|Xs],Ys,[X|Zs]) :- append(Xs,Ys,Zs).

Εισαγωγή στις Γλώσσες Προγραμματισμού 39


Νέες γλώσσες προγραμματισμού

• “Καθαρότητα” σχεδίασης: δεν υπάρχει η ανάγκη να


διατηρηθεί η συμβατότητα με υπάρχοντα προγράμματα
• Όμως πλέον οι νέες γλώσσες δεν είναι προϊόντα
παρθενογέννησης: συνήθως χρησιμοποιούν ιδέες από
ήδη υπάρχουσες γλώσσες
• Κάποιες από αυτές (λίγες) χρησιμοποιούνται ευρέως,
άλλες όχι
• Ανεξάρτητα της χρήσης τους, αποτελούν πηγή ιδεών για
τις επόμενες γενεές των γλωσσών προγραμματισμού

Εισαγωγή στις Γλώσσες Προγραμματισμού 40

Ευρέως χρησιμοποιούμενη: Java

• Αρκετά δημοφιλής από το 1995 και έκτοτε


• Η Java χρησιμοποιεί πολλές ιδέες από τη C++,
κάποιες άλλες ιδέες από τη Mesa και τη Modula,
την ιδέα της αυτόματης διαχείρισης μνήμης από τη Lisp,
και άλλες ιδέες από άλλες γλώσσες
• Η C++ περιλαμβάνει το μεγαλύτερο κομμάτι της C και
την επέκτεινε με ιδέες από τις γλώσσες Simula 67, Ada,
Clu, ML και Algol 68
• Η C προέκυψε από τη B, που προέκυψε από τη BCPL,
που προέκυψε από τη CPL, που προέκυψε από την Algol
60, που προέκυψε από την Algol 58
Εισαγωγή στις Γλώσσες Προγραμματισμού 41
Μη ευρέως χρησιμοποιούμενη: Algol

• Μια από τις πρώτες γλώσσες: ALGOrithmic Language


• Εκδόσεις: Algol 58, Algol 60, Algol 68
• Ποτέ δε χρησιμοποιήθηκε ευρέως
• Όμως εισήγαγε πολλές ιδέες που στη συνέχεια
χρησιμοποιήθηκαν από άλλες γλώσσες, όπως για
παράδειγμα:
– Δομή ανά μπλοκ και εμβέλεια μεταβλητών
– Αναδρομικές συναρτήσεις
– Πέρασμα παραμέτρων κατά τιμή (parameter passing by value)

Εισαγωγή στις Γλώσσες Προγραμματισμού 42

Διάλεκτοι

• Η εμπειρία από τη χρήση γλωσσών αναδεικνύει πιθανές


ατέλειες του σχεδιασμού τους και συχνά οδηγεί σε νέες
διαλέκτους
• Νέες ιδέες πολλές φορές ενσωματώνονται σε νέες
διαλέκτους παλαιών γλωσσών

Εισαγωγή στις Γλώσσες Προγραμματισμού 43


Κάποιες διάλεκτοι της Fortran

• Αρχική Fortran, IBM,1954


• Βασικά standards: • Αποκλίσεις σε
– Fortran II κάθε υλοποίηση
– Fortran III
– Fortran IV • Παράλληλη επεξεργασία
– Fortran 66 – HPF
– Fortran 77 – Fortran M
– Fortran 90 – Vienna Fortran
– Fortran 95 – και πολλές άλλες
– Fortran 2K

Εισαγωγή στις Γλώσσες Προγραμματισμού 44

Η σχέση των γλωσσών με τον προγραμματισμό

• Οι γλώσσες επηρεάζουν τον προγραμματισμό


– Η κάθε γλώσσα ενθαρρύνει ένα συγκεκριμένο τρόπο
προγραμματισμού / αλγοριθμικής επίλυσης προβλημάτων

• Οι εμπειρίες από τον προγραμματισμό εφαρμογών


επηρεάζουν το σχεδιασμό (στοιχείων) νέων γλωσσών

• Διαφορετικές γλώσσες ενθαρρύνουν διαφορετικά στυλ


προγραμματισμού
– Αντικειμενοστρεφείς: αντικείμενα και χρήση get/set μεθόδων
– Συναρτησιακές: πολλές μικρές συναρτήσεις χωρίς παρενέργειες
– Λογικές: διαδικασία της αναζήτησης σ’ένα λογικά ορισμένο χώρο

Εισαγωγή στις Γλώσσες Προγραμματισμού 45


Αντίσταση κατά των γλωσσών;

• Γλώσσες που ενθαρρύνουν συγκεκριμένους τρόπους


προγραμματισμού συνήθως δεν τους επιβάλλουν
πλήρως
• Κατά συνέπεια, είναι δυνατό να παρακάμψουμε ή και να
αγνοήσουμε πλήρως τη “φιλοσοφία” κάποιας γλώσσας
• Συνήθως όμως αυτό δεν είναι καλή ιδέα…

Εισαγωγή στις Γλώσσες Προγραμματισμού 46

Προστακτική ML

Η ML αποθαρρύνει τη χρήση αναθέσεων και παρενεργειών.


Παρόλα αυτά:
fun fact n =
let
val i = ref 1;
val xn = ref n
in
while !xn > 1 do (
i := !i * !xn;
xn := !xn - 1
);
!i
end;

Εισαγωγή στις Γλώσσες Προγραμματισμού 47


Μη αντικειμενοστρεφής Java

Η Java, σε μεγαλύτερο βαθμό από τη C++, ενθαρρύνει τον


αντικειμενοστρεφή προγραμματισμό. Παρόλα αυτά:

class Fubar {
public static void main (String[] args) {
// όλο το πρόγραμμα εδώ!
}
}

Εισαγωγή στις Γλώσσες Προγραμματισμού 48

Συναρτησιακή Pascal

• Κάθε προστακτική γλώσσα που υποστηρίζει αναδρομή,


μπορεί να χρησιμοποιηθεί ως συναρτησιακή γλώσσα
function ForLoop(Low, High: Integer): Boolean;
begin
if Low <= High then
begin
{όλο το σώμα του for loop εδώ}
ForLoop := ForLoop(Low+1, High)
end
else
ForLoop := True
end;

Εισαγωγή στις Γλώσσες Προγραμματισμού 49


Γλώσσες και θεωρία τυπικών γλωσσών

Θεωρία των τυπικών γλωσσών: μία από τις θεμελιώδεις


μαθηματικές περιοχές της επιστήμης των υπολογιστών
• Κανονικές γραμματικές, αυτόματα πεπερασμένων καταστάσεων
– Αποτελούν τη βάση για το λεκτικό των γλωσσών προγραμματισμού
και του λεκτικού αναλυτή (scanner) ενός compiler
• Γραμματικές ελεύθερες συμφραζομένων, αυτόματα στοίβας
– Αποτελούν τη βάση για το συντακτικό των γλωσσών προγραμ-
ματισμού και του συντακτικού αναλυτή (parser) ενός compiler
• Μηχανές Turing
– Προσφέρουν το θεωρητικό υπόβαθρο για να μελετήσουμε την
υπολογιστική ισχύ των γλωσσών προγραμματισμού

Εισαγωγή στις Γλώσσες Προγραμματισμού 50

Ισοδυναμία κατά Turing (Turing equivalence)

• Οι (περισσότερες) γλώσσες προγραμματισμού έχουν


διαφορετικά χαρακτηριστικά και πλεονεκτήματα χρήσης,
αλλά όλες έχουν την ίδια ισχύ επίλυσης προβλημάτων
{προβλήματα επιλύσιμα στη Java}
= {προβλήματα επιλύσιμα στη Fortran}
= {προβλήματα επιλύσιμα στη C}
=…

• Και όλες έχουν την ίδια ισχύ με διάφορα


υπολογιστικά μοντέλα
{προβλήματα επιλύσιμα σε μηχανές Turing}
= {προβλήματα επιλύσιμα σε λάμδα λογισμό}
=…

• Το παραπάνω είναι γνωστό ως η θέση των Church-Turing


Εισαγωγή στις Γλώσσες Προγραμματισμού 51
Συμπερασματικά

• Γιατί είναι ενδιαφέρουσες οι γλώσσες προγραμματισμού


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

• Επίσης, λόγω του ότι θα μάθετε αρκετά καλά τρεις


επιπλέον γλώσσες!

Εισαγωγή στις Γλώσσες Προγραμματισμού 52

Θέματα Σχεδιασμού
Γλωσσών Προγραμματισμού
Εν αρχή ην... η χακεριά

• Έστω ότι θέλουμε να βρούμε κάποια πληροφορία για


τους χρήστες ενός μηχανήματος Unix από το αρχείο
/etc/passwd το οποίο έχει εγγραφές (πιθανώς κάποιες
εγγραφές είναι ανενεργές – σχόλια #) της μορφής:

mailman:*:78:78:Mailman user:/var/empty:/usr/bin/false

Βασικά πρέπει να κάνουμε τα εξής


1. Να κοιτάξουμε μόνο τις γραμμές που δεν είναι σχόλια
2. Από αυτές να απομονώσουμε το όνομα χρήστη (username),
το home directory, και το shell του κάθε χρήστη
3. Για να βρούμε ποιοι όντως είναι χρήστες, κοιτάμε κατά πόσο το shell
τους υπάρχει ως (εκτελέσιμο) πρόγραμμα
Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 2

Χακεριά στη C++

• Κύριο πρόγραμμα
– Δηλώσεις αρχείων επικεφαλίδων
– Δηλώσεις μεταβλητών

#include <fstream>
#include <iostream>
#include <string>

using namespace std;


int main() {
ifstream infile;
char newrecord[256];

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 3


Χακεριά στη C++

• Άνοιγμα αρχείου
– Έλεγχος σφάλματος

infile.open("/etc/passwd");
if (!infile) {
cout << "Error opening /etc/passwd.\n";
exit(-1);
}

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 4

Χακεριά στη C++

• Κύρια επανάληψη
infile.getline(newrecord, 256);
while (!infile.fail()) {
// -- Make array into a String
String urecord(newrecord);
if (urecord.find("#") == string::npos) {
// Process an entry
...
}
infile.getline(newrecord, 256);
}
return 0; // Done
}

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 5


Χακεριά στη C++

• Κύριο σώμα επανάληψης

string::size_type name_index = urecord.find(":");


string::size_type shell_index = urecord.find_last_of(":");
string::size_type home_index = urecord.find_last_of(":",
shell_index);
string name = urecord.substr(0, name_index);
string home = urecord.substr(home_index+1, shell_index-1);
string shell = urecord.substr(shell_index+1);
// For Part 3, check to see if shell is a valid file
// and if so, execute the next line; otherwise, do nothing.
cout << name << "\t" << home << "\t" << shell << endl;

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 6

Χακεριά στη Java

• Είναι παρόμοια με τη C++


– Αλλά η γλώσσα έρχεται με μια καλή βιβλιοθήκη (StringTokenizer)

StringTokenizer st =
new StringTokenizer(record, ":");
String username = st.nextToken();
st.nextToken(); // Don’t care about these
st.nextToken();
st.nextToken();
st.nextToken();
String home = st.nextToken();
String shell = st.nextToken();

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 7


Χακεριά σε Perl

#!/usr/bin/perl

open PASSWD, "<", "/etc/passwd" ||


die "Could not open /etc/passwd: $!";
while (<PASSWD>) {
if (m/^[^#]/) { # Skip comments
my @fields = split(/:/, $_); # Split on :
if (-x $fields[6]) {
print STDOUT "$fields[0]\t$fields[5]\t$fields[6]\n";
}
}
}
close (PASSWD);;

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 8

Χακεριά σε awk (με χρήση cat)

% cat passwd | awk –F: ’/[^#]/{ print "$1\t$6\t$7"}’

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 9


Κάποια ερωτήματα σχεδιασμού γλωσσών

• Τι θέλουμε να είναι ενσωματωμένο (built-in) στη γλώσσα;


• Πόση προσπάθεια χρειάζεται για να γράψουμε ένα
τυπικό πρόγραμμα;
• Πώς είναι ο τυπικός κύκλος ανάπτυξης προγραμμάτων;
• Ποια είναι τα ενδεχόμενα σφάλματα λογισμικού;
• Πόσο εύκολη είναι η ανάγνωση και η κατανόηση των
προγραμμάτων στη γλώσσα;
• Πόσο εύκολη είναι η συνεργασία μ’ ένα άλλο πρόγραμμα;
• Τι υποστήριξη υπάρχει από πλευράς βιβλιοθηκών;

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 10

Επίδοση σε ταχύτητα

• Το πρόγραμμα σε C++ είναι περίπου τρεις φορές πιο


γρήγορο από ότι σε Perl
– Όμως η ταχύτητα και των δύο προγραμμάτων είναι
ικανοποιητική (για την προβλεπόμενη χρήση τους)
– Σε τελική ανάλυση, η ταχύτητα εκτέλεσης έχει
περισσότερο να κάνει με το χρόνο που χρειάζεται να
διαβάσουμε το αρχείο (από το δίσκο) παρά με τη
γλώσσα

• Είναι αυτός λόγος για να γράψουμε Χ φορές


περισσότερο κώδικα;

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 11


Επιτυχής σχεδιασμός γλωσσών

Λαμβάνει υπόψη του τα χαρακτηριστικά των εφαρμογών


– C:
• προγραμματισμός συστήματος
• δυνατότητα παρέμβασης σε πολύ χαμηλό επίπεδο
– Lisp, ML, Prolog: συμβολικός υπολογισμός (symbolic computation)
– Erlang:
• εφαρμογές ταυτοχρονισμού με απαιτήσεις για αδιάκοπη λειτουργία
– Perl, Python: επεξεργασία αρχείων χαρακτήρων
– Java, C#: εφαρμογές διαδικτύου
– Javascript: light-weight client-side προγραμματισμός
– SQL: εφαρμογές βάσεων δεδομένων

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 12

Στοιχεία σχεδιασμού γλωσσών

• Το κλειδί της επιτυχίας:


ευκολία προγραμματισμού κάποιου συνόλου εφαρμογών

• Οι εφαρμογές βοηθούν τους σχεδιαστές γλωσσών να


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

• Ένα από τα βασικότερα συστατικά σχεδιασμού γλωσσών


και συγχρόνως μια από τις πιο δύσκολες αποφάσεις είναι
το ποια στοιχεία θα μείνουν εκτός της γλώσσας!
Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 13
Στοιχεία σχεδιασμού γλωσσών

• Αφηρημένο υπολογιστικό μοντέλο / μηχανή όπως αυτό


παρουσιάζεται στον προγραμματιστή
– Fortran: Πίνακες, αριθμοί κινητής υποδιαστολής, κ.λπ.
– C: Μοντέλο μηχανής υπολογιστή, διευθυνσιοδότηση κατά bytes
– Lisp: Λίστες, συναρτήσεις, αυτόματη διαχείριση μνήμης
– Smalltalk: Αντικείμενα και μέθοδοι, επικοινωνία με μηνύματα
– Java: Αντικείμενα, ενδοσκόπηση (reflection), ασφάλεια, JVM
– Άλλες: Ιστοσελίδες, βάσεις δεδομένων

• Θεωρητική θεμελίωση
– Τυπικές γλώσσες, λ-λογισμός, θεωρία τύπων, σημασιολογία

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 14

Θέματα Σχεδιασμού Γλωσσών


Σύνταξη και Σημασιολογία

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 15


Σύνταξη και σημασιολογία

• Σύνταξη γλωσσών προγραμματισμού: πώς δείχνουν τα


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

• Σημασιολογία γλωσσών προγραμματισμού: τι κάνουν τα


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

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 16

Σύνταξη και σημασιολογία: Παραδείγματα

• Φράση λεκτικά λάθος


οπα πάς οπα χύςέ φαγ επα χιάφα κή
• Φράση λεκτικά ορθή αλλά συντακτικά λάθος
ο παπάς ο φακή έφαγε παχιά παχύς
• Φράση συντακτικά (και λεκτικά) ορθή αλλά
σημασιολογικά λάθος
ο παπάς ο παχιά έφαγε παχύς φακή
• Φράση συντακτικά και σημασιολογικά ορθή
ο παπάς ο παχύς έφαγε παχιά φακή

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 17


Λεκτική ανάλυση γλωσσών προγραμματισμού

• Η λεκτική ανάλυση δεν είναι τετριμμένο πρόβλημα γιατί


οι γλώσσες προγραμματισμού συνήθως είναι πιο
περίπλοκες λεκτικά από τα Ελληνικά
*p->f++ = -.12345e-6
• Άλλο παράδειγμα
float x, y, z;
float * p = &z;

x = y/*p;

• Τι συμβαίνει σε αυτή την περίπτωση;


/* στη C είναι ο συνδυασμός με τον οποίο αρχίζει ένα σχόλιο!

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 18

Λεκτικές συμβάσεις στις γλώσσες

• Η C είναι γλώσσα ελεύθερης μορφής (free-form) όπου τα


κενά απλώς διαχωρίζουν τις λεκτικές μονάδες (tokens).
Ποια από τα παρακάτω είναι τα ίδια;
1+2 foo bar return this
1 + 2 foobar returnthis

• Τα κενά θεωρούνται σημαντικά σε μερικές γλώσσες.


Για παράδειγμα, η γλώσσα Python χρησιμοποιεί στοίχιση
(indentation) για ομαδοποίηση, οπότε τα παρακάτω είναι
διαφορετικά: if x < 3: if x < 3:
y = 2 y = 2
z = 1 z = 1

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 19


Δήλωση λεκτικών μονάδων

• Πώς δηλώνονται οι λεκτικές μονάδες;


– Λέξεις κλειδιά (keywords) – μέσω συμβολοσειρών (strings)
– Πώς ορίζονται τα ονόματα των μεταβλητών (identifiers);
– Πώς ορίζονται οι αριθμοί κινητής υποδιαστολής;

• Κανονικές εκφράσεις (regular expressions)


– Ένας εύχρηστος τρόπος να ορίσουμε σειρές από χαρακτήρες
– Χρησιμοποιούνται ευρέως: grep, awk, perl, κ.λπ..

• Παραδείγματα:
– ’0’ – ταιριάζει μόνο με το χαρακτήρα 0 (μηδέν)
– ’0’|’1’ – ταιριάζει με μηδέν ή με ένα
– ’0’|’1’|’2’|’3’|’4’|’5’|’6’|’7’|’8’|’9’ – ταιριάζει με ψηφία
– [0-9] – το ίδιο με το παραπάνω αλλά σε πιο συμπαγή μορφή
– [0-9]* – σειρά από ψηφία (πιθανώς κενή)
Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 20

Θέματα σχεδιασμού λεκτικών μονάδων

• Ακέραιοι αριθμοί (π.χ. 10)


– Οι αρνητικοί ακέραιοι είναι μία λεκτική μονάδα ή όχι;
• Χαρακτήρες (π.χ. 'a')
– Πώς αναπαρίστανται οι μη εκτυπώσιμοι χαρακτήρες ή το ' ;
• Αριθμοί κινητής υποδιαστολής (π.χ. 3.14e-5)
– Τι συμβαίνει με αριθμούς που δεν αναπαρίστανται κατά IEEE;
• Συμβολοσειρών (π.χ. "hello world")
– Πώς αναπαρίσταται ο χαρακτήρας ";

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 21


Σύνταξη γλωσσών προγραμματισμού

• Συγκεκριμένη σύνταξη (concrete syntax)


– Ποια είναι τα ακριβή σύμβολα (χαρακτήρες ή άλλες
αναπαραστάσεις) με χρήση των οποίων γράφεται το πρόγραμμα;

• Αφηρημένη σύνταξη (abstract syntax)


– Μια αφαίρεση της συγκεκριμένης σύνταξης η οποία είναι η
λογική αναπαράσταση της γλώσσας
– Πιο συγκεκριμένα:
• Μη διφορούμενη: δεν εγείρεται θέμα για τη σημασιολογία
των προγραμμάτων
• Μια σύνταξη πιο κοντά στο “τι σημαίνει” το πρόγραμμα
– Συχνά κάτι που χρησιμοποιείται από το μεταγλωττιστή (compiler)
ή το διερμηνέα (interpreter) της γλώσσας

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 22

Συγκεκριμένη και αφηρημένη σύνταξη

Παράδειγμα 1
while i < N do while (i < N)
begin {
i := i + 1 i = i + 1
end }
Pascal C/C++

• Τα παραπάνω προγράμματα κάνουν το ίδιο πράγμα


• Η συγκεκριμένη σύνταξή τους διαφέρει σε αρκετά
σημεία
while i < N
• Η αφηρημένη τους σύνταξη είναι η ίδια i = i + 1
Πιθανή αφηρημένη σύνταξη
• Η σημασιολογία τους είναι η ίδια

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 23


Συγκεκριμένη και αφηρημένη σύνταξη

Παράδειγμα 2
• Infix: 3 + (4 * 5)
• Prefix: (+ 3 (* 4 5))
• Postfix: 3 4 5 * +
Οι παραπάνω εκφράσεις έχουν
• Διαφορετική συγκεκριμένη σύνταξη +

• Την ίδια αφηρημένη σύνταξη (το 3 *


συντακτικό δένδρο που φαίνεται δίπλα)
4 5

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 24

Σύνταξη και θέματα σχεδιασμού γλωσσών (1)

• Είναι σημαντική η σύνταξη των γλωσσών;


– Και ναι και όχι

• Η σημασιολογία των γλωσσών είναι πιο σημαντική


• Όμως η σύνταξη μιας γλώσσας είναι στενά συνδεδεμένη
με την αισθητική – και θέματα αισθητικής είναι σημαντικά
για τους ανθρώπους
• Η “φλυαρία” παίζει σημαντικό ρόλο – συνήθως το
μικρότερο πρόγραμμα είναι προτιμότερο
• Όμως και το πολύ μικρό μέγεθος είναι προβληματικό:
– Η APL είναι συμπαγής γλώσσα με δικό της σύνολο χαρακτήρων
αλλά τα προγράμματά της καταλήγουν να είναι δυσνόητα
Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 25
Σύνταξη και θέματα σχεδιασμού γλωσσών (2)

• Είναι σημαντικό η σύνταξη να μην οδηγεί σε σφάλματα


Κλασικό παράδειγμα: FORTRAN
DO 5 I = 1,25 ! Loop header (for i = 1 to 25)
DO 5 I = 1.25 ! Assignment to variable DO5I

“Consistently separating words by spaces became
a general custom about the tenth century A.D.,
and lasted until about 1957, when FORTRAN 
abandoned this practice.”
– Sun FORTRAN Reference Manual

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 26

Σύνταξη και θέματα σχεδιασμού γλωσσών (3)

• Είναι σημαντικό η σύνταξη να μην οδηγεί σε σφάλματα


Άλλο παράδειγμα: προσπάθεια χρησιμοποίησης της ήδη
υπάρχουσας σύνταξης της C στη C++
vector< vector<int> > foo;
vector<vector<int>> foo; // Syntax error

Το συντακτικό λάθος συμβαίνει γιατί η C θεωρεί τις


λεκτικές μονάδες > και >> διαφορετικούς τελεστές

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 27


Οι επιλογές σχεδιασμού της γλώσσας C

“C: a programming language which combines 
the power of assembly language
with the flexibility of assembly language.”

“C is quirky, flawed, and a tremendous success.”
– Dennis Ritchie

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 28

Η ιστορία και οι επιλογές της C

• Αναπτύχθηκε μεταξύ 1969 και 1973 μαζί με το Unix από


τον Dennis Ritchie
• Σχεδιάστηκε για προγραμματισμό συστήματος
– Λειτουργικά συστήματα
– Εργαλεία υποστήριξης / μεταγλωττιστές
– Φίλτρα / Ενσωματωμένα συστήματα

• Η μηχανή ανάπτυξης (DEC PDP-11) είχε


– 24Κ bytes μνήμη – από τα οποία 12K για το OS

• Πολλά στοιχεία της C λόγω έλλειψης μνήμης


– Μεταγλωττιστής ενός περάσματος
– Συναρτήσεις ενός επιπέδου (χωρίς φώλιασμα)

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 29


Μετατροπές (conversions)

• Η C ορίζει κάποιες αυτόματες μετατροπές :


– Ένας char μπορεί να χρησιμοποιηθεί ως int
– Η αριθμητική κινητής υποδιαστολής πάντα γίνεται με doubles
(Οι floats προάγονται αυτόματα σε doubles)
– Οι int και char μπορούν να μετατραπούν σε float ή σε
double και αντίστροφα (Το αποτέλεσμα είναι απροσδιόριστο
όταν υπάρχει υπερχείλιση)
– Η πρόσθεση ενός αριθμού (int) σε ένα δείκτη (pointer) δίνει
αποτέλεσμα ένα δείκτη
– Η αφαίρεση δύο δεικτών σε αντικείμενα του ίδιου (πάνω-κάτω)
τύπου δίνει ως αποτέλεσμα έναν ακέραιο (int)

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 30

Πίνακες (arrays) και δείκτες (pointers) στη C

• Ένας πίνακας χρησιμοποιείται σαν να είναι ένας δείκτης


στο πρώτο στοιχείο
• E1[E2] είναι ισοδύναμο με *((E1)+(E2))
• Οι αριθμητικές πράξεις με δείκτες δεν είναι κάτι που
υπάρχει στις άλλες γλώσσες προγραμματισμού

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 31


Δηλωτές (declarators) της C

• Οι δηλώσεις έχουν τη μορφή:


basic type
static unsigned int * (*f[10])(int, char*);
specifiers declarator

• Οι δηλωτές συντακτικά μοιάζουν πολύ με τις εκφράσεις:


χρησιμοποιούνται για να επιστρέψουν το βασικό τύπο
• Συντακτικά ίσως το χειρότερο χαρακτηριστικό της C:
διότι συνδυάζει τόσο prefix (pointers) όσο και postfix
τελεστές (arrays, functions)
cdecl> explain static unsigned int * (*f[10])(int,
char*);
declare f as static array 10 of pointer to function (int,
pointer to char) returning pointer to unsigned int
Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 32

Δηλώσεις συναρτήσεων (προ ANSI C)

Είχαν τη γενική μορφή


type-specifier declarator ( parameter-list )
type-decl-list
{
Για παράδειγμα
declaration-list
int max(a, b, c)
statement-list
int a, b, c;
} {
int m;
m = (a > b) ? a : b ;
return m > c ? m : c ;
}

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 33


Επιλογές σχεδιασμού της C

• Οι πρώτοι compilers της C δεν έλεγχαν τον αριθμό και


τον τύπο των ορισμάτων των συναρτήσεων
• Η μεγαλύτερη αλλαγή που έγινε στη C όταν αυτή έγινε
ANSI standard ήταν η απαίτηση οι συναρτήσεις να
ορίζουν τους τύπους των παραμέτρων τους
int f(); int f(int, int, double);

int f(a, b, c) int f(int a, int b, double c)


int a, b; {
double c; }
{
}

Παλιό στυλ δηλώσεων Νέο στυλ δηλώσεων


Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 34

Δηλώσεις δεδομένων στη C

• Έχουν τη μορφή
type-specifier init-declarator-list ;
declarator optional-initializer

• Οι αρχικοποιητές (initializers) μπορεί να είναι σταθερές,


ή σταθερές εκφράσεις οι οποίες διαχωρίζονται με
κόμματα και είναι κλεισμένες σε αγκύλες
• Παραδείγματα:
– int a;
– struct { int x; int y; } b = { 1, 2 };
– float a, *b, c = 3.14;

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 35


Κανόνες εμβέλειας (scope rules)

Δύο είδη εμβέλειας στη C:


1. Λεκτική εμβέλεια
– Βασικά, το μέρος του προγράμματος όπου
δεν υπάρχουν λάθη αδήλωτων μεταβλητών
(“undeclared identifier” errors)

2. Εμβέλεια των external identifiers


– Όταν δύο identifiers σε διαφορετικά αρχεία
αναφέρονται στο ίδιο αντικείμενο.
– Π.χ., μια συνάρτηση που είναι ορισμένη σε
ένα αρχείο καλείται από μια συνάρτηση σε
ένα άλλο αρχείο.

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 36

Λεκτική εμβέλεια

• Εκτείνεται από το σημείο ορισμού στο αντίστοιχο } ή


στο τέλος του αρχείου
int a;

int foo()
{
int b;
if (a == 0) {
printf("a was 0");
}
b = a; /* OK */
}

int bar()
{
a = 3; /* OK */
b = 2; /* Error: b out of scope */
}
Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 37
Εμβέλεια των external δηλώσεων

file1.c file2.c
int x; int boo()
{
int foo() foo(); /* Warning */
{ x = 2; /* Error */
bar(); /* Warning */ }
x = 1; /* OK */
} extern int foo();
extern int x;
int bar()
{ int baz()
foo(); /* OK */ {
} foo(); /* OK */
x = 3; /* OK */
}

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 38

Ο C Preprocessor

• Έρχεται σε αντίθεση με την ελευθέρας μορφής φύση


της C: οι γραμμές του προεπεξεργαστή πρέπει να
αρχίζουν με το χαρακτήρα #
• Το κείμενο του προγράμματος περνάει μέσα από τον
προεπεξεργαστή πριν εισαχθεί στο μεταγλωττιστή
Αντικατάσταση ενός identifier:
# define identifier token-string
Αντικατάσταση μιας γραμμής με τα περιεχόμενα ενός
αρχείου:
# include "filename "
Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 39
Βιβλιοθήκες της C
Header file Περιγραφή Τυπική χρήση
<assert.h> Generate runtime errors assert(a>0)
<ctype.h> Character classes isalpha(c)
<errno.h> System error numbers errno
<float.h> Floating-point constants FLT_MAX
<limits.h> Integer constants INT_MAX
<locale.h> Internationalization setlocale(…)
<math.h> Math functions sin(x)
<setjmp.h> Non-local goto setjmp(jb)
<signal.h> Signal handling signal(SIGINT,&f)
<stdarg.h> Variable-length arguments va_start(ap, st)
<stddef.h> Some standard types size t
<stdio.h> File I/O, printing printf("%d", i)
<stdlib.h> Miscellaneous functions malloc(1024)
<string.h> String manipulation strcmp(s1, s2)
Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 40

Σχεδιασμός γλωσσών προγραμματισμού

“Language design is library design.”
– Bjarne Stroustrup

• Τα περισσότερα προγράμματα φτιάχνονται


από συναρμολογούμενα κομμάτια
• Μια από τις κύριες δυσκολίες στο σχεδιασμό γλωσσών
είναι το πως τα κομμάτια αυτά μπορούν να βρεθούν μαζί
και να συνυπάρξουν αρμονικά και σωστά

Θέματα Σχεδιασμού Γλωσσών Προγραμματισμού 41


Θέματα Σχεδιασμού
Γλωσσών Προγραμματισμού

Μια γραμματική για τα Αγγλικά

Μια πρόταση αποτελείται


από μια ουσιαστική φράση, <S> ::= <NP> <V> <NP>
ένα ρήμα, και μια ουσιαστική
φράση

Μια ουσιαστική φράση


αποτελείται από ένα άρθρο <NP> ::= <A> <N>
και ένα ουσιαστικό

Ρήματα είναι τα εξής… <V> ::= loves | hates | eats

Άρθρα είναι τα εξής… <A> ::= a | the


Ουσιαστικά είναι τα εξής... <N> ::= dog | cat | rat

Σύνταξη και Συντακτική Ανάλυση 2


Πως δουλεύει μια γραμματική

• Μια γραμματική είναι ένα σύνολο κανόνων που ορίζουν


το πως κατασκευάζεται ένα συντακτικό δένδρο
• Ξεκινάμε βάζοντας το <S> στη ρίζα του δένδρου
• Οι κανόνες της γραμματικής λένε πως μπορούμε να
προσθέσουμε παιδιά σε κάθε σημείο του δένδρου
• Για παράδειγμα, ο κανόνας
<S> ::= <NP> <V> <NP>
λέει ότι μπορούμε να προσθέσουμε κόμβους <NP>, <V>,
και <NP>, με αυτή τη σειρά, ως παιδιά του κόμβου <S>

Σύνταξη και Συντακτική Ανάλυση 3

Γραμματική για αριθμητικές εκφράσεις

<expr> ::= <expr> + <expr>


| <expr> * <expr>
| ( <expr> )
| a | b | c

Η παραπάνω γραμματική ορίζει ότι μια αριθμητική έκφραση


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

Σύνταξη και Συντακτική Ανάλυση 4


Συντακτικό δένδρο

<expr>

( <expr> )

<expr> * <expr>

( <expr> ) c

<expr> + <expr>
a b

Σύνταξη και Συντακτική Ανάλυση 5

Συστατικά μιας γραμματικής

αρχικό σύμβολο <S> ::= <NP> <V> <NP>

κανόνας
παραγωγής <NP> ::= <A> <N>

<V> ::= loves | hates|eats

μη τερματικά <A> ::= a | the


σύμβολα
<N> ::= dog | cat | rat

τερματικά σύμβολα
(λεκτικές μονάδες)
Σύνταξη και Συντακτική Ανάλυση 6
Ορισμός γραμματικών σε μορφή Backus-Naur

Μια γραμματική σε μορφή Backus-Naur αποτελείται από


– Ένα σύνολο από λεκτικές μονάδες (tokens)
• Συμβολοσειρές που αποτελούν τα μικρότερα αδιαίρετα κομμάτια
της σύνταξης του προγράμματος
– Ένα σύνολο από μη τερματικά σύμβολα (non-terminals)
• Συμβολοσειρές που εγκλείονται σε αγκύλες, π.χ. <NP>, και
αντιπροσωπεύουν κομμάτια του συντακτικού της γλώσσας
• Δε συναντιούνται στο πρόγραμμα, αλλά είναι σύμβολα που
βρίσκονται στο αριστερό μέρος κάποιων κανόνων της γραμματικής
– Το αρχικό σύμβολο (start symbol) της γραμματικής
• Ένα συγκεκριμένο μη τερματικό σύμβολο που αποτελεί τη ρίζα του
συντακτικού δένδρου για κάθε αποδεκτό από τη γλώσσα πρόγραμμα
– Ένα σύνολο από κανόνες παραγωγής (production rules)

Σύνταξη και Συντακτική Ανάλυση 7

Κανόνες παραγωγής

• Οι κανόνες παραγωγής χρησιμοποιούνται για την


κατασκευή του συντακτικού δένδρου
• Κάθε κανόνας έχει τη μορφή Α ::= Δ
– Το αριστερό μέρος Α αποτελείται από ένα μη τερματικό σύμβολο
– Το δεξί μέρος Δ είναι μια ακολουθία από τερματικά (λεκτικές μονάδες)
και μη τερματικά σύμβολα

• Κάθε κανόνας προσδιορίζει έναν πιθανό τρόπο


κατασκευής του συντακτικού υποδένδρου που
– έχει ως ρίζα του το μη τερματικό σύμβολο στο αριστερό μέρος Α του
κανόνα και
– έχει ως παιδιά αυτής της ρίζας (με την ίδια σειρά εμφάνισης) τα
σύμβολα στο δεξί μέρος Δ του κανόνα

Σύνταξη και Συντακτική Ανάλυση 8


Επιλογές στη γραφή των κανόνων παραγωγής

• Όταν υπάρχουν περισσότεροι από ένας κανόνες παραγωγής με


το ίδιο αριστερό μέρος, μπορούμε να κάνουμε χρήση της
παρακάτω συντομογραφίας
• Στη BNF γραμματική μπορούμε να δώσουμε το αριστερό
μέρος, το διαχωριστή ::=, και μετά μια ακολουθία από δεξιά
μέρη που διαχωρίζονται από το ειδικό σύμβολο |
• Οι δύο γραμματικές είναι ίδιες <expr> ::= <expr> + <expr>
<expr> ::= <expr> * <expr>
<expr> ::= <expr> + <expr>
<expr> ::= ( <expr> )
| <expr> * <expr>
<expr> ::= a
| ( <expr> )
<expr> ::= b
| a | b | c
<expr> ::= c
Σύνταξη και Συντακτική Ανάλυση 9

Κανόνες παραγωγής του κενού

• Το ειδικό μη τερματικό <empty> χρησιμοποιείται σε


περιπτώσεις που θέλουμε κάποιος κανόνας να μην
παράγει τίποτα
• Για παράδειγμα, οι παρακάτω κανόνες ορίζουν τη δομή
if-then των περισσοτέρων γλωσσών, η οποία επιτρέπει
την ύπαρξη ενός προαιρετικού else
<if-stmt> ::= if <expr> then <stmt> <else-part>
<else-part> ::= else <stmt> | <empty>

Σύνταξη και Συντακτική Ανάλυση 10


Κατασκευή συντακτικών δένδρων

• Αρχίζουμε την κατασκευή βάζοντας το αρχικό σύμβολο


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

Σύνταξη και Συντακτική Ανάλυση 11

<expr> ::= <expr> + <expr>

Παραδείγματα | <expr> * <expr>


| ( <expr> )
| a | b | c
• Τα συντακτικά δένδρα για τις παρακάτω εκφράσεις
a+b (a+b) (a+(b)) a+b*c

• Η κατασκευή των συντακτικών δένδρων είναι η δουλειά


του συντακτικού αναλυτή ενός μεταγλωττιστή
• Υπάρχουν διάφοροι αποδοτικοί αλγόριθμοι και εργαλεία
για ημιαυτόματη κατασκευή του συντακτικού αναλυτή
Σύνταξη και Συντακτική Ανάλυση 12
Τυπικός ορισμός σύνταξης γλωσσών

• Για να ορίσουμε τη σύνταξη των γλωσσών


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

Σύνταξη και Συντακτική Ανάλυση 13

Παράδειγμα κατασκευής γραμματικής (1)

Συνήθως γίνεται με χρήση της τεχνικής “διαίρει και


βασίλευε” (divide and conquer)

• Παράδειγμα: κατασκευή της γλώσσας των δηλώσεων της


Java (η οποία είναι παρόμοια με αυτή της C):
– αρχικά, η δήλωση έχει ένα όνομα τύπου
– στη συνέχεια μια ακολουθία από μεταβλητές που διαχωρίζονται με
κόμματα (όπου κάθε μεταβλητή μπορεί να πάρει μια αρχική τιμή)
– και στο τέλος ένα ερωτηματικό (semicolon)

float a;
boolean a, b, c;
int a = 1, b, c = 1 + 2;
Σύνταξη και Συντακτική Ανάλυση 14
Παράδειγμα κατασκευής γραμματικής (2)

• Αρχικά ας αγνοήσουμε την πιθανή ύπαρξη αρχικοποιητών:


<var-decl> ::= <type-name> <declarator-list> ;
• Ο κανόνας για τα ονόματα των πρωτόγονων τύπων
(primitive types) της Java είναι απλούστατος:
<type-name> ::= boolean | byte | short | int
| long | char | float | double

Σημείωση: δεν παίρνουμε υπόψη κατασκευασμένους τύπους (constructed


types): ονόματα κλάσεων, ονόματα διεπαφών (interfaces), και τύπους
πινάκων
Σύνταξη και Συντακτική Ανάλυση 15

Παράδειγμα κατασκευής γραμματικής (3)

• Η ακολουθία των μεταβλητών που διαχωρίζονται με


κόμματα έχει ως εξής:
<declarator-list> ::= <declarator>
| <declarator> , <declarator-list>

• Όπου ξανά, έχουμε προς το παρόν αγνοήσει τους


πιθανούς αρχικοποιητές των μεταβλητών

Σύνταξη και Συντακτική Ανάλυση 16


Παράδειγμα κατασκευής γραμματικής (4)

• Οι δηλωτές μεταβλητών, με ή χωρίς αρχικοποιήσεις,


ορίζονται ως:
<declarator> ::= <variable-name>
| <variable-name> = <expr>

• Για ολόκληρη τη Java:


– Πρέπει να επιτρέψουμε και ζεύγη από αγκύλες μετά το όνομα
των μεταβλητών για τη δήλωση των πινάκων
– Πρέπει επίσης να ορίσουμε και τη σύνταξη των αρχικοποιητών
πινάκων
– (Φυσικά θέλουμε και ορισμούς για τα μη τερματικά σύμβολα
<variable-name> και <expr>)

Σύνταξη και Συντακτική Ανάλυση 17

Τί αποτελεί λεκτική μονάδα (token);

• Όποια κομμάτια της γλώσσας επιλέξουμε να θεωρήσουμε


ως μη κατασκευαζόμενα από μικρότερα κομμάτια
• Μεταβλητές (i, j), λέξεις κλειδιά (if), τελεστές (==,++),
σταθερές (123.4), κ.λπ.
• Οι γραμματικές που έχουμε ορίσει δίνουν τη δομή των
φράσεων (phrase structure): πως το πρόγραμμα
κατασκευάζεται από μια σειρά λεκτικών μονάδων
• Πρέπει επιπλέον να ορίσουμε και τη λεκτική δομή (lexical
structure): πως ένα αρχείο χωρίζεται σε λεκτικές μονάδες

Σύνταξη και Συντακτική Ανάλυση 18


Δύο επιλογές ορισμού της λεκτικής δομής

• Με μια κοινή γραμματική


– Οι χαρακτήρες είναι οι μοναδικές λεκτικές μονάδες
– Συνήθως δεν ακολουθείται αυτή η επιλογή: κενά και σχόλια
περιπλέκουν αρκετά τη γραμματική και την καθιστούν μη
αναγνώσιμη

• Με ξεχωριστές γραμματικές
1. Μία που ορίζει πώς προκύπτουν οι λεκτικές μονάδες από ένα
αρχείο με χαρακτήρες
• Η γραμματική αυτή συνήθως είναι μια κανονική γραμματική (regular
grammar) και χρησιμοποιείται από το λεκτικό αναλυτή (scanner)
2. Μία που ορίζει πως προκύπτουν τα συντακτικά δένδρα από μία
ακολουθία λεκτικών μονάδων
• Η γραμματική αυτή συνήθως είναι μια γραμματική ελεύθερη
συμφραζομένων (context-free grammar) και χρησιμοποιείται από το
συντακτικό αναλυτή (parser)
Σύνταξη και Συντακτική Ανάλυση 19

Ιστορικές σημειώσεις (1)

• Παλιά, κάποιες γλώσσες προγραμματισμού δε


διαχώριζαν τη λεκτική από την φραστική δομή
– Παλιές εκδόσεις της Fortran και της Algol επέτρεπαν κενά σε
οποιοδήποτε σημείο, ακόμα και στο μέσο μιας λέξης κλειδί!
– Άλλες γλώσσες, π.χ. η PL/I, επιτρέπουν τη χρήση λέξεων
κλειδιών ως μεταβλητές
• (Το ίδιο συμβαίνει και στην ML, αλλά εκεί δεν αποτελεί πρόβλημα.)

• Τα παραπάνω
– προσθέτουν δυσκολία στην λεκτική και συντακτική ανάλυση και
– μειώνουν την αναγνωσιμότητα των προγραμμάτων

Σύνταξη και Συντακτική Ανάλυση 20


Ιστορικές σημειώσεις (2)

• Κάποιες γλώσσες έχουν λεκτική δομή σταθερής μορφής


(fixed-format )—τα κενά είναι σημαντικά
– Μία εντολή ανά γραμμή (π.χ. της διάτρητης κάρτας)
– Οι πρώτες 7 θέσεις κάθε γραμμής για την ταμπέλα (label)

• Οι πρώτες διάλεκτοι της Fortran, Cobol, και της Basic


• Σχεδόν οι περισσότερες μοντέρνες γλώσσες είναι
ελεύθερης μορφής (free-format ): τα κενά αγνοούνται
– Π.χ. Algol, Pascal, Java
– Μερικές άλλες (C, C++) διαφέρουν
λίγο λόγω του προεπεξεργαστή

Σύνταξη και Συντακτική Ανάλυση 21

Άλλες μορφές γραμματικών

• Μικρές διαφοροποιήσεις της μορφής Backus-Naur (BNF)


– Χρήση → ή = αντί για ::=
– Όχι <> αλλά κάποιο ειδικό font ή χρήση αποστρόφων για τις
λεκτικές μονάδες ώστε να ξεχωρίζονται εύκολα από τα μη
τερματικά σύμβολα

• Επεκτάσεις της μορφής Backus-Naur (EBNF)


– Πρόσθετος συμβολισμός για την απλοποίηση κάποιων κανόνων:
• {x} υποδηλώνει μηδέν ή περισσότερες επαναλήψεις του x
• [x] υποδηλώνει ότι το x είναι προαιρετικό (δηλαδή x | <empty>)
• () για ομαδοποίηση
• | οπουδήποτε για να υποδηλώσει επιλογή
• Αποστρόφους γύρω από τις λεκτικές μονάδες ούτως ώστε να
ξεχωρίζονται από τα παραπάνω μετασύμβολα

• Συντακτικά διαγράμματα
Σύνταξη και Συντακτική Ανάλυση 22
Παραδείγματα EBNF

<if-stmt> ::= if <expr> then <stmt> [else <stmt>]

<stmt-list> ::= {<stmt> ;}

<thing-list> ::= { (<stmt> | <declaration>) ;}

Σύνταξη και Συντακτική Ανάλυση 23

Συντακτικά διαγράμματα (1)

• Έστω ότι έχουμε μια γραμματική σε EBNF


• Ο κάθε κανόνας παραγωγής μετατρέπεται σε μια σειρά
από κουτιά
– Ορθογώνια για τα μη τερματικά σύμβολα
– Οβάλ για τα τερματικά σύμβολα
– Τα παραπάνω ενώνονται με βέλη
– (Πιθανώς κάποια βέλη να παρακάμπτουν κάποια από τα κουτιά.)

<if-stmt> ::= if <expr> then <stmt> [ else <stmt> ]

if-stmt
if expr then stmt else stmt

Σύνταξη και Συντακτική Ανάλυση 24


Συντακτικά διαγράμματα (2)

• Πολλαπλοί κανόνες
exp + exp
παραγωγής χρησιμοποιούν
exp * exp
διακλαδώσεις (branching)
exp ( exp )

• Η επανάληψη υποδηλώνεται exp


addend
με χρήση βρόχων
<exp> ::= <addend> {+ <addend>} +

Σύνταξη και Συντακτική Ανάλυση 25

Παράδειγμα διαφορετικού συμβολισμού EBNF

WhileStatement:
while ( Expression ) Statement

DoStatement:
do Statement while ( Expression );

ForStatement:
for ( ForInitopt ; Expressionopt ; ForUpdateopt)
Statement

από το βιβλίο The Java™ Language Specification, James Gosling et al.

Σύνταξη και Συντακτική Ανάλυση 26


Από τη Σύνταξη προς τη Σημασιολογία

Σύνταξη και Συντακτική Ανάλυση 27

Τρεις “ισοδύναμες” γραμματικές

G1: <subexp> ::= a | b | c | <subexp> - <subexp>

G2: <subexp> ::= <var> - <subexp> | <var>


<var> ::= a | b | c

G3: <subexp> ::= <subexp> - <var> | <var>


<var> ::= a | b | c

• Και οι τρεις γραμματικές ορίζουν την ίδια γλώσσα: τη


γλώσσα όλων των συμβολοσειρών που περιλαμβάνουν
ένα ή περισσότερα a, b, ή c τα οποία διαχωρίζονται από
ένα μείον. Αλλά...

Σύνταξη και Συντακτική Ανάλυση 28


<subexp>

<var> - <subexp>

G2 parse tree: a <var> - <subexp>

b <var>

<subexp>

<subexp> - <var>

G3 parse tree: <subexp> - <var> c

<var> b

Σύνταξη και Συντακτική Ανάλυση 29

Γιατί είναι σημαντικά τα συντακτικά δένδρα;

• Θέλουμε η δομή του συντακτικού δένδρου να


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

Τα συντακτικά δένδρα είναι το μέρος που η σύνταξη


αρχίζει να συναντά τη σημασιολογία των γλωσσών

Σύνταξη και Συντακτική Ανάλυση 30


Τελεστές (operators)

• Τελεστές χρησιμοποιούνται για λειτουργίες που γίνονται


συχνά, π.χ. πρόσθεση, αφαίρεση, πολλαπλασιασμό, …
• Ο όρος τελεστής αναφέρεται τόσο στη λεκτική μονάδα
(π.χ. +, *) όσο και στη λειτουργία αυτή καθ’ αυτή
– Μοναδιαίοι τελεστές δέχονται ένα όρισμα: -1
– Δυαδικοί τελεστές δέχονται δύο ορίσματα: 1+2
– Τριαδικοί τελεστές δέχονται τρία ορίσματα: a?b:c

• Στις περισσότερες γλώσσες


– Οι δυαδικοί τελεστές γράφονται σε infix μορφή: π.χ. 1+2
– Οι μοναδιαίοι τελεστές γράφονται σε prefix (-2) ή σε postfix
μορφή (i++)

Σύνταξη και Συντακτική Ανάλυση 31

Προτεραιότητα (precedence) τελεστών

• Έστω η γραμματική G4: • Ένα συντακτικό δένδρο


<exp> ::= <exp> + <exp> για τη συμβολοσειρά
| <exp> * <exp>
a+b*c είναι το
| ( <exp> )
| a | b | c <exp >

<exp > * <exp>

<exp > + <exp> c

a b

• Στο δένδρο αυτό η πρόσθεση γίνεται πριν από τον


πολλαπλασιασμό, κάτι που δεν είναι σε αρμονία με τις
συνήθεις προτεραιότητες των τελεστών + και *
Σύνταξη και Συντακτική Ανάλυση 32
Προτεραιότητα τελεστών στη γραμματική

• Για να έχουμε τη σωστή προτεραιότητα τελεστών,


αλλάζουμε τη γραμματική με τέτοιο τρόπο ώστε ο
τελεστής με την μεγαλύτερη προτεραιότητα να
καταλήγει “πιο κάτω” στο συντακτικό δένδρο

G5: <exp> ::= <exp> + <exp> | <mulexp>


<mulexp> ::= <mulexp> * <mulexp>
| ( <exp> )
| a | b | c

Σύνταξη και Συντακτική Ανάλυση 33

Παραδείγματα προτεραιότητας τελεστών

• C (15 επίπεδα προτεραιότητας—πάρα πολλά;)


a = b < c ? * p + b * c : 1 << d ()

• Pascal (5 επίπεδα—όχι αρκετά;)


a <= 0 or 100 <= a Συντακτικό λάθος!

• Smalltalk (1 επίπεδο για όλους τους δυαδικούς τελεστές)

a + b * c

Σύνταξη και Συντακτική Ανάλυση 34


Συντακτικό δένδρο με σωστή προτεραιότητα

<exp>

<exp> + <exp>
G5 parse tree: <mulexp> <mulexp>

a <mulexp> * <mulexp>

b c

Η γραμματική G5 παράγει μόνο αυτό το δένδρο για a+b*c


Αναγνωρίζει την ίδια γλώσσα με τη G4, αλλά δεν παράγει
πλέον δένδρα με λάθος προτεραιότητα τελεστών
Σύνταξη και Συντακτική Ανάλυση 35

Προσεταιριστικότητα (associativity) τελεστών

<exp> <exp>

<exp> + <exp> <exp> + <exp>

<mulexp> <exp> + <exp> <exp> + <exp> <mulexp>

a <mulexp> <mulexp> <mulexp> <mulexp> c

b c a b

Η γραμματική G5 παράγει τα παραπάνω δένδρα για a+b+c


Το πρώτο από αυτά δεν αντικατοπτρίζει τη συνήθη
προσεταιριστικότητα του τελεστή +

Σύνταξη και Συντακτική Ανάλυση 36


Προσεταιριστικότητα τελεστών

• Έχουν χρησιμότητα όταν η σειρά της αποτίμησης δεν


καθορίζεται από παρενθέσεις ή προτεραιότητα τελεστών
• Οι αριστερά προσεταιριστικοί τελεστές ομαδοποιούν από
αριστερά προς τα δεξιά: a+b+c+d = ((a+b)+c)+d
• Οι δεξιά προσεταιριστικοί τελεστές ομαδοποιούν από
δεξιά προς τα αριστερά: a+b+c+d = a+(b+(c+d))
• Στις περισσότερες γλώσσες, οι περισσότεροι τελεστές
είναι αριστερά προσεταιριστικοί, αλλά υπάρχουν και
εξαιρέσεις

Σύνταξη και Συντακτική Ανάλυση 37

Παραδείγματα προσεταιριστικότητας

• C
a<<b<<c — τελεστές είναι αριστερά προσεταιριστικοί
a=b=0 — δεξιά προσεταιριστικός (ανάθεση)

• ML
3-2-1 — τελεστές είναι αριστερά προσεταιριστικοί
1::2::nil — δεξιά προσεταιριστικός (κατασκευή λίστας)

• Fortran
a/b*c — τελεστές είναι αριστερά προσεταιριστικοί
a**b**c — δεξιά προσεταιριστικός (ύψωση σε δύναμη)

Σύνταξη και Συντακτική Ανάλυση 38


Προσεταιριστικότητα στη γραμματική

G5: <exp> ::= <exp> + <exp> | <mulexp>


<mulexp> ::= <mulexp> * <mulexp>
| ( <exp> )
| a | b | c

• Για να διορθώσουμε το πρόβλημα, τροποποιούμε τη


γραμματική ώστε να κάνουμε δένδρα με τελεστές + να
μεγαλώνουν προς τα αριστερά (παρόμοια για τον *)

G6: <exp> ::= <exp> + <mulexp> | <mulexp>


<mulexp> ::= <mulexp> * <rootexp> | <rootexp>
<rootexp> ::= ( <exp> )
| a | b | c

Σύνταξη και Συντακτική Ανάλυση 39

Δένδρο με σωστή προσεταιριστικότητα

<ex p>

<ex p> + <mulex p>

<ex p> + <mulex p> <rootex p>

<mulexp> <rootex p> c

<rootex p> b

Η γραμματική G6 παράγει μόνο αυτό το δένδρο για a+b+c


Παράγει την ίδια γλώσσα με τη G5, αλλά δεν παράγει
πλέον δένδρα με λάθος προσεταιριστικότητα τελεστών
Σύνταξη και Συντακτική Ανάλυση 40
Πρακτική άσκηση

Ξεκινώντας από τη γραμματική


G6: <exp> ::= <exp> + <mulexp> | <mulexp>
<mulexp> ::= <mulexp> * <rootexp> | <rootexp>
<rootexp> ::= ( <exp> )
| a | b | c
1. Προσθέστε έναν αριστερά προσεταιριστικό τελεστή &,
με προτεραιότητα μικρότερη από όλους τους άλλους
2. Στη συνέχεια προσθέστε ένα δεξιά προσεταιριστικό
τελεστή **, με προτεραιότητα μεγαλύτερη από όλους
τους άλλους

Σύνταξη και Συντακτική Ανάλυση 41

Διφορούμενη ερμηνεία (ambiguity)

• Η γραμματική G4 είναι διφορούμενη (ambiguous):


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

Σύνταξη και Συντακτική Ανάλυση 42


Το πρόβλημα του “ξεκρέμαστου else”

<stmt> ::= <if-stmt> | s1 | s2


<if-stmt> ::= if <expr> then <stmt> else <stmt>
| if <expr> then <stmt>
<expr> ::= e1 | e2

• Η γραμματική αυτή είναι διφορούμενη ως προς τo


“ξεκρέμαστο else” (“dangling-else ambiguity”).
• Η παρακάτω εντολή
if e1 then if e2 then s1 else s2
έχει δύο συντακτικά δένδρα
Σύνταξη και Συντακτική Ανάλυση 43

< if-stm t>

if < ex p> t h en < stm t> e ls e < stmt>

e1 < if-stm t> s2

if < ex p> th en < stmt>

e2 s1
Οι περισσότερες γλώσσες
που έχουν αυτό το πρόβλημα
< if-stm t> επιλέγουν το κάτω συντακτικό
δένδρο: το else πηγαίνει με το
if < ex p> th en < stmt>
κοντινότερο αταίριαστο then
e1 < if-stm t>

if < ex p> t h en < stm t> e ls e < stmt>

e2 s1 s2

Σύνταξη και Συντακτική Ανάλυση 44


Διόρθωση του προβλήματος (1)
<stmt> ::= <if-stmt> | s1 | s2
<if-stmt> ::= if <expr> then <stmt> else <stmt>
| if <expr> then <stmt>
<expr> ::= e1 | e2

• Θέλουμε να επιβάλλουμε ότι εάν αυτό επεκταθεί σε ένα


if, τότε το if πρέπει ήδη να έχει το δικό του else.
Πρώτα, δημιουργούμε ένα νέο μη τερματικό <full-stmt>
που παράγει όλες τις εντολές που παράγονται από το
<stmt>, αλλά απαγορεύει τις if εντολές χωρίς else

<full-stmt> ::= <full-if> | s1 | s2


<full-if> ::= if <expr> then <full-stmt> else <full-stmt>

Σύνταξη και Συντακτική Ανάλυση 45

Διόρθωση του προβλήματος (2)

• Μετά χρησιμοποιούμε το νέο μη τερματικό εδώ


<stmt> ::= <if-stmt> | s1 | s2
<if-stmt> ::= if <expr> then <full-stmt> else <stmt>
| if <expr> then <stmt>
<expr> ::= e1 | e2
• Το αποτέλεσμα είναι ότι η παραπάνω γραμματική μπορεί
να ταιριάξει ένα else με ένα if μόνο όταν όλα τα
κοντινά if έχουν ήδη κάποιο else ως ταίρι τους.

Σύνταξη και Συντακτική Ανάλυση 46


Τώρα παράγουμε μόνο το συντακτικό δένδρο

<if-stmt>

if <exp> then <stmt>

e1 <if-stmt>

if <exp> then <full-stmt> else <stmt>

e2 s1 s2

Σύνταξη και Συντακτική Ανάλυση 47

Ξεκρέμαστα else και αναγνωσιμότητα

• Διορθώσαμε τη γραμματική, αλλά…


• Το πρόβλημα στη γραμματική αντικατοπτρίζει κάποιο
πρόβλημα στη γλώσσα, την οποία δεν αλλάξαμε
• Μια ακολουθία από if-then-else δεν είναι εύκολα
αναγνώσιμη, ειδικά εάν κάποια από τα else λείπουν

int a=0;
if (0==0) Ποια είναι η τιμή του a
if (0==1) a=17; μετά την εκτέλεση του
else a=42; προγράμματος;

Σύνταξη και Συντακτική Ανάλυση 48


Καλύτερα στυλ προγραμματισμού

int a=0;
if (0==0)
if (0==1) a=17; Καλύτερο: σωστή στοίχιση
else a=42;

int a=0;
if (0==0) { Ακόμα καλύτερο: η χρήση
if (0==1) a=17; μπλοκ δείχνει καθαρά τη
else a=42; δομή του κώδικα
}

Σύνταξη και Συντακτική Ανάλυση 49

Γλώσσες χωρίς ξεκρέμαστα else

• Μερικές γλώσσες ορίζουν τα if-then-else με τρόπο


τέτοιο που να επιβάλλει τη σαφήνεια στη χρήση τους
• Η Algol δεν επιτρέπει μέσα στο then να αρχίζει άμεσα
κάποιο άλλο if
– (αλλά μπορεί να αρχίζει ένα μπλοκ με ένα άλλο if)
• Η Algol 68 επιβάλλει σε κάθε if να τερματίζεται με ένα
fi (υπάρχουν επίσης do-od και case-esac)
• Η Ada επιβάλλει σε κάθε if να τερματίζεται με ένα
end if

Σύνταξη και Συντακτική Ανάλυση 50


Γραμματική για ολόκληρη τη γλώσσα

• Κάθε ρεαλιστική γλώσσα περιέχει πολλά μη τερματικά


σύμβολα
• Ειδικά εάν από τη γραμματική έχει εξαλειφθεί η ασάφεια
• Τα επιπλέον μη τερματικά καθορίζουν τον τρόπο με τον
οποίο προκύπτει ένα μοναδικό συντακτικό δένδρο
• Όταν κατασκευαστεί το συντακτικό δένδρο, τα επιπλέον
μη τερματικά δεν έχουν κάποια χρησιμότητα πλέον
• Οι υλοποιήσεις των γλωσσών συνήθως αποθηκεύουν μια
συνεπτυγμένη μορφή του συντακτικού δένδρου, η οποία
ονομάζεται αφηρημένο συντακτικό δένδρο
Σύνταξη και Συντακτική Ανάλυση 51

Συγκεκριμένο συντακτικό δένδρο


<exp>

<exp> + <mulexp>
<rootexp>
<exp> + <mulexp>
<mulexp> <rootexp> c

<rootexp> b +

a
+ c

a b
Αφηρημένο συντακτικό δένδρο
Σύνταξη και Συντακτική Ανάλυση 52
Συμπερασματικά

• Για τον ορισμό του συντακτικού (και όχι μόνο!) των


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

• Υπάρχει ισχυρή σύνδεση μεταξύ θεωρίας και πράξης


– Δύο γραμματικές, δύο μέρη του μεταγλωττιστή (compiler)
– Σημείωση: Υπάρχουν προγράμματα, γεννήτριες συντακτικών
αναλυτών (parser generators), που μπορούν να δημιουργήσουν
αυτόματα τον κώδικα του λεκτικού και του συντακτικού αναλυτή
από τη γραμματική μιας γλώσσας
Σύνταξη και Συντακτική Ανάλυση 53

Εισαγωγή στη Γλώσσα ML


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

• Ένας τρόπος διαχωρισμού


– Ο προστακτικός προγραμματισμός επικεντρώνει στο πως θα
υλοποιήσουμε τα συστατικά του προγράμματός μας
– Ο συναρτησιακός προγραμματισμός επικεντρώνει στο τι
συστατικά θα πρέπει να έχει το πρόγραμμά μας

• Συναρτησιακός προγραμματισμός
– Βασίζεται στο μαθηματικό μοντέλο του λ-λογισμού (Church)
– “Προγραμματισμός χωρίς μεταβλητές”
– Είναι από τη φύση του κομψός, σύντομος και σαφής τρόπος
προγραμματισμού, στον οποίο αποφεύγονται τελείως κάποιου
είδους προγραμματιστικά σφάλματα
– Θεωρείται από πολλούς ως ανώτερος τρόπος προγραμματισμού

Εισαγωγή στη γλώσσα ML 2

Διαφάνεια αναφοράς (referential transparency)

• Σε μία γλώσσα συναρτησιακού προγραμματισμού,


η αποτίμηση μιας συνάρτησης δίνει πάντα το ίδιο
αποτέλεσμα για τις ίδιες τιμές των παραμέτρων της
• Η σημαντική αυτή ιδιότητα δεν ισχύει κατ΄ ανάγκη στις
γλώσσες προστακτικού προγραμματισμού
• Στον προστακτικό προγραμματισμό αυτό συμβαίνει λόγω:
– Μεταβλητών που ορίζονται και αλλάζουν τιμές εκτός του
σώματος της συνάρτησης (global variables)
– Εξάρτησης από την κατάσταση (state) του υπολογισμού
– Άλλων παρενεργειών (side-effects) που μπορεί να υπάρχουν στο
πρόγραμμα

Εισαγωγή στη γλώσσα ML 3


Παράδειγμα σε Pascal
program example(output)
var flag:boolean;
function f(n:int): int
begin Τι τυπώνει το πρόγραμμα;
if flag then f := n
else f := 2*n;
5 και μετά 4
flag := not flag • Περίεργο διότι
end περιμένουμε ότι
begin f(1)+ f(2) = f(2)+ f(1)
flag := true; • Στα μαθηματικά
writeln(f(1)+f(2)); οι συναρτήσεις
writeln(f(2)+f(1)); εξαρτώνται μόνο από
end τα ορίσματά τους

Εισαγωγή στη γλώσσα ML 4

Μεταβλητές και “μεταβλητές”

• Στην καρδιά του προβλήματος είναι το γεγονός ότι η


μεταβλητή flag επηρεάζει την τιμή της f
• Ειδικότερα, η συμπεριφορά οφείλεται στην ανάθεση
flag := not flag
• Σε μια γλώσσα χωρίς πολλαπλές αναθέσεις μεταβλητών
δεν υπάρχουν τέτοια προβλήματα
• Στις συναρτησιακές γλώσσες, οι μεταβλητές είναι
ονόματα για συγκεκριμένες τιμές, δεν είναι ονόματα για
συγκεκριμένες θέσεις μνήμης
• Μπορούμε να τις θεωρήσουμε «όχι πολύ μεταβλητές»
Εισαγωγή στη γλώσσα ML 5
Η γλώσσα ML (Meta Language)

• Γλώσσα συναρτησιακού προγραμματισμού με τύπους


• Σχεδιασμένη για αλληλεπιδραστική χρήση (interactive use)
• Συνδυάζει τα παρακάτω στοιχεία:
– Βασισμένη στο λ-λογισμό και στην αποτίμηση εκφράσεων
– Συναρτήσεις υψηλής τάξης (higher-order functions)
– Αυτόματη διαχείριση μνήμης (με χρήση συλλογής σκουπιδιών)
– Αφηρημένους τύπους δεδομένων (abstract data types)
– Σύστημα αρθρωμάτων (module system)
– Εξαιρέσεις (exceptions)

• Γενικής χρήσης μη προστακτική, μη αντικειμενοστρεφής


γλώσσα
– Σχετικές γλώσσες: OCaml, Haskell, …
Εισαγωγή στη γλώσσα ML 6

Γιατί εξετάζουμε την ML;

• Τύποι και αυστηρό σύστημα τύπων


– Γενικά θέματα για στατικό έναντι δυναμικού ελέγχου των τύπων
– Συμπερασμός τύπων (type inference)
– Πολυμορφισμός και γενικός προγραμματισμός (generic
programming)

• Διαχείριση μνήμης
– Στατική εμβέλεια και δομή κατά μπλοκ
– Εγγραφές ενεργοποίησης συναρτήσεων (function activation
records) και υλοποίηση συναρτήσεων υψηλής τάξης

• Έλεγχος και δομές ροής


– Εξαιρέσεις
– Αναδρομή “ουράς” (tail recursion) και συνέχειες (continuations)

Εισαγωγή στη γλώσσα ML 7


Σύντομη ιστορία της γλώσσας ML

• Robin Milner (ACM Turing Award)


• Logic for Computable Functions
– Stanford 1970-71
– Edinburgh 1972-1995
– Cambridge 1996-2010
• Μεταγλώσσα του συστήματος LCF
– Απόδειξη θεωρημάτων
(theorem proving)
– Σύστημα τύπων (type system)
– Συναρτήσεις υψηλής τάξης
(higher-order functions)

• Θα χρησιμοποιήσουμε την υλοποίηση


SML/NJ (Standard ML of New Jersey)
Εισαγωγή στη γλώσσα ML 8

Η γλώσσα ML μέσα από παραδείγματα

% sml
Standard ML of New Jersey, v110.XX
- 42;
val it = 42 : int
- 2 + 3;
val it = 5 : int
- fun square x = x * x;
val square = fn : int -> int
- square 5;
val it = 25 : int
- square;
val it = fun : int -> int

Εισαγωγή στη γλώσσα ML 9


Βασικοί τύποι της ML

• Booleans
– true, false : bool

• Ακέραιοι και τελεστές τους


– 0, 1, 2, … : int
– +, -, *, mod, div, ~ (μοναδιαίο μείον)

• Συμβολοσειρές και τελεστές τους


– "Robin Milner" : string
– ^ (συνένωση συμβολοσειρών)

• Αριθμοί κινητής υποδιαστολής και τελεστές τους


– 1.0, 2.2, 3.14159, …
– +, -, *, /, ~
Οι τελεστές είναι αριστερά προσεταιριστικοί, με προτεραιότητες {+,-} < {*,/,div,mod} < {~}.
Εισαγωγή στη γλώσσα ML 10

Η γλώσσα ML μέσα από παραδείγματα

- 1 = 2;
val it = false : bool
- 1 <> 2 andalso true <> false;
val it = true : bool
- true = false orelse 1 <= 2;
val it = true : bool
- "Robin" > "Milner";
val it = true : bool
- 2.56 < 3.14;
val it = true : bool
- 2.56 = 3.14;
stdIn: Error: operator and operand don’t agree
operator domain: ’’Z * ’’Z
operand: real * real
Εισαγωγή στη γλώσσα ML 11
Υπερφόρτωση τελεστών (operator overloading)

- 6 * 7
val it = 42 : int
- 6.0 * 7.0;
val it = 42.0 : real
- 2.0 * 21;
stdIn: Error: operator and operand don’t agree
operator domain: real * real
operand: real * int
in expression: 2.0 * 21

• Ο τελεστής * (και άλλοι όπως ο +) είναι υπερφορτωμένοι


• Έχουν διαφορετική ερμηνεία σε ζεύγη ακεραίων και
διαφορετική σε ζεύγη αριθμών κινητής υποδιαστολής
• Η ML δεν κάνει αυτόματη μετατροπή από ακεραίους σε
πραγματικούς αριθμούς (όπως π.χ. κάνει η C)
Εισαγωγή στη γλώσσα ML 12

Η γλώσσα ML μέσα από παραδείγματα

- fun max a b =
= if a > b then a else b;
val max = fn : int -> int -> int
- max 17 5;
val it = 17 : int
- max 10 42;
val it = 42 : int

• Προσέξτε τον περίεργο τύπο


int -> int -> int
• Λέει ότι η max είναι μια συνάρτηση που παίρνει έναν
ακέραιο και επιστρέφει μια συνάρτηση που παίρνει έναν
ακέραιο και επιστρέφει έναν ακέραιο
Εισαγωγή στη γλώσσα ML 13
Currying

• Οι συναρτήσεις είναι αντικείμενα


πρώτης τάξης τα οποία μπορούμε να
τα διαχειριστούμε όπως όλα τα άλλα
αντικείμενα (π.χ. τους ακεραίους)
Haskell B. Curry

- fun max a b = if a > b then a else b;


val max = fn : int -> int -> int
- val max_five = max 5;
val max_five = fn : int -> int
- max_five 42;
val it = 42 : int
- max_five 3;
val it = 5 : int

Εισαγωγή στη γλώσσα ML 14

Currying vs. Tuples

• Αν θέλουμε, μπορούμε να χρησιμοποιήσουμε πλειάδες


(tuples) ως ορίσματα ή αποτελέσματα συναρτήσεων

- fun max (a,b) = if a > b then a else b;


val max = fn : int * int -> int
- max (17,42);
val it = 42 : int
- fun reverse (a,b) = (b,a);
val reverse = fn : ’a * ’b -> ’b * ’a
- reverse (17,42);
val it = (42,17) : int * int
- max (reverse (17,42));
val it = 42 : int

Εισαγωγή στη γλώσσα ML 15


Πολυμορφισμός

• Η συνάρτηση reverse έχει έναν ενδιαφέροντα τύπο


- fun reverse (a,b) = (b,a);
val reverse = fn : ’a * ’b -> ’b * ’a
• Αυτό σημαίνει ότι μπορούμε να αντιστρέψουμε μια
δυάδα όπου το πρώτο όρισμα είναι οποιουδήποτε τύπου
και το δεύτερο όρισμα επίσης είναι οποιουδήποτε τύπου
- reverse (42,3.14);
val it = (3.14,42) : real * int
- reverse ("foo",(1,2));
val it = ((1,2),"foo") : (int * int) * string
Εισαγωγή στη γλώσσα ML 16

Αναδρομή

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


έννοια, τα προγράμματα χρησιμοποιούν αναδρομή για να
εκφράσουν επανάληψη
- fun sum n =
= if n = 0 then 0 else sum (n-1) + n;
val sum = fn : int -> int
- sum 2; Αναδρομή
val it = 3 : int • Επειδή δεν υπάρχουν μεταβλητές με την παραδοσιακή
έννοια, τα προγράμματα χρησιμοποιούν αναδρομή για να
- sum 3; εκφράσουν επανάληψη

val it = 6 : int - fun sum n =


= if n = 0 then 0 else sum (n-1) + n;
val sum = fn : int -> int
- sum 4; - sum 2;
val it = 3 : int Αναδρομή

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


- sum 3;
val it = 10 : int val it = 6 : int
- sum 4;
=
έννοια, τα προγράμματα χρησιμοποιούν αναδρομή για να
εκφράσουν επανάληψη
- fun sum n =
if n = 0 then 0 else sum (n-1) + n;
val sum = fn : int -> int
- sum 2; Αναδρομή

val it = 3 : int • Επειδή δεν υπάρχουν μεταβλητές με την παραδοσιακή

val it = 10 : int
έννοια, τα προγράμματα χρησιμοποιούν αναδρομή για να

- sum 3; εκφράσουν επανάληψη


- fun sum n =
= if n = 0 then 0 else sum (n-1) + n;
val it = 6 : int val sum = fn : int -> int
- sum 2;
val it = 3 : int
- sum 4; - sum 3;
val it = 6 : int
- sum 4;
val it = 10 : int val it = 10 : int

Εισαγωγή στη γλώσσα ML 11

Εισαγωγή στη γλώσσα ML 11

Εισαγωγή στη γλώσσα ML 11

Εισαγωγή στη γλώσσα ML 17


Τελεστής ύψωσης σε δύναμη

• Μπορούμε επίσης να ορίσουμε


νέους αριθμητικούς τελεστές
ως συναρτήσεις
- fun x ^ y =
= if y = 0 then 1
= else x * (x ^ (y-1));
val ^ = fn : int * int -> int
- 2 ^ 2;
val it = 4 : int
- 2 ^ 3;
val it = 8 : int
- 2 ^ 4;
val it = 16 : int
Εισαγωγή στη γλώσσα ML 18

Επαναχρησιμοποίηση αποτελεσμάτων

• Επειδή δεν έχουμε μεταβλητές, είμαστε αναγκασμένοι


να επαναλάβουμε εκφράσεις (και υπολογισμούς)
fun f x =
g(square(max(x,4))) +
(if x < 1 then 1
else g(square(max(x,4))));

• Μια μέθοδος για να γράψουμε πιο εύκολα την παραπάνω


συνάρτηση είναι με χρήση μιας βοηθητικής συνάρτησης
fun f1(a,b) = b + (if a < 1 then 1 else b)
fun f x = f1(x, g(square(max(x,4)));

Εισαγωγή στη γλώσσα ML 19


Η έκφραση let

• Ένας πιο εύκολος τρόπος είναι ο ορισμός ενός τοπικού


ονόματος για την επαναχρησιμοποιούμενη έκφραση

fun f x =
let
val gg = g(square(max(x,4)))
in
gg + (if x < 1 then 1 else gg)
end;

Εισαγωγή στη γλώσσα ML 20

Η έκφραση let δεν είναι ανάθεση

- let
= val a = 2
= in
= (let
= val a = a + 2
= in
= a
= end,
= a)
= end;
val it = (4,2) : int * int

Εισαγωγή στη γλώσσα ML 21


Σύνθετοι τύποι δεδομένων στην ML

• Προγράμματα που επεξεργάζονται μόνο βαθμωτά


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

Εισαγωγή στη γλώσσα ML 22

Λίστες

• Οι πλειάδες περικλείονται από παρενθέσεις,


οι λίστες από αγκύλες
- (1,2);
val it = (1,2) : int * int
- [1,2];
val it = [1,2] : int list

• Ο τελεστής @ συνενώνει δύο λίστες


- [1,2] @ [3,4];
val it = [1,2,3,4] : int list

Εισαγωγή στη γλώσσα ML 23


Cons

• Μπορούμε να προσθέσουμε στοιχεία στην αρχή μιας


λίστας με τον τελεστή :: (προφέρεται cons)
- 1 :: 2 :: 3 :: [];
val it = [1,2,3] : int list
- 0 :: it;
val it = [0,1,2,3] : int list
• Η συνένωση δύο λιστών δεν είναι το ίδιο με τη χρήση ::
-[1,2] :: [3,4];
stdIn: Error: operator and operand don’t agree
operator domain: int list * int list list
operand: int list * int list
in expression:
(1 :: 2 :: nil) :: 3 :: 4 :: nil
Εισαγωγή στη γλώσσα ML 24

Άλλες συναρτήσεις για λίστες

- null [];
val it = true : bool
- null [1,2];
val it = false : bool
- val l = [1,2,3,4];
val l = [1,2,3,4] : int list
- hd l;
val it = 1 : int
- tl l;
val it = [2,3,4] : int list
- length l;
val it = 4 : int
- nil;
val it = [] : ’a list
Εισαγωγή στη γλώσσα ML 25
Ορισμός συναρτήσεων για λίστες

- fun addto (l,v) =


= if null l then nil
= else hd l + v :: addto (tl l,v);
val addto = fn : int list * int -> int list
-
-
- addto ([1,2,3],2);
val it = [3,4,5] : int list
- addto ([1,2,3],~2);
val it = [~1,0,1] : int list

Εισαγωγή στη γλώσσα ML 26

Ορισμός συναρτήσεων για λίστες

- fun map (f, l) =


= if null l then nil
= else f (hd l) :: map (f, tl l);
val map = fn : (’a -> ’b) * ’a list -> ’b list
-
-
- fun add2 x = x + 2;
val add2 = fn : int -> int
- map (add2, [10,11,12]);
val it = [12,13,14] : int list

Εισαγωγή στη γλώσσα ML 27


Ανώνυμες συναρτήσεις (λ-εκφράσεις)

- map (fn x => x + 2, [10,11,12]);


val it = [12,13,14] : int list

• Το πρώτο όρισμα της παραπάνω συνάρτησης λέγεται


λάμδα έκφραση: είναι μια συνάρτηση χωρίς όνομα

• Ο τελεστής fun είναι ισοδύναμος


με μία λάμδα έκφραση
- val add2 = fn x => x + 2;
val add2 = fn : int -> int
- add2 10;
val it = 12 : int

Εισαγωγή στη γλώσσα ML 28

Αναδρομικές λάμδα εκφράσεις

• Πώς καλούμε αναδρομικά κάτι το οποίο δεν έχει όνομα;


• Του δίνουμε ένα!

- let
= val rec f =
= fn x => if null x then nil
= else (hd x + 3) :: f (tl x)
= in
= f
= end
= [1,2,3,4];
val it = [4,5,6,7] : int list

Εισαγωγή στη γλώσσα ML 29


Ταίριασμα προτύπων (pattern matching)

• Στα μαθηματικά, οι συναρτήσεις πολλές φορές ορίζονται


με διαφορετικές εκφράσεις βάση κάποιων συνθηκών
x εάν x ≥ 0
{
f(x) = -x εάν x < 0
• Οι συναρτήσεις της ML δε διαφέρουν και επιτρέπουν τον
ορισμό κατά περιπτώσεις και την αποφυγή της χρήσης if
fun map (f,[]) = []
| map (f,l) = f (hd l) :: map (f,tl l);
• Όμως, ο ορισμός ανά περιπτώσεις είναι ευαίσθητος ως
προς τη σειρά εμφάνισης των συναρτησιακών προτάσεων
fun map (f,l) = f (hd l) :: map (f,tl l)
| map (f,[]) = [];
Εισαγωγή στη γλώσσα ML 30

Καλύτερος ορισμός μέσω ταιριάσματος προτύπων

• Το πρότυπο _ ταιριάζει με όλα τα αντικείμενα


• Το πρότυπο h :: t ταιριάζει με μια λίστα και δένει
– τη μεταβλητή h με την κεφαλή της λίστας και
– τη μεταβλητή t με την ουρά της λίστας
fun map (_, []) = []
| map (f, h::t) = f h :: map (f, t);

Εισαγωγή στη γλώσσα ML 31


Χρήση σταθερών ως πρότυπα

- fun is_zero 0 = "yes";


stdIn: Warning: match nonexhaustive
0 => ...
val is_zero = fn : int -> string
- is_zero 0;
val it = "yes" : string

• Κάθε σταθερά ενός τύπου που υποστηρίζει ισότητα


μπορεί να χρησιμοποιηθεί ως πρότυπο
• Αλλά δεν μπορούμε να γράψουμε
fun is_zero 0.0 = "yes";

Εισαγωγή στη γλώσσα ML 32

Μη εξαντλητικό ταίριασμα προτύπων

• Στο προηγούμενο παράδειγμα, ο τύπος της is_zero


ήταν int -> string, αλλά ταυτόχρονα υπήρξε η
προειδοποίηση “Warning: match nonexhaustive”
• Αυτό σημαίνει ότι η συνάρτηση ορίστηκε με πρότυπα
που δεν εξάντλησαν το πεδίο ορισμού της συνάρτησης
• Κατά συνέπεια, είναι δυνατό να υπάρχουν προβλήματα
χρόνου εκτέλεσης, όπως:
- is_zero 42;
uncaught exception Match: [nonexhaustive
match failure]
raised at ...
Εισαγωγή στη γλώσσα ML 33
Κανόνες ταιριάσματος προτύπων στην ML
• Το πρότυπο _ ταιριάζει με οτιδήποτε
• Μια μεταβλητή είναι ένα πρότυπο που ταιριάζει με
οποιαδήποτε τιμή και δένει τη μεταβλητή με την τιμή
• Μια σταθερά (ενός τύπου ισότητας) είναι ένα πρότυπο που
ταιριάζει μόνο με τη συγκεκριμένη σταθερά
• Μια πλειάδα (x,y,…,z)είναι ένα πρότυπο που ταιριάζει με
κάθε πλειάδα του ίδιου μεγέθους, της οποίας τα περιεχόμενα
ταιριάζουν με τη σειρά τους με τα x,y,…,z
• Μια λίστα [x,y,…,z]είναι ένα πρότυπο που ταιριάζει με κάθε
λίστα του ίδιου μήκους, της οποίας τα στοιχεία ταιριάζουν με
τη σειρά τους με τα x,y,…,z
• Ένα cons h::t είναι ένα πρότυπο που ταιριάζει με κάθε μη
κενή λίστα, της οποίας η κεφαλή ταιριάζει με το h και η ουρά
με το t
Εισαγωγή στη γλώσσα ML 34

Παράδειγμα χρήσης ταιριάσματος προτύπων

• Παραγοντικό με χρήση if-then-else


fun fact n =
if n = 0 then 1 else n * fact (n-1);

• Παραγοντικό με χρήση ταιριάσματος προτύπων


fun fact 0 = 1
| fact n = n * fact (n-1);

• Παρατηρήστε ότι υπάρχει επικάλυψη στα πρότυπα


• Η εκτέλεση δοκιμάζει πρότυπα με τη σειρά που αυτά
εμφανίζονται (από πάνω προς τα κάτω)
Εισαγωγή στη γλώσσα ML 35
Άλλα παραδείγματα

Η παρακάτω δομή είναι πολύ συνηθισμένη σε αναδρομικές


συναρτήσεις που επεξεργάζονται λίστες: μία περίπτωση
για την κενή λίστα (nil) και μία περίπτωση για όταν η
λίστα δεν είναι κενή (h::t).
• Άθροισμα όλων των στοιχείων μιας λίστας
fun sum nil = 0
| sum (h::t) = h + sum t;

• Αριθμός των στοιχείων μιας λίστας με κάποια ιδιότητα


fun ctrue nil = 0
| ctrue (true::t) = 1 + ctrue t
| ctrue (false::t) = ctrue t;
Εισαγωγή στη γλώσσα ML 36

Ένας περιορισμός: γραμμικά πρότυπα

• Δεν επιτρέπεται η χρήση της ίδιας μεταβλητής


περισσότερες από μία φορές στο ίδιο πρότυπο

• Για παράδειγμα, το παρακάτω δεν επιτρέπεται:


fun f (a,a) = … for pairs of equal elements
| f (a,b) = … for pairs of unequal elements

• Αντί αυτού πρέπει να χρησιμοποιηθεί το παρακάτω:


fun f (a,b) =
if (a=b) then … for pairs of equal elements
else … for pairs of unequal elements

Εισαγωγή στη γλώσσα ML 37


Συνδυασμός προτύπων και let

fun halve nil = (nil, nil)


| halve [a] = ([a], nil)
| halve (a::b::cs) =
let
val (x, y) = halve cs
in
(a::x, b::y)
end;
• Με τη χρήση προτύπων στους ορισμούς ενός let,
μπορούμε να “αποσυνθέσουμε” εύκολα ένα αποτέλεσμα
• Η παραπάνω συνάρτηση παίρνει ως όρισμα μια λίστα και
επιστρέφει ένα ζεύγος από λίστες, η κάθε μία από τις
οποίες έχει τα μισά στοιχεία της αρχικής λίστας
Εισαγωγή στη γλώσσα ML 38

Χρήση της συνάρτησης halve

- fun halve nil = (nil, nil)


= | halve [a] = ([a], nil)
= | halve (a::b::cs) =
= let
= val (x, y) = halve cs
= in
= (a::x, b::y)
= end;
val halve = fn : 'a list -> 'a list * 'a list
- halve [1];
val it = ([1],[]) : int list * int list
- halve [1,2];
val it = ([1],[2]) : int list * int list
- halve [1,2,3,4,5,6];
val it = ([1,3,5],[2,4,6]) : int list * int list

Εισαγωγή στη γλώσσα ML 39


Ένα μεγαλύτερο παράδειγμα: Merge Sort

• Η συνάρτηση halve διανείμει τα στοιχεία μιας λίστας σε


δύο περίπου ίσα κομμάτια
• Είναι το πρώτο βήμα για ταξινόμηση συγχώνευσης
• Η συνάρτηση merge συγχωνεύει δύο ταξινομημένες λίστες
- fun merge (nil, ys) = ys
= | merge (xs, nil) = xs
= | merge (x::xs, y::ys) =
= if (x < y) then x :: merge (xs, y::ys)
= else y :: merge (x::xs, ys);
val merge = fn : int list * int list -> int list
- merge ([2],[1,3]);
val it = [1,2,3] : int list
- merge ([1,3,4,7,8],[2,3,5,6,10]);
val it = [1,2,3,3,4,5,6,7,8,10] : int list
Εισαγωγή στη γλώσσα ML 40

Η συνάρτηση Merge Sort

fun mergeSort nil = nil


| mergeSort [a] = [a]
| mergeSort theList =
let
val (x, y) = halve theList
in
merge (mergeSort x, mergeSort y)
end;

Ο τύπος της παραπάνω συνάρτησης είναι


int list -> int list
λόγω του τύπου της συνάρτησης merge

Εισαγωγή στη γλώσσα ML 41


Παράδειγμα χρήσης της Merge Sort

- fun mergeSort nil = nil


= | mergeSort [a] = [a]
= | mergeSort theList =
= let
= val (x, y) = halve theList
= in
= merge(mergeSort x, mergeSort y)
= end;
val mergeSort = fn : int list -> int list
- mergeSort [4,3,2,1];
val it = [1,2,3,4] : int list
- mergeSort [4,2,3,1,5,3,6];
val it = [1,2,3,3,4,5,6] : int list

Εισαγωγή στη γλώσσα ML 42

Φωλιασμένοι ορισμοί συναρτήσεων

• Μπορούμε να ορίσουμε τοπικές συναρτήσεις, ακριβώς


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

Εισαγωγή στη γλώσσα ML 43


(* Sort a list of integers. *)
fun mergeSort nil = nil
| mergeSort [e] = [e]
| mergeSort theList =
let
(* From the given list make a pair of lists
* (x,y), where half the elements of the
* original are in x and half are in y. *)
fun halve nil = (nil, nil)
| halve [a] = ([a], nil)
| halve (a::b::cs) =
let
val (x, y) = halve cs
in
(a::x, b::y)
end;
(* Merge two sorted lists of integers into
* a single sorted list. *)
fun merge (nil, ys) = ys
| merge (xs, nil) = xs
| merge (x::xs, y::ys) =
if (x < y) then x :: merge(xs, y::ys)
else y :: merge(x::xs, ys);

val (x, y) = halve theList


in
merge(mergeSort x, mergeSort y)
end;
Εισαγωγή στη γλώσσα ML 44

Ανακεφαλαίωση της γλώσσας ML

• Βασικοί τύποι της ML: int, real, bool, char, string


• Τελεστές: ~, +, -, *, div, mod, /, ^, ::, @, <, >, <=, >=,
=, <>, not, andalso, orelse
• Επιλογή μεταξύ δύο: if … then … else
• Ορισμός συναρτήσεων: fun, fn => και τιμών: val, let
• Κατασκευή πλειάδων: (x,y,…,z)
• Κατασκευή λιστών: [x,y,…,z], ::, @
• Κατασκευαστές τύπων: *, list, και ->
• Ταίριασμα προτύπων
• Φωλιασμένες συναρτήσεις
Εισαγωγή στη γλώσσα ML 45
Εισαγωγή στους Τύπους

Περιεχόμενα

• Τύποι στις γλώσσες προγραμματισμού


– Τι είναι οι τύποι;
– Κατηγορίες και αναπαραστάσεις των τύπων

• Χρήση και χρησιμότητα των τύπων


– Γλώσσες με στατικό και γλώσσες και δυναμικό σύστημα τύπων
– Έλεγχος τύπων
• κατά το χρόνο μετάφρασης (compile-time checking)
• κατά το χρόνο εκτέλεσης (run-time checking)
– Η έννοια της συντηρητικής ανάλυσης προγραμμάτων
– Ισοδυναμία τύπων

Εισαγωγή στους Τύπους 2


Τι είναι οι τύποι;

• Ένας τύπος είναι ένα σύνολο από τιμές


• Όταν ορίζουμε ότι μια μεταβλητή έχει ένα συγκεκριμένο
τύπο, δηλώνουμε ότι οι τιμές της μεταβλητής θα είναι
πάντα στοιχεία του συγκεκριμένου συνόλου
int n;

• Άρα ένας τύπος είναι ένα σύνολο από τιμές


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

• Το ποια σύνολα θεωρούνται ή δεν θεωρούνται τύποι


εξαρτάται από τη γλώσσα
Εισαγωγή στους Τύπους 3

Πρωτόγονοι και κατασκευαζόμενοι τύποι

• Κάθε τύπος που μπορεί να χρησιμοποιηθεί αλλά δε


μπορεί να οριστεί από ένα πρόγραμμα μιας γλώσσας
είναι ένας πρωτόγονος τύπος της γλώσσας
– Πρωτόγονοι τύποι της ML: int, real, char
– Ένα πρόγραμμα ML δε μπορεί να ορίσει έναν τύπο που να
δουλεύει σαν τον προκαθορισμένο τύπο int

• Κάθε τύπος που μπορεί να οριστεί από ένα πρόγραμμα


(με βάση πρωτόγονους ή ήδη ορισμένους τύπους) είναι
ένας κατασκευαζόμενος τύπος
– Π.χ. κατασκευαζόμενος τύπος στην ML: int list
– Ορίζεται με χρήση του πρωτόγονου τύπου int και του
κατασκευαστή τύπων list

Εισαγωγή στους Τύπους 4


Πρωτόγονοι τύποι

• Ο ορισμός της κάθε γλώσσας είναι αυτός που καθορίζει


ποιοι είναι πρωτόγονοι τύποι της γλώσσας
• Κάποιες γλώσσες ορίζουν τους πρωτόγονους τύπους πιο
αυστηρά από κάποιες άλλες:
– Π.χ. η Java ορίζει τους πρωτόγονους τύπους επακριβώς
– Από την άλλη μεριά, π.χ. η C και η ML αφήνουν κάποια περιθώρια
ελευθερίας στον ορισμό των πρωτόγονων τύπων μεταξύ
διαφορετικών υλοποιήσεων της γλώσσας

Εισαγωγή στους Τύπους 5

Παράδειγμα: πρωτόγονοι τύποι ακεραίων

C: Java:
char byte (1-byte signed)
unsigned char char (2-byte unsigned)
short int short (2-byte signed)
unsigned short int int (4-byte signed)
int long (8-byte signed)
unsigned int Scheme:
long int integer
unsigned long int
Ακέραιοι “απείρου” εύρους
long long
Δεν υπάρχει προκαθορισμένη Haskell:
υλοποίηση, αλλά οι “μεγαλύτεροι” int (4-byte signed)
τύποι πρέπει να έχουν τουλάχιστον Integer (“άπειρο” εύρος)
το εύρος των “μικρότερων” τύπων
Εισαγωγή στους Τύπους 6
Θέματα σχεδιασμού

• Σε ποια σύνολα αντιστοιχούν οι πρωτόγονοι τύποι;


– Τι είναι μέρος του ορισμού της γλώσσας, τι επαφίεται στη
διακριτική ευχέρεια της υλοποίησης της γλώσσας;
– Εάν χρειαστεί, πως μπορεί ένα πρόγραμμα να ανακαλύψει
πληροφορίες σχετικές με τα μέλη του συνόλου;
• (INT_MAX στη C, Int.maxInt στην ML, …)

• Τι λειτουργίες υποστηρίζονται και πώς;


– Λεπτομερείς ορισμοί περί στρογγυλοποίησης, εξαιρέσεων, κ.λπ.

• Η επιλογή της αναπαράστασης είναι καθοριστική για


κάποιες από τις αποφάσεις

Εισαγωγή στους Τύπους 7

Κατασκευαζόμενοι τύποι

• Πρόσθετοι τύποι οι οποίοι ορίζονται με χρήση της


γλώσσας
• Παραδείγματα: απαριθμήσεις, πλειάδες, πίνακες,
συμβολοσειρές, λίστες, ενώσεις, υποτύποι, και τύποι
συναρτήσεων
• Για κάθε έναν από αυτούς, υπάρχει στενή σχέση μεταξύ
του πώς ορίζονται τα σύνολα στα μαθηματικά και του
πώς ορίζονται οι τύποι στις γλώσσες προγραμματισμού

Εισαγωγή στους Τύπους 8


Απαριθμήσεις (enumerations)

• Στα μαθηματικά μπορούμε ορίσουμε ένα σύνολο απλώς


με την απαρίθμηση των μελών του:
S = {a , b , c}
• Πολλές γλώσσες υποστηρίζουν τύπους απαρίθμησης:
C: enum coin {penny, nickel, dime, quarter};
Ada: type GENDER is (MALE, FEMALE);
Pascal: type primaryColors = (red, green, blue);
ML: datatype day = M | Tu | W | Th | F | Sa | Su;

• Ορίζουν ένα νέο τύπο (= σύνολο τιμών)


• Ορίζουν επίσης μια συλλογή από ονοματισμένες
σταθερές αυτού του τύπου (= στοιχεία του συνόλου)
Εισαγωγή στους Τύπους 9

Αναπαράσταση τιμών μιας απαρίθμησης

• Ένας συνήθης τρόπος αναπαράστασης απαριθμήσεων


είναι η χρησιμοποίηση μικρών ακεραίων για τις τιμές
• Η αναπαράσταση μπορεί να είναι εμφανής στον
προγραμματιστή, όπως π.χ. στη C:
enum coin { penny = 1, nickel = 5, dime = 10, quarter = 25 };

enum escapes { BELL = '\a', BACKSPACE = '\b', TAB = '\t',


NEWLINE = '\n', VTAB = '\v', RETURN = '\r' };

Εισαγωγή στους Τύπους 10


Λειτουργίες απαριθμήσεων

• Έλεγχος ισότητας, φυσικά:


fun isWeekend x = (x = Sa orelse x = Su);

• Εάν η “ακέραια φύση” της αναπαράστασης των


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

Pascal: for c := red to blue do p(c)

C: int x = penny + nickel + dime;

Εισαγωγή στους Τύπους 11

Πλειάδες (tuples)

• Το καρτεσιανό γινόμενο δύο ή περισσοτέρων συνόλων


ορίζει σύνολα από πλειάδες:
S = X × Y = {( x , y ) | x ∈ X ∧ y ∈ Y }
• Κάποιες γλώσσες υποστηρίζουν καθαρές πλειάδες:
fun get1 (x : real * real) = #1 x;

• Πολλές άλλες υποστηρίζουν εγγραφές (records), που


είναι πλειάδες τα πεδία των οποίων έχουν ονόματα:
struct complex { type complex = {
C: double rp; rp:real, :ML
double ip; ip:real
}; };
fun getip (x : complex) = #ip x;
Εισαγωγή στους Τύπους 12
Αναπαράσταση των πλειάδων

• Η συνήθης αναπαράσταση των πλειάδων είναι τα πεδία


τους να διατάσσονται το ένα μετά το άλλο στη μνήμη
• Αλλά υπάρχουν πολλές λεπτομέρειες:
– Με ποια σειρά;
– Επιτρέπονται “τρύπες” για ευθυγράμμιση (alignment) των πεδίων
(π.χ. σε αρχές διαφορετικών λέξεων) στη μνήμη;
– Είναι κάτι από όλα αυτά ορατό στον προγραμματιστή;

Εισαγωγή στους Τύπους 13

Παράδειγμα: ANSI C

The members of a structure have addresses increasing in the


order of their declarations. A non-field member of a structure is
aligned at an addressing boundary depending on its type;
therefore, there may be unnamed holes in a structure. If a
pointer to a structure is cast to the type of a pointer to its first
member, the result refers to the first member…

Adjacent field members of structures are packed into


implementation-dependent storage units in an implementation-
dependent direction...

The C Programming Language, 2nd ed.


Brian W. Kernighan and Dennis M. Ritchie

Εισαγωγή στους Τύπους 14


Λειτουργίες πλειάδων

• Επιλογή στοιχείων, φυσικά:


C: x.ip
ML: #ip x

• Άλλες λειτουργίες ανάλογα με το ποιο/πόσο μέρος της


αναπαράστασης είναι εμφανές στον προγραμματιστή:

C: double y = *((double *) &x);


struct person {
char *firstname;
char *lastname;
} p1 = {"dennis","ritchie"};

Εισαγωγή στους Τύπους 15

Ανύσματα (vectors)

• Ανύσματα γνωστού (σταθερού) μήκους:


S = X n
= {( x 1 , K , xn )| ∀ i . xi ∈ X }
• Ανύσματα αγνώστου μήκους:

S = X = U X i

• Τύποι σχετικοί με τα ανύσματα:


– Πίνακες, συμβολοσειρές και λίστες
– Είναι σαν τις πλειάδες αλλά συνήθως έχουν τον ίδιο τύπο
στοιχείων και πολλές παραλλαγές από γλώσσα σε γλώσσα
– Ένα παράδειγμα διαφοροποίησης: δείκτες (indices) σε πίνακες
• Ποιες είναι οι τιμές των δεικτών;
• Καθορίζεται το μέγεθος των πινάκων στο χρόνο μεταγλώττισης ή
στο χρόνο εκτέλεσης;

Εισαγωγή στους Τύπους 16


Τιμές δεικτών

• Στη C, C++, Java και C#:


– Το πρώτο στοιχείο ενός πίνακα a είναι το a[0]
– Οι δείκτες είναι πάντα ακέραιοι που αρχίζουν από το 0

• Η Pascal είναι πιο ευέλικτη:


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

Εισαγωγή στους Τύπους 17

Παράδειγμα πίνακα σε Pascal

type
LetterCount = array['a'..'z'] of Integer;
var
Counts: LetterCount;
begin
Counts['a'] = 1

Εισαγωγή στους Τύπους 18


Σχεδιαστικά θέματα σχετικά με τα ανύσματα

• Τι είδους δείκτες μπορούν να έχουν οι πίνακες;


• Πρέπει το μέγεθος των πινάκων να είναι καθορισμένο
κατά το χρόνο μετάφρασης;
• Μπορεί να αλλάξει (π.χ. επεκταθεί) το μέγεθος δυναμικά;
• Μπορούν οι πίνακες να έχουν πολλές διαστάσεις;
• Είναι ένας πίνακας με πολλές διαστάσεις ισοδύναμος με
έναν πίνακα από πίνακες;
• Πώς διατάσσονται τα στοιχεία ενός πίνακα στη μνήμη;
• Οι συμβολοσειρές έχουν το δικό τους τύπο ή είναι
πίνακες από bytes; Υπάρχει τύπος λίστας;
Εισαγωγή στους Τύπους 19

Ενώσεις (unions)

• Η ένωση δύο συνόλων δίνει ένα καινούριο σύνολο


S = X UY

• Πολλές γλώσσες υποστηρίζουν τύπους ενώσεων:


C: union element { ML: datatype element =
int i; I of int |
double d; R of real;
};

• Η αναπαράσταση των ενώσεων μπορεί να είναι ή μπορεί


να μην είναι εμφανής στον προγραμματιστή:
sizeof(u) == max(sizeof(u.i),sizeof(u.d))
Εισαγωγή στους Τύπους 20
Αυστηρότητα τύπων και ενώσεις

• Στην ML, το μόνο που μπορεί να κάνουμε σε μία ένωση


είναι να εξάγουμε τα περιεχόμενά της
• Και στην περίπτωση αυτή πρέπει να πούμε τι μπορεί να
γίνει με τις τιμές ενώσεων διαφορετικών τύπων:

datatype element =
I of int |
R of real;

fun getReal (R x) = x
| getReal (I x) = real x;

Εισαγωγή στους Τύπους 21

Ενώσεις χωρίς αυστηρό σύστημα τύπων

• Σε μερικές γλώσσες οι λεπτομέρειες της υλοποίησης


των ενώσεων είναι εμφανείς στον προγραμματιστή
• Σε κάποιες περιπτώσεις τα προγράμματα μπορούν να
εκμεταλλευθούν το γεγονός ότι ο τύπος κάποιας τιμής
χάνεται:
union element {
int i;
double d;
};

union element e;
e.i = 100;
double x = e.d;
Εισαγωγή στους Τύπους 22
Τι λέει η ANSI C για τις ενώσεις;

A union may be thought of as a structure all of whose


members begin at offset 0 and whose size is sufficient to
contain any of its members. At most one of the members can
be stored in a union at any time. If a pointer to a union is cast
to the type of a pointer to a member, the result refers to that
member.

In general, a member of a union may not be inspected unless


the value of the union has been assigned using that same
member.

The C Programming Language, 2nd ed.


Brian W. Kernighan and Dennis M. Ritchie

Εισαγωγή στους Τύπους 23

Υποσύνολα (subsets)

• Μπορούμε να ορίσουμε το υποσύνολο που ορίζεται με


βάση ένα κατηγόρημα P :
S = {x ∈ X | P ( x )}
• Κάποιες γλώσσες υποστηρίζουν υποτύπους (subtypes),
με λίγη ή περισσότερη γενικότητα
– Λίγη γενικότητα: Ορισμός υποδιαστημάτων στην Pascal
type digit = 0..9;
– Κάμποση γενικότητα: Ορισμός υποτύπων στην Ada
subtype DIGIT is INTEGER range 0..9;
subtype WEEKDAY is DAY range MON..FRI;
– Πολλή γενικότητα: Ορισμός τύπων στη Lisp μέσω
κατηγορημάτων

Εισαγωγή στους Τύπους 24


Παράδειγμα: Ada Subtypes

type DEVICE is (PRINTER, DISK);

type PERIPHERAL(Unit: DEVICE) is


record
HoursWorking: INTEGER;
case Unit is
when PRINTER =>
Line_count: INTEGER;
when DISK =>
Cylinder: INTEGER;
Track: INTEGER;
end case;
end record;

subtype DISK_UNIT is PERIPHERAL(DISK);

Εισαγωγή στους Τύπους 25

Παράδειγμα: Ορισμός τύπων στη Lisp

(declare (type integer x))

(declare (type (or null cons) x))

(declare (type (and number (not integer)) x))

(declare (type (and integer (satisfies evenp)) x))

Εισαγωγή στους Τύπους 26


Αναπαράσταση των τιμών ενός υποτύπου

• Συνήθως, ο υποτύπος χρησιμοποιεί την ίδια εσωτερική


αναπαράσταση με τον υπερτύπο (supertype) του
• Ερωτήσεις:
– Μπορεί να χρησιμοποιηθεί κάποια πιο “φθηνή” αναπαράσταση;
Π.χ. οι δύο παρακάτω τύποι χρειάζονται τον ίδιο χώρο
αποθήκευσης;
X: 1..9
X: Integer?
– Οι υποτύποι επιβάλλονται και ελέγχονται από το μεταγλωττιστή;
X := 10
X := X + 1?

Εισαγωγή στους Τύπους 27

Λειτουργίες υποτύπων

• Συνήθως οι επιτρεπόμενες λειτουργίες είναι όλες οι


λειτουργίες που είναι διαθέσιμες και για τον υπερτύπο
• Καθώς και κάποιες άλλες που δεν έχουν τόσο πολύ
νόημα στον υπερτύπο:
function toDigit (X: Digit) : Char;

Θέμα για περισυλλογή


Ένας υποτύπος είναι ένα υποσύνολο όλων
των τιμών ενός υπερτύπου, αλλά συνήθως
υποστηρίζει ένα υπερσύνολο από λειτουργίες.

Εισαγωγή στους Τύπους 28


Κάποια λόγια για τις κλάσεις (classes)

• Μια από τις βασικές ιδέες του αντικειμενοστρεφούς


προγραμματισμού
• Μια κλάση είναι ένα είδος τύπου: κάποια δεδομένα και οι
λειτουργίες τους σε “συσκευασία πακέτου”
• Μια υποκλάση είναι ένας υποτύπος: περιλαμβάνει μόνο
ένα υποσύνολο των αντικειμένων, αλλά υποστηρίζει ένα
υπερσύνολο των λειτουργιών
• Περισσότερα για τις κλάσεις στις διαλέξεις των
γλωσσών αντικειμενοστρεφούς προγραμματισμού

Εισαγωγή στους Τύπους 29

Συναρτήσεις (functions)

• Το σύνολο των συναρτήσεων με κάποιο πεδίο ορισμού


και τιμών:
S = D → R = { f | dom f = D ∧ ran f = R }
• Οι περισσότερες γλώσσες υποστηρίζουν την έννοια των
συναρτήσεων:
C: int f(char a, char b) {
return a == b;
}

ML: fun f(a:char, b:char) = (a = b);

Εισαγωγή στους Τύπους 30


Λειτουργίες συναρτήσεων

• Φυσικά, κλήση συναρτήσεων


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

Εισαγωγή στους Τύπους 31

Χρησιμότητα των τύπων

Εισαγωγή στους Τύπους 32


Χρησιμότητα των τύπων

• Βοηθούν στην οργάνωση και τεκμηρίωση προγραμμάτων


– Διαφορετικοί τύποι για διαφορετικές έννοιες
• Αναπαριστάνουν έννοιες από το πεδίο του προβλήματος
– Υποδηλώνουν την προτιθέμενη χρήση των μεταβλητών
• Οι τύποι μπορούν να ελεγχθούν αυτόματα και να μείνουν σε
συμφωνία με αλλαγές στο πρόγραμμα, σε αντίθεση με τα
σχόλια

• Υποδεικνύουν και ανιχνεύουν κάποιου είδους σφάλματα


– Ο έλεγχος τύπων μπορεί να ανιχνεύσει υπολογισμούς χωρίς
σημασιολογία, όπως π.χ. 3 + true - "Bill"

• Υποστηρίζουν τη βελτιστοποίηση (optimization)


– Παράδειγμα: οι short ακέραιοι αποθηκεύονται σε λιγότερα bits
– Αποφυγή ελέγχου εξαιρέσεων ή υπερχείλισης

Εισαγωγή στους Τύπους 33

Σφάλματα τύπων (type errors)

• Όπως είδαμε, ένας τύπος ορίζεται από:


– Τρόπους εισαγωγής τιμών για τον τύπο
– Τρόπους χρησιμοποίησης των τιμών για την παραγωγή νέων
τύπων τιμών

• Κάτω από αυτό το πρίσμα


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

Εισαγωγή στους Τύπους 34


Επισημειώσεις τύπων

• Πολλές γλώσσες απαιτούν, ή τουλάχιστον επιτρέπουν,


επισημειώσεις τύπων σε μεταβλητές, συναρτήσεις, κ.λπ.
• Χρησιμοποιούνται από τον προγραμματιστή για την
παροχή στατικής πληροφορίας τύπων στο σύστημα
υλοποίησης της γλώσσας
• Οι επισημειώσεις είναι επίσης ένα είδος τεκμηρίωσης,
που καθιστά τα προγράμματα πιο εύκολα αναγνώσιμα
από άλλους
• Μέρος της σύνταξης της γλώσσας συνήθως έχει σχέση
με τον ορισμό τύπων (π.χ. *, -> και list στην ML)

Εισαγωγή στους Τύπους 35

Εγγενείς τύποι (intrinsic types)

• Κάποιες γλώσσες χρησιμοποιούν ονομαστικές συμβάσεις


για τον ορισμό των τύπων μεταβλητών
– Σε διαλέκτους της BASIC: S$ ορίζει μια συμβολοσειρά
– Σε διαλέκτους της Fortran: I ορίζει έναν ακέραιο

• Όπως και οι ρητές επισημειώσεις τύπων, οι εγγενείς


τύποι προμηθεύουν με πληροφορία τύπων τόσο την
υλοποίηση της γλώσσας όσο και τον αναγνώστη του
προγράμματος

Εισαγωγή στους Τύπους 36


Απλός συμπερασμός τύπων

• Οι περισσότερες γλώσσες υλοποιούν κάποιες απλές


μορφές συμπερασμού τύπων
• Οι σταθερές έχουν τύπο που καθορίζεται στατικά
Π.χ. στη Java: Η σταθερά 10 έχει τύπο int, η 10L έχει τύπο long

• Οι εκφράσεις με τη σειρά τους μπορεί να έχουν στατικά


καθοριζόμενους τύπους, που συμπεραίνονται εύκολα
από τους τελεστές και τους τύπους των ορισμάτων τους
Π.χ. στη Java: εάν ο a είναι double, τότε η έκφραση a*0 έχει
τύπο double (0.0)

• Η γλώσσα ML πηγαίνει την παραπάνω ιδέα στα άκρα...


Εισαγωγή στους Τύπους 37

Έλεγχος τύπων: Στατικός και δυναμικός

Εισαγωγή στους Τύπους 38


Έλεγχος χρόνου μετάφρασης έναντι εκτέλεσης

• Οι γλώσσες Lisp, Prolog, Python, Ruby, … ελέγχουν τους


τύπους στο χρόνο εκτέλεσης του προγράμματος
Π.χ. στη Lisp σε μια έκφραση (car x) ελέγχουμε εάν το x είναι
λίστα πριν πάρουμε το car του x
• Οι γλώσσες ML και Haskell ελέγχουν τους τύπους κατά
το χρόνο μετάφρασης του προγράμματος
Π.χ. σε μια κλήση f(x) πρέπει να έχουμε f: A → B και x ∈ A

• Ένα από τα κύρια “πάρε-δώσε” του σχεδιασμού γλωσσών


– Και οι δύο τρόποι αποφεύγουν τα σφάλματα
– Ο δυναμικός έλεγχος καθυστερεί την εκτέλεση
– Ο στατικός έλεγχος περιορίζει την εκφραστικότητα
• Λίστες σε Lisp: τα στοιχεία μπορούν να έχουν διαφορετικούς τύπους
• Λίστες σε ML: όλα τα στοιχεία πρέπει να έχουν τον ίδιο τύπο

Εισαγωγή στους Τύπους 39

Στατικός έλεγχος τύπων

• Στο στατικό έλεγχο τύπων, ο τύπος της κάθε έκφρασης


καθορίζεται πριν την εκτέλεση του προγράμματος
• Ο μεταγλωττιστής τυπώνει μηνύματα λάθους όταν οι
τύποι που εξάγονται στατικά τον κάνουν να καταλάβει
κάποια ασυνέπεια ή ασυμφωνία στη χρήση των τύπων
– Όσον αφορά σε τελεστές: 1 + "abc"
– Όσον αφορά σε συναρτήσεις: round("abc")
– Όσον αφορά σε εντολές της γλώσσας: if "abc" then …

• Πολλές μοντέρνες γλώσσες προγραμματισμού έχουν


στατικά (ή ως επί το πλείστον στατικά) συστήματα
τύπων

Εισαγωγή στους Τύπους 40


Δυναμικός έλεγχος τύπων (dynamic typing)

• Σε πολλές άλλες γλώσσες (π.χ. Lisp, Scheme, Smalltalk,


Erlang, Prolog, Python, Ruby, …), τα προγράμματα δεν
ελέγχονται στατικά για πιθανά σφάλματα τύπων
• Ελέγχονται όμως δυναμικά για τέτοιου είδους σφάλματα
• Με άλλα λόγια, κατά το χρόνο εκτέλεσης, η υλοποίηση
της γλώσσας ελέγχει ότι τα ορίσματα των τελεστών
είναι τύπων οι οποίοι είναι συμβατοί με τους τελεστές

Εισαγωγή στους Τύπους 41

Παράδειγμα: Lisp

• Μια συνάρτηση σε Lisp που προσθέτει δύο αριθμούς:


(defun f (a b) (+ a b))
• Θα εγείρει εξαίρεση εάν ο a ή ο b δεν είναι αριθμοί
• Κλήσεις με ορίσματα λάθος τύπου, π.χ. (f nil nil),
δεν υποδεικνύονται κατά το χρόνο μεταγλώττισης
• Στο δυναμικό έλεγχο τύπων τα παραπάνω λάθη
πιάνονται κατά το χρόνο εκτέλεσης του προγράμματος
– Κατά κάποιο τρόπο, στις γλώσσες με δυναμικό έλεγχο τύπων οι
τύποι χρησιμοποιούνται παραπάνω από αυτές με στατικό έλεγχο,
διότι οι τύποι πρέπει να ελεγχθούν κατά το χρόνο εκτέλεσης
– Αυτό σημαίνει ότι η υλοποίηση της γλώσσας πρέπει να
διατηρήσει πληροφορία για τον τύπο της κάθε μεταβλητής
Εισαγωγή στους Τύπους 42
Εκφραστικότητα

• Στη Lisp, είναι δυνατό να γράψουμε τη συνάρτηση


(lambda (x) (cond ((less x 10) x) (T (car x))))

Κάποιες χρήσεις της συνάρτησης μπορεί να οδηγήσουν σε


σφάλματα τύπων, κάποιες άλλες όμως όχι

• Ο στατικός έλεγχος τύπων είναι πάντα συντηρητικός


if (big-hairy-boolean-expression)
then ((lambda (x) … ) 5)
else ((lambda (x) … ) nil)
Δε μπορούμε να αποφασίσουμε κατά το χρόνο μεταγλώττισης εάν
θα υπάρξει κάποιο σφάλμα τύπου

Εισαγωγή στους Τύπους 43

Στατικός έλεγχος τύπων έναντι δυναμικού

• Δεν είναι όλα άσπρα ή μαύρα…


• Γλώσσες με στατικό έλεγχο τύπων πολλές φορές
χρησιμοποιούν κάποιο δυναμικό έλεγχο
– Π.χ. λόγω της ύπαρξης υποτύπων (subtyping),
ειδικά στις αντικειμενοστρεφείς γλώσσες προγραμματισμού
– Π.χ. λόγω υπερφορτωμένων τελεστών

• Γλώσσες με δυναμικό έλεγχο τύπων πολλές φορές


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

• Κάποιες γλώσσες επιτρέπουν στον προγραμματιστή να


ελέγξει τύπους κατά την εκτέλεση του προγράμματος:
Π.χ. η Java επιτρέπει τον έλεγχο του τύπου κάποιου αντικειμένου
με χρήση του τελεστή instanceof
Π.χ. η Modula-3 επιτρέπει τη διακλάδωση (branch) με βάση τον
τύπο κάποιου αντικειμένου με χρήση της έκφρασης typecase

• Τα παραπάνω απαιτούν ότι η πληροφορία για τύπους


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

Εισαγωγή στους Τύπους 45

Αυστηρά και ασθενή συστήματα τύπων

• Η επιδίωξη του ελέγχου των τύπων είναι η αποφυγή της


εφαρμογής τελεστών σε ορίσματα των οποίων ο τύπος
δεν έχει νόημα για το συγκεκριμένο τελεστή
• Σε κάποιες γλώσσες, όπως στην ML και στη Java, ο
έλεγχος τύπων είναι αρκετά εξονυχιστικός και
εξασφαλίζει σε μεγάλο βαθμό το παραπάνω—λέμε ότι η
γλώσσα έχει αυστηρό σύστημα τύπων (strong typing)
• Σε πολλές γλώσσες (όπως π.χ. η C) αυτό δε συμβαίνει:
το σύστημα τύπων είναι ασθενές (weak) και διάτρητο
– Αυτό παρέχει ευελιξία στον προγραμματιστή
– Αλλά δεν προσφέρει αρκετή ασφάλεια
Εισαγωγή στους Τύπους 46
Σχετική ασφάλεια τύπων των γλωσσών

Μη ασφαλείς: Η οικογένεια BCPL συμπεριλαμβανομένης


της C και της C++
– “Σουλουπώματα” τύπων (type casts), αριθμητική με δείκτες, …

Περίπου ασφαλείς: Η οικογένεια της Algol, Pascal, Ada


– Ξεκρέμαστοι δείκτες
• Δέσμευσε ένα δείκτη p σε έναν ακέραιο, αποδέσμευσε τη μνήμη που
δείχνει ο p, και στη συνέχεια χρησιμοποίησε την τιμή που δείχνει ο p
• Καμία γλώσσα με ρητή αποδέσμευση μνήμης δεν προσφέρει πλήρη
ασφάλεια τύπων!

Ασφαλείς: Η οικογένεια της Lisp, η ML, η Smalltalk, και οι


Java, C#
– Lisp, Smalltalk, Prolog, Erlang, Ruby: γλώσσες δυναμικών τύπων
– ML, Haskell, Java, C#: γλώσσες στατικών τύπων (statically typed)
Εισαγωγή στους Τύπους 47

Εργαλεία στατικής ανάλυσης προγραμμάτων

• Τομέας με αρκετή ερευνητική δραστηριότητα πρόσφατα


– Lint, Purify, Coverity, PreFix, PreFast, Dialyzer, Valgrind, …

• Δύο κατηγορίες ανάλυσης


– Συντηρητικές (= Sound for correctness)
• Εάν η ανάλυση πει ότι το πρόγραμμα είναι σωστό, τότε είναι
• Εάν η ανάλυση πει “όχι σωστό”, μπορεί να μην υπάρχουν λάθη
– Μη συντηρητικές (= Sound for errors)
• Εάν η ανάλυση πει “σωστό”, το πρόγραμμα μπορεί να είναι
εσφαλμένο
• Εάν η ανάλυση βρει κάποιο πρόβλημα, τότε όντως υπάρχει
κάποια “ανωμαλία ” στο πρόγραμμα
– Μια καλύτερη κατηγοριοποίηση, κατά τη γνώμη μου:
• Συντηρητικά εργαλεία χρειάζονται για απόδειξη ορθότητας
• Μη συντηρητικά εργαλεία για εύρεση σφαλμάτων (bug finding)
Εισαγωγή στους Τύπους 48
Θέματα ισοδυναμίας τύπων

Εισαγωγή στους Τύπους 49

Ισοδυναμία τύπων

• Πότε δύο τύποι θεωρούνται ίδιοι;


• Σημαντική ερώτηση τόσο για το στατικό όσο και για το
δυναμικό έλεγχο τύπων
• Για παράδειγμα, η γλώσσα μπορεί να επιτρέπει μια
ανάθεση a := b μόνο εάν η μεταβλητή b έχει “τον ίδιο”
τύπο με την a
• Διαφορετικές γλώσσες ορίζουν την ισοδυναμία των
τύπων με διαφορετικούς τρόπους

Εισαγωγή στους Τύπους 50


Ισοδυναμίες τύπων

• Ισοδυναμία ονόματος (name equivalence): δύο τύποι


είναι ίδιοι αν και μόνο αν έχουν το ίδιο όνομα
• Ισοδυναμία δομής (structural equivalence): δύο τύποι
είναι ίδιοι αν και μόνο αν έχουν προκύψει από τους ίδιους
πρωτόγονους τύπους με χρήση της ίδιας σειράς ίδιων
κατασκευαστών τύπων

• (Οι παραπάνω, δεν είναι οι μόνοι τρόποι ορισμού της


ισοδυναμίας αλλά είναι οι πιο εύκολοι να εξηγηθούν.)
Εισαγωγή στους Τύπους 51

Παράδειγμα ισοδυναμίας τύπων

type irpair1 = int * real;


type irpair2 = int * real;
fun f(x:irpair1) = #1 x;

• Τι θα συμβεί εάν περάσουμε ως όρισμα στην f μια


παράμετρο τύπου irpair2;
– Η ισοδυναμία ονόματος δεν το επιτρέπει: irpair2 και irpair1
είναι διαφορετικά ονόματα
– Η ισοδυναμία δομής το επιτρέπει: οι τύποι κατασκευάζονται με
τον ίδιο τρόπο

• Στην ML επιτρέπεται: η γλώσσα υποστηρίζει ισοδυναμία


δομής

Εισαγωγή στους Τύπους 52


Παράδειγμα ισοδυναμίας τύπων

var
Counts1: array['a'..'z'] of Integer;
Counts2: array['a'..'z'] of Integer;

• Τι θα συμβεί εάν προσπαθήσουμε να αναθέσουμε τον


πίνακα Counts1 στον πίνακα Counts2;
– Η ισοδυναμία ονόματος δεν το επιτρέπει: οι τύποι των πινάκων
Counts1 και Counts2 δεν έχουν τα ίδια ονόματα
– Η ισοδυναμία δομής το επιτρέπει: οι τύποι κατασκευάζονται με
τον ίδιο τρόπο

• Οι περισσότερες υλοποιήσεις της Pascal δεν επιτρέπουν


την παραπάνω ανάθεση

Εισαγωγή στους Τύπους 53

Συμπερασματικά

• Οι τύποι παίζουν σημαντικό ρόλο σε πολλές γλώσσες


προγραμματισμού
– Για την οργάνωση και την τεκμηρίωση ενός προγράμματος
– Για την αποφυγή σφαλμάτων λογισμικού
– Για την παροχή πληροφορίας στο μεταγλωττιστή της γλώσσας

Εισαγωγή στους Τύπους 54


Βασική ερώτηση για τα συστήματα τύπων

• Πόσο μέρος της αναπαράστασης κάνει το σύστημα


τύπων εμφανές στον προγραμματιστή;
– Μερικοί προγραμματιστές προτιμούν γλώσσες σαν τη C, διότι
• Προσφέρουν τη δυνατότητα για απ’ ευθείας προσπέλαση στη
λεπτομερή αναπαράσταση των τύπων όταν για κάποιο λόγο
αυτό είναι επιθυμητό
– Μερικοί άλλοι προτιμούν γλώσσες σαν την ML, διότι
• Κρύβουν τις λεπτομέρειες της υλοποίησης των τύπων και
• Προσφέρουν “καθαρούς” τρόπους χρησιμοποίησης
δεδομένων που επιτρέπουν την ανάπτυξη προγραμμάτων
χωρίς σφάλματα κάποιου είδους, και διευκολύνουν την
απόδειξη της ορθότητάς τους

Εισαγωγή στους Τύπους 55

Η γλώσσα ML σε βάθος
Τι σημαίνουν οι τύποι συναρτήσεων στην ML

• f : A → B σημαίνει:
– Για κάθε x ∈ A,

για κάποιο στοιχείο y = f(x) ∈ B


f(x) = ατέρμονη εκτέλεση
η εκτέλεση τερματίζει εγείροντας κάποια εξαίρεση

• Με λόγια:
“εάν η αποτίμηση f(x) τερματίσει κανονικά, τότε f(x)∈ B”

• Δηλαδή, η πρόσθεση δε θα εκτελεστεί σε μια έκφραση


της μορφής f(x)+3 εάν η f(x) εγείρει κάποια εξαίρεση

Η γλώσσα ML σε βάθος 2

Επισημειώσεις τύπων (type annotations)

- fun prod (a,b) = a*b;


val prod = fn : int * int -> int

• Γιατί int και όχι real;


• Διότι ο προεπιλεγμένος τύπος (default type) του
αριθμητικού τελεστή * (όπως και των +, –) είναι
int * int -> int
• Αν θέλουμε να χρησιμοποιήσουμε τη συνάρτηση με
ορίσματα τύπου real μπορούμε να βάλουμε μια
υποσημείωση τύπου στα συγκεκριμένα ορίσματα

Η γλώσσα ML σε βάθος 3
Παράδειγμα επισημειώσεων τύπων στην ML

- fun prod (a:real,b:real):real = a*b;


val prod = fn : real * real -> real

• Οι επισημειώσεις τύπων αποτελούνται από μια άνω κάτω


τελεία και έναν τύπο και μπορούν να μπουν παντού
• Όλοι τα παρακάτω ορισμοί είναι ισοδύναμοι:
fun prod (a,b):real = a * b;
fun prod (a:real,b) = a * b;
fun prod (a,b:real) = a * b;
fun prod (a,b) = (a:real) * b;
fun prod (a,b) = a * b:real;
fun prod (a,b) = (a*b):real;
fun prod ((a,b):real * real) = a*b;
Η γλώσσα ML σε βάθος 4

Συναρτήσεις μετατροπής τύπων


- real 123;
val it = 123.0 : real
- floor 3.6;
val it = 3 : int
- str #"a";
val it = "a" : string

Ενσωματωμένες συναρτήσεις μετατροπής τύπων:


– real (int → real),
– floor (real → int), ceil (real → int),
– round (real → int), trunc (real → int),
– ord (char → int),
– chr (int → char),
– str (char → string)
Η γλώσσα ML σε βάθος 5
Σύνταξη ταιριάσματος

• Ένας κανόνας έχει την παρακάτω σύνταξη στην ML:


<rule> ::= <pattern> => <expression>
• Ένα ταίριασμα αποτελείται από έναν ή περισσότερους
κανόνες που διαχωρίζονται μεταξύ τους από ‘|’ :
<match> ::= <rule> | <rule> '|' <match>
• Σε ένα ταίριασμα κάθε κανόνας πρέπει να έχει τον ίδιο
τύπο με την έκφραση (expression) στο δεξί μέρος του
κανόνα
• Ένα ταίριασμα δεν είναι έκφραση από μόνο του, αλλά
αποτελεί μέρος διαφόρων εκφράσεων της ML
Η γλώσσα ML σε βάθος 6

Εκφράσεις case

- case 1+1 of
= 3 => "three" |
= 2 => "two" |
= _ => "hmmm...";
val it = "two" : string

• Έχουν τη σύνταξη:
<case-expr> ::= case <expression> of <match>
• Η έκφραση case της ML είναι μια πολύ ισχυρή δομή—και
αντίθετα με τις περισσότερες άλλες γλώσσες, μπορεί να
κάνει περισσότερα από απλή σύγκριση με σταθερές

Η γλώσσα ML σε βάθος 7
Παράδειγμα χρήσης case

case list of
_::_::c::_ => c |
_::b::_ => b |
a::_ => a |
nil => 0

• Η τιμή αυτής της έκφρασης είναι:


– το τρίτο στοιχείο της λίστας list, αν η λίστα έχει τουλάχιστον τρία
στοιχεία, ή
– το δεύτερο στοιχείο της λίστας αν η λίστα έχει μόνο δύο στοιχεία
– το πρώτο στοιχείο της λίστας list αν έχει μόνο ένα, ή
– ο ακέραιος 0 αν η λίστα list είναι κενή
• Λόγω του τελευταίου κανόνα, η λίστα πρέπει να είναι μια λίστα ακεραίων

Η γλώσσα ML σε βάθος 8

Η έκφραση case είναι μια γενίκευση της if

if exp1 then exp2 else exp3

case exp1 of
true => exp2 |
false => exp3

Οι δύο παραπάνω εκφράσεις είναι ισοδύναμες


Με άλλα λόγια, η έκφραση if-then-else είναι ειδική
περίπτωση μιας έκφρασης case

Η γλώσσα ML σε βάθος 9
Αποτίμηση “βραχυκύκλωσης” στην ML

- true orelse 1 div 0 = 0;


val it = true : bool

• Οι τελεστές andalso και orelse “βραχυκυκλώνουν”


(short-circuit) στην ML:
– Εάν η έκφραση του πρώτου ορίσματος του orelse αποτιμάται
ως αληθής (true), η έκφραση του δεύτερου δεν αποτιμάται
– Παρόμοια, εάν το πρώτο όρισμα του andalso είναι ψευδές

• Με βάση το “γράμμα” της θεωρίας, δεν είναι πραγματικοί


τελεστές αλλά λέξεις κλειδιά
• Αυτό διότι, σε μια γλώσσα σαν την ML, όλοι οι τελεστές
αποτιμούν πλήρως τα ορίσματά τους
Η γλώσσα ML σε βάθος 10

Πολυμορφικές συναρτήσεις για λίστες

• Αναδρομική συνάρτηση που υπολογίζει το μήκος μιας


λίστας (οποιουδήποτε τύπου)
- fun length x =
= if null x then 0
= else 1 + length (tl x);
val length = fn : 'a list -> int
- length [true,false,true];
val it = 3 : int
- length [4.0,3.0,2.0,1.0];
val it = 4 : int

Σημείωση: η συνάρτηση length είναι μέρος της ML, οπότε


ο παραπάνω ορισμός είναι περιττός
Η γλώσσα ML σε βάθος 11
Πολυμορφισμός για τύπους ισότητας
- fun length_eq x =
= if x = [] then 0
= else 1 + length_eq (tl x);
val length_eq = fn : ''a list -> int
- length_eq [true,false,true];
val it = 3 : int
- length_eq [4.0,3.0,2.0,1.0];
Error: operator and operand don't agree
[equality type required]

• Μεταβλητές τύπων που αρχίζουν με δύο αποστρόφους, όπως ο ''a,


περιορίζονται σε τύπους ισότητας

• Η ML συμπεραίνει αυτόν τον περιορισμό διότι συγκρίναμε τη


μεταβλητή x για ισότητα με την κενή λίστα. Αυτό δε θα συνέβαινε
εάν είχαμε χρησιμοποιήσει τη συνθήκη null x αντί για την x=[]
Η γλώσσα ML σε βάθος 12

Αποδοτικές συναρτήσεις για λίστες

• Αναστροφή μιας λίστας


fun reverse nil = nil
| reverse (x::xs) = (reverse xs) @ [x];

• Ερωτήσεις:
– Πόσο αποδοτική είναι η συνάρτηση reverse?
– Μπορούμε να αναστρέψουμε μια λίστα με ένα μόνο πέρασμα;

Η γλώσσα ML σε βάθος 13
Πιο αποδοτική συνάρτηση reverse

fun reverse xs =
let
fun rev (nil, z) = z
| rev (y::ys, z) = rev (ys, y::z)
in
rev (xs, nil)
end;

1 3
2 2 2 2
3 3 1 3 1 1

Η γλώσσα ML σε βάθος 14

Συναρτήσεις Υψηλής Τάξης

Η γλώσσα ML σε βάθος 15
Η λέξη κλειδί op

- op *;
val it = fn : int * int -> int
- quicksort ([1,4,3,2,5], op <);
val it = [1,2,3,4,5] : int list

• Οι δυαδικοί τελεστές είναι ειδικές συναρτήσεις


• Όμως μερικές φορές θέλουμε να τις χρησιμοποιήσουμε
σαν κοινές συναρτήσεις: για παράδειγμα, να περάσουμε
τον τελεστή < σαν όρισμα τύπου int * int -> bool
• Η λέξη κλειδί op πριν από κάποιο τελεστή επιστρέφει τη
αντίστοιχη συνάρτηση

Η γλώσσα ML σε βάθος 16

Συναρτήσεις υψηλής τάξης

• Κάθε συνάρτηση έχει μία τάξη (order):


– Μια συνάρτηση που δεν παίρνει άλλες συναρτήσεις ως
παραμέτρους και δεν επιστρέφει ως αποτέλεσμα μια άλλη
συνάρτηση έχει τάξη 1
– Μια συνάρτηση που παίρνει άλλες συναρτήσεις ως παραμέτρους
ή επιστρέφει ως αποτέλεσμα μια άλλη συνάρτηση έχει τάξη n+1,
όπου n είναι η μέγιστη τάξη των παραμέτρων της και του
αποτελέσματός της
• Η συνάρτηση quicksort που μόλις είδαμε είναι
συνάρτηση δεύτερης τάξης
- quicksort;
val it = fn :
('a list) * ('a * 'a -> bool) -> 'a list

Η γλώσσα ML σε βάθος 17
Πρακτική εξάσκηση

• Τι τάξεως είναι οι συναρτήσεις της ML με τους


παρακάτω τύπους;
int * int -> bool
int list * (int * int -> bool) -> int list
int -> int -> int
(int -> int) * (int -> int) -> (int -> int)
int -> bool -> real -> string

• Τι μπορούμε να πούμε για την τάξη της συνάρτησης με


τον παρακάτω τύπο;
('a -> 'b) * ('c -> 'a) -> 'c -> 'b
Η γλώσσα ML σε βάθος 18

Προκαθορισμένες συναρτήσεις υψηλής τάξης

• Τρεις σημαντικές προκαθορισμένες συναρτήσεις


υψηλής τάξης:
1. map
2. foldr
3. foldl
• Η foldr και η foldl είναι παρόμοιες

Η γλώσσα ML σε βάθος 19
Η συνάρτηση map

• Εφαρμόζει μια συνάρτηση σε κάθε στοιχείο μιας λίστας και


επιστρέφει τα αποτελέσματα της εφαρμογής σε μια νέα λίστα
- map ~ [1,2,3,4];
val it = [~1,~2,~3,~4] : int list
- map (fn x => x+1) [1,2,3,4];
val it = [2,3,4,5] : int list
- map (fn x => x mod 2 = 0) [1,2,3,4];
val it = [false,true,false,true] : bool list
- map (op +) [(1,2),(3,4),(5,6)];
val it = [3,7,11] : int list
- val f = map (op +);
val f = fn : (int * int) list -> int list
- f [(1,2),(3,4)];
val it = [3,7] : int list
Η γλώσσα ML σε βάθος 20

Η συνάρτηση foldr

• Συνδυάζει, μέσω μιας συνάρτησης, όλα τα στοιχεία μιας


λίστας
• Παίρνει ως ορίσματα μια συνάρτηση f, μια αρχική τιμή c,
και μια λίστα x = [x1, …, xn] και υπολογίζει την τιμή:
f ( x 1 , f ( x 2 , L f ( x n − 1 , f ( x n , c ))L ))

• Για παράδειγμα η κλήση:


foldr (op +) 0 [1,2,3,4]
αποτιμάται σε 1+(2+(3+(4+0)))=10

Η γλώσσα ML σε βάθος 21
Παραδείγματα χρήσης foldr
- foldr (op +) 0 [1,2,3,4];
val it = 10 : int
- foldr (op * ) 1 [1,2,3,4];
val it = 24 : int
- foldr (op ^) "" ["abc","def","ghi"];
val it = "abcdefghi" : string
- foldr (op ::) [5] [1,2,3,4];
val it = [1,2,3,4,5] : int list
- foldr;
val it = fn : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b
- foldr (op +);
val it = fn : int -> int list -> int
- foldr (op +) 0;
val it = fn : int list -> int
- val addup = foldr (op +) 0;
val addup = fn : int list -> int
- addup [1,2,3,4,5];
val it = 15 : int
Η γλώσσα ML σε βάθος 22

Η συνάρτηση foldl

• Συνδυάζει, μέσω μιας συνάρτησης, όλα τα στοιχεία μιας


λίστας (όπως η foldr)
• Παίρνει ως ορίσματα μια συνάρτηση f, μια αρχική τιμή c,
και μια λίστα x = [x1, …, xn] και υπολογίζει την τιμή:
f ( x n , f ( x n − 1 , L f ( x 2 , f ( x 1 , c ))L ))

• Για παράδειγμα η κλήση:


foldl (op +) 0 [1,2,3,4]
αποτιμάται σε 4+(3+(2+(1+0)))=10

Σημείωση: Η foldr αποτιμήθηκε ως 1+(2+(3+(4+0)))=10


Η γλώσσα ML σε βάθος 23
Παραδείγματα χρήσης foldl

• Η foldl αρχίζει από αριστερά, η foldr από τα δεξιά


• Φυσικά, δεν υπάρχει κάποια διαφορά όταν η συνάρτηση
είναι αντιμεταθετική και προσεταιριστική, όπως οι + και *
• Για άλλες συναρτήσεις όμως υπάρχει διαφορά
- foldr (op ^) "" ["abc","def","ghi"];
val it = "abcdefghi" : string
- foldl (op ^) "" ["abc","def","ghi"];
val it = "ghidefabc" : string
- foldr (op -) 0 [1,2,3,4];
val it = ~2 : int
- foldl (op -) 0 [1,2,3,4];
val it = 2 : int
Η γλώσσα ML σε βάθος 24

Δηλώσεις Τύπων Δεδομένων

Η γλώσσα ML σε βάθος 25
Ορισμοί τύπων δεδομένων

• Προκαθορισμένος τύπος, αλλά όχι πρωτόγονος στην ML

datatype bool = true | false;

• Παραμετρικός κατασκευαστής τύπου (parametric type


constructor) για λίστες:
datatype 'e list = nil
| :: of 'e * 'e list

• Ορίζεται για την ML στην ML!

Η γλώσσα ML σε βάθος 26

Ορισμοί τύπων δεδομένων

• Έχουν τη γενική μορφή


datatype <name> = <clause> | … | <clause>
<clause> ::= <constructor> | <constructor> of <type>

• Παραδείγματα:
– datatype color = Red | Yellow | Blue
• στοιχεία : Red, Yellow, και Blue
– datatype atom = Atm of string | Nmbr of int
• στοιχεία : Atm("a"), Atm("b"), …, Nmbr(0), Nmbr(1), ...
– datatype list = Nil | Cons of atom * list
• στοιχεία : Nil, Cons(Atm("a"),Nil), …
Cons(Nmbr(2),Cons(Atm("ugh"),Nil)), ...

Η γλώσσα ML σε βάθος 27
Ορισμοί αναδρομικών τύπων δεδομένων
datatype ’d tree = Leaf of ’d
| Node of ’d * ’d tree * ’d tree;

• Παράδειγμα στιγμιότυπου δένδρου


4
Node(4, Node(3,Leaf(1),Leaf(2)),
Node(5,Leaf(6),Leaf(7))) 3 5

1 2 6 7

• Αναδρομική συνάρτηση χρήσης του τύπου δεδομένων


fun sum (Leaf n) = n
| sum (Node (n,t1,t2)) = n + sum(t1) + sum(t2);

Η γλώσσα ML σε βάθος 28

Αυστηρό σύστημα τύπων


- datatype flip = Heads | Tails;
datatype flip = Heads | Tails
- fun isHeads x = (x = Heads);
val isHeads = fn : flip -> bool
- isHeads Tails;
val it = false : bool
- isHeads Mon;
Error: operator and operand don't agree [tycon mismatch]
operator domain: flip
operand: day

• Η ML είναι αυστηρή σε σχέση με τους νέους τύπους,


ακριβώς όπως θα περιμέναμε
• Σε αντίθεση π.χ. με τις enum δηλώσεις της C, οι
λεπτομέρειες της υλοποίησης δεν είναι εμφανείς στον
προγραμματιστή
Η γλώσσα ML σε βάθος 29
Κατασκευαστές έναντι συναρτήσεων
- datatype exint = Value of int | PlusInf | MinusInf;
datatype exint = MinusInf | PlusInf | Value of int
- PlusInf;
val it = PlusInf : exint
- MinusInf;
val it = MinusInf : exint
- Value;
val it = fn : int -> exint
- Value 3;
val it = Value 3 : exint

• Ο Value είναι ένας κατασκευαστής δεδομένων με μία


παράμετρο: την τιμή του ακεραίου int που αποθηκεύει
• Δείχνει σα συνάρτηση που παίρνει έναν ακέραιο (int)
και επιστρέφει έναν exint που περιέχει τον ακέραιο
Η γλώσσα ML σε βάθος 30

Όμως ένας Value δεν είναι int

- val x = Value 5;
val x = Value 5 : exint
- x + x;
Error: overloaded variable not defined at type symbol: +
type: exint

• Ένας Value 5 είναι ένας exint, όχι ένας ακέραιος


(int), παρότι εμπεριέχει έναν
• Μπορούμε να ανακτήσουμε τις παραμέτρους ενός
κατασκευαστή χρησιμοποιώντας ταίριασμα προτύπων
• Κατά συνέπεια, ο κατασκευαστής Value δεν είναι
συνάρτηση: οι κανονικές συναρτήσεις δε μπορούν να
χρησιμοποιηθούν με αυτόν τον τρόπο ως πρότυπα
Η γλώσσα ML σε βάθος 31
Κατασκευαστές και ταίριασμα προτύπων
- fun square PlusInf = PlusInf
= | square MinusInf = PlusInf
= | square (Value x) = Value (x*x);
val square = fn : exint -> exint
- square MinusInf;
val it = PlusInf : exint
- square (Value 3);
val it = Value 9 : exint

• Διαχειριζόμαστε νέους τύπους δεδομένων με


συναρτήσεις σαν την παραπάνω που ορίζονται μέσω
ταιριάσματος προτύπων
• Επειδή ένας exint είναι είτε PlusInf, ή MinusInf, ή
Value, η παραπάνω συνάρτηση είναι εξαντλητική ως
προς το ταίριασμα προτύπων
Η γλώσσα ML σε βάθος 32

Χειρισμός εξαιρέσεων στην ML

• Μέσω ταιριάσματος προτύπων μπορούμε επίσης να


χειριστούμε εξαιρέσεις
- fun square PlusInf = PlusInf
= | square MinusInf = PlusInf
= | square (Value x) = Value (x*x)
= handle Overflow => PlusInf;
val square = fn : exint -> exint
- square (Value 10000);
val it = Value 100000000 : exint
- square (Value 100000);
val it = PlusInf : exint

• Θα δούμε περισσότερα για τις εξαιρέσεις στη Java


Η γλώσσα ML σε βάθος 33
Ένα ακόμα παράδειγμα: bunch

datatype 'x bunch =


One of 'x |
Group of 'x list;

• Ένα 'x bunch είναι είτε ένα πράγμα τύπου 'x, είτε μια
λίστα από πράγματα τύπου 'x
• Όπως συνήθως, η ML συμπεραίνει τύπους αυτόματα:

- One 1.0;
val it = One 1.0 : real bunch
- Group [true,false];
val it = Group [true,false] : bool bunch

Η γλώσσα ML σε βάθος 34

Παράδειγμα: Πολυμορφικός συμπερασμός

• Η ML μπορεί να συμπεράνει πολυμορφικούς bunch


τύπους, αλλά δεν χρειάζεται πάντα να τους επιλύσει
πλήρως, όπως για παράδειγμα συμβαίνει όταν σε αυτούς
περιλαμβάνονται λίστες

- fun size (One _) = 1


= | size (Group x) = length x;
val size = fn : 'a bunch -> int
- size (One 1.0);
val it = 1 : int
- size (Group [true,false]);
val it = 2 : int

Η γλώσσα ML σε βάθος 35
Παράδειγμα: Μη πολυμορφικός συμπερασμός

- fun sum (One x) = x


= | sum (Group xlist) = foldr op + 0 xlist;
val sum = fn : int bunch -> int
- sum (One 5);
val it = 5 : int
- sum (Group [1,2,3]);
val it = 6 : int

• Χρησιμοποιήσαμε τον τελεστή + (ως όρισμα της foldr)


στα στοιχεία της λίστας
• Κατά συνέπεια, η ML μπορεί να συμπεράνει ότι ο τύπος
της παραμέτρου της συνάρτησης sum είναι int bunch
Η γλώσσα ML σε βάθος 36

Αυτή ήταν η ML

• … ή τουλάχιστον, όλη η ML που θα δούμε στις διαλέξεις


• Φυσικά, υπάρχουν κάποια μέρη ακόμα:
– Εγγραφές (records) που είναι σαν τις πλειάδες αλλά έχουν πεδία
με ονόματα
• π.χ. {name="Arnold", age=42} : {name : string, age : int}
– Πίνακες (arrays) με στοιχεία που μπορούν να τροποποιηθούν
– Αναφορές (references) για τιμές που μπορούν να τροποποιηθούν
– Χειρισμός εξαιρέσεων (exception handling)
– Υποστήριξη encapsulation και απόκρυψης δεδομένων:
• structures: συλλογές από τύπους δεδομένων + συναρτήσεων
• signatures: interfaces για τα structures
• functors: κάτι σα συναρτήσεις για structures, που όμως
επιτρέπουν μεταβλητές τύπων και την ανάθεση τιμών
(instantiation) στις παραμέτρους των structures
Η γλώσσα ML σε βάθος 37
Κάποια άλλα μέρη της ML
– API: the standard basis
• Προκαθορισμένες συναρτήσεις, τύποι, κ.λπ.
• Κάποιες από αυτές είναι σε structures: Int.maxInt,
Real.Math.sqrt, List.nth, κ.λπ.
– eXene: μια βιβλιοθήκη της ML για εφαρμογές σε γραφικό
περιβάλλον X windows
– Ο Compilation Manager για διαχείριση μεγαλύτερων projects

• Άλλες διάλεκτοι της ML


– Objective Caml (OCaml)
– Η επέκταση της ML για ταυτοχρονισμό (Concurrent ML – CML)

Η γλώσσα ML σε βάθος 38

Συμπερασματικά για τις συναρτησιακές γλώσσες

• Η ML είναι η μόνη γλώσσα που θα εξετάσουμε από τις


συναρτησιακές γλώσσες προγραμματισμού
• Σε αυτό το είδος προγραμματισμού, η εκτέλεση γίνεται
μέσω αποτίμησης εκφράσεων και ταιριάσματος προτύπων
• Εάν σας αρέσει αυτό το στυλ προγραμματισμού,
υπάρχουν και άλλες γλώσσες για εξερεύνηση, όπως η
Lisp, η Scheme, η Haskell, η Clean και η Erlang

Η γλώσσα ML σε βάθος 39
Συμπερασμός Τύπων
και Πολυμορφισμός

Εισαγωγή: Σύγκριση μεταξύ γλωσσών

C: int f(char a, char b) {


return a == b;
}

ML: - fun f(a, b) = (a = b);


val f = fn : ''a * ''a -> bool

• Η συνάρτηση σε ML γράφεται πιο εύκολα: ο


προγραμματιστής δε χρειάζεται να ορίσει τύπους
• Η συνάρτηση σε ML είναι πιο ευέλικτη: μπορεί να
χρησιμοποιηθεί για κάθε τύπο (που υποστηρίζει ισότητα)
• Συναρτήσεις σαν και την παραπάνω, οι οποίες δουλεύουν
για πολλούς τύπους, ονομάζονται πολυμορφικές
Συμπερασμός Τύπων και Πολυμορφισμός 2
Περιεχόμενα

• Συμπερασμός τύπων (type inference)


– Καλό παράδειγμα αλγόριθμου και εφαρμογής στατικής
ανάλυσης προγραμμάτων
– Θα δούμε τον αλγόριθμο σε κάποια παραδείγματα
• Υπερφόρτωση (overloading)
• Αυτόματη μετατροπή τύπων (type coercion)
• Πολυμορφισμός
– Πολυμορφισμός έναντι υπερφόρτωσης
– Υλοποίηση του πολυμορφισμού σε διαφορετικές γλώσσες
– Παραμετρικός πολυμορφισμός (parametric polymorphism)
– Πολυμορφισμός υποτύπων (subtype polymorphism)
• Ανακεφαλαίωση ορισμών
Συμπερασμός Τύπων και Πολυμορφισμός 3

Συμπερασμός τύπων

Συμπερασμός Τύπων και Πολυμορφισμός 4


Έλεγχος τύπων έναντι συμπερασμού τύπων

• Έλεγχος τύπων
int f(int x) { return x+1; };
int g(int y) { return f(y+1)*2; };
– Κοιτάμε στο σώμα κάθε συνάρτησης χρησιμοποιώντας τις δηλώσεις
τύπων των μεταβλητών για τον έλεγχο της συνέπειάς τους

• Συμπερασμός τύπων
int f(int x) { return x+1; };
int g(int y) { return f(y+1)*2; };
– Κοιτάμε στον κώδικα, ο οποίος δεν περιέχει πληροφορία τύπων, και
«μαντεύουμε» ποιοι τύποι θα έπρεπε να είχαν δηλωθεί ώστε το
πρόγραμμα να είναι συνεπές ως προς τη χρήση των τύπων

• Η ML έχει σχεδιαστεί ώστε ο συμπερασμός τύπων να είναι


βατός (tractable)
Συμπερασμός Τύπων και Πολυμορφισμός 5

Χρησιμότητα

• Τύποι και έλεγχος τύπων


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

• Συμπερασμός τύπων
– Θεωρείται ως μια από τις σημαντικότερες εξελίξεις στη θεωρία
και την πρακτική των γλωσσών προγραμματισμού
– Ο συμπερασμός τύπων της ML μας δίνει μια ιδέα
• του πώς δουλεύουν πολλοί άλλοι αλγόριθμοι συμπερασμού
τύπων αλλά και
• της στατικής ανάλυσης προγραμμάτων

Συμπερασμός Τύπων και Πολυμορφισμός 6


Συμπερασμός τύπων στην ML

• Παράδειγμα
- fun add2 x = 2+x;
val add2 = fn : int -> int
• Πώς συμπεραίνουμε τον παραπάνω τύπο;
– Ο + έχει δύο τύπους: int * int -> int
ή real * real -> real
– Η σταθερά 2 έχει τύπο int
– Αυτό σημαίνει ότι χρησιμοποιούμε τον τύπο : int*int -> int
– Αυτό με τη σειρά του σημαίνει ότι x:int
– Επομένως η συνάρτηση add2 έχει τύπο int -> int

Οι υπερφορτωμένοι τελεστές και συναρτήσεις, όπως ο + είναι σπάνιοι.


Τα περισσότερα σύμβολα στην ML έχουν μοναδικό τύπο.
Σε πολλές περιπτώσεις, ο μοναδικός αυτός τύπος είναι πολυμορφικός.
Συμπερασμός Τύπων και Πολυμορφισμός 7

Μια διαφορετική παρουσίαση του συμπερασμού

• Παράδειγμα Γράφος για λx. ((plus 2) x)

- fun add2 x = 2+x;


val add2 = fn : int -> int

• Πώς συμπεραίνεται ο τύπος; λ t→int = int→int

Αναθέτουμε τύπους @ int (t = int)


στα φύλλα
@ int→int x : t
Προωθούμε τύπους στους
εσωτερικούς κόμβους και + 2 : int
γεννάμε περιορισμούς int → int → int
Επιλύουμε μέσω real → real→real
αντικατάστασης
(ενοποίησης)
Συμπερασμός Τύπων και Πολυμορφισμός 8
Εφαρμογή και ορισμός συναρτήσεων

:r (s = t→ r) :s→t
@ λ
f :s x :t x:s e :t

• Εφαρμογή συνάρτησης • Ορισμός συνάρτησης


– Η f έχει τύπο συνάρτησης – Ο τύπος της συνάρτησης είναι
πεδίο ορισμού → πεδίο τιμών πεδίο ορισμού → πεδίο τιμών
– Το πεδίο ορισμού της f είναι ίδιο – Πεδίο ορισμού είναι ο τύπος
με τον τύπο του ορίσματος x της μεταβλητής x
– Ο τύπος του αποτελέσματος – Πεδίο τιμών είναι ο τύπος του
είναι ο τύπος του πεδίου τιμών αποτελέσματος του σώματος e
της f της συνάρτησης

Συμπερασμός Τύπων και Πολυμορφισμός 9

Τύποι με μεταβλητές τύπων

• Παράδειγμα
- fun f(g) = g(2); Γράφος για λg. (g 2)
val f = fn : (int -> ’a) -> ’a

s→t = (int→t)→t
• Πώς συμπεραίνεται ο τύπος; λ
Αναθέτουμε τύπους t (s = int→t)
@
στα φύλλα
Προωθούμε τύπους στους g:s 2 : int
εσωτερικούς κόμβους και
γεννάμε περιορισμούς
Επιλύουμε μέσω
αντικατάστασης
(ενοποίησης)
Συμπερασμός Τύπων και Πολυμορφισμός 10
Χρήση πολυμορφικών συναρτήσεων

• Συνάρτηση
- fun f(g) = g(2);
val f = fn : (int -> ’a) -> ’a

• Πιθανές χρήσεις
- fun add2(x) = 2+x;
val add2 = fn : int -> int
- f(add2);
val it = 4 : int

- fun isEven(x) = (x mod 2) = 0;


val isEven = fn : int -> bool
- f(isEven);
val it = true : bool
Συμπερασμός Τύπων και Πολυμορφισμός 11

Αναγνώριση σφάλματος τύπων

• Έστω η συνάρτηση:
- fun f(g) = g(2);
val f = fn : (int -> ’a) -> ’a

• Λάθος χρήση:

- fun not(x) = if x then false else true;


val not = fn : bool -> bool
- f(not);

• Σφάλμα τύπου: δεν είναι δυνατόν ο τύπος bool -> bool


να είναι στιγμιότυπο του τύπου int -> ’a

Συμπερασμός Τύπων και Πολυμορφισμός 12


Ακόμα ένα παράδειγμα συμπερασμού τύπων

• Έστω η συνάρτηση
- fun f(g,x) = g(g(x));
val f = fn : (’a -> ’a) * ’a -> ’a

Γράφος για λ〈g,x〉. g(g x)


• Συμπερασμός τύπου
s*t→v = (v→v)*v→v
λ
Αναθέτουμε τύπους
στα φύλλα @ v (s = u→v)
Προωθούμε τύπους στους
εσωτερικούς κόμβους και g: s
@ u (s = t→u)
γεννάμε περιορισμούς
g: s x: t
Επιλύουμε μέσω
ενοποίησης
Συμπερασμός Τύπων και Πολυμορφισμός 13

Πολυμορφικοί τύποι δεδομένων

• Τύποι δεδομένων με μεταβλητές τύπου


- datatype ’a list = nil | cons of ’a * (’a list);
nil : ’a list
cons : ’a * (’a list) -> ’a list

• Πολυμορφική συνάρτηση
- fun length nil = 0
= | length cons(x,rest) = 1 + length(rest);
val length = fn : ’a list -> int
• Συμπερασμός τύπων
– Συμπεραίνουμε κάποιο τύπο για κάθε πρόταση ξεχωριστά
– Συνδυάζουμε τους τύπους με τον περιορισμό ότι πρέπει να είναι
συμβατοί μεταξύ τους (οι τύποι ενοποιούνται αν αυτό είναι
αναγκαίο)
Συμπερασμός Τύπων και Πολυμορφισμός 14
Συμπερασμός τύπων και αναδρομή

• Η δεύτερη πρόταση:
length cons(x,rest) = 1 + length(rest)

• Συμπερασμός τύπων 'a list→int = t


– Αναθέτουμε τύπους λ
στα φύλλα
– Συνεχίζουμε ως @
συνήθως @
@
– Προσθέτουμε τον @
περιορισμό ότι ο τύπος cons
του σώματος της : 'a*'a list + 1 length rest
συνάρτησης ισούται με →'a list
τον τύπο του ονόματος
:t
x
της συνάρτησης

Συμπερασμός Τύπων και Πολυμορφισμός 15

Κύρια σημεία για το συμπερασμό τύπων

• Υπολογίζουμε τον τύπο της έκφρασης


– Δε χρειαζόμαστε δηλώσεις για τον τύπο των μεταβλητών
– Βρίσκουμε τον πιο γενικό τύπο μέσω επίλυσης περιορισμών
– Το παραπάνω αυτόματα οδηγεί σε πολυμορφισμό συναρτήσεων

• Στατικός έλεγχος τύπων χωρίς προδιαγραφές τύπων


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

Συμπερασμός Τύπων και Πολυμορφισμός 16


Χρήσιμη πληροφορία από συμπερασμό τύπων

• Μια συνάρτηση για λίστες:


fun reverse nil = nil
| reverse (h::t) = reverse t;

• Ο τύπος που συμπεραίνεται από την ML είναι:


reverse : ’a list -> ’b list

• Τι σημαίνει αυτός ο τύπος;


Αφού η αναστροφή μιας λίστας δεν αλλάζει τον τύπο των
στοιχείων της λίστας, πρέπει να υπάρχει κάποιο λάθος στον
παραπάνω ορισμό της “reverse”

Συμπερασμός Τύπων και Πολυμορφισμός 17

Πολυμορφισμός και Υπερφόρτωση

Συμπερασμός Τύπων και Πολυμορφισμός 18


Υπερφόρτωση (overloading)

• Μια συνάρτηση (ή ένας τελεστής) είναι υπερφορτωμένη


όταν έχει τουλάχιστον δύο ορισμούς για διαφορετικούς
τύπους ορισμάτων
• Πολλές γλώσσες έχουν υπερφορτωμένους τελεστές
ML: Pascal:
val x = 1 + 2; a := 1 + 2;
val y = 1.0 + 2.0; b := 1.0 + 2.0;
c := "hello " + "there";
d := ['a'..'d'] + ['f'];

• Επίσης, κάποιες γλώσσες επιτρέπουν τον ορισμό νέων


υπερφορτωμένων συναρτήσεων ή τελεστών
Συμπερασμός Τύπων και Πολυμορφισμός 19

Προσθήκη σε ήδη υπερφορτωμένους τελεστές

• Κάποιες γλώσσες, όπως η C++, επιτρέπουν πρόσθετη


υπερφόρτωση των ήδη υπερφορτωμένων τελεστών
class complex {
double rp, ip; // real part, imaginary part
public:
complex(double r, double i) {rp = r; ip = i;}
friend complex operator+(complex, complex);
friend complex operator*(complex, complex);
};

void f(complex a, complex b, complex c) {


complex d = a + b * c;

}

Συμπερασμός Τύπων και Πολυμορφισμός 20


Υπερφόρτωση τελεστών στη C++

• Η C++ επιτρέπει σχεδόν σε όλους τους τελεστές την


πρόσθετη υπερφόρτωση, συμπεριλαμβανομένων των:
– Πιο συχνά χρησιμοποιούμενων τελεστών (+,-,*,/,%,^,&,|,~,!,
=,<,>, +=,-=,=,*=,/=,%=,^=,&=,|=,<<,>>,>>=,<<=,==,
!=,<=,>=,&&,||,++,--,->*,,)
– Αποδεικτοδότησης (dereferencing) (*p και p->x)
– Χρήσης δεικτών (a[i])
– Κλήσης συνάρτησης (f(a,b,c))
– Δέσμευσης και αποδέσμευσης μνήμης (new και delete)

Συμπερασμός Τύπων και Πολυμορφισμός 21

Ορισμός υπερφορτωμένων συναρτήσεων

• Κάποιες γλώσσες, όπως η C++, επιτρέπουν την


υπερφόρτωση των ονομάτων των συναρτήσεων

int square(int x) {
return x*x;
}

double square(double x) {
return x*x;
}

Συμπερασμός Τύπων και Πολυμορφισμός 22


Όμως η υπερφόρτωση εξαφανίζεται στη C++

int square(int x) { square_i


return x*x;
}

double square(double x) {
square_d
return x*x;
}

void f() {
int a = square(3);
Δίνουμε καινούργια
double b = square(3.0);
(μοναδικά) ονόματα σε
}
υπερφορτωμένους
ορισμούς συναρτήσεων…

Συμπερασμός Τύπων και Πολυμορφισμός 23

Εξαφάνιση υπερφόρτωσης στη C++

int square_i(int x) {
return x*x;
}

double square_d(double x) {
return x*x;
}

void f() {
int a = square_i(3); Και στη συνέχεια
double b = square_d(3.0); μετονομάζουμε τις
} κλήσεις (ανάλογα
με τους τύπους των
ορισμάτων τους)

Συμπερασμός Τύπων και Πολυμορφισμός 24


Υλοποίηση υπερφόρτωσης στη C++

• Οι μεταγλωττιστές συνήθως υλοποιούν την υπερφόρτωση:


– Δημιουργούν μονομορφικές συναρτήσεις, μια για κάθε ορισμό
– Εφευρίσκουν ένα νέο όνομα για κάθε ορισμό το οποίο κωδικοποιεί
την πληροφορία για τους τύπους
– Κάθε κλήση χρησιμοποιεί το κατάλληλο όνομα ανάλογα με τους
τύπους των παραμέτρων

C++: int shazam(int a, int b) {return a+b;}


double shazam(double a, double b) {return a+b;}
shazam__Fii:
Assembler: lda $30,-32($30)
.frame $15,32,$26,0

shazam__Fdd:
lda $30,-32($30)
.frame $15,32,$26,0

Συμπερασμός Τύπων και Πολυμορφισμός 25

Αυτόματος εξαναγκασμός τύπου (Coercion)

• Σε πολλές γλώσσες ο μεταγλωττιστής εξαναγκάζει την


αυτόματη μετατροπή τύπου (type coercion), ακόμα και
σε περιπτώσεις που οι μετατροπές δεν είναι άμεσα
δηλωμένες από τον προγραμματιστή

Δήλωση μετατροπής double x;


τύπου στη Java: x = (double) 2;

double x;
Coercion στη Java: x = 2;

Συμπερασμός Τύπων και Πολυμορφισμός 26


Αυτόματη μετατροπή παραμέτρων

• Διαφορετικές γλώσσες υποστηρίζουν διαφορετικές


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

Συμπερασμός Τύπων και Πολυμορφισμός 27

Παράδειγμα: Java

void f(double x) {

}
Η συνάρτηση f μπορεί να κληθεί
f((byte) 1); με κάθε τύπο παραμέτρου που
f((short) 2); μπορεί να μετατραπεί αυτόματα
f('a'); σε double στη Java
f(3);
f(4L);
f(5.6F);

Συμπερασμός Τύπων και Πολυμορφισμός 28


Ορισμός αυτόματων μετατροπών τύπων

• Οι γλώσσες ξοδεύουν μεγάλο μέρος του τυπικού ορισμού


τους στο να ορίσουν επακριβώς τους επιτρεπόμενους
αυτόματους εξαναγκασμούς μετατροπής τύπου και το
πώς αυτοί λαμβάνουν χώρα
• Κάποιες γλώσσες, ειδικά κάποιες παλιές γλώσσες όπως η
Algol 68 και η PL/I, επιτρέπουν πολλές αυτόματες
μετατροπές τύπων
• Κάποιες άλλες, όπως η ML, δεν επιτρέπουν καμία
• Οι περισσότερες, όπως η Java, είναι κάπου ενδιάμεσα

Συμπερασμός Τύπων και Πολυμορφισμός 29

Παράδειγμα: Java

5.6.1 Unary Numeric Promotion


Some operators apply unary numeric promotion to a single
operand, which must produce a value of a numeric type: If the
operand is of compile-time type byte, short, or char,
unary numeric promotion promotes it to a value of type int
by a widening conversion (§5.1.2). Otherwise, a unary
numeric operand remains as is and is not converted.
Unary numeric promotion is performed on expressions in the
following situations: the dimension expression in array
creations (§15.9); the index expression in array access
expressions (§15.12); operands of the unary operators plus +
(§15.14.3) and minus - (§15.14.4) ...

The Java Language Specification


James Gosling, Bill Joy, Guy Steele
Συμπερασμός Τύπων και Πολυμορφισμός 30
Αυτόματες μετατροπές τύπων και υπερφόρτωση

• Η αυτόματη μετατροπή τύπων συνήθως έχει περίεργες


αλληλεπιδράσεις με την υπερφόρτωση συναρτήσεων
• Αυτό συμβαίνει διότι
– Η υπερφόρτωση χρησιμοποιεί τους τύπους για την επιλογή του
ορισμού που θα χρησιμοποιηθεί
– Η αυτόματη μετατροπή τύπων χρησιμοποιεί τον ορισμό για να
αποφασίσει τι είδους μετατροπή θα πρέπει να γίνει

Συμπερασμός Τύπων και Πολυμορφισμός 31

Παραδείγματα… σύγχυσης

• Έστω ότι, όπως στη C++, η γλώσσα επιτρέπει την


αυτόματη μετατροπή char σε int ή σε double
• Ποια square καλείται σε μια κλήση square('a');
int square(int x) { double square(double x) {
return x*x; return x*x;
} }
• Έστω ότι, όπως στη C++, η γλώσσα επιτρέπει την
αυτόματη μετατροπή char σε int
• Ποια f καλείται σε μια κλήση f('a','b');
void f(int x, char y) { void f(char x, int y) {
… …
} }
Συμπερασμός Τύπων και Πολυμορφισμός 32
Πολυμορφισμός

Συμπερασμός Τύπων και Πολυμορφισμός 33

Παραμετρικός πολυμορφισμός

• Μια συνάρτηση είναι παραμετρικά πολυμορφική εάν έχει


τύπο που περιέχει μία ή περισσότερες μεταβλητές τύπου
• Ένας τύπος με μεταβλητές τύπων είναι ένας πολυτύπος
• Παραμετρικός πολυμορφισμός συναντιέται σε γλώσσες
όπως η ML, η C++ και η Ada

Συμπερασμός Τύπων και Πολυμορφισμός 34


Παράδειγμα: C++ Function Templates

template<class X> X max(X a, X b) {


return a>b ? a : b;
}

void g(int a, int b, char c, char d) {


int m1 = max(a,b);
char m2 = max(c,d);
}

Ο τελεστής σύγκρισης > μπορεί να είναι πρόσθετα υπερφορτωμένος,


οπότε η μεταβλητή τύπου X δεν περιορίζεται μόνο σε τύπους για τους
οποίους ο τελεστής > είναι προκαθορισμένος.

Συμπερασμός Τύπων και Πολυμορφισμός 35

Παράδειγμα: Συναρτήσεις σε ML

- fun identity x = x;
val identity = fn : 'a -> 'a
- identity 3;
val it = 3 : int
- identity "hello";
val it = "hello" : string
- fun reverse x =
= if null x then nil
= else (reverse (tl x)) @ [(hd x)];
val reverse = fn : 'a list -> 'a list

Συμπερασμός Τύπων και Πολυμορφισμός 36


Υλοποίηση παραμετρικού πολυμορφισμού

• Το ένα άκρο: πολλά αντίγραφα του κώδικα


– Δημιουργείται ένα σύνολο από μονομορφικές συναρτήσεις, μία
για κάθε πιθανό στιγμιότυπο των μεταβλητών τύπου
• Κάθε αντίγραφο είναι μια μονομορφική υλοποίηση
• Η οποία όμως μπορεί να βελτιστοποιηθεί/προσαρμοστεί στο
συγκεκριμένο τύπο

• Το άλλο άκρο: ο ίδιος κώδικας


– Δημιουργείται μία μόνο υλοποίηση και χρησιμοποιείται για όλες
τις κλήσεις (αληθινός καθολικός πολυμορφισμός)
– Δε μπορεί να βελτιστοποιηθεί για χρήση συγκεκριμένων τύπων

• Βεβαίως υπάρχουν και πολλές ενδιάμεσες υλοποιήσεις

Συμπερασμός Τύπων και Πολυμορφισμός 37

Πολυμορφισμός υποτύπων

• Μια συνάρτηση (ή ένας τελεστής) είναι πολυμορφική ως


προς υποτύπους εάν κάποια από τις παραμέτρους τύπων
της έχει υποτύπους
• Είναι σημαντική πηγή πολυμορφισμού σε γλώσσες με
πλούσια δομή υποτύπων
• Τέτοιες είναι οι περισσότερες αντικειμενοστρεφείς
γλώσσες προγραμματισμού (π.χ. η Java)

Συμπερασμός Τύπων και Πολυμορφισμός 38


Παράδειγμα: Pascal

type
Day = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
Weekday = Mon..Fri;
function nextDay(D: Day): Day;
begin
if D=Sun then nextDay := Mon else nextDay := D+1
end;
procedure p(D: Day; W: Weekday);
begin
D := nextDay(D);
D := nextDay(W)
end;

Πολυμορφισμός υποτύπων: η
συνάρτηση nextDay μπορεί να
κληθεί με μια παράμετρο υποτύπου
Συμπερασμός Τύπων και Πολυμορφισμός 39

Παράδειγμα: Java

class Car {
void brake() { … }
} Υποτύπος της κλάσης Car
είναι η ManualCar
class ManualCar extends Car
{ Η συνάρτηση g έχει έναν
void clutch() { … } απεριόριστο αριθμό
}
τύπων—ένα για κάθε
void g(Car z) { κλάση που είναι μια
z.brake(); υποκλάση της κλάσης Car
}
void f(Car x, ManualCar y) { Λέμε ότι αυτό είναι
g(x); πολυμορφισμός υποτύπων
g(y);
}

Συμπερασμός Τύπων και Πολυμορφισμός 40


Ορισμοί (Ανακεφαλαίωση)

Συμπερασμός Τύπων και Πολυμορφισμός 41

Πολυμορφισμός

• Είδαμε 4 κατηγορίες πολυμορφισμού


• Υπάρχουν και άλλες χρήσεις του πολυμορφισμού
– Πολυμορφισμός μεταβλητών, κλάσεων, πακέτων, συναρτήσεων
– Είναι άλλο ένα όνομα για κλήση μεθόδων κατά το χρόνο
εκτέλεσης: όταν μια κλήση x.f() μπορεί να καλέσει
διαφορετικές μεθόδους ανάλογα με την κλάση του αντικειμένου
x κατά το χρόνο εκτέλεσης
• Ορισμός που καλύπτει όλες τις χρήσεις:
• Μια συνάρτηση (ή ένας τελεστής) είναι πολυμορφική εάν έχει
τουλάχιστον δύο πιθανούς τύπους
– Λέμε ότι έχει περιστασιακό πολυμορφισμό (ad hoc polymorphism) εάν
έχει τουλάχιστον δύο αλλά πεπερασμένο πλήθος πιθανών τύπων
– Λέμε ότι έχει καθολικό πολυμορφισμό (universal polymorphism) εάν έχει
άπειρο πλήθος πιθανών τύπων
Συμπερασμός Τύπων και Πολυμορφισμός 42
Υπερφόρτωση

• Περιστασιακός πολυμορφισμός (ad hoc polymorphism)


• Κάθε διαφορετικός τύπος πρέπει να έχει το δικό του
ορισμό
• Αλλά οι ορισμοί αυτοί είναι πεπερασμένοι σε ένα
πεπερασμένο πρόγραμμα

Συμπερασμός Τύπων και Πολυμορφισμός 43

Αυτόματη μετατροπή τύπων παραμέτρων

• Περιστασιακός πολυμορφισμός (ad hoc polymorphism)


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

Συμπερασμός Τύπων και Πολυμορφισμός 44


Παραμετρικός πολυμορφισμός

• Καθολικός πολυμορφισμός
• Τουλάχιστον όσο το πλήθος των πιθανών τιμών των
μεταβλητών τύπων είναι άπειρο

Συμπερασμός Τύπων και Πολυμορφισμός 45

Πολυμορφισμός υποτύπων

• Καθολικός πολυμορφισμός
• Όσο δεν υπάρχει κάποιο όριο στο πλήθος των
διαφορετικών υποτύπων που μπορεί να δηλωθούν για
κάποιο συγκεκριμένο τύπο
• Συνηθισμένος σε αντικειμενοστρεφείς γλώσσες
προγραμματισμού, όπως η Java

Συμπερασμός Τύπων και Πολυμορφισμός 46


Συμπερασματικά

• Συμπερασμός τύπων
– Προσπαθεί να εξάγει τον καλύτερο τύπο για κάθε έκφραση, με
βάση πληροφορία για (κάποια από) τα σύμβολα της έκφρασης

• Πολυμορφισμός
– Όταν κάποια συνάρτηση ή αλγόριθμος μπορεί να δουλέψει σε
πολλούς τύπους δεδομένων

• Υπερφόρτωση (overloading)
– Όταν σύμβολα έχουν πολλαπλές χρήσεις οι οποίες επιλύονται
στο χρόνο μεταγλώττισης (compile time)

Συμπερασμός Τύπων και Πολυμορφισμός 47

Add 4-5 slides from Edwards “types.pdf”


(Polymorphism)

Συμπερασμός Τύπων και Πολυμορφισμός 48


Πολυμορφισμός έναντι Υπερφόρτωσης

• Παραμετρικός πολυμορφισμός
– Single αλγόριθμοι may be given many τύποι
– Type variable may be replaced by any type
– f : t→t => f : int→int, f : bool→bool, ...

• Υπερφόρτωση
– A single symbol may refer to more than one αλγόριθμοι
– Each αλγόριθμοι may have different type
– Η επιλογή του αλγορίθμου determined by type context
– Οι τύποι κάποιου συμβόλου μπορεί να είναι αρκετά διαφορετικοί
– Π.χ. στην ML ο τελεστής + έχει μόνο τους δύο παρακάτω
τύπους:
int * int → int
real * real → real

Συμπερασμός Τύπων και Πολυμορφισμός 49

Παραμετρικός Πολυμορφισμός: ML vs. C++

• Πολυμορφική συνάρτηση στην ML


– Η δήλωση δεν έχει πληροφορία για τους τύπους
– Συμπερασμός τύπων: type expression with variables
– Συμπερασμός τύπων: substitute for variables as needed

• Templates στη C++


– Declaration gives type of function arg, result
– Place inside template to define type variables
– Function application: type checker does instantiation

Συμπερασμός Τύπων και Πολυμορφισμός 50


Παράδειγμα: ανταλλαγή δύο τιμών

• ML
- fun swap(x,y) =
= let val z = !x in x := !y; y := z end;
val swap = fn : 'a ref * 'a ref -> unit

• C++
template <typename T>
void swap(T& x, T& y){
T tmp = x; x = y; y = tmp;
}

• Οι δηλώσεις δείχνουν παρόμοιες, αλλά όμως


μεταφράζονται με πολύ διαφορετικούς τρόπους
Συμπερασμός Τύπων και Πολυμορφισμός 51

Υλοποίηση

• ML
– Swap is compiled into one function
– Type checker determines how function can be used

• C++
– Swap is compiled into linkable format
– Linker duplicates code for each type of use

• Γιατί αυτή η διαφορά?


– ML ref cell is passed by pointer, local x is pointer to value on heap
– C++ arguments passed by reference (pointer), but local x is on
stack, size depends on type

Συμπερασμός Τύπων και Πολυμορφισμός 52


Ακόμα ένα παράδειγμα

• Πολυμορφική συνάρτηση ταξινόμησης στη C++


<typename T>
void sort(int count, T * A[count]) {
for (int i=0; i < count-1; i++)
for (int j=i+1; j < count-1; j++)
if (A[j] < A[i]) swap(A[i],A[j]);
}

• Ποια μέρη της υλοποίησης εξαρτώνται από τον τύπο;


• Η δεικτοδότηση (indexing) του πίνακα
• Η ερμηνεία και η υλοποίηση του τελεστή <

Συμπερασμός Τύπων και Πολυμορφισμός 53

Υπερφόρτωση στην ML

• Κάποιοι από τους προκαθορισμένους τελεστές είναι


υπερφορτωμένοι
• User-defined functions must have unique type
- fun plus (x,y) = x + y;
This is compiled to int or real function, not both

• Why is a unique type needed?


– Need to compile code ⇒ need to know which +
– Efficiency of type inference
– Aside: General overloading is NP-complete
Έστω δύο τύποι, true και false
Υπερφορτωμένες συναρτήσεις
and : {true * true -> true,
false * true -> false, …}
Συμπερασμός Τύπων και Πολυμορφισμός 54
Ονόματα και Εμβέλεια

Ανακύκλωση ονομάτων

• Η κατανόηση της εμβέλειας είναι άμεση εάν το κάθε τι


έχει το δικό του όνομα
fun square a = a * a;
fun double b = b + b;

• Αλλά στις μοντέρνες γλώσσες προγραμματισμού, συχνά


χρησιμοποιούμε το ίδιο όνομα ξανά και ξανά
fun square x = x * x;
fun double x = x + x;

• Τι ακριβώς συμβαίνει;

Ονόματα και Εμβέλεια 2


Περιεχόμενα

• Ορισμοί και εμβέλεια


• Εμβέλεια μέσω μπλοκ
• Εμβέλεια με προσδιορισμένους τόπους ονομάτων
(labeled namespaces)
• Εμβέλεια με πρωτογενείς τόπους ονομάτων
(primitive namespaces)
• Δυναμική εμβέλεια
• Ξεχωριστή μεταγλώττιση

Ονόματα και Εμβέλεια 3

Ορισμοί

• Όταν υπάρχουν διαφορετικές μεταβλητές με το ίδιο


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

Ονόματα και Εμβέλεια 4


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

fun square n = n * n;
fun square square = square * square;

const
Low = 1;
High = 10;
type
Ints = array [Low..High] of Integer;
var
X: Ints;

Ονόματα και Εμβέλεια 5

Εμβέλεια

• Μπορεί να υπάρχουν περισσότεροι από ένας ορισμοί για


κάποιο όνομα
• Κάθε εμφάνιση του ονόματος (εκτός των ορισμών)
πρέπει να είναι δεμένη με κάποιον από τους ορισμούς
του ονόματος
• Λέμε ότι η εμφάνιση ενός ονόματος είναι στην εμβέλεια
κάποιου ορισμού του ονόματος όποτε ο ορισμός
κυβερνά το δέσιμο της συγκεκριμένης εμφάνισης

Ονόματα και Εμβέλεια 6


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

- fun square square = square * square;


val square = fn : int -> int
- square 3;
val it = 9 : int

• Κάθε εμφάνιση πρέπει να είναι δεμένη με κάποιον από


τους ορισμούς
• Αλλά με ποιόν από όλους;
• Υπάρχουν πολλοί τρόποι να λύσουμε αυτό το πρόβλημα
εμβέλειας

Ονόματα και Εμβέλεια 7

Μπλοκ (blocks)

• Ένα μπλοκ είναι ένα οικοδόμημα των γλωσσών που


περιέχει ορισμούς, και περικλείει μια περιοχή του
προγράμματος στην οποία έχουν εφαρμογή οι ορισμοί

let
val x = 1
val y = 2
in
x + y
end

Ονόματα και Εμβέλεια 8


Είδη μπλοκ στην ML

• Ένα let απλώς ορίζει ένα μπλοκ: δεν έχει άλλο σκοπό
• Ένας fun ορισμός περιλαμβάνει ένα μπλοκ:
fun cube x = x * x * x;

• Οι διαφορετικές εναλλακτικές προτάσεις των


συναρτήσεων ορίζουν διαφορετικά μπλοκ:
fun f (a::b::_) = a + b
| f [a] = a
| f [] = 0;

• Κάθε κανόνας σε ένα ταίριασμα αποτελεί ένα μπλοκ:


case x of (a,0) => a | (_,a) => a
Ονόματα και Εμβέλεια 9

Μπλοκ στη Java

• Στη Java και σε άλλες γλώσσες σαν τη C, μπορούμε να


συνδυάσουμε εντολές σε μια σύνθετη εντολή
(compound statement ) περικλείοντάς τες σε { και }
• Μια σύνθετη εντολή χρησιμεύει επίσης σαν ένα μπλοκ:

while (i < 0) {
int c = i * i;
p += c;
q += c;
i -= step;
}

Ονόματα και Εμβέλεια 10


Φωλιασμένα μπλοκ (nested blocks)

• Τι συμβαίνει όταν ένα μπλοκ let


val n = 1
περιλαμβάνει ένα άλλο in
μπλοκ, και τα δύο μπλοκ let
val m = n + 2
εμπεριέχουν ορισμούς για
val n = n + 3
το ίδιο όνομα; val j = n + m
• Παράδειγμα σε ML: ποια in
(2 + n) * j
είναι η τιμή της διπλανής end
έκφρασης; end

Ονόματα και Εμβέλεια 11

Κλασικός κανόνας εμβέλειας σε μπλοκ

• Η εμβέλεια ενός ορισμού αποτελείται από το μπλοκ που


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

Ονόματα και Εμβέλεια 12


Παράδειγμα

Η εμβέλεια αυτού του ορισμού είναι A‐B


let
val n = 1
in A
let Η έκφραση αποτιμάται ως:
val m = n + 2 m = n + 2 = 1 + 2 = 3
val n = n + 3 n = n + 3 = 1 + 3 = 4
val j = n + m B j = n + m = 4 + 3 = 7
in
(2 + n) * j (2 + n) * j =
end (2 + 4) * 7 =
end 6 * 7 = 42

Η εμβέλεια αυτού του ορισμού είναι B


Ονόματα και Εμβέλεια 13

Προσδιορισμένοι τόποι ονομάτων

• Ένας προσδιορισμένος τόπος ονομάτων είναι οτιδήποτε


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

• Η ML έχει ένα τέτοιο οικοδόμημα — τη δομή (structure)

Ονόματα και Εμβέλεια 14


Δομές στην ML (ML structures)

structure Fred = struct


val a = 1;
fun f x = x + a;
end;

• Οι δομές είναι περίπου σα μπλοκ: η τιμή a μπορεί να


χρησιμοποιηθεί παντού από τον ορισμό της μέχρι το
τέλος της δομής
• Αλλά οι ορισμοί μέσα στη δομή είναι προσπελάσιμοι και
εκτός της δομής, με χρήση του ονόματός της
– στο παράδειγμά μας οι ορισμοί της δομής είναι προσπελάσιμοι
ως Fred.a και Fred.f

Ονόματα και Εμβέλεια 15

Άλλοι προσδιορισμένοι τόποι ονομάτων

• Τόποι ονομάτων που είναι απλώς τόποι ονομάτων:


– C++ namespace
– Modula-3 module
– Ada package
– Java package
• Τόποι ονομάτων που έχουν και άλλους σκοπούς:
– Ορισμοί κλάσεων σε αντικειμενοστρεφείς γλώσσες
προγραμματισμού που είναι βασισμένες σε
προσδιορισμένους τόπους ονομάτων

Ονόματα και Εμβέλεια 16


Παράδειγμα σε Java

public class Month {


public static int min = 1;
public static int max = 12;

}
• Οι μεταβλητές min και max είναι ορατές σε όλη την
υπόλοιπη κλάση
• Αλλά είναι προσπελάσιμες και εκτός της κλάσης, ως
Month.min και Month.max
• Φυσικά, όπως θα δούμε, οι κλάσεις στη Java έχουν και
άλλους σκοπούς…

Ονόματα και Εμβέλεια 17

Πλεονεκτήματα τόπων ονομάτων

• Δύο αλληλοσυγκρουόμενοι στόχοι:


– Χρησιμοποίηση ενός απλού μνημονικού ονόματος όπως π.χ. max
– Για ότι θέλουμε να είναι απροσπέλαστο από τον υπόλοιπο κόσμο,
χρησιμοποιούμε ασυνήθη ονόματα όπως maxSupplierBid,
δηλαδή ονόματα που δε θα συγκρούονται με ονόματα σε άλλα
μέρη του προγράμματος

• Με χρήση τόπων ονομάτων, μπορούμε να επιτύχουμε και


τους δύο στόχους:
– Εντός του τόπου ονόματος, μπορούμε να χρησιμοποιήσουμε το
όνομα max
– Από έξω, το όνομα SupplierBid.max

Ονόματα και Εμβέλεια 18


Εκλέπτυνση τόπων ονομάτων

• Οι περισσότεροι τόποι ονομάτων έχουν κάποιο τρόπο να


επιτρέπουν μέρος του τόπου ονομάτων να παραμένει
ιδιωτικής (δηλ. μη κοινής) χρήσης
• Με τον τρόπο αυτό κρύβουμε πληροφορία από κάποια
μέρη του προγράμματος
• Τα προγράμματα είναι πιο εύκολα συντηρούμενα όταν
χρησιμοποιούν ονόματα με σχετικά μικρή εμβέλεια
• Για παράδειγμα, οι αφηρημένοι τύποι δεδομένων (abstract
data types) αποκαλύπτουν μια αυστηρή διαπροσωπεία και
ταυτόχρονα κρύβουν τις λεπτομέρειες της υλοποίησης…

Ονόματα και Εμβέλεια 19

Παράδειγμα αφηρημένου τύπου δεδομένων

namespace dictionary contains


a constant definition for initialSize
a type definition for hashTable
a function definition for hash
a function definition for reallocate
a function definition for create Οι λεπτομέρειες
a function definition for insert της υλοποίησης
a function definition for search πρέπει να μείνουν
a function definition for delete κρυφές
end namespace

Οι ορισμοί της διαπροσωπείας


όμως πρέπει να είναι ορατοί

Ονόματα και Εμβέλεια 20


Δύο προσεγγίσεις

• Σε κάποιες γλώσσες, όπως η C++, ο τόπος ονομάτων


καθορίζει λεπτομερώς την ορατότητα των συστατικών
στοιχείων του
• Σε άλλες γλώσσες, όπως η ML, κάποιο ξεχωριστό
οικοδόμημα ορίζει τη διαπροσωπεία του τόπου ονομάτων
(π.χ. ένα signature στην ML)
• Και κάποιες γλώσσες, όπως η Ada και η Java,
συνδυάζουν και τις δύο παραπάνω προσεγγίσεις

Ονόματα και Εμβέλεια 21

Ο τόπος ονομάτων καθορίζει την ορατότητα

namespace dictionary contains


private:
a constant definition for initialSize
a type definition for hashTable
a function definition for hash
a function definition for reallocate
public:
a function definition for create
a function definition for insert
a function definition for search
a function definition for delete
end namespace

Ονόματα και Εμβέλεια 22


Ξεχωριστή διαπροσωπεία
interface dictionary contains
a function type definition for create
a function type definition for insert
a function type definition for search
a function type definition for delete
end interface

namespace myDictionary implements dictionary contains


a constant definition for initialSize
a type definition for hashTable
a function definition for hash
a function definition for reallocate
a function definition for create
a function definition for insert
a function definition for search
a function definition for delete
end namespace

Ονόματα και Εμβέλεια 23

Μη το δοκιμάσετε στο σπίτι!

- val int = 42;


val int = 42 : int

• Είναι επιτρεπτό να ορίσουμε μια μεταβλητή με όνομα int


• Η ML δε μπερδεύεται
• Ακόμα και το επόμενο επιτρέπεται (η ML καταλαβαίνει ότι
η έκφραση int * int δεν αναφέρεται σε κάποιο τύπο):

- fun f int = int * int;


val f = fn : int -> int
- f 7 - 7;
val it = 42 : int

Ονόματα και Εμβέλεια 24


Πρωτογενείς τόποι ονομάτων

• Η σύνταξη της ML κρατάει ξεχωριστούς τους τύπους


και τις εκφράσεις
• Η ML πάντα ξέρει κατά πόσο κάτι είναι ένας τύπος ή κάτι
άλλο, διότι υπάρχει ξεχωριστός τόπος ονομάτων για
τους τύπους

fun f(int:int) = (int:int)*(int:int);

Τόπος ονομάτων Τόπος ονομάτων


των μεταβλητών των τύπων

Ονόματα και Εμβέλεια 25

Πρωτογενείς τόποι ονομάτων

• Είναι τόποι ονομάτων που δε μπορούν να δημιουργηθούν


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

Ονόματα και Εμβέλεια 26


Δυναμική εμβέλεια

Ονόματα και Εμβέλεια 27

Πότε επιλύεται η εμβέλεια;

• Όλες οι μέθοδοι εμβέλειας που είδαμε μέχρι στιγμής


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

Ονόματα και Εμβέλεια 28


Δυναμική εμβέλεια

• Κάθε συνάρτηση έχει ένα περιβάλλον από ορισμούς


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

Ονόματα και Εμβέλεια 29

Κλασικός κανόνας δυναμικής εμβέλειας

• Η εμβέλεια ενός ορισμού είναι η συνάρτηση που περιέχει


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

Ονόματα και Εμβέλεια 30


Στατική εμβέλεια έναντι δυναμικής

• Οι κανόνες εμβέλειας είναι παρόμοιοι


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

Ονόματα και Εμβέλεια 31

Παράδειγμα

fun g x = Ποια θα ήταν η τιμή της


let κλήσης g 5 αν η γλώσσα
val inc = 1 χρησιμοποιούσε
fun f y = y + inc α) στατική εμβέλεια;
fun h z = β) δυναμική εμβέλεια;
let
val inc = 2
in
f z
end
in
h x
end;

Ονόματα και Εμβέλεια 32


Στατική εμβέλεια (εμβέλεια μπλοκ)

fun g x = Στη στατική εμβέλεια,


let η μεταβλητή inc δένεται
val inc = 1 με τον προηγούμενο
fun f y = y + inc ορισμό στο ίδιο μπλοκ.
fun h z = Ο ορισμός της inc στο
let περιβάλλον κλήσης της f
val inc = 2 είναι μη προσπελάσιμος.
in
f z g 5 = 6
end σε αυτήν την περίπτωση
in (όπως και στην ML)
h x
end;

Ονόματα και Εμβέλεια 33

Δυναμική εμβέλεια

fun g x = Στη δυναμική εμβέλεια,


let η μεταβλητή inc
val inc = 1 δένεται με τον χρονικά
fun f y = y + inc πιο πρόσφατο ορισμό
fun h z = στο περιβάλλον κλήσης
let της f.
val inc = 2
in g 5 = 7
f z σε αυτήν την περίπτωση
end
in
h x
end;

Ονόματα και Εμβέλεια 34


Που χρησιμοποιείται η δυναμική εμβέλεια

• Σε λίγες μόνο γλώσσες


– Σε κάποιες διαλέκτους της Lisp και της APL
– Υπάρχει διαθέσιμη ως επιλογή στην Common Lisp

• Μειονεκτήματα:
– Δυσκολία αποδοτικής υλοποίησης
– Δημιουργεί μεγάλες και περίπλοκες εμβέλειες
(οι εμβέλειες επεκτείνονται στις καλούμενες συναρτήσεις)
– Η επιλογή του ονόματος κάποιας μεταβλητής στη συνάρτηση
κλήσης μπορεί να επηρεάσει τη συμπεριφορά της καλούμενης
συνάρτησης

• Πλεονέκτημα:
– Δυνατότητα αλλαγής/επιλογής του περιβάλλοντος εκτέλεσης

Ονόματα και Εμβέλεια 35

Πιθανή χρήση της δυναμικής εμβέλειας

program messages;
var message : string;
procedure complain;
begin
writeln(message);
end;
procedure out_of_mem;
var message : string;
begin
message := "Out of memory"; complain;
end;
procedure timeout;
var message : string;
begin
message := "Timeout"; complain;
end;
Ονόματα και Εμβέλεια 36
Αμοιβαία αναδρομή μέσω δηλώσεων forward

• Γλώσσες όπως η C, C++ και η Pascal


χρειάζονται δηλώσεις forward για
αμοιβαία αναδρομικούς ορισμούς
int foo();
int bar() { ... foo(); ... }
int foo() { ... bar(); ... }

• Εν μέρει, το παραπάνω είναι παρενέργεια της διαδικασίας


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

Ονόματα και Εμβέλεια 37

Αμοιβαία αναδρομικές συναρτήσεις στην ML

Οι αμοιβαία αναδρομικές συναρτήσεις στην ML πρέπει να


γραφούν μαζί, με χρήση ενός συνδετικού and:
- fun even x =
= if (x <= 0) then true else odd (x-1)
= and odd x =
= if (x <= 0) then false else even (x-1);
val even = fn : int -> bool
val odd = fn : int -> bool
- even 42;
val it = true : bool
- odd 42;
val it = false : bool

Ονόματα και Εμβέλεια 38


Ξεχωριστή μεταγλώττιση

• Μέρη του προγράμματος μεταγλωττίζονται ξεχωριστά,


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

Ονόματα και Εμβέλεια 39

Η προσέγγιση της C: η μεριά του μεταγλωττιστή

• Δύο διαφορετικά είδη ορισμών ονομάτων:


– Πλήρης ορισμός (definition)
– Όνομα και τύπος μόνο: μια δήλωση (declaration) κατά τη C
• Εάν πολλές διαφορετικές μεταγλωττίσεις/αρχεία θέλουν
να χρησιμοποιήσουμε την ίδια ακέραια μεταβλητή x:
– Μόνο ένα αρχείο θα έχει τον ορισμό:
int x = 42;
– Όλα τα άλλα αρχεία θα έχουν τη δήλωση:
extern int x;

Ονόματα και Εμβέλεια 40


Η προσέγγιση της C: η μεριά του συνδέτη

• Όταν τρέχει ο συνδέτης, θεωρεί κάθε δήλωση ως μια


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

Ονόματα και Εμβέλεια 41

Τάσεις ξεχωριστής μεταγλώττισης

• Στις σύγχρονες γλώσσες, η ξεχωριστή μεταγλώττιση


είναι λιγότερο ξεχωριστή από ότι ήταν πριν από κάποια
χρόνια
– Οι κλάσεις της Java μπορεί να εξαρτώνται κυκλικά μεταξύ
τους, και ο μεταγλωττιστής της Java πρέπει να είναι σε
θέση να μεταφράσει πολλές ξεχωριστές κλάσεις
ταυτόχρονα
– Η ML δεν προσφέρεται καθόλου για ξεχωριστή
μεταγλώττιση, αλλά υπάρχει ο CM (Compilation Manager,
ένα ξεχωριστό εργαλείο του συστήματος SML/NJ) μπορεί
να το κάνει για τα περισσότερα προγράμματα σε ML

Ονόματα και Εμβέλεια 42


Συμπερασματικά

• Τέσσερεις διαφορετικές προσεγγίσεις για εμβέλεια


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

Ονόματα και Εμβέλεια 43

Εγγραφές
Δραστηριοποίησης
Ερώτηση για… δέσιμο

• Κατά την εκτέλεση του προγράμματος, οι μεταβλητές


δένονται (δυναμικά) με τιμές
• Οι τιμές αυτές πρέπει να αποθηκευτούν κάπου
• Κατά συνέπεια, οι μεταβλητές πρέπει κάπως να δεθούν
με θέσεις μνήμης
• Πώς γίνεται αυτό;

Εγγραφές δραστηριοποίησης 2

Συναρτησιακές γλώσσες συναντούν προστακτικές

• Οι προστακτικές γλώσσες κάνουν εμφανή την έννοια


των θέσεων μνήμης: a := 42
– Αποθήκευσε μηδέν στη θέση μνήμης της μεταβλητής a
• Οι συναρτησιακές γλώσσες τις κρύβουν: val a = 42
– Δέσε το όνομα a με την τιμή μηδέν

• Και τα δύο είδη γλωσσών πρέπει να “ενώσουν”


μεταβλητές με τιμές που αναπαρίστανται στη μνήμη
• Άρα και οι δύο πρέπει να αντιμετωπίσουν την ίδια
ερώτηση δεσίματος

Εγγραφές δραστηριοποίησης 3
Περιεχόμενα

• Εγγραφές δραστηριοποίησης (activation records)


• Στατική δέσμευση εγγραφών δραστηριοποίησης
• Στοίβες από εγγραφές δραστηριοποίησης
• Χειρισμός φωλιασμένων ορισμών συναρτήσεων
• Συναρτήσεις ως παράμετροι
• Μακρόβιες εγγραφές δραστηριοποίησης

Εγγραφές δραστηριοποίησης 4

Δραστηριοποιήσεις συναρτήσεων

• Ο χρόνος ζωής της εκτέλεσης μιας συνάρτησης, από τη


στιγμή της κλήσης μέχρι την αντίστοιχη επιστροφή,
ονομάζεται δραστηριοποίηση (activation) της
συνάρτησης
• Όταν κάθε δραστηριοποίηση έχει το δικό της δέσιμο
μεταβλητών σε θέσεις μνήμης, λέμε ότι έχουμε
μεταβλητές ειδικές για κάθε δραστηριοποίηση
• Οι μεταβλητές αυτές ονομάζονται επίσης δυναμικές
(dynamic) ή αυτόματες (automatic) μεταβλητές

Εγγραφές δραστηριοποίησης 5
Μεταβλητές ειδικές για κάθε δραστηριοποίηση

Στις περισσότερες μοντέρνες γλώσσες προγραμματισμού,


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

fun days2ms days =


let
val hours = days * 24.0
val minutes = hours * 60.0
val seconds = minutes * 60.0
in
seconds * 1000.0
end;

Εγγραφές δραστηριοποίησης 6

Δραστηριοποίηση των μπλοκ

• Για μπλοκ που περιέχουν κώδικα, μιλάμε για


– Τη δραστηριοποίηση του μπλοκ
– Το χρόνο ζωής μιας εκτέλεσης του μπλοκ
• Μια μεταβλητή μπορεί να είναι ειδική για κάποια μόνο
δραστηριοποίηση ενός συγκεκριμένου μπλοκ μιας
συνάρτησης:
fun fact n =
if n = 0 then 1
else let val fac = fact (n-1) in n*fac
end;

Εγγραφές δραστηριοποίησης 7
Χρόνοι ζωής για τις μεταβλητές

• Οι περισσότερες προστακτικές γλώσσες έχουν κάποιο


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

int count = 0;
int nextcount() {
count = count + 1;
return count;
}

Εγγραφές δραστηριοποίησης 8

Η εμβέλεια και ο χρόνος ζωής διαφέρουν

• Στις περισσότερες μοντέρνες γλώσσες, οι μεταβλητές


με τοπική εμβέλεια έχουν χρόνο ζωής που συνήθως
ταυτίζεται με τη δραστηριοποίηση του μπλοκ
• Όμως, οι δύο έννοιες είναι διαφορετικές μεταξύ τους,
όπως π.χ. μπορεί να γίνει στη C μέσω του προσδιοριστή
static:
int nextcount() {
static int count;
count = count + 1;
return count;
}

Εγγραφές δραστηριοποίησης 9
Άλλοι χρόνοι ζωής για τις μεταβλητές

• Οι γλώσσες αντικειμενοστρεφούς προγραμματισμού


χρησιμοποιούν μεταβλητές των οποίων ο χρόνος ζωής
είναι συσχετισμένος με το χρόνο ζωής των αντίστοιχων
αντικειμένων
• Κάποιες γλώσσες έχουν μεταβλητές των οποίων οι τιμές
είναι επίμονες (persistent): με άλλα λόγια διατηρούν την
τιμή τους για πολλαπλές εκτελέσεις του ίδιου
προγράμματος

Εγγραφές δραστηριοποίησης 10

Εγγραφές δραστηριοποίησης

• Οι υλοποιήσεις των γλωσσών συνήθως συσκευάζουν όλες


τις μεταβλητές που αναφέρονται σε μια συγκεκριμένη
δραστηριοποίηση της συνάρτησης σε μια εγγραφή
δραστηριοποίησης (activation record)
• Οι εγγραφές δραστηριοποίησης περιέχουν επίσης και όλα
τα άλλα δεδομένα που σχετίζονται με δραστηριοποιήσεις,
όπως:
– Τη διεύθυνση επιστροφής (return address) της δραστηριοποίησης:
δείχνει σε ποιο σημείο του προγράμματος πρέπει να πάει ο
έλεγχος όταν επιστρέψει η συγκεκριμένη δραστηριοποίηση
– Ένα σύνδεσμο πρόσβασης (access link) που δείχνει την εγγραφή
δραστηριοποίησης της καλούσας συνάρτησης (calling function)

Εγγραφές δραστηριοποίησης 11
Εγγραφές δραστηριοποίησης για μπλοκ

• Όταν εκτελείται ένα μπλοκ κώδικα, χρειαζόμαστε χώρο


για τις τοπικές μεταβλητές του συγκεκριμένου μπλοκ
• Υπάρχουν διάφοροι τρόποι δέσμευσης χώρου:
– Δεσμεύουμε από πριν χώρο στην εγγραφή δραστηριοποίησης
της περικλείουσας συνάρτησης
– Επεκτείνουμε την εγγραφή δραστηριοποίησης της συνάρτησης
όταν η εκτέλεση εισέλθει στο μπλοκ (και τη μειώνουμε ξανά
όταν εξέλθουμε του μπλοκ)
– Δεσμεύουμε κάποια ξεχωριστή εγγραφή δραστηριοποίησης για
το μπλοκ

• Θα δούμε τον πρώτο από αυτούς τους τρόπους

Εγγραφές δραστηριοποίησης 12

Στατική δέσμευση (static allocation)

• Η απλούστερη προσέγγιση: δέσμευσε μία εγγραφή


δραστηριοποίησης για κάθε μια συνάρτηση, στατικά
• Οι παλιές διάλεκτοι της Fortran και της Cobol
χρησιμοποιούσαν (μόνο) αυτό το σύστημα δέσμευσης
• Αποτελεί απλό και πολύ γρήγορο τρόπο υλοποίησης

Εγγραφές δραστηριοποίησης 13
Παράδειγμα

FUNCTION AVG (ARR, N)


N address
DIMENSION ARR(N)
SUM = 0.0
DO 100 I = 1, N ARR address
SUM = SUM + ARR(I)
100 CONTINUE return address
AVG = SUM / FLOAT(N)
RETURN I
END

SUM

AVG

Εγγραφές δραστηριοποίησης 14

Μειονέκτημα στατικής δέσμευσης

• Κάθε συνάρτηση έχει μία εγγραφή δραστηριοποίησης


• Μπορεί να υπάρξει μία μόνο δραστηριοποίηση
συνάρτησης ζωντανή κάθε χρονική στιγμή
• Οι μοντέρνες γλώσσες (συμπεριλαμβανομένων των
μοντέρνων διαλέκτων της Cobol και της Fortran) δεν
υπακούν σε αυτόν τον περιορισμό λόγω:
– Αναδρομής
– Πολυνηματικότητας (multithreading)

Εγγραφές δραστηριοποίησης 15
Στοίβες από εγγραφές δραστηριοποίησης (1)

• Για την υποστήριξη της αναδρομής, πρέπει να


δεσμεύσουμε μια νέα εγγραφή δραστηριοποίησης για
κάθε δραστηριοποίηση της συνάρτησης
• Δυναμική δέσμευση: η εγγραφή δραστηριοποίησης
δεσμεύεται όταν η συνάρτηση καλείται
• Σε πολλές γλώσσες, όπως η C, η εγγραφή αυτή
αποδεσμεύεται όταν η συνάρτηση επιστρέψει

Εγγραφές δραστηριοποίησης 16

Στοίβες από εγγραφές δραστηριοποίησης (2)

• Με άλλα λόγια, η εκτέλεση δημιουργεί μια στοίβα από


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

Εγγραφές δραστηριοποίησης 17
Τρέχουσα εγγραφή δραστηριοποίησης

• Στη στατική δέσμευση η θέση κάθε εγγραφής


δραστηριοποίησης καθορίζεται πριν την έναρξη
εκτέλεσης του προγράμματος
• Στη δυναμική δέσμευση η θέση της τρέχουσας
εγγραφής δραστηριοποίησης (current activation record)
δεν είναι γνωστή παρά μόνο κατά το χρόνο εκτέλεσης
• Κάθε συνάρτηση πρέπει να ξέρει πως θα βρει τη
διεύθυνση της τρέχουσας εγγραφής δραστηριοποίησης
• Συχνά, ένας καταχωρητής της μηχανής δεσμεύεται για
να περιέχει/κρατάει τη συγκεκριμένη τιμή

Εγγραφές δραστηριοποίησης 18

int fact(int n) {
Παράδειγμα σε C int result;
if (n < 2) result = 1;
else result = n * fact(n-1);
Αποτιμούμε την κλήση return result;
fact(3). Η εικόνα }
δείχνει τα περιεχόμενα
της στοίβας ακριβώς
πριν την αναδρομική current activation
κλήση της fact(2) record
που θα δημιουργήσει
τη δεύτερη εγγραφή
n: 3
δραστηριοποίησης.
return address
previous
activation record
result: ?

Εγγραφές δραστηριοποίησης 19
int fact(int n) {
Τα περιεχόμενα της int result;
μνήμης ακριβώς πριν if (n < 2) result = 1;
την τρίτη else result = n * fact(n-1);
δραστηριοποίηση. return result;
}

current
activation record

n: 2 n: 3

return address return address


previous previous
activation record activation record
result: ? result: ?

Εγγραφές δραστηριοποίησης 20

int fact(int n) {
Τα περιεχόμενα της int result;
μνήμης ακριβώς πριν if (n < 2) result = 1;
επιστρέψει η τρίτη else result = n * fact(n-1);
δραστηριοποίηση. return result;
}

current
activation record

n: 1 n: 2 n: 3

return address return address return address


previous previous previous
activation record activation record activation record
result: 1 result: ? result: ?

Εγγραφές δραστηριοποίησης 21
int fact(int n) {
Η δεύτερη int result;
δραστηριοποίηση if (n < 2) result = 1;
ακριβώς πριν else result = n * fact(n-1);
επιστρέψει. return result;
}

current
activation record

n: 1 n: 2 n: 3

return address return address return address


previous previous previous
activation record activation record activation record
result: 1 result: 2 result: ?

Εγγραφές δραστηριοποίησης 22

int fact(int n) {
Η πρώτη int result;
δραστηριοποίηση if (n < 2) result = 1;
ακριβώς πριν else result = n * fact(n-1);
επιστρέψει με το return result;
αποτέλεσμα }
fact(3) = 6.
current
activation record

n: 1 n: 2 n: 3

return address return address return address


previous previous previous
activation record activation record activation record
result: 1 result: 2 result: 6

Εγγραφές δραστηριοποίησης 23
Οι εικόνες περιλαμβάνουν μια μικρή
“απλοποίηση” όσον αφορά στο τι
Παράδειγμα σε ML πράγματι αποθηκεύεται στη στοίβα

Αποτιμούμε την κλήση current acti vation


record
halve [1,2,3,4]
Η εικόνα δείχνει τα parameter:
[1,2,3,4]
περιεχόμενα της μνήμης
ακριβώς πριν την αναδρομική return address

κλήση που δημιουργεί τη previous


activation record
δεύτερη δραστηριοποίηση.
a: 1

fun halve nil = (nil, nil) b: 2


| halve [a] = ([a], nil)
cs: [3,4]
| halve (a::b::cs) =
let
x: ?
val (x, y) = halve cs
in y: ?
(a::x, b::y)
end; value to return: ?

Εγγραφές δραστηριοποίησης 24

current
activation record
Τα περιεχόμενα της
μνήμης ακριβώς
πριν την τρίτη parameter: parameter:
[3,4] [1,2,3,4]
δραστηριοποίηση.
return address return address
previous previous
activation record activation record
a: 3 a: 1

b: 4 b: 2
fun halve nil = (nil, nil)
| halve [a] = ([a], nil) cs: [] cs: [3,4]
| halve (a::b::cs) =
let x: ? x: ?
val (x, y) = halve cs
in y: ? y: ?
(a::x, b::y) value to return: ? value to return: ?
end;
Εγγραφές δραστηριοποίησης 25
Τα περιεχόμενα της
μνήμης ακριβώς πριν
current
activation record
επιστρέψει η τρίτη
δραστηριοποίηση.

parameter: [] parameter: parameter:


[3,4] [1,2,3,4]

return address return address return address


previous previous previous
activation record activation record activation record
value to return: a: 3 a: 1
([], [])
b: 4 b: 2
fun halve nil = (nil, nil)
| halve [a] = ([a], nil) cs: [] cs: [3,4]
| halve (a::b::cs) =
let x: ? x: ?
val (x, y) = halve cs
in y: ? y: ?
(a::x, b::y)
value to return: ? value to return: ?
end;
Εγγραφές δραστηριοποίησης 26

Η δεύτερη
δραστηριοποίηση
ακριβώς πριν current
activation record
επιστρέψει.

parameter: [] parameter: parameter:


[3,4] [1,2,3,4]

return address return address return address


previous previous previous
activation record activation record activation record
value to return: a: 3 a: 1
([], [])
b: 4 b: 2
fun halve nil = (nil, nil)
| halve [a] = ([a], nil) cs: [] cs: [3,4]
| halve (a::b::cs) =
let x: [] x: ?
val (x, y) = halve cs
in y: [] y: ?
(a::x, b::y)
value to return: value to return: ?
end; ([3], [4])
Εγγραφές δραστηριοποίησης 27
Η πρώτη δραστηριοποίηση ακριβώς πριν
επιστρέψει με το αποτέλεσμα current
halve [1,2,3,4] = ([1,3],[2,4]) activation record

parameter: [] parameter: parameter:


[3,4] [1,2,3,4]

return address return address return address


previous previous previous
activation record activation record activation record
value to return: a: 3 a: 1
([], [])
b: 4 b: 2
fun halve nil = (nil, nil)
| halve [a] = ([a], nil) cs: [] cs: [3,4]
| halve (a::b::cs) =
let x: [] x: [3]
val (x, y) = halve cs
in y: [] y: [4]
(a::x, b::y) value to return:
value to return:
end; ([1,3],[2,4])
([3], [4])
Εγγραφές δραστηριοποίησης 28

Χειρισμός φωλιασμένων συναρτήσεων

Εγγραφές δραστηριοποίησης 29
Φωλιασμένες συναρτήσεις

• Ότι είδαμε μέχρι στιγμής επαρκεί για πολλές γλώσσες,


συμπεριλαμβανομένης της C
• Αλλά όχι για γλώσσες που επιτρέπουν το ακόλουθο τρικ:
– Οι ορισμοί των συναρτήσεων μπορεί να είναι φωλιασμένες μέσα
σε άλλους ορισμούς συναρτήσεων
– Οι εσωτερικές συναρτήσεις μπορεί να αναφέρονται σε τοπικές
μεταβλητές των εξωτερικών συναρτήσεων (ακολουθώντας τους
συνήθεις κανόνες εμβέλειας όταν υπάρχουν μπλοκ)

• Δηλαδή δεν επαρκεί για γλώσσες όπως η ML, η Ada, η


Pascal, κ.α.

Εγγραφές δραστηριοποίησης 30

Παράδειγμα

fun quicksort nil = nil


| quicksort (pivot::rest) =
let
fun split (nil) = (nil, nil)
| split (x::xs) =
let
val (below, above) = split (xs)
in
if x < pivot then (x::below, above)
else (below, x::above)
end;
val (below, above) = split (rest)
in
quicksort below @ [pivot] @ quicksort above
end;

Εγγραφές δραστηριοποίησης 31
Το πρόβλημα

• Πώς μπορεί η δραστηριοποίηση της εσωτερικής


συνάρτησης (split) να βρει την εγγραφή
δραστηριοποίησης της εξωτερικής συνάρτησης
(quicksort);
• Παρατηρήστε ότι δεν είναι απαραίτητα η προηγούμενη
εγγραφή δραστηριοποίησης, επειδή η συνάρτηση που
κάλεσε την εσωτερική συνάρτηση:
– μπορεί να είναι μια άλλη εσωτερική συνάρτηση, ή
– μπορεί να είναι μια αναδρομική κλήση συνάρτησης, όπως
συμβαίνει με τη split

Εγγραφές δραστηριοποίησης 32

current
activation record
first caller: a
a split another split quicksort
activation activation activation

parameter parameter parameter


...
return address return address return address
previous previous previous
activation record activation record activation record

quicksort’s
split’s split’s
variables:
variables: variables:
pivot, rest,
x, xs, etc. x, xs, etc.
etc.

Εγγραφές δραστηριοποίησης 33
Σύνδεσμος φωλιάσματος (nesting link)

• Μια εσωτερική συνάρτηση πρέπει να είναι σε θέση να


βρει τη διεύθυνση της πιο πρόσφατης δραστηριοποίησης
της αμέσως εξωτερικής της συνάρτησης
• Μπορούμε να κρατήσουμε αυτό το σύνδεσμο στην
εγγραφή δραστηριοποίησης

Εγγραφές δραστηριοποίησης 34

current
activation record
first caller: a
a split another split quicksort
activation activation activation

parameter parameter parameter


...
return address return address return address

nesting link nesting link nesting link: null


previous previous previous
activation record activation record activation record

quicksort’s
split’s split’s
variables:
variables: variables:
pivot, rest,
x, xs, etc. x, xs, etc.
etc.

Εγγραφές δραστηριοποίησης 35
Πως παίρνουν τιμές οι σύνδεσμοι φωλιάσματος;

• Εύκολα αν υπάρχει μόνο ένα επίπεδο φωλιάσματος:


– Όταν καλείται μια εξωτερική συνάρτηση: θέτουμε την τιμή null
στο σύνδεσμο
– Όταν καλείται μια εσωτερική συνάρτηση από μία εξωτερική: ο
σύνδεσμος φωλιάσματος παίρνει ως τιμή την εγγραφή
δραστηριοποίησης της καλούσας συνάρτησης
– Όταν καλείται μια εσωτερική συνάρτηση από μία εσωτερική: ο
σύνδεσμος φωλιάσματος παίρνει ως τιμή το σύνδεσμο
φωλιάσματος της καλούσας συνάρτησης

• Πιο πολύπλοκα από τον παραπάνω τρόπο εάν υπάρχουν


πολλαπλά επίπεδα φωλιάσματος…

Εγγραφές δραστηριοποίησης 36

Πολλαπλά επίπεδα φωλιάσματος

function f1
variable v1

function f2
variable v2

function f3
variable v3

• Οι αναφορές στο ίδιο επίπεδο (π.χ. της f1 στη v1, της


f2 στη v2, …) χρησιμοποιούν την τρέχουσα εγγραφή
δραστηριοποίησης
• Οι αναφορές σε ονόματα που βρίσκονται σε κ επίπεδα
φωλιάσματος διατρέχουν κ συνδέσμους της αλυσίδας
Εγγραφές δραστηριοποίησης 37
Άλλες λύσεις

• Το πρόβλημα είναι οι αναφορές από εσωτερικές


συναρτήσεις σε μεταβλητές κάποιας εξωτερικής
• Λύσεις του προβλήματος αποτελούν οι:
– Σύνδεσμοι φωλιάσματος των εγγραφών δραστηριοποίησης
(όπως δείξαμε στις προηγούμενες διαφάνειες)
– Displays: οι σύνδεσμοι φωλιάσματος δε βρίσκονται στις
εγγραφές δραστηριοποίησης, αλλά συλλέγονται σε ένα στατικό
πίνακα
– Λάμδα άρση (Lambda lifting): οι αναφορές του προγράμματος
αντικαθίστανται από αναφορές σε νέες, κρυφές παραμέτρους

Εγγραφές δραστηριοποίησης 38

Παράδειγμα λάμδα άρσης

fun quicksort nil = nil


| quicksort (pivot::rest) =
let
fun split (nil, _) = (nil, nil)
| split (x::xs, lpv) =
let
val (below, above) = split (xs, lpv)
in
if x < lpv then (x::below, above)
else (below, x::above)
end;
val (below, above) = split (rest, pivot)
in
quicksort below @ [pivot] @ quicksort above
end;

Εγγραφές δραστηριοποίησης 39
Συναρτήσεις ως παράμετροι

Εγγραφές δραστηριοποίησης 40

Συναρτήσεις ως παράμετροι

• Όταν περνάμε μια συνάρτηση ως παράμετρο, τι ακριβώς


είναι αυτό που πρέπει να μεταβιβαστεί;
• Ο κώδικας της συνάρτησης πρέπει να είναι μέρος αυτού
που περνιέται: είτε σε μορφή πηγαίου κώδικα, είτε σε
μορφή μεταγλωττισμένου κώδικα, δείκτη σε κώδικα, ή
μέσω υλοποίησης κάποιας άλλης μορφής
• Κάποιες γλώσσες, χρειάζονται κάτι περισσότερο…

Εγγραφές δραστηριοποίησης 41
Παράδειγμα

fun addXToAll (x,theList) =


let
fun addX y =
y + x;
in
map addX theList
end;

• Η παραπάνω συνάρτηση προσθέτει την τιμή του x σε


κάθε στοιχείο της λίστας theList
• Η addXToAll καλεί τη map, η map καλεί τη addX, και η
addX αναφέρεται στη μεταβλητή x στην εγγραφή
δραστηριοποίησης της συνάρτησης addXToAll
Εγγραφές δραστηριοποίησης 42

Σύνδεσμοι φωλιάσματος ξανά

• Όταν η map καλέσει την addX, τι σύνδεσμος


φωλιάσματος θα δοθεί στην addX;
– Όχι η εγγραφή δραστηριοποίησης της map διότι η addX δεν
είναι φωλιασμένη μέσα στη map
– Όχι ο σύνδεσμος φωλιάσματος της map διότι η map δεν είναι
κάπου φωλιασμένη
• Για να δουλέψει η συνάρτηση addXToAll, η
παράμετρος που περνά η addX στη map πρέπει να
περιλαμβάνει το σύνδεσμο φωλιάσματος που θα
χρησιμοποιηθεί όταν κληθεί η addX funletaddXToAll (x,theList) =

fun addX y =
y + x;
in
map addX theList
end;

Εγγραφές δραστηριοποίησης 43
Όχι μόνο για τις παραμέτρους

• Πολλές γλώσσες επιτρέπουν σε συναρτήσεις να


περαστούν ως παράμετροι
• Οι συναρτησιακές γλώσσες επιτρέπουν περισσότερα
είδη λειτουργιών σε συναρτήσεις-τιμές:
– Πέρασμα συναρτήσεων ως παραμέτρους
– Επιστροφή συναρτήσεων από συναρτήσεις
– Κατασκευή συναρτήσεων από εκφράσεις

• Οι συναρτήσεις-τιμές περιλαμβάνουν τόσο τον κώδικα


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

Εγγραφές δραστηριοποίησης 44

Παράδειγμα
fun addXToAll (x,theList) =
let
curre nt acti vatio n fun addX y =
record y + x;
in
parameter
map addX theList
end;
return address
y => y + x
nesti ng li nk
previo us
activa tion reco rd
Τα περιεχόμενα της μνήμης
x
ακριβώς πριν την κλήση της
theList map. Η μεταβλητή addX είναι
δεμένη με μια συνάρτηση-τιμή η
addX οποία περιλαμβάνει κώδικα και
ένα σύνδεσμο φωλιάσματος.
Εγγραφές δραστηριοποίησης 45
Μακρόβιες εγγραφές δραστηριοποίησης

Εγγραφές δραστηριοποίησης 46

Μια ακόμα περιπλοκή

• Τι συμβαίνει εάν μια συνάρτηση-τιμή χρησιμοποιείται


από τη συνάρτηση που τη δημιούργησε μετά την
επιστροφή της συνάρτησης-δημιουργού;

val test = fun funToAddX x =


let let
val f = funToAddX 3; fun addX y =
in y + x;
f 5 in
end; addX
end;

Εγγραφές δραστηριοποίησης 47
val test = current activation
let record
val f = funToAddX 3;
in
f 5 parameter x: 3 return address
end;
return address nesting link: null
fun funToAddX x =
previous
let nesting link: null
activation record
fun addX y =
previous
y + x; f: ?
activation record
in
addX addX
end;

y => y + x
Τα περιεχόμενα της
μνήμης ακριβώς πριν η
funToAddX επιστρέψει.
Εγγραφές δραστηριοποίησης 48

val test = current activation


let record
val f = funToAddX 3;
in
f 5 parameter x: 3 return address
end;
return address nesting link: null
fun funToAddX x =
let previous
nesting link: null
fun addX y = activation record
y + x; previous f
activation record
in
addX addX
end;

Μετά την επιστροφή y => y + x

της funToAddX, η f
δεσμεύεται με τη νέα
συνάρτηση-τιμή.
Εγγραφές δραστηριοποίησης 49
Το πρόβλημα

• Όταν η test καλέσει την f, η συνάρτηση θα


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

Εγγραφές δραστηριοποίησης 50

Η λύση του προβλήματος

• Στην ML, και σε άλλες γλώσσες όπου υπάρχει η ίδια


περιπλοκή, οι εγγραφές δραστηριοποίησης δε μπορούν
πάντα να δεσμευθούν και να αποδεσμευθούν
ακολουθώντας δομή στοίβας
• Αυτό διότι ακόμα και όταν μια συνάρτηση επιστρέψει,
μπορεί να υπάρχουν σύνδεσμοι στην εγγραφή
δραστηριοποίησής της
• Οι εγγραφές δε μπορεί να αποδεσμευθούν παρά μόνο
όταν καταστούν απροσπέλαστες
• Αυτό συνήθως συμβαίνει με αυτόματη διαχείριση μνήμης ή αλλιώς
με συλλογή σκουπιδιών (garbage collection)
Εγγραφές δραστηριοποίησης 51
Συμπερασματικά

• Όσο πιο “σοφιστικέ” είναι μια γλώσσα, τόσο πιο δύσκολο είναι να
δεθούν οι μεταβλητές με θέσεις μνήμης
– Στατική δέσμευση: δουλεύει σε γλώσσες που επιτρέπουν το
πολύ μια δραστηριοποίηση κάθε συνάρτησης κάθε στιγμή (όπως
σε πρώιμες διαλέκτους της Fortran και της Cobol)
– Δυναμική δέσμευση σε στοίβα: δουλεύει σε γλώσσες που δεν
επιτρέπουν φωλιασμένες συναρτήσεις (όπως η C)
– Σύνδεσμοι φωλιάσματος (ή κάτι αντίστοιχο): επιβάλλεται σε
γλώσσες που επιτρέπουν φωλιασμένες συναρτήσεις (όπως η ML,
η Ada και η Pascal). Οι συναρτήσεις τιμές πρέπει να
περιλαμβάνουν τόσο κώδικα όσο και σύνδεσμο φωλιάσματος
– Κάποιες γλώσσες (όπως η ML) επιτρέπουν αναφορές σε
εγγραφές δραστηριοποιήσεων συναρτήσεων που έχουν
περατώσει την εκτέλεσή τους. Κατά συνέπεια, οι εγγραφές
δραστηριοποίησης δε μπορούν να αποδεσμευθούν αμέσως μετά
την επιστροφή τους και η χρήση στοίβας δεν επαρκεί.
Εγγραφές δραστηριοποίησης 52

Εισαγωγή στη
γλώσσα Java
Παράδειγμα αντικειμενοστρεφούς τρόπου σκέψης

• Έγχρωμα σημεία στην My x: 20


οθόνη My y: 10
My color: dark grey
Things I can do:
• Τι δεδομένα αποθηκεύονται move
report x
στο καθένα; My x: 5
report y
My y: 20
– Οι συντεταγμένες του My color: black
Things I can do:
– Το χρώμα του My x: 17
move
My y: 25
• Τι θέλουμε να μπορεί να report x
report y
My color: light grey
Things I can do:
κάνει το κάθε σημείο; move
report x
– Να μετακινηθεί report y
– Να αναφέρει τη θέση του

Εισαγωγή στη γλώσσα Java 2

Η ορολογία της Java

• Κάθε σημείο είναι ένα


αντικείμενο (object)
My x: 10
• Που περιλαμβάνει τρία My y: 50
My color: black
πεδία (fields) Things I can do:
move
• Έχει τρεις μεθόδους report x
(methods) report y

• Και κάθε αντικείμενο είναι


ένα στιγμιότυπο (instance)
της ίδιας κλάσης (class)

Εισαγωγή στη γλώσσα Java 3


Αντικειμενοστρεφές στυλ προγραμματισμού

• Η επίλυση προβλημάτων γίνεται μέσω αντικειμένων:


– μικρά δέματα από δεδομένα που ξέρουν πώς να κάνουν
πράγματα στον εαυτό τους
• Δηλαδή η ιδέα δεν είναι ότι π.χ. το πρόγραμμα ξέρει πώς
να μετακινήσει ένα σημείο, αλλά ότι το σημείο ξέρει πώς
να μετακινήσει τον εαυτό του
• Οι γλώσσες αντικειμενοστρεφούς προγραμματισμού
κάνουν πιο εύκολο το συγκεκριμένο τρόπο σκέψης και
προγραμματισμού

Εισαγωγή στη γλώσσα Java 4

Παράδειγμα ορισμού κλάσης στη Java

public class Point {


private int x,y; field definitions
private Color myColor;

public int currentX() {


return x;
}

public int currentY() {


return y;
}

public void move(int newX, int newY) {


x = newX;
y = newY;
}
}
method definitions
Εισαγωγή στη γλώσσα Java 5
Πρωτόγονοι τύποι της Java

• char: 0..216-1, γράφονται ως 'a', '\n', …, με χρήση του συνόλου


χαρακτήρων Unicode
• byte: -27..27-1
• short: -215..215-1
• int: -231..231-1, γράφονται με το συνηθισμένο τρόπο
• long: -263..263-1, γράφονται με χρήση ενός L στο τέλος
• float: IEEE 32-bit standard, γράφονται με χρήση ενός F στο τέλος
• double: IEEE 64-bit standard, γράφονται ως αριθμοί κινητής
υποδιαστολής (π.χ., 1.2, 1.2e-5, ή 1e3)
• boolean: true και false
• Εκκεντρικοί τύποι: void και null

Εισαγωγή στη γλώσσα Java 6

Κατασκευαζόμενοι τύποι στη Java

• Όλοι οι κατασκευαζόμενοι τύποι είναι τύποι αναφορών


(reference types)
• Με άλλα λόγια είναι αναφορές σε αντικείμενα
– Ονόματα κλάσεων, όπως π.χ. Point
– Ονόματα κάποιας διαπροσωπείας (interface)
– Ονόματα τύπων πινάκων, όπως π.χ. Point[] ή int[]

Εισαγωγή στη γλώσσα Java 7


Συμβολοσειρές (strings)

• Προκαθορισμένος τύπος αλλά όχι


πρωτόγονος: η κλάση String
• Μια σειρά από χαρακτήρες που My data: Hello there
Mylength: 11
περικλείονται από διπλές Things I cando:
report my length
αποστρόφους και συμπεριφέρονται report my ithchar
make anuppercase
σα μια σταθερή συμβολοσειρά version of
myself
• Αλλά στην πραγματικότητα είναι etc.

ένα στιγμιότυπο της κλάσης


"Hello there"
String, δηλαδή ένα αντικείμενο
που περιέχει τη συγκεκριμένη σειρά
χαρακτήρων
Εισαγωγή στη γλώσσα Java 8

Αριθμητικοί τελεστές

• Για int: +, -, *, /, %, μοναδιαίος –

Έκφραση Java Τιμή


1+2*3 7
15/7 2
15%7 1
-(6*7) -42

• Για double: +, -, *, /, μοναδιαίος –


Έκφραση Java Τιμή
6.0*7.0 42.0
15.0/7.0 2.142857142857143

Εισαγωγή στη γλώσσα Java 9


Τελεστής συνένωσης

• Ο τελεστής + έχει ειδική υπερφόρτωση και εξαναγκασμό


μετατροπής τύπου για την κλάση String

Έκφραση Java Τιμή


"123"+"456" "123456"
"The answer is " + 42 "The answer is 42"
"" + (1.0/3.0) "0.3333333333333333"
1 + "2" "12"
"1" + 2 + 3 "123"
2 + 2 + "2" "42"

Εισαγωγή στη γλώσσα Java 10

Τελεστές σύγκρισης

• Υπάρχουν οι συνήθεις τελεστές σύγκρισης: <, <=, >=,


και >, για αριθμητικούς τύπους
• Επίσης ορίζεται η ισότητα == και η ανισότητα != για
κάθε τύπο, συμπεριλαμβανομένου του τύπου double
(σε αντίθεση με την ML)

Έκφραση Java Τιμή


1 <= 2 true
1 == 2 false
true != false true

Εισαγωγή στη γλώσσα Java 11


Boolean τελεστές

• && και ||, που βραχυκυκλώνουν


(όπως οι τελεστές andalso και orelse της ML)
• !, όπως ο τελεστής not της ML
• a?b:c, όπως το if a then b else c της ML

Έκφραση Java Τιμή


1 <= 2 && 2 <= 3 true
1 < 2 || 1 > 2 true
1 < 2 ? 3 : 4 3

Εισαγωγή στη γλώσσα Java 12

Τελεστές με παρενέργειες

• Ένας τελεστής έχει μια παρενέργεια (side effect) εάν


αλλάζει κάτι στο περιβάλλον του προγράμματος, όπως
για παράδειγμα την τιμή μιας μεταβλητής ή ένα στοιχείο
ενός πίνακα
• Στην ML είδαμε μόνο αγνούς (pure) τελεστές — δηλαδή
τελεστές χωρίς παρενέργειες
• Στη Java υπάρχουν και τελεστές με παρενέργειες

Εισαγωγή στη γλώσσα Java 13


Αναθέσεις, Rvalues και Lvalues

• a = b: αλλάζει την τιμή του a και την κάνει ίση με τη b


• Η ύπαρξη αναθέσεων είναι ένα σημαντικό συστατικό και
χαρακτηριστικό των προστακτικών γλωσσών
• Γιατί η ανάθεση a = 1 έχει νόημα, αλλά όχι η 1 = a ;
• Οι εκφράσεις στα δεξιά του = πρέπει να έχουν μια τιμή:
π.χ. a, 1, a+1, f() (εκτός αν είναι void), κ.λπ.
• Οι εκφράσεις στα αριστερά του = πρέπει να είναι θέσεις
μνήμης: π.χ. a ή d[2], αλλά όχι 1 ή a+1
• Τα δύο αυτά χαρακτηριστικά των εκφράσεων πολλές
φορές αναφέρονται ως rvalue και lvalue
Εισαγωγή στη γλώσσα Java 14

Τελεστές με παρενέργειες στη Java

• Σύνθετες αναθέσεις

Έκφραση Java Σύντομη Έκφραση Java


a = a+b a += b
a = a-b a -= b
a = a*b a *= b

• Αύξηση και μείωση κατά ένα


Έκφραση Java Σύντομη Έκφραση Java
a = a+1 a++
a = a-1 a--

Εισαγωγή στη γλώσσα Java 15


Τιμές και παρενέργειες

• Οι εκφράσεις με παρενέργειες έχουν τόσο τιμή όσο και


κάποια παρενέργεια
• Η τιμή της ανάθεσης x = y είναι η τιμή του y και η
παρενέργειά της είναι να αλλάξει την τιμή του x σε
αυτήν την τιμή
Έκφραση Java Τιμή Παρενέργεια
a+(x=b)+c το άθροισμα των a, b και c αλλάζει την τιμή του x, και
την κάνει ίση με b
(a=d)+(b=d)+(c=d) τρεις φορές η τιμή του d αλλάζει τις τιμές των a, b
και c, και τις κάνει όλες
ίσες με το d
a=b=c η τιμή του c αλλάζει τις τιμές των a και
b, και τις κάνει ίσες με το c
Εισαγωγή στη γλώσσα Java 16

Εκφράσεις αύξησης και μείωσης κατά ένα

• Οι τιμές χρήσης των τελεστών αύξησης και μείωσης


κατά ένα εξαρτώνται από την τοποθέτησή τους

Έκφραση Java Τιμή Παρενέργεια


a++ η παλιά τιμή του a προσθέτει ένα στο a
++a η νέα τιμή του a προσθέτει ένα στο a
a-- η παλιά τιμή του a αφαιρεί ένα από το a
--a η νέα τιμή του a αφαιρεί ένα από το a

Εισαγωγή στη γλώσσα Java 17


Κλήσεις μεθόδου στιγμιότυπου (instance method)

Έκφραση Java Τιμή

s.length() το μήκος του String s

s.equals(r) true εάν s και r είναι ίδια και


false εάν όχι
r.equals(s) το ίδιο με το παραπάνω

r.toUpperCase() ένα αντικείμενο String που είναι


το String r αλλά με κεφαλαία
r.charAt(3) η τιμή του χαρακτήρα στη θέση 3
στο String r (δηλαδή, ο τέταρτός
του χαρακτήρας)
r.toUpperCase().charAt(3) η τιμή του χαρακτήρα στη θέση 3
στο String r με όλα κεφαλαία

Εισαγωγή στη γλώσσα Java 18

Κλήσεις μεθόδου κλάσης (class method calls)

• Οι μέθοδοι μιας κλάσης (class methods) ορίζουν


λειτουργίες που η κλάση ξέρει πώς να κάνει — δεν
ορίζουν αντικείμενα της κλάσης
• Οι κλάσεις χρησιμεύουν ως τόποι ονομάτων
• Οι κλήσεις μεθόδων είναι κάτι σαν τις συνήθεις κλήσεις
συναρτήσεων στις μη αντικειμενοστρεφείς γλώσσες
Έκφραση Java Τιμή

String.valueOf(1==2) "false"

String.valueOf(6*7) "42"

String.valueOf(1.0/3.0) "0.3333333333333333"

Εισαγωγή στη γλώσσα Java 19


Σύνταξη των κλήσεων μεθόδων

• Κλήση μεθόδου στιγμιότυπου:


<method-call> ::= <reference-expression>.<method-name>
(<parameter-list>)

• Κλήση μεθόδου κλάσης:


<method-call> ::= <class-name>.<method-name>
(<parameter-list>)

• Οποιαδήποτε από τις παραπάνω κλήσεις, αλλά από μια


μέθοδο της ίδιας κλάσης:
<method-call> ::= <method-name>(<parameter-list>)

Εισαγωγή στη γλώσσα Java 20

Εκφράσεις δημιουργίας αντικειμένων

• Δημιουργία ενός νέου αντικειμένου το οποίο είναι


στιγμιότυπο κάποιας συγκεκριμένης κλάσης
<creation-expression> ::= new <class-name>
(<parameter-list>)
• Οι παράμετροι περνιούνται σε έναν κατασκευαστή
(constructor)—κάτι σαν μια ειδική μέθοδο της κλάσης
Έκφραση Java Τιμή
new String() ένα νέο String με μήκος μηδέν
new String(s) ένα νέο String που περιλαμβάνει ένα
αντίγραφο του String s
new String(chars) ένα νέο String που περιλαμβάνει τους
χαρακτήρες από τον πίνακα chars
Εισαγωγή στη γλώσσα Java 21
Δεν υπάρχει τρόπος να καταστρέψουμε αντικείμενα

• Τα αντικείμενα δημιουργούνται με κλήση της new


• Όμως δεν υπάρχει άμεσος τρόπος να τα καταστρέψουμε
ή να αποδεσμεύσουμε τη μνήμη που καταλαμβάνουν
• Αυτό γίνεται αυτόματα μέσω συλλογής σκουπιδιών
(garbage collection)

Ανακύκλωση στην Αθήνα: Πού είναι ο κάδος, ο-έ-ο;


Εισαγωγή στη γλώσσα Java 22

Γενικές πληροφορίες για τελεστές στη Java

• Όλοι οι τελεστές είναι αριστερά προσεταιριστικοί, εκτός


από την ανάθεση
• Υπάρχουν 15 επίπεδα προτεραιότητας
– Κάποια επίπεδα είναι προφανή: π.χ. ο τελεστής * έχει
μεγαλύτερη προτεραιότητα από τον τελεστή +
– Άλλα επίπεδα είναι λιγότερο προφανή: π.χ. ο τελεστής < έχει
μεγαλύτερη προτεραιότητα από τον !=

• Επιτρέπονται πολλοί εξαναγκασμοί μετατροπής τύπου


– Από null σε κάθε τύπο αναφοράς
– Κάθε τιμή μπορεί να μετατραπεί σε String σε κάποια συνένωση
– Ένας τύπος αναφοράς σε έναν άλλον (κάποιες φορές)

Εισαγωγή στη γλώσσα Java 23


Κάποιες αριθμητικές μετατροπές τύπων

• Από char σε int πριν εφαρμοστεί κάποιος αριθμητικός


τελεστής (εκτός της συνένωσης συμβολοσειρών)
• Από int σε double για δυαδικούς τελεστές που
περιλαμβάνουν και τους δύο τύπους

Έκφραση Java Τιμή


'a'+'b' 195
1/3 0
1/3.0 0.3333333333333333
1/2+0.0 0.0
1/(2+0.0) 0.5

Εισαγωγή στη γλώσσα Java 24

Εντολές εκφράσεων (expression statements)

• Όπως σε όλες τις προστακτικές γλώσσες, οι εντολές


εκτελούνται για τις παρενέργειές τους
<expression-statement> ::= <expression> ;
• Η τιμή της έκφρασης, εάν υπάρχει, απορρίπτεται
• Η Java δεν επιτρέπει στην έκφραση να είναι κάτι χωρίς
παρενέργειες, π.χ. x == y
Εντολή Java Επεξήγηση
speed = 0; Αποθήκευσε την τιμή 0 στη speed.
a++; Αύξησε την τιμή του a κατά 1.
inTheRed = cost > balance; Εάν η τιμή του cost είναι μεγαλύτερη
από balance, θέσε την τιμή της
inTheRed σε true, αλλιώς σε false.

Εισαγωγή στη γλώσσα Java 25


Σύνθετες εντολές (compound statements)

<compound-statement> ::= { <statement-list> }


< statement-list> ::= <statement> <statement-list> | <empty>

Εντολή Java Επεξήγηση


• Οι εντολές εκτελούνται
με τη σειρά που {
a = 0; Αποθήκευσε μηδέν στο a,
εμφανίζονται b = 1; μετά αποθήκευσε ένα στο b.
}
• Οι σύνθετες εντολές
{
χρησιμοποιούνται και ως a++; Αύξησε το a, μετά
b++; αύξησε το b, μετά
μπλοκ που δηλώνουν την c++; αύξησε το c.
}
εμβέλεια των μεταβλητών
{ } Μην κάνεις τίποτα.

Εισαγωγή στη γλώσσα Java 26

Εντολές δηλώσεων
<declaration-statement> ::= <declaration> ;
<declaration> ::= <type> <variable-name>
| <type> <variable-name> = <expression>

• Ορισμός μεταβλητών με εμβέλεια μπλοκ

boolean done = false; Ορίζει μια νέα μεταβλητή με όνομα done


τύπου boolean, και την αρχικοποιεί σε
false.
Point p; Ορίζει μια νέα μεταβλητή με όνομα p
τύπου Point. (και δεν την αρχικοποιεί.)
{
int temp = a; Ανταλλάζει τις τιμές των ακέραιων
a = b; μεταβλητών a και b.
b = temp;
}

Εισαγωγή στη γλώσσα Java 27


Η εντολή if

<if-statement> ::= if (<expression>) <statement>


| if (<expression>) <statement> else <statement>

• Το ξεκρέμαστο else επιλύεται με το συνήθη τρόπο

Εντολή Java Επεξήγηση

if (i > 0) i--; Μείωσε το i, αλλά μόνο εάν είναι


μεγαλύτερο από το μηδέν.
if (a < b) b -= a; Αφαίρεσε το μικρότερο από τα a και b
else a -= b; από το μεγαλύτερο.
if (reset) { Εάν η τιμή της reset είναι true,
a = b = 0; μηδένισε τα a και b και θέσε την τιμή
reset = false; της reset σε false.
}
Εισαγωγή στη γλώσσα Java 28

Η εντολή while

<while-statement> ::= while (<expression>) <statement>

• Αποτίμησε την έκφραση expression — εάν είναι false μην


κάνεις τίποτε
• Αλλιώς εκτέλεσε το statement και επανάλαβε
• Η επανάληψη είναι άλλο ένα χαρακτηριστικό των
προστακτικών γλωσσών προγραμματισμού
• (Παρατηρήστε ότι επανάληψη χωρίς παρενέργειες δεν
έχει νόημα, διότι η τιμή της έκφρασης πρέπει να αλλάζει)
• Εκτός από while η Java έχει επίσης do και for loops

Εισαγωγή στη γλώσσα Java 29


Εντολή Java Επεξήγηση

while (a < 100) a += 5; Όσο το a είναι μικρότερο του 100,


εξακολούθησε να προσθέτεις 5 στο a.

while (a != b) Αφαίρεσε το μικρότερο των a και b από


if (a < b) b -= a; το μεγαλύτερο, ξανά και ξανά μέχρι να
else a -= b; γίνουν ίσοι. (Αλγόριθμος του Ευκλείδη.)
while (time > 0) { Όσο η μεταβλητή time είναι
simulate(); μεγαλύτερη του μηδενός, κάλεσε τη
time--; μέθοδο simulate της τρέχουσας
} κλάσης και στη συνέχεια μείωσε κατά
ένα την time.
while (true) work(); Κάλεσε τη μέθοδο work της τρέχουσας
κλάσης ξανά και ξανά, για πάντα.

Εισαγωγή στη γλώσσα Java 30

Η εντολή return

<return-statement> ::= return <expression>;


| return;

• Οι μέθοδοι που επιστρέφουν κάποια τιμή πρέπει να


εκτελέσουν μια εντολή return της πρώτης μορφής
• Οι μέθοδοι που δεν επιστρέφουν κάποια τιμή (δηλαδή οι
μέθοδοι που έχουν ορισθεί ως void) μπορούν να
εκτελέσουν μια εντολή return της δεύτερης μορφής

Εισαγωγή στη γλώσσα Java 31


Ορισμοί κλάσεων

Εισαγωγή στη γλώσσα Java 32

Παράδειγμα κλάσης: ConsCell

/**
* A ConsCell is an element in a linked list of
* ints.
*/
public class ConsCell {
private int head; // the first item in the list
private ConsCell tail; // rest of the list, or null

/**
* Construct a new ConsCell given its head and tail.
* @param h the int contents of this cell
* @param t the next ConsCell in the list, or null
*/
public ConsCell(int h, ConsCell t) {
head = h;
tail = t;
}

Εισαγωγή στη γλώσσα Java 33


/**
* Accessor for the head of this ConsCell.
* @return the int contents of this cell
*/
public int getHead() {
return head;
}

/**
* Accessor for the tail of this ConsCell.
* @return the next ConsCell in the list, or null
*/
public ConsCell getTail() {
return tail;
}
}

Εισαγωγή στη γλώσσα Java 34

Χρήση της κλάσης ConsCell

• Είναι αντίστοιχης λειτουργίας με το cons της ML


• Θέλουμε οι λίστες στη Java να είναι αντικειμενοστρεφείς:
όπου η ML εφαρμόζει :: σε μια λίστα, το αντικείμενο-
λίστα σε Java πρέπει να είναι σε θέση να εφαρμόσει τη
μέθοδο ConsCell στον εαυτό του
• Η ML εφαρμόζει length σε μια λίστα. Οι λίστες σε Java
πρέπει να είναι σε θέση να υπολογίσουν το μήκος τους
• Κατά συνέπεια, δε μπορούμε να χρησιμοποιήσουμε null
για την κενή λίστα

Εισαγωγή στη γλώσσα Java 35


/**
* An IntList is a list of ints.
*/
public class IntList {
private ConsCell start; // list head, or null

/**
* Construct a new IntList given its first ConsCell.
* @param s the first ConsCell in the list, or null
*/
public IntList(ConsCell s) {
start = s;
}

/**
* Cons the given element h onto us and return the
* resulting IntList.
* @param h the head int for the new list
* @return the IntList with head h, and us as tail
*/
public IntList cons (int h) {
return new IntList(new ConsCell(h,start));
}
Εισαγωγή στη γλώσσα Java 36

/**
* Get our length.
* @return our int length
*/
public int length() {
int len = 0;
ConsCell cell = start;
while (cell != null) { // while not at end of list
len++;
cell = cell.getTail();
}
return len;
}
}

Εισαγωγή στη γλώσσα Java 37


Χρήση της IntList

ML: val a = nil;


val b = 2::a;
val c = 1::b;
val x = (length a) + (length b) + (length c);

Java: IntList a = new IntList(null);


IntList b = a.cons(2);
IntList c = b.cons(1);
int x = a.length() + b.length() + c.length();

Εισαγωγή στη γλώσσα Java 38

Τι είναι μια αναφορά;

• Μια αναφορά (reference) είναι μια τιμή που προσδιορίζει


μονοσήμαντα κάποιο συγκεκριμένο αντικείμενο
public IntList(ConsCell s) {
start = s;
}

• Αυτό που περνάμε ως όρισμα στον κατασκευαστή


IntList δεν είναι ένα αντικείμενο—είναι μια αναφορά
σε ένα αντικείμενο
• Αυτό που αποθηκεύεται στη μεταβλητή start δεν είναι
ένα αντίγραφο του αντικειμένου αλλά μια αναφορά στο
συγκεκριμένο αντικείμενο (το οποίο δεν αντιγράφεται)
Εισαγωγή στη γλώσσα Java 39
Δείκτες

• Σε μια γλώσσα όπως η C ή η C++, υπάρχει ένας εύκολος


τρόπος να σκεφτόμαστε τις αναφορές: μια αναφορά
είναι ένας δείκτης (pointer)
• Με άλλα λόγια, μια αναφορά είναι η διεύθυνση ενός
αντικειμένου στη μνήμη
• Τα συστήματα Java μπορούν, αν θέλουν, να
υλοποιήσουν τις αναφορές με αυτόν τον τρόπο

Εισαγωγή στη γλώσσα Java 40

Ναι, αλλά νόμιζα ότι…

• Έχω ακούσει από κάποιους ότι η Java είναι σαν τη C++


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

Εισαγωγή στη γλώσσα Java 41


Σύγκριση μεταξύ Java και C++

• Μια μεταβλητή στη C++ μπορεί να έχει ως τιμή ένα


αντικείμενο ή ένα δείκτη σε ένα αντικείμενο
• Υπάρχουν δύο επιλογείς:
– a->x επιλέγει μια μέθοδο ή ένα πεδίο x όταν το a είναι ένας
δείκτης σε ένα αντικείμενο
– a.x επιλέγει το x όταν το a είναι ένα αντικείμενο

• Μια μεταβλητή στη Java δε μπορεί να έχει ως τιμή ένα


αντικείμενο, μόνο μια αναφορά σε ένα αντικείμενο
• Δηλαδή υπάρχει μόνο ένας επιλογέας:
– a.x επιλέγει το x όταν το a είναι μια αναφορά σε ένα
αντικείμενο

Εισαγωγή στη γλώσσα Java 42

Σύγκριση C++ και Java

Πρόγραμμα σε C++ Αντίστοιχο στη Java


IntList* p; IntList p;
p = new IntList(0); p = new IntList(null);
p->length(); p.length();
p = q; p = q;
IntList p(0); Δεν υπάρχει αντίστοιχο
p.length();
p = q;

Εισαγωγή στη γλώσσα Java 43


Σύντομες οδηγίες χρήσης για τη Java

Εισαγωγή στη γλώσσα Java 44

Εκτύπωση κειμένου εξόδου

• Υπάρχει το προκαθορισμένο αντικείμενο: System.out


• Το οποίο έχει δύο μεθόδους:
– print(x) που τυπώνει το x, και
– println(x) που τυπώνει το x και ένα χαρακτήρα
νέας γραμμής
• Οι μέθοδοι αυτοί είναι υπερφορτωμένες για όλους τους
τύπους παραμέτρων

Εισαγωγή στη γλώσσα Java 45


Εκτύπωση μιας IntList

/**
* Print ourself to System.out.
*/
public void print() {
System.out.print("[");
ConsCell a = start;
while (a != null) {
System.out.print(a.getHead());
a = a.getTail();
if (a != null) System.out.print(",");
}
System.out.println("]");
}

Εισαγωγή στη γλώσσα Java 46

Η μέθοδος main

• Μια κλάση μπορεί να έχει μια μέθοδο main ως εξής:


public static void main(String[] args) {

}

• Η μέθοδος αυτή χρησιμοποιείται ως το σημείο έναρξης


της κλάσης όταν αυτή τρέξει ως εφαρμογή
• Η λέξη κλειδί static την κάνει μια μέθοδο της κλάσης
(class method). Πρέπει να χρησιμοποιείται με φειδώ!

Εισαγωγή στη γλώσσα Java 47


Η κλάση Driver

class Driver {
public static void main(String[] args) {
IntList a = new IntList(null);
IntList b = a.cons(2);
IntList c = b.cons(1);
int x = a.length() + b.length() + c.length();
a.print();
b.print();
c.print();
System.out.println(x);
}
}

Εισαγωγή στη γλώσσα Java 48

Μετάφραση και τρέξιμο του προγράμματος

• Τρεις κλάσεις προς μετάφραση, σε τρία αρχεία:


ConsCell.java, IntList.java και Driver.java
• (Όνομα αρχείου = όνομα κλάσης + .java)
• Μεταφράζουμε τα αρχεία με χρήση της εντολής javac
– Μπορούν να μεταγλωττιστούν ένα προς ένα
– Η με χρήση της εντολής javac Driver.java όλα μαζί

• Ο compiler παράγει.class αρχεία


• Χρησιμοποιούμε τον Java launcher (εντολή java) για να
τρέξουμε τη μέθοδο main ενός .class αρχείου

Εισαγωγή στη γλώσσα Java 49


Διαχείριση Μνήμης

Δυναμική δέσμευση μνήμης

• Κατά την εκτέλεση του προγράμματος υπάρχει ανάγκη


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

• Οι υλοποιήσεις των γλωσσών παρέχουν τη δυνατότητα


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

Διαχείριση μνήμης 2
Περιεχόμενα

• Μοντέλο μνήμης με πίνακες στη Java


• Στοίβες (Stacks)
• Σωροί (Heaps)
• Τρέχοντες σύνδεσμοι στο σωρό
• Συλλογή σκουπιδιών (garbage collection)

Διαχείριση μνήμης 3

Μοντέλο μνήμης

• Προς το παρόν, ας υποθέσουμε ότι το λειτουργικό


σύστημα παρέχει στο πρόγραμμα μία ή περισσότερες
περιοχές μνήμης κάποιου προκαθορισμένου μεγέθους
για δυναμική δέσμευση
• Θα μοντελοποιήσουμε τις περιοχές αυτές ως πίνακες
στη Java (Java arrays)
– Για να δούμε παραδείγματα κώδικα διαχείρισης μνήμης, και
– Για πρακτική εξάσκηση με τη Java

Διαχείριση μνήμης 4
Δήλωση πινάκων στη Java

• Στη Java οι πίνακες ορίζονται ως:


int[] a = null;
• Οι τύποι των πινάκων είναι τύποι αναφοράς—ένας
πίνακας στην πραγματικότητα είναι ένα αντικείμενο, το
οποίο μπορούμε να διαχειριστούμε με κάποια ειδική
σύνταξη
• Στο παραπάνω, η μεταβλητή a αρχικοποιείται σε null
• Μπορεί να της ανατεθεί μια αναφορά σε έναν πίνακα με
ακέραιες τιμές, αλλά στο παραπάνω παράδειγμα κάποιος
τέτοιος πίνακας δεν της έχει ανατεθεί μέχρι στιγμής

Διαχείριση μνήμης 5

Δημιουργία πινάκων στη Java

• Νέα αντικείμενα πίνακες δημιουργούνται με τη new:


int[] a = null;
a = new int[666];

• Και μπορούμε να συνδυάσουμε τις δύο παραπάνω


δηλώσεις σε μία ως εξής:
int[] a = new int[666];

Διαχείριση μνήμης 6
Χρησιμοποίηση πινάκων στη Java

int i = 0;
while (i < a.length) {
a[i] = 42;
i++;
}

• Για να προσπελάσουμε ένα στοιχείο του πίνακα


χρησιμοποιούμε την έκφραση a[i] (ως lvalue ή rvalue):
όπου a είναι μια έκφραση αναφοράς σε πίνακα και i είναι
μια ακέραια έκφραση
• Για το μέγεθος ενός πίνακα χρησιμοποιούμε a.length
• Οι δείκτες των πινάκων έχουν εύρος 0..(a.length-1)
Διαχείριση μνήμης 7

Διαχειριστές μνήμης στη Java

public class MemoryManager {


private int[] memory;

/**
* MemoryManager constructor.
* @param initialMemory int[] of memory to manage
*/
public MemoryManager(int[] initialMemory) {
memory = initialMemory;
}

}

Ο πίνακας initialMemory είναι


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

Διαχείριση μνήμης 8
Στοίβες (stacks)

Διαχείριση μνήμης 9

Στοίβες από εγγραφές δραστηριοποίησης

• Στις περισσότερες γλώσσες, οι εγγραφές


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

Διαχείριση μνήμης 10
Εικονογράφηση μιας στοίβας

top: 8
Μια κενή στοίβα με 8
7:
λέξεις. Η στοίβα
6: μεγαλώνει προς τα κάτω,
5:
από μεγάλες σε μικρές
διευθύνσεις. Μια
4: προκαθορισμένη θέση
μνήμης (ή πιθανόν ένας
3:
καταχωρητής) αποθηκεύει
2: τη διεύθυνση της
κορυφής της στοίβας
1:
(της μικρότερης
0: χρησιμοποιούμενης
θέσης μνήμης).
Διαχείριση μνήμης 11

top: 4

7: Το πρόγραμμα καλεί την


first activation m.push(3), η οποία
6:
record επιστρέφει 5: τη διεύθυνση
5: της πρώτης από τις 3 λέξεις
4: 8
που δεσμεύονται για την
εγγραφή δραστηριοποίησης.
3: Η διαχείριση της μνήμης
2:
χρησιμοποιεί μια επιπλέον
λέξη για να καταγράψει την
1: προηγούμενη τιμή της top.
0:

Διαχείριση μνήμης 12
Το πρόγραμμα τώρα καλεί
την m.push(2), η οποία
top: 1
γυρνάει 2: τη διεύθυνση της
7: πρώτης από τις 2 λέξεις που
6: first activation δεσμεύονται για την
record εγγραφή δραστηριοποίησης.
5: Η στοίβα τώρα γέμισε—δεν
4: 8
υπάρχει χώρος ούτε για μια
κλήση m.push(1).
3:
second Για μια κλήση m.pop(),
2: activation record
αναθέτουμε
1: 4
top = memory[top]
για να επιστρέψουμε στην
0: προηγούμενη κατάσταση.

Διαχείριση μνήμης 13

Υλοποίηση της μνήμης στοίβας στη Java

public class StackManager {


private int[] memory; // the memory we manage
private int top; // index of top stack block

/**
* StackManager constructor.
* @param initialMemory int[] of memory to manage
*/
public StackManager(int[] initialMemory) {
memory = initialMemory;
top = memory.length;
}

Διαχείριση μνήμης 14
/**
* Allocate a block and return its address.
* @param requestSize int size of block, > 0
* @return block address
* @throws StackOverflowError if out of stack space
*/
public int push(int requestSize) {
int oldtop = top;
top -= (requestSize+1); // extra word for oldtop
if (top < 0) throw new StackOverflowError();
memory[top] = oldtop;
return top+1;
}
/** Pop the top stack frame.
* This works only if the stack is not empty.
*/
public void pop() { Θα δούμε την εντολή throw
top = memory[top];
}
και το χειρισμό εξαιρέσεων
} σε επόμενο μάθημα.
Διαχείριση μνήμης 15

Σωροί (heaps)

Διαχείριση μνήμης 16
Διάταξη σε σωρό

• Η διάταξη σε στοίβα έχει σχετικά εύκολη υλοποίηση


• Αλλά δεν επαρκεί πάντα: τι συμβαίνει για παράδειγμα
εάν οι δεσμεύσεις και οι αποδεσμεύσεις κομματιών
μνήμης ακολουθούν τυχαία σειρά;
• Ένας σωρός (heap) είναι μία ακολουθία από μπλοκ
μνήμης τα οποία μας δίνουν τη δυνατότητα να έχουμε
μη ταξινομημένη δέσμευση και αποδέσμευση μνήμης
• Υπάρχουν πολλοί μηχανισμοί υλοποίησης μνήμης σωρού

Διαχείριση μνήμης 17

Υλοποίηση μέσω του αλγόριθμου First Fit

• Ένας σωρός υλοποιείται ως μια συνδεδεμένη λίστα από


ελεύθερα μπλοκ (free list)
• Αρχικά αποτελείται από μόνο ένα μεγάλο μπλοκ
• Για δέσμευση κάποιου κομματιού μνήμης:
– Ψάχνουμε στη free list για το πρώτο μπλοκ με μέγεθος τέτοιο
ώστε να ικανοποιούνται οι απαιτήσεις της ζήτησης
– Εάν το μπλοκ που βρήκαμε είναι μεγαλύτερο από τη ζήτηση,
τότε επιστρέφουμε το αχρησιμοποίητο κομμάτι του στη free list
– Ικανοποιούμε την απαίτηση δέσμευσης μνήμης και επιστρέφουμε
την αρχική διεύθυνση του δεσμευμένου μπλοκ

• Για αποδέσμευση, απλώς προσθέτουμε το ελευθερωθέν


μπλοκ στην αρχή της free list
Διαχείριση μνήμης 18
Εικονογράφηση της μνήμης σωρού

Ένας διαχειριστής μνήμης σωρού m που 9:


αποτελείται από ένα πίνακα από 10 8:
λέξεις, ο οποίος αρχικά είναι κενός.
7:
Ο σύνδεσμος στην αρχή της free list
κρατιέται στη μεταβλητή freeStart. 6:

Κάθε μπλοκ, δεσμευμένο ή μη, φυλάει 5:


το μέγεθός του στην πρώτη λέξη του. 4:

Τα ελεύθερα μπλοκ έχουν ένα σύνδεσμο 3:


στο επόμενο μπλοκ της free list στη
δεύτερη λέξη τους, ή την τιμή -1 εάν 2:

πρόκειται για το τελευταίο μπλοκ. 1: -1

freeStart: 0 0: 10

Διαχείριση μνήμης 19

p1 = m.allocate(4);
9:

8:

7:
Η αναφορά p1 θα πάρει την τιμή 1,
6: -1
δηλαδή τη διεύθυνση της πρώτης από
τις τέσσερις δεσμευμένες λέξεις. 5: 5

Μία επιπλέον λέξη χρησιμοποιείται 4:


για να κρατήσει το μέγεθος του μπλοκ. 3:
first allocated
Το υπόλοιπο μέρος του μπλοκ 2:
block
επεστράφη στη free list.
1:

freeStart: 5 0: 5

Διαχείριση μνήμης 20
p1 = m.allocate(4);
p2 = m.allocate(2); 9: -1

8: 2

7:
second allocated
Η αναφορά p2 θα πάρει την τιμή 6, block
6:
δηλαδή τη διεύθυνση της πρώτης από
τις δύο δεσμευμένες λέξεις. 5: 3

Μία επιπλέον λέξη χρησιμοποιείται 4:


για να κρατήσει το μέγεθος του μπλοκ. 3:
first allocated
Το υπόλοιπο μέρος του μπλοκ 2:
block
επεστράφη στη free list.
1:

freeStart: 8 0: 5

Διαχείριση μνήμης 21

p1 = m.allocate(4);
p2 = m.allocate(2); 9: -1
m.deallocate(p1); 8: 2

7:
second allocated
6: block

Αποδεσμεύει το πρώτο δεσμευμένο 5: 3


μπλοκ και το επιστρέφει στην κεφαλή
της free list. 4:

3:

2:

1: 8

freeStart: 0 0: 5

Διαχείριση μνήμης 22
p1 = m.allocate(4);
p2 = m.allocate(2); 9: -1
m.deallocate(p1); 8: 2
p3 = m.allocate(1);
7:
second allocated
Η αναφορά p3 θα πάρει την τιμή 1, block
6:
δηλαδή τη διεύθυνση της πρώτης
δεσμευμένης λέξης. 5: 3

Προσέξτε ότι υπήρχαν δύο μπλοκ που 4:


θα μπορούσαν να εξυπηρετήσουν την 3: 8
απαίτηση δέσμευσης. Το άλλο μπλοκ
θα προσέφερε ένα τέλειο ταίριασμα 2: 3
και θα επιλέγονταν από το μηχανισμό 1: third allocated
δέσμευσης μνήμης Best Fit. block
freeStart: 2 0: 2

Διαχείριση μνήμης 23

Υλοποίηση της μνήμης σωρού στη Java

public class HeapManager {


static private final int NULL = -1; // null link
public int[] memory; // the memory we manage
private int freeStart; // start of the free list

/**
* HeapManager constructor.
* @param initialMemory int[] of memory to manage
*/
public HeapManager(int[] initialMemory) {
memory = initialMemory;
memory[0] = memory.length; // one big free block
memory[1] = NULL; // free list ends with it
freeStart = 0; // free list starts with it
}

Διαχείριση μνήμης 24
/**
* Allocate a block and return its address.
* @param requestSize int size of block, > 0
* @return block address
* @throws OutOfMemoryError if no block big enough
*/
public int allocate(int requestSize) {
int size = requestSize + 1; // size with header

// Do first-fit search: linear search of the free


// list for the first block of sufficient size.
int p = freeStart; // head of free list
int lag = NULL;
while (p != NULL && memory[p] < size) {
lag = p; // lag is previous p
p = memory[p+1]; // link to next block
}
if (p == NULL) // no block large enough
throw new OutOfMemoryError();
int nextFree = memory[p+1]; // block after p
Διαχείριση μνήμης 25

// Now p is the index of a block of sufficient size,


// and lag is the index of p's predecessor in the
// free list, or NULL, and nextFree is the index of
// p's successor in the free list, or NULL.
// If the block has more space than we need, carve
// out what we need from the front and return the
// unused end part to the free list.
int unused = memory[p]-size; // extra space
if (unused > 1) { // if more than a header's worth
nextFree = p+size; // index of the unused piece
memory[nextFree] = unused; // fill in size
memory[nextFree+1] = memory[p+1]; // fill in link
memory[p] = size; // reduce p's size accordingly
}

// Link out the block we are allocating and done.


if (lag == NULL) freeStart = nextFree;
else memory[lag+1] = nextFree;
return p+1; // index of useable word (after header)
}
Διαχείριση μνήμης 26
/**
* Deallocate an allocated block. This works only
* if the block address is one that was returned
* by allocate and has not yet been deallocated.
* @param address int address of the block
*/
public void deallocate(int address) {
int addr = address-1;
memory[addr+1] = freeStart;
freeStart = addr;
}
}

Διαχείριση μνήμης 27

Ένα πρόβλημα

• Έστω η παρακάτω ακολουθία εντολών:


p1 = m.allocate(4);
p2 = m.allocate(4);
m.deallocate(p1);
m.deallocate(p2);
p3 = m.allocate(7);

• Η τελευταία κλήση της allocate θα αποτύχει διότι τα


ελεύθερα μπλοκ σπάνε σε μικρά κομμάτια αλλά πουθενά
δε ξαναενώνονται
• Χρειαζόμαστε ένα μηχανισμό ο οποίος συνασπίζει
συνεχόμενα γειτονικά μπλοκ που είναι ελεύθερα
Διαχείριση μνήμης 28
Μια από τις λύσεις του προβλήματος

• Υλοποιούμε μια πιο έξυπνη μέθοδο deallocate:


– Διατηρούμε τη λίστα των ελεύθερων μπλοκ ταξινομημένη με
βάση τις διευθύνσεις των μπλοκ
– Όταν ελευθερώνουμε ένα μπλοκ, κοιτάμε το προηγούμενο και το
επόμενό του μπλοκ που είναι ελεύθερα
– Εάν το μπλοκ που ελευθερώνουμε είναι συνεχόμενο με κάποιο
από αυτά, τα συνασπίζουμε

• Η μέθοδος αυτή είναι πιο αποτελεσματική από την απλή


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

Διαχείριση μνήμης 29

/**
* Deallocate an allocated block. This works only
* if the block address is one that was returned
* by allocate and has not yet been deallocated.
* @param address int address of the block
*/
public void deallocate(int address) {
int addr = address-1; // real start of the block

// Find the insertion point in the sorted


// free list for this block.

int p = freeStart;
int lag = NULL;
while (p != NULL && p < addr) {
lag = p;
p = memory[p+1];
}

Διαχείριση μνήμης 30
// Now p is the index of the block to come after
// ours in the free list, or NULL, and lag is the
// index of the block to come before ours in the
// free list, or NULL.

// If the one to come after ours is adjacent to it,


// merge it into ours and restore the property
// described above.

if (addr+memory[addr] == p) {
memory[addr] += memory[p]; // add its size to ours
p = memory[p+1];
}

Διαχείριση μνήμης 31

if (lag == NULL) { // ours will be first free


freeStart = addr;
memory[addr+1] = p;
}
else if (lag+memory[lag]==addr) { // block before is
// adjacent to ours
memory[lag] += memory[addr]; // merge ours into it
memory[lag+1] = p;
}
else { // neither: just a simple insertion
memory[lag+1] = addr;
memory[addr+1] = p;
}
}

Διαχείριση μνήμης 32
Ελεύθερες λίστες διαχωρισμένες βάσει μεγέθους

• Τα μικρού (και συνήθως προκαθορισμένου) μεγέθους


μπλοκ συνήθως δεσμεύονται και αποδεσμεύονται πολύ
πιο συχνά από τα μεγάλου ή τυχαίου μεγέθους μπλοκ
• Μια συνήθης βελτιστοποίηση: διατηρούμε ξεχωριστές
λίστες ελεύθερων μπλοκ για (μικρού) μεγέθους μπλοκ τα
οποία είναι δημοφιλή
• Με άλλα λόγια διαχωρίζουμε τις λίστες με βάση το
μέγεθος των μπλοκ που αποθηκεύουν και αναφερόμαστε
σε ελεύθερες λίστες διαχωρισμένες βάσει μεγέθους
(size-segregated free lists)

Διαχείριση μνήμης 33

Κατακερματισμός (fragmentation)

• Λέμε ότι η μνήμη είναι κατακερματισμένη όταν οι


ελεύθερες περιοχές διαχωρίζονται από δεσμευμένα
μπλοκ, και κατά συνέπεια δεν είναι δυνατό να
δεσμεύσουμε όλη την ελεύθερη μνήμη ως ένα μπλοκ
• Γενικότερα, η μνήμη είναι κατακερματισμένη όταν ο
διαχειριστής της μνήμης σωρού δεν είναι σε θέση να
εξυπηρετήσει τις αιτήσεις δέσμευσης μνήμης παρόλο
που υπάρχει αρκετή ελεύθερη μνήμη συνολικά

Διαχείριση μνήμης 34
p1 = m.allocate(4);
p2 = m.allocate(1); 9:
m.deallocate(p1); 8: -1
p3 = m.allocate(5);
7: 3

6: second allocated
Η τελευταία δέσμευση θα block

αποτύχει λόγω τεμαχισμού 5: 2


των ελεύθερων μπλοκ. 4:

3:

2:

1: 7

freeStart: 0 0: 5

Διαχείριση μνήμης 35

Τρέχοντες Σύνδεσμοι στο Σωρό

Διαχείριση μνήμης 36
Τρέχοντες σύνδεσμοι στο σωρό

• Μέχρι στιγμής, βλέπαμε το υπό εκτέλεση πρόγραμμα ως


ένα μαύρο κουτί που απλώς απαιτεί δεσμεύσεις και
αποδεσμεύσεις μνήμης
• Τι κάνει το πρόγραμμα με τις διευθύνσεις μνήμης που
του παραχωρούνται;
• Τις αποθηκεύει σε κάποια ονόματα (μεταβλητές)

• Ένας τρέχων σύνδεσμος στο σωρό (current heap link)


είναι μια θέση μνήμης που περιέχει μια τιμή η οποία θα
χρησιμοποιηθεί από το πρόγραμμα ως δείκτης στο σωρό
Διαχείριση μνήμης 37

Τρέχοντες σύνδεσμοι στο σωρό

a: start:
IntList a =
b: 2
c: 1 (an IntList) new IntList(null);
int b = 2;
free
(activation record int c = 1;
for main) head: 2 a = a.cons(b);
tail: null a = a.cons(c);
(a ConsCell)
free
head: 1
free tail: Πού είναι οι τρέχοντες
(a ConsCell) σύνδεσμοι στο σωρό
στη διπλανή εικόνα;
free

Στοίβα
the stack Σωρός
the heap

Διαχείριση μνήμης 38
Εύρεση των τρεχόντων συνδέσμων στο σωρό

• Αρχίζουμε με ένα σύνολο ριζών (root set): θέσεις μνήμης


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

• Για κάθε στοιχείο του συνόλου, κοιτάμε το δεσμευμένο


μπλοκ στο οποίο δείχνει ο σύνδεσμος και προσθέτουμε
όλες τις θέσεις μνήμης του συγκεκριμένου μπλοκ στο
σύνολό μας
• Επαναλαμβάνουμε την παραπάνω διαδικασία μέχρις του
σημείου όπου δε βρίσκουμε άλλες νέες θέσεις μνήμης
Διαχείριση μνήμης 39

Λάθη κατά την εύρεση των τρεχόντων συνδέσμων

• Παραλείψεις: ξεχνάμε να περιλάβουμε μια θέση μνήμης που


έχει έναν τρέχοντα σύνδεσμο στο σωρό
• Άχρηστες τιμές: συμπεριλαμβάνουμε θέσεις μνήμης για τις
οποίες το πρόγραμμα ποτέ δε θα χρησιμοποιήσει τις τιμές που
αποθηκεύουν
• Διευθύνσεις που δεν είναι θέσεις μνήμης:
συμπεριλαμβάνουμε κάποιες θέσεις μνήμης στο σωρό μόνο
και μόνο επειδή δε μπορούμε να ξεχωρίσουμε τιμές που
χρησιμοποιούνται π.χ. ως δείκτες στο σωρό ή ως ακέραιοι (με
τιμή που φαίνεται ως μια διεύθυνση μνήμης στο σωρό)

Διαχείριση μνήμης 40
Δε μπορούμε πάντα να αποφύγουμε τα λάθη

• Για τη σωστή διαχείριση του σωρού, τα λάθη παράλειψης


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

Τιμές που δείχνουν σαν διευθύνσεις μνήμης

• Για κάποιες μεταβλητές μπορεί να μην είμαστε σε θέση


να καταλάβουμε πώς χρησιμοποιούνται οι τιμές τους
• Για παράδειγμα, η παρακάτω μεταβλητή x μπορεί να
χρησιμοποιηθεί είτε ως δείκτης είτε ως πίνακας από
τέσσερις χαρακτήρες
union {
char *p;
char tag[4];
} x;
Σημείωση: το παραπάνω πρόβλημα είναι ακόμα χειρότερο στη C, διότι
οι C compilers δεν κρατούν πληροφορία για τους τύπους των
μεταβλητών κατά τη διάρκεια εκτέλεσης του προγράμματος
Διαχείριση μνήμης 42
Συμπίεση του σωρού (heap compaction)

• Μια λειτουργία που χρειάζεται πληροφορία για το


σύνολο των τρεχόντων συνδέσμων στο σωρό
• Ο διαχειριστής της μνήμης μετακινεί δεσμευμένα μπλοκ:
– Αντιγράφει το μπλοκ σε μια νέα θέση, και
– Επικαιροποιεί όλους τους συνδέσμους στο (ή κάπου μέσα στο)
μπλοκ

• Κατά συνέπεια συμπιέζει το σωρό, μετακινώντας όλα τα


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

Διαχείριση μνήμης 43

Συλλογή Σκουπιδιών

Διαχείριση μνήμης 44
Κάποια συνηθισμένα προβλήματα με δείκτες

type
p: ^Integer;
begin Ξεκρέμαστος δείκτης: το διπλανό
new(p); πρόγραμμα Pascal χρησιμοποιεί ένα
p^ := 42; δείκτη σε μπλοκ μνήμης και μετά την
dispose(p);
p^ := p^ + 1 αποδέσμευση του συγκεκριμένου μπλοκ
end

procedure Leak;
type Διαρροή μνήμης: το διπλανό πρόγραμμα
p: ^Integer; Pascal δεσμεύει ένα μπλοκ μνήμης αλλά
begin
ξεχνάει να το αποδεσμεύσει
new(p)
end;

Διαχείριση μνήμης 45

Συλλογή σκουπιδιών (garbage collection)

• Αφού τόσα σφάλματα λογισμικού συμβαίνουν λόγω


λαθών αποδέσμευσης μνήμης…
• …και επειδή δεν είναι βολικό για τον προγραμματιστή να
σκέφτεται για το πώς θα γίνει σωστά η αποδέσμευση
μνήμης…
• …για ποιο λόγο να μην είναι ευθύνη της υλοποίησης της
γλώσσας η αυτόματη ανακύκλωση μνήμης;

Διαχείριση μνήμης 46
Τρεις βασικοί μηχανισμοί ανακύκλωσης μνήμης

1. Μαρκάρισμα και σκούπισμα (mark and sweep)


2. Αντιγραφή (copying)
3. Μέτρημα αναφορών (reference counting)

Διαχείριση μνήμης 47

Μαρκάρισμα και σκούπισμα

• Ένας συλλέκτης μαρκαρίσματος και σκουπίσματος


(mark-and-sweep collector) χρησιμοποιεί τους τρέχοντες
συνδέσμους στο σωρό σε μια διαδικασία που έχει δύο
φάσεις:
– Μαρκάρισμα: βρίσκουμε όλους τους τρέχοντες συνδέσμους στο
σωρό και μαρκάρουμε όλα τα μπλοκ του σωρού τα οποία
δείχνονται από κάποιον από τους συνδέσμους
– Σκούπισμα: κάνουμε ένα πέρασμα στο σωρό και επιστρέφουμε
τα αμαρκάριστα μπλοκ στη λίστα με τα ελεύθερα μπλοκ

• Τα μπλοκ που μαρκάρονται στην πρώτη φάση του


αλγόριθμου δε μετακινούνται (non-moving collector)

Διαχείριση μνήμης 48
Συλλογή σκουπιδιών μέσω αντιγραφής

• Ένας συλλέκτης αντιγραφής (copying collector) διαιρεί


τη μνήμη σε δύο κομμάτια και χρησιμοποιεί τη μισή μόνο
μνήμη για να ικανοποιήσει τις αιτήσεις δέσμευσης
• Όταν το μισό αυτό μέρος γεμίσει, βρίσκουμε
συνδέσμους που δείχνουν σε μπλοκ στο πρώτο μισό και
αντιγράφουμε αυτά τα μπλοκ στο δεύτερο μισό
• Η διαδικασία αυτή συμπιέζει τα χρησιμοποιούμενα μπλοκ,
με αποτέλεσμα να εξαφανίζει τον κατακερματισμό
• Συλλέκτης μετακίνησης (moving collector) μπλοκ

Διαχείριση μνήμης 49

Μέτρημα αναφορών

• Κάθε μπλοκ έχει ένα μετρητή που κρατάει τον αριθμό


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

Διαχείριση μνήμης 50
Πρόβλημα μετρήματος αναφορών

circle: 2
link:

(activation record) Ένα πρόβλημα


free μετρήματος αναφορών:
1 δε μπορούμε να
link:
ανακαλύψουμε
κύκλους από άχρηστα
free free
μπλοκ.
1
link: Στην εικόνα, μια
κυκλικά συνδεδεμένη
λίστα που δείχνεται από
free τη μεταβλητή circle.
Στοίβα
the stack
Σωρός
the heap

Διαχείριση μνήμης 51

Πρόβλημα μετρήματος αναφορών

circle: null 1
link:

(activation record) Αν αναθέσουμε π.χ.


free την τιμή null στη
1 μεταβλητή circle,
link: η τιμή του μετρητή
θα μειωθεί.
free
free
1 Κανένας μετρητής
link: αναφορών δε γίνεται
μηδέν, παρόλο που
όλα τα μπλοκ είναι
free άχρηστα (σκουπίδια).
Στοίβα
the stack
Σωρός
the heap

Διαχείριση μνήμης 52
Μέτρημα αναφορών

• Δε μπορεί να συλλέξει κύκλους σκουπιδιών


• Επιβάλλει κάποιο μη αμελητέο κόστος στην εκτέλεση
του προγράμματος, λόγω της ανάγκης να ενημερώσουμε
τους μετρητές αναφορών σε κάθε ανάθεση
• Ένα πλεονέκτημα: ο αλγόριθμος είναι αυξητικός
(incremental), δηλαδή εκτελείται από τη φύση του σε
μικρά βήματα και δεν επιβάλλει μεγάλες παύσεις κατά
την εκτέλεση του προγράμματος

Διαχείριση μνήμης 53

Εκλεπτυσμένοι συλλέκτες σκουπιδιών

• Συλλέκτες γενεών (Generational collectors)


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

• Αυξητικοί συλλέκτες (Incremental collectors)


– Συλλέγουν σκουπίδια σε μικρά χρονικά διαστήματα τα οποία
παρεμβάλλονται με την εκτέλεση του προγράμματος
– Κατά συνέπεια αποφεύγουν το σταμάτημα της εφαρμογής για
μεγάλο χρονικό διάστημα και οι παύσεις λόγω αυτόματης
ανακύκλωσης μνήμης έχουν πολύ μικρή διάρκεια

Διαχείριση μνήμης 54
Γλώσσες με αυτόματη διαχείριση μνήμης

• Σε κάποιες γλώσσες η αυτόματη διαχείριση μνήμης είναι


απαίτηση: Lisp, Scheme, ML, Haskell, Erlang, Clean,
Prolog, Java, C#
• Κάποιες άλλες, όπως η Ada, απλώς την ενθαρρύνουν
• Τέλος, κάποιες άλλες γλώσσες όπως η C και η C++ την
καθιστούν πολύ δύσκολη
– Όμως ακόμα και για αυτές είναι δυνατή σε κάποιο βαθμό
– Υπάρχουν βιβλιοθήκες που αντικαθιστούν τη συνηθισμένη
υλοποίηση των malloc/free με ένα διαχειριστή μνήμης που
χρησιμοποιεί συντηρητική συλλογή σκουπιδιών (conservative
garbage collection) για την αυτόματη ανακύκλωση μνήμης

Διαχείριση μνήμης 55

Τάσεις στην αυτόματη διαχείριση μνήμης

• Μια ιδέα από τις αρχές του 1960, η δημοτικότητα της


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

Διαχείριση μνήμης 56
Συμπερασματικά

• Η διαχείριση μνήμης είναι ένα σημαντικό συστατικό των


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

Διαχείριση μνήμης 57

Περισσότερη Java
Πολυμορφισμός υποτύπων

Person p;
• Είναι το παραπάνω μια δήλωση ότι το p είναι μια αναφορά
σε ένα αντικείμενο της κλάσης Person;
• Όχι ακριβώς—ο τύπος Person μπορεί να περιλαμβάνει
αναφορές σε αντικείμενα άλλων κλάσεων
• Αυτό διότι η Java υποστηρίζει πολυμορφισμό υποτύπων
(subtype polymorphism)

Περισσότερη Java 2

Περιεχόμενα

• Υλοποίηση διαπροσωπείας των κλάσεων


• Επέκταση των κλάσεων
• Επέκταση και υλοποίηση
• Πολλαπλή κληρονομικότητα
• Παραμετρικότητα μέσω γενικών μεθόδων (generics)

Περισσότερη Java 3
Διαπροσωπείες (interfaces)

• Ένα πρωτότυπο μεθόδου (method prototype) απλώς δίνει


το όνομα της μεθόδου και τον τύπο της—όχι το σώμα της
• Οι διαπροσωπείες είναι συλλογές από πρωτότυπα μεθόδων
public interface Drawable {
void show(int xPos, int yPos);
void hide();
}
• Μια κλάση μπορεί να δηλώσει ότι υλοποιεί μια
συγκεκριμένη διαπροσωπεία
• Μετά πρέπει να παρέχει ορισμούς public μεθόδων οι
οποίοι ταιριάζουν με εκείνους της διαπροσωπείας
Περισσότερη Java 4

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

public class Icon implements Drawable {


public void show(int x, int y) {
… σώμα της μεθόδου …
}
public void hide() {
… σώμα της μεθόδου …
}
…περισσότερες μέθοδοι και πεδία…
}

public class Square implements Drawable, Scalable {


… πρέπει να υλοποιεί όλες τις μεθόδους όλων των διαπροσωπειών …
}

Περισσότερη Java 5
Γιατί χρησιμοποιούμε διαπροσωπείες;

• Μια διαπροσωπεία μπορεί να υλοποιείται από πολλές


διαφορετικές κλάσεις:
public class Window implements Drawable …
public class Icon implements Drawable …
public class Oval implements Drawable …

• Το όνομα της διαπροσωπείας μπορεί να χρησιμοποιηθεί


ως ένας τύπος αναφοράς:
Drawable d;
d = new Icon("i1.gif");
d.show(0,0);
d = new Oval(20,30);
d.show(0,0);

Περισσότερη Java 6

Πολυμορφισμός με διαπροσωπείες
static void flashoff(Drawable d, int k) {
for (int i = 0; i < k; i++) {
d.show(0,0);
d.hide();
}
}

• Η παραπάνω μέθοδος είναι πολυμορφική: η κλάση του


αντικειμένου που αναφέρεται από την παράμετρο d δεν
είναι γνωστή κατά το χρόνο μετάφρασης
• Το μόνο που είναι γνωστό είναι ότι είναι μια κλάση που
υλοποιεί τη διαπροσωπεία Drawable (implements
Drawable), και κατά συνέπεια είναι μια κλάση που έχει
μεθόδους show και hide οι οποίες μπορούν να κληθούν
Περισσότερη Java 7
Ένα πιο ολοκληρωμένο παράδειγμα

• Η επόμενη διαφάνεια δείχνει τη διαπροσωπεία μιας


κλάσης Worklist που είναι μια συλλογή από String
αντικείμενα
• Η κλάση περιέχει μεθόδους με τις οποίες μπορούμε
– να προσθέσουμε ένα αντικείμενο στη συλλογή,
– να αφαιρέσουμε ένα αντικείμενο από τη συλλογή, και
– να ελέγξουμε κατά πόσο μια συλλογή είναι κενή ή όχι

Περισσότερη Java 8

public interface Worklist {


/**
* Add one String to the worklist.
* @param item the String to add
*/
void add(String item);

/**
* Test whether there are more elements in the
* worklist: that is, test whether more elements
* have been added than have been removed.
* @return true iff there are more elements
*/
boolean hasMore();
/**
* Remove one String from the worklist and return it.
* There must be at least one element in the worklist.
* @return the String item removed
*/
String remove();
}
Περισσότερη Java 9
Σχόλια στις διαπροσωπείες

• Η ύπαρξη σχολίων είναι σημαντική για μια διαπροσωπεία,


διότι δεν υπάρχει κώδικας ώστε ο αναγνώστης/χρήστης
να καταλάβει τι (σκοπεύει να) κάνει η κάθε μέθοδος
• Η διαπροσωπεία της Worklist δεν προσδιορίζει κάποια
συγκεκριμένη δομή ή ταξινόμηση: μπορεί να υλοποιείται
από μια στοίβα, μια ουρά, ή κάτι άλλο
• Θα την υλοποιήσουμε ως στοίβα, μέσω συνδεδεμένης
λίστας

Περισσότερη Java 10

/**
* A Node is an object that holds a String and a link
* to the next Node. It can be used to build linked
* lists of Strings.
*/
public class Node {
private String data; // Each node has a String...
private Node link; // and a link to the next Node

/**
* Node constructor.
* @param theData the String to store in this Node
* @param theLink a link to the next Node
*/
public Node(String theData, Node theLink) {
data = theData;
link = theLink;
}

Περισσότερη Java 11
/**
* Accessor for the String data stored in this Node.
* @return our String item
*/
public String getData() {
return data;
}

/**
* Accessor for the link to the next Node.
* @return the next Node
*/
public Node getLink() {
return link;
}
}

Περισσότερη Java 12

/**
* A Stack is an object that holds a collection of
* Strings.
*/
public class Stack implements Worklist {
private Node top = null; // top Node in the stack

/**
* Push a String on top of this stack.
* @param data the String to add
*/
public void add(String data) {
top = new Node(data,top);
}

Περισσότερη Java 13
/**
* Test whether this stack has more elements.
* @return true if this stack is not empty
*/
public boolean hasMore() {
return (top != null);
}

/**
* Pop the top String from this stack and return it.
* This should be called only if the stack is
* not empty.
* @return the popped String
*/
public String remove() {
Node n = top;
top = n.getLink();
return n.getData();
}
}
Περισσότερη Java 14

Ένα παράδειγμα χρήσης

Worklist w;
w = new Stack();
w.add("ο Παρασκευάς.");
w.add("βας, ");
w.add("Βας, βας,");
System.out.print(w.remove());
System.out.print(w.remove());
System.out.println(w.remove());

• Έξοδος: Βας, βας, βας, ο Παρασκευάς.


• Άλλες υλοποιήσεις της κλάσης Worklist είναι πιθανές:
με χρήση Queue, PriorityQueue, κ.α.

Περισσότερη Java 15
Επέκταση των κλάσεων

Περισσότερη Java 16

Περισσότερος πολυμορφισμός

• Θα δούμε μια άλλη, πιο πολύπλοκη, πηγή πολυμορφισμού


• Μια κλάση μπορεί να παράγεται από μια άλλη, με χρήση
της λέξης κλειδί extends

Για παράδειγμα: θα ορίσουμε μια κλάση PeekableStack η


οποία είναι σαν την κλάση Stack, αλλά έχει επίσης μια
μέθοδο peek που εξετάζει το στοιχείο στην κορυφή της
στοίβας χωρίς όμως να το απομακρύνει από αυτή

Περισσότερη Java 17
/**
* A PeekableStack is an object that does everything
* a Stack can do, and can also peek at the top
* element of the stack without popping it off.
*/
public class PeekableStack extends Stack {

/**
* Examine the top element on the stack, without
* popping it off. This should be called only if
* the stack is not empty.
* @return the top String from the stack
*/
public String peek() {
String s = remove();
add(s);
return s;
}
}

Περισσότερη Java 18

Κληρονομικότητα (inheritance)

• Επειδή η κλάση PeekableStack επεκτείνει την κλάση


Stack, κληρονομεί όλες τις μεθόδους και τα πεδία της
(Κάτι τέτοιο δε συμβαίνει με τις διαπροσωπείες: όταν
μια κλάση υλοποιεί μια διαπροσωπεία, το μόνο που
αναλαμβάνει είναι μια υποχρέωση να υλοποιήσει
κάποιες μεθόδους.)
• Εκτός από κληρονομικότητα, η επέκταση των κλάσεων
οδηγεί και σε πολυμορφισμό

Περισσότερη Java 19
Stack s1 = new PeekableStack();
PeekableStack s2 = new PeekableStack();
s1.add("drive");
s2.add("cart");
System.out.println(s2.peek());

Προσέξτε ότι μια κλήση s1.peek() δε θα ήταν


νόμιμη, παρόλο που η s1 είναι μια αναφορά σε
ένα αντικείμενο της κλάσης PeekableStack.
Οι λειτουργίες που επιτρέπονται στη Java
καθορίζονται από το στατικό τύπο της αναφοράς
και όχι από την κλάση του αντικειμένου.

Περισσότερη Java 20

Ερώτηση

• Η υλοποίηση της μεθόδου peek δεν ήταν η πιο


αποδοτική:
public String peek() {
String s = remove();
add(s);
return s;
}

• Γιατί δεν κάνουμε το παρακάτω;


public String peek() {
return top.getData();
}

Περισσότερη Java 21
Απάντηση

• Το πεδίο top της κλάσης Stack έχει δηλωθεί private


• Η κλάση PeekableStack δε μπορεί να το προσπελάσει
• Για μια πιο αποδοτική μέθοδο peek, η κλάση Stack
πρέπει να καταστήσει το πεδίο top ορατό στις κλάσεις
που την επεκτείνουν
• Δηλαδή πρέπει να το δηλώσει ως protected αντί για
private
• Συνήθης πρόκληση σχεδιασμού για αντικειμενοστρεφείς
γλώσσες: πως ο σχεδιασμός θα κάνει εύκολη την
επαναχρησιμοποίηση μεθόδων μέσω κληρονομικότητας
Περισσότερη Java 22

Αλυσίδες κληρονομικότητας

• Στη Java, μια κλάση μπορεί να έχει πολλές κλάσεις που


παράγονται από αυτή
• Για την ακρίβεια, όλες οι κλάσεις της Java (εκτός από
μία) παράγονται από κάποια άλλη κλάση
• Εάν στον ορισμό μιας κλάσης δεν προσδιορίζεται κάποια
πρόταση extends, η Java παρέχει μια τέτοια πρόταση:
extends Object
• Η κλάση Object είναι η πρωταρχική κλάση της Java
(δεν παράγεται από κάποια άλλη)

Περισσότερη Java 23
Η κλάση Object

• Όλες οι κλάσεις παράγονται, άμεσα ή έμμεσα, από την


προκαθορισμένη κλάση Object
– (εκτός φυσικά από την κλάση Object)

• Όλες οι κλάσεις κληρονομούν μεθόδους από την κλάση


Object, για παράδειγμα:
– getClass, επιστρέφει την κλάση του αντικειμένου
– toString, για μετατροπή του αντικειμένου σε String
– equals, για σύγκριση με άλλα αντικείμενα
– hashcode, για υπολογισμό ενός ακεραίου (int) που αντιστοιχεί
στην τιμή του κωδικού κατακερματισμού (hash code) του
αντικειμένου
– κ.λπ.

Περισσότερη Java 24

Υπερκάλυψη κληρονομημένων ορισμών

• Κάποιες φορές μπορεί να θέλουμε να επανακαθορίσουμε


τη λειτουργικότητα μια κληρονομημένης μεθόδου
• Αυτό δε γίνεται με χρήση κάποιου ειδικού κατασκευαστή:
ένας νέος ορισμός μιας μεθόδου αυτόματα υπερκαλύπτει
(overrides) έναν κληρονομημένο ορισμό του ίδιου
ονόματος και τύπου

Περισσότερη Java 25
Παράδειγμα υπερκάλυψης

• Η κληρονομημένη μέθοδος toString απλώς συνδυάζει


το όνομα της κλάσης και τον κωδικό κατακερματισμού
(σε μορφή δεκαεξαδικού αριθμού)
• Με άλλα λόγια, ο κώδικας της default μεθόδου τυπώνει
κάτι σαν το εξής: Stack@b3d
• Μια ειδική μέθοδος toString στη μέθοδο Stack, σαν
την παρακάτω, μπορεί να τυπώσει ένα πιο διευκρινιστικό
μήνυμα:
public String toString() {
return "Stack with top at " + top;
}
Περισσότερη Java 26

Ιεραρχίες κληρονομικότητας

• Η σχέση κληρονομικότητας δημιουργεί μια ιεραρχία


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

Περισσότερη Java 27
Δύο κλάσεις με πολλά κοινά στοιχεία—αλλά
καμία δεν είναι μια απλή επέκταση της άλλης.
public class Label { public class Icon {
private int x, y; private int x, y;
private int width; private int width;
private int height; private int height;
private String text; private Gif image;
public void move public void move
(int newX, int newY) (int newX, int newY)
{ {
x = newX; x = newX;
y = newY; y = newY;
} }
public String getText() public Gif getImage()
{ {
return text; return image;
} }
} }

Περισσότερη Java 28

Ο κώδικας και τα δεδομένα που είναι κοινά


έχουν εξαχθεί σε μια κοινή “βασική” κλάση.
public class Graphic {
protected int x,y;
protected int width,height;
public void move(int newX, int newY) {
x = newX;
y = newY;
}
}

public class Label public class Icon


extends Graphic { extends Graphic {
private String text; private Gif image;
public String getText() public Gif getImage()
{ {
return text; return image;
} }
} }
Περισσότερη Java 29
Ένα πρόβλημα σχεδιασμού

• Πολλές φορές όταν γράφουμε τον ίδιο κώδικα ξανά και


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

Περισσότερη Java 30

Υποτύποι και κληρονομικότητα

• Μια παραγόμενη κλάση είναι ένας υποτύπος


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

• Κατά το σχεδιασμό της ιεραρχίας των κλάσεων, Σχήμα


πρέπει να σκεφτόμαστε την κληρονομικότητα
της λειτουργικότητάς τους Πολύπλευρο
• Όμως οι “φυσικές” ιεραρχίες δεν είναι πάντα
Τετράπλευρο
ότι πιο κατάλληλο μπορεί να υπάρξει όσον
αφορά στην κληρονομικότητα των λειτουργιών Τετράγωνο
Περισσότερη Java 31
Επέκταση και υλοποίηση

Περισσότερη Java 32

Επέκταση και υλοποίηση

• Οι κλάσεις μπορούν να χρησιμοποιήσουν τις λέξεις


κλειδιά extends και implements συγχρόνως
• Για κάθε κλάση, η υλοποίηση ενός συστήματος Java
κρατάει πληροφορίες για αρκετές ιδιότητες, όπως για
παράδειγμα:
Α: τις διαπροσωπείες που η κλάση υλοποιεί
Β: τις μεθόδους που είναι υποχρεωμένη να ορίσει
Γ: τις μεθόδους που ορίζονται για την κλάση
Δ: τα πεδία που περιλαμβάνει η κλάση

Περισσότερη Java 33
Απλές περιπτώσεις

• Ένας ορισμός μεθόδου επηρεάζει μόνο το Γ


• Ένας ορισμός πεδίου επηρεάζει μόνο το Δ
• Μια implements δήλωση επηρεάζει τα A και B
– Όλες οι διαπροσωπείες προσθέτονται στο A
– Όλες οι μέθοδοι τους προσθέτονται στο B

Α: τις διαπροσωπείες που η κλάση υλοποιεί


Β: τις μεθόδους που είναι υποχρεωμένη να ορίσει
Γ: τις μεθόδους που ορίζονται για την κλάση
Δ: τα πεδία που περιλαμβάνει η κλάση

Περισσότερη Java 34

Η δύσκολη περίπτωση
• Μια extends δήλωση επηρεάζει όλες τις πληροφορίες:
– Όλες οι διαπροσωπείες της βασικής κλάσης προσθέτονται στο Α
– Όλες οι μέθοδοι που υποχρεούται η βασική κλάση να ορίσει
προσθέτονται στο Β
– Όλες οι μέθοδοι της βασικής κλάσης προσθέτονται στο Γ
– Όλα τα πεδία της βασικής κλάσης προσθέτονται στο Δ

Α: τις διαπροσωπείες που η κλάση υλοποιεί


Β: τις μεθόδους που είναι υποχρεωμένη να ορίσει
Γ: τις μεθόδους που ορίζονται για την κλάση
Δ: τα πεδία που περιλαμβάνει η κλάση

Περισσότερη Java 35
Το προηγούμενο παράδειγμά μας

public class Stack implements Worklist {…}


public class PeekableStack extends Stack {…}

• Η κλάση PeekableStack έχει ως:


– Α: τη διαπροσωπεία Worklist, από κληρονομιά
– Β: τις υποχρεώσεις για υλοποίηση των μεθόδων add, hasMore,
και remove, επίσης από κληρονομιά
– Γ: τις μεθόδους add, hasMore, και remove, κληρονομημένες,
όπως επίσης και τη δική της μέθοδο peek
– Δ: το πεδίο top, επίσης κληρονομημένο

Περισσότερη Java 36

Μια ματιά στις abstract κλάσεις

• Παρατηρείστε ότι το Γ είναι υπερσύνολο του Β: η κλάση


πρέπει να έχει ορισμούς για όλες τις μεθόδους
• Η Java συνήθως απαιτεί το παραπάνω
• Οι κλάσεις μπορούν να απαλλαγούν από αυτήν την
υποχρέωση με το να δηλωθούν αφηρημένες (abstract)
• Μια abstract κλάση μπορεί να χρησιμοποιηθεί μόνο ως
βασική κλάση
• (Αυτό σημαίνει ότι δε μπορούν να δημιουργηθούν
αντικείμενα της συγκεκριμένης κλάσης.)

Περισσότερη Java 37
Τελικές (final) κλάσεις και μέθοδοι

• Περιορίζουν την κληρονομικότητα


– Οι τελικές κλάσεις δε μπορούν να επεκταθούν και οι τελικές
μέθοδοί τους δε μπορούν να ξαναοριστούν

• Παράδειγμα, η κλάση
java.lang.String

• Η ύπαρξη τελικών κλάσεων είναι σημαντική για ασφάλεια


– Ο προγραμματιστής μπορεί να ελέγξει πλήρως τη συμπεριφορά
όλων των υποκλάσεων (και κατά συνέπεια των υποτύπων)

Σημ.: Η δήλωση final μπορεί να χρησιμοποιηθεί και σε πεδία: εκεί, το


final σημαίνει ότι μπορεί να ανατεθεί τιμή μόνο μια φορά στο πεδίο

Πολλαπλή κληρονομικότητα

Περισσότερη Java 39
Πολλαπλή κληρονομικότητα (multiple inheritance)

• Σε κάποιες γλώσσες (όπως η C++) μια κλάση μπορεί να


έχει περισσότερες από μία βασικές κλάσεις
• Παράδειγμα: ένα πολυμηχάνημα (multifunction printer)

Printer Copier Scanner Fax

MultiFunction

• Επιφανειακά, τόσο η σημασιολογία όσο και η υλοποίηση


φαίνονται εύκολες: η κλάση απλά κληρονομεί όλα τα
πεδία και τις μεθόδους των βασικών της κλάσεων
Περισσότερη Java 40

Προβλήματα συγκρούσεων

• Οι διαφορετικές βασικές κλάσεις είναι άσχετες μεταξύ


τους και μπορεί να μην έχουν σχεδιαστεί έτσι ώστε να
συνδυάζονται
• Για παράδειγμα, τόσο η κλάση Scanner όσο και η Fax
μπορεί να έχουν ορίσει μια μέθοδο transmit
• Το ερώτημα είναι: τι πρέπει να συμβεί όταν καλέσουμε
τη μέθοδο MultiFunction.transmit;

Περισσότερη Java 41
Το πρόβλημα του διαμαντιού

• Μια κλάση μπορεί να κληρονομεί από την ίδια βασική


κλάση μέσω περισσοτέρων του ενός μονοπατιού
A

B C

D
• Εάν η κλάση A ορίζει ένα πεδίο x, τότε τόσο η B όσο και
η C έχουν ένα
• Δηλαδή η κλάση D έχει δύο τέτοια πεδία;

Περισσότερη Java 42

Το πρόβλημα είναι επιλύσιμο, αλλά…

• Μια γλώσσα που υποστηρίζει πολλαπλή κληρονομικότητα


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

Περισσότερη Java 43
Ζωή χωρίς πολλαπλή κληρονομικότητα

• Ένα πλεονέκτημα της πολλαπλής κληρονομικότητας


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

• Ένα επιπλέον πλεονέκτημα είναι η δυνατότητα


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

Περισσότερη Java 44

Προώθηση (forwarding)
public class MultiFunction {
private Printer myPrinter;
private Copier myCopier;
private Scanner myScanner;
private Fax myFax;

public void copy() {


myCopier.copy();
}
public void transmitScanned() {
myScanner.transmit();
}
public void sendFax() {
myFax.transmit();
}

}

Περισσότερη Java 45
Παραμετρικότητα μέσω Generics

Περισσότερη Java 46

Ανυπαρξία γενικών κλάσεων στις Java 1.0-1.4

• Το προηγούμενο παράδειγμα κλάσης Stack ορίστηκε ως


μια στοίβα από συμβολοσειρές
• Κατά συνέπεια, δε μπορεί να επαναχρησιμοποιηθεί για
στοίβες άλλων τύπων
• Στην ML μπορούσαμε να χρησιμοποιήσουμε μεταβλητές
τύπων για περιπτώσεις σαν και αυτές:
datatype 'a node =
NULL |
CELL of 'a * 'a node;

• Η Ada και η C++ έχουν κάτι παρόμοιο, αλλά όχι η Java

Περισσότερη Java 47
Ζωή χωρίς γενικές κλάσεις στις Java 1.0-1.4

• Μπορούμε να ορίσουμε μια στοίβα της οποίας τα


στοιχεία είναι αντικείμενα της κλάσης Object
• Ο τύπος Object είναι ο πιο γενικός τύπος της Java και
περιλαμβάνει όλες τις αναφορές
• Κατά συνέπεια ο ορισμός μέσω της κλάσης Object
επιτρέπει σε αντικείμενα οποιασδήποτε κλάσης να
τοποθετηθούν στη στοίβα
• Το παραπάνω προσφέρει κάποιου είδους πολυμορφισμό
υποτύπων

Περισσότερη Java 48

public class GenericNode {


private Object data;
private GenericNode link;
public GenericNode(Object theData,
GenericNode theLink) {
data = theData;
link = theLink;
}
public Object getData() {
return data;
}
public GenericNode getLink() {
return link;
}
}

Κατά παρόμοιο τρόπο, θα μπορούσαμε να ορίσουμε την


κλάση GenericStack (και μια GenericWorklist
διαπροσωπεία) με χρήση Object στη θέση της String
Περισσότερη Java 49
Μειονέκτημα

• Για να ανακτήσουμε τον τύπο του αντικειμένου στη


στοίβα, θα πρέπει να χρησιμοποιήσουμε ένα type cast:
GenericStack s1 = new GenericStack();
s1.add("hello");
String s = (String) s1.remove();
• Το παραπάνω μάλλον δεν είναι ότι πιο φιλικό για τον
προγραμματιστή
• Επίσης δεν είναι ότι πιο αποδοτικό σε χρόνο εκτέλεσης:
η Java πρέπει να ελέγξει κατά τη διάρκεια εκτέλεσης
του προγράμματος ότι το type cast επιτρέπεται—δηλαδή
ότι το αντικείμενο είναι πράγματι ένα String
Περισσότερη Java 50

Άλλο μειονέκτημα

• Οι πρωτόγονοι τύποι πρέπει πρώτα να αποθηκευθούν σε


ένα αντικείμενο εάν θέλουμε να τους βάλουμε σε μια
στοίβα:
GenericStack s2 = new GenericStack();
s2.add(new Integer(42));
int i = ((Integer) s2.remove()).intValue();

• Το παραπάνω είναι επίπονο και όχι ότι το πιο αποδοτικό


• Η κλάση Integer είναι η προκαθορισμένη κλάση
περιτύλιγμα (wrapper class) για τους ακεραίους
• Υπάρχει μια τέτοια κλάση για κάθε πρωτόγονο τύπο
Περισσότερη Java 51
Πραγματικά Generics (Java 1.5, “Tiger”)

• Ξεκινώντας με τη Java 1.5, η Java έχει generics, δηλαδή


παραμετρικές πολυμορφικές κλάσεις (και διαπροσωπείες)
• Η σύνταξή τους μοιάζει με τη σύνταξη των C++ templates
public class Stack<T> implements Worklist<T> {
private Node<T> top = null;
public void add(T data) {
top = new Node<T>(data,top);
}
public boolean hasMore() {
return (top != null);
}
public T remove() {
Node<T> n = top;
top = n.getLink();
return n.getData();
}
}
Περισσότερη Java 52

Χρησιμοποίηση των Generics

Stack<String> s1 = new Stack<String>();


Stack<int> s2 = new Stack<int>();
s1.add("hello");
String s = s1.remove();
s2.add(42);
int i = s2.remove();

Περισσότερη Java 53
Java 1.0 έναντι Java με Generics
class Stack { class Stack<T> {
void push(Object o) { void push(T a) { ...
... } }
Object pop() { ... } T pop() { ... }
... ...
} }
String s = "Hello"; String s = "Hello";
Stack st = new Stack(); Stack<String> st =
... new Stack<String>();
st.push(s); st.push(s);
... ...
s = (String) st.pop(); s = st.pop();

Περισσότερη Java 54

Γιατί δεν υπήρξαν generics στις πρώτες Java;

• Υπήρξαν αρκετές προτάσεις για επέκταση


• Σε συμφωνία με τους βασικούς σκοπούς της γλώσσας
• Όμως “ο διάβολος είναι στις λεπτομέρειες”, όπως:
– Τι παρενέργειες έχει η ύπαρξη generics για τη διαδικασία
ελέγχου των τύπων;
– Ποιος είναι ο καλύτερος τρόπος να γίνει η υλοποίηση;
• Μπορεί η αφηρημένη μηχανή της Java να υποστηρίξει τα
generics;
• Αν ναι, μέσω πρόσθετων bytecodes ή με κάποιον άλλο τρόπο;
• Μέσω ξεχωριστού κώδικα για κάθε στιγμιότυπο;
• Ή μέσω του ίδιου κώδικα (με χρήση casts) για όλα τα
στιγμιότυπα;
Το Java Community proposal (JSR 14) ενσωματώθηκε στη Java 1.5
Περισσότερη Java 55
Generics στη Java 1.5 (“Tiger”)

• Υιοθετήθηκε η σύνταξη που μόλις είδαμε


• Προστέθηκε αυτόματη μετατροπή boxing + unboxing

Τι θα γράφαμε χωρίς αυτόματη μετατροπή Τι γράφουμε στη Java 1.5


Stack<Integer> st = Stack<Integer> st =
new Stack<Integer>(); new Stack<Integer>();
st.push(new Integer(42)); st.push(42);
... ...
int i = (st.pop()).intValue(); int i = st.pop();

Περισσότερη Java 56

Οι τύποι των generics της Java ελέγχονται

• Μια γενική κλάση μπορεί να θελήσει να χρησιμοποιήσει


λειτουργίες σε αντικείμενα ενός τύπου-παραμέτρου
Παράδειγμα: PriorityQueue<T> … if x.less(y) then …

• Δύο πιθανές προσεγγίσεις:


– C++: Κατά το χρόνο σύνδεσης (linking) ελέγχεται το κατά πόσο
όλες οι λειτουργίες μπορούν να επιλυθούν
– Java: Οι τύποι ελέγχονται στατικά και δε χρειάζεται να γίνει
κάποιος έλεγχος των generics κατά το χρόνο σύνδεσης
• Αυτή η προσέγγιση επιβάλλει στο πρόγραμμα να έχει
πληροφορία για τον τύπο της παραμέτρου
• Παράδειγμα : PriorityQueue<T extends ...>

Περισσότερη Java 57
Παράδειγμα: Πίνακας κατακερματισμού

interface Hashable {
int HashCode();
};
class HashTable <Key extends Hashable, Value> {
void Insert(Key k, Value v) {
int bucket = k.HashCode();
InsertAt(bucket, k, v);
}

};
Η έκφραση πρέπει να μην πετάει σφάλμα
κατά τη διαδικασία ελέγχου των τύπων
Χρησιμοποιούμε “Key extends Hashable”
Περισσότερη Java 58

Παράδειγμα: Ουρά προτεραιότητας

interface Comparable<I> {
boolean lessThan(I);
};
class PriorityQueue<T implements Comparable<T>> {
T queue[]; …
void insert(T t) {
… if (t.lessThan(queue[i]) …
}
T remove() { … }

};
Ένα τελευταίο παράδειγμα …

interface LessAndEqual<I> {
boolean lessThan(I);
boolean equal(I);
}
class Relations<C implements LessAndEqual<C>> extends C {
boolean greaterThan(Relations<C> a) {
return a.lessThan(this);
}
boolean greaterEqual(Relations<C> a) {
return greaterThan(a) || equal(a);
}
boolean notEqual(Relations<C> a) { ... }
boolean lessEqual(Relations<C> a) { ... }
...
}

Υλοποίηση των Generics

• Διαγραφή τύπων (type erasure)


– Ο έλεγχος τύπων κατά τη μετάφραση χρησιμοποιεί τα generics
– Στη συνέχεια ο compiler απαλείφει τα generics μέσω διαγραφής
• Δηλαδή μεταγλωττίζει List<T> σε List, T σε Object, και
προσθέτει αυτόματες μετατροπές τύπων (casts)

• Tα generics της Java δεν είναι σαν τα templates της C++


– Οι δηλώσεις των generics ελέγχονται ως προς τους τύπους τους
– Τα generics μεταφράζονται άπαξ
• Δεν λαμβάνει χώρα κάποια στιγμιοτυποποίηση (instantiation)
• Ο παραγόμενος κώδικας δεν διογκώνεται

Περισσότερη Java 61
Αντικειμενοστρέφεια

Ορισμοί αντικειμενοστρέφειας

• Ποιοι είναι οι ορισμοί των παρακάτω;


– Αντικειμενοστρεφής γλώσσα προγραμματισμού
– Αντικειμενοστρεφής προγραμματισμός

• Αλλά από την άλλη μεριά, για ποιο λόγο να τους ξέρουμε;

Κάποιες γενικές παρατηρήσεις:


• Ο αντικειμενοστρεφής προγραμματισμός δεν είναι απλά
προγραμματισμός σε μια αντικειμενοστρεφή γλώσσα
• Οι αντικειμενοστρεφείς γλώσσες δεν είναι όλες σαν τη
Java

Αντικειμενοστρέφεια 2
Περιεχόμενα

• Αντικειμενοστρεφής προγραμματισμός στην ML


• Μη αντικειμενοστρεφής προγραμματισμός στη Java

Χαρακτηριστικά αντικειμενοστρεφών γλωσσών


• Κλάσεις
• Πρωτότυπα
• Κληρονομικότητα (inheritance)
• Ενθυλάκωση (encapsulation)
• Πολυμορφισμός

Αντικειμενοστρέφεια 3

public class Node {


private String data;
private Node link;
public Node(String theData, Node theLink) {
data = theData;
link = theLink;
}
public String getData() {
return data;
}
public Node getLink() {
return link;
}
}

Ένα προηγούμενο παράδειγμα σε Java: ένας


κόμβος που μπορεί να χρησιμοποιηθεί για την
κατασκευή μιας στοίβας από συμβολοσειρές
Αντικειμενοστρέφεια 4
Η κλάση Node

• Δύο πεδία: data και link


• Ένας κατασκευαστής που αναθέτει αρχικές τιμές στα
πεδία data και link
• Δύο μέθοδοι: getData και getLink
• Σε κάποιο αφηρημένο επίπεδο μπορούμε να θεωρήσουμε
ότι ένα αντικείμενο δέχεται ένα μήνυμα (π.χ. το μήνυμα
“get data” ή “get link”) και επιστρέφει μια απάντηση στο
μήνυμα (π.χ. κάποιο String ή κάποιο άλλο αντικείμενο)
• Δηλαδή ένα αντικείμενο συμπεριφέρεται σα μια
συνάρτηση με τύπο message -> response
Αντικειμενοστρέφεια 5

Αντικειμενοστρεφής προγραμματισμός στην ML

• Την ίδια ιδέα μπορούμε να την υλοποιήσουμε και σε ML


• Ορίζουμε τύπους για τα μηνύματα και τις απαντήσεις
τους datatype message = GetData | GetLink;

datatype response =
Data of string
| Object of message -> response;

• Για την κατασκευή ενός κόμβου καλούμε τη συνάρτηση


node, περνώντας τις δύο πρώτες παραμέτρους
• Το αποτέλεσμα είναι μια συνάρτηση με τύπο
message -> response
fun node data link GetData = Data data
| node data link GetLink = Object link;
Αντικειμενοστρέφεια 6
Παράδειγμα χρήσης της node

- val n1 = node "Hello" null;


val n1 = fn : message -> response
- val n2 = node "world" n1;
val n2 = fn : message -> response
- n1 GetData;
val it = Data "Hello" : response
- n2 GetData;
val it = Data "world" : response

• Αντικείμενα που αποκρίνονται σε μηνύματα


• Το null πρέπει να είναι ένα αντικείμενο του τύπου
message -> response, όπως π.χ.
fun null _ = Data "null";

Αντικειμενοστρέφεια 7

Η κλάση Stack

• Ένα πεδίο: top


• Τρεις μέθοδοι: hasMore, add, και remove
• Υλοποιείται με χρήση μιας συνδεδεμένης λίστας από
αντικείμενα Νode

Αντικειμενοστρέφεια 8
Η επέκταση των μηνυμάτων και των απαντήσεων, 
τόσο για node όσο και για stack
datatype message =
IsNull
| Add of string
| HasMore
| Remove
| GetData
| GetLink;

datatype response =
Pred of bool
| Data of string
| Removed of (message -> response) * string
| Object of message -> response;

fun root _ = Pred false;

Η root χειρίζεται όλα τα μηνύματα με το να επιστρέφει Pred


false Αντικειμενοστρέφεια 9

fun null IsNull = Pred true


| null message = root message;

fun node data link GetData = Data data


| node data link GetLink = Object link
| node _ _ message = root message;

fun stack top HasMore =


let val Pred(p) = top IsNull
in Pred(not p) end
| stack top (Add data) =
Object(stack (node data top))
| stack top Remove =
let
val Object(next) = top GetLink
val Data(data) = top GetData
in
Removed(stack next, data)
end
| stack _ message = root message;
Αντικειμενοστρέφεια 10
- val a = stack null;
val a = fn : message -> response
- val Object(b) = a (Add "the plow.");
val b = fn : message -> response
- val Object(c) = b (Add "forgives ");
val c = fn : message -> response
- val Object(d) = c (Add "The cut worm ");
val d = fn : message -> response
- val Removed(e,s1) = d Remove;
val e = fn : message -> response
val s1 = "The cut worm " : string
- val Removed(f,s2) = e Remove;
val f = fn : message -> response
val s2 = "forgives " : string
- val Removed(_,s3) = f Remove;
val s3 = "the plow." : string
- s1^s2^s3;
val it = "The cut worm forgives the plow." : string

Αντικειμενοστρέφεια 11

Κληρονομικότητα (στο περίπου...)

• Ορίζουμε μια peekableStack σαν αυτή της Java


fun peekableStack top Peek = top GetData
| peekableStack top message = stack top message;

• Το συγκεκριμένο στυλ προγραμματισμού είναι συναφές


με αυτό που υποστηρίζει η γλώσσα Smalltalk
– Έχουμε ανταλλαγή μηνυμάτων
– Τα μηνύματα δεν έχουν στατικούς τύπους
– Όταν μια κλάση δε χειρίζεται κάποια μηνύματα τα προωθεί για
χειρισμό στην υπερκλάση της

Αντικειμενοστρέφεια 12
Κάποιες σκέψεις

• Προφανώς, αυτός δεν είναι ο καλύτερος τρόπος να


χρησιμοποιήσουμε τη γλώσσα ML
– Τα μηνύματα και οι απαντήσεις τους δεν έχουν σωστούς τύπους
– Δε δείξαμε ότι η ML βγάζει πολλές “binding not exhaustive”
προειδοποιήσεις στα προηγούμενα παραδείγματα

• Το “ηθικό δίδαγμα” όμως είναι ότι: ακόμα και στην ML ο


αντικειμενοστρεφής προγραμματισμός είναι δυνατός
• Ο αντικειμενοστρεφής προγραμματισμός δεν είναι κάτι που
απαιτεί την ύπαρξη μιας αντικειμενοστρεφούς γλώσσας

Σημείωση: Η Objective CAML είναι μια διάλεκτος της ML που εισάγει


αντικειμενοστρεφή στοιχεία στην ML

Αντικειμενοστρέφεια 13

Μη αντικειμενοστρεφής προγραμματισμός στη Java

• Παρόλο που η Java υποστηρίζει καλύτερα από την ML


το αντικειμενοστρεφές στυλ προγραμματισμού, η χρήση
της δεν αποτελεί εγγύηση αντικειμενοστρέφειας
– Μπορούμε να χρησιμοποιήσουμε μόνο στατικές μεθόδους
– Μπορούμε να βάλουμε όλον τον κώδικα σε μια μόνο κλάση
– Μπορούμε να χρησιμοποιήσουμε τις κλάσεις σαν records ή σαν
C structures—με πεδία που είναι public και χωρίς μεθόδους
public class Node {
public String data; // Each node has a String...
public Node link; // ...and a link to the next Node
}

public class Stack {


public Node top; // The top node in the stack
}

Αντικειμενοστρέφεια 14
Μη αντικειμενοστρεφής Stack

public class Main {


private static void add(Stack s, String data) {
Node n = new Node();
n.data = data;
n.link = s.top;
s.top = n;
}
private static boolean hasMore(Stack s) {
return (s.top != null);
}
private static String remove(Stack s) {
Node n = s.top;
s.top = n.link;
return n.data;
}
Παρατηρήστε τις άμεσες αναφορές σε
… public πεδία (δεν απαιτούνται get
} μέθοδοι), επίσης τα δεδομένα και ο
κώδικάς τους είναι εντελώς ξεχωριστά
Αντικειμενοστρέφεια 15

Πολυμορφισμός χωρίς αντικειμενοστρέφεια

• Σε προηγούμενη διάλεξη είδαμε μια διαπροσωπεία


Worklist να υλοποιείται από μια Stack, Queue, κ.λπ.

• Υπάρχει ένα κοινό τρικ υποστήριξης της συγκεκριμένης


δυνατότητας πολυμορφισμού σε μη αντικειμενοστρεφείς
γλώσσες
• Κάθε εγγραφή (record) αρχίζει με ένα στοιχείο κάποιας
απαρίθμησης, που προσδιορίζει το είδος της Worklist

Αντικειμενοστρέφεια 16
Μη αντικειμενοστρεφής Worklist

public class Worklist {


public static final int STACK = 0;
public static final int QUEUE = 1;
public static final int PRIORITYQUEUE = 2;
public int type; // one of the above Worklist types
public Node front; // front Node in the list
public Node rear; // unused when type == STACK
public int length; // unused when type == STACK
}

• Το πεδίο type προσδιορίζει το είδος της Worklist


• Η ερμηνεία των άλλων πεδίων εξαρτάται από το type
• Οι μέθοδοι που διαχειρίζονται τις Worklist εγγραφές
έχουν κάποια διακλάδωση με βάση την τιμή του type…

Αντικειμενοστρέφεια 17

Διακλάδωση με βάση τον τύπο


private static void add(Worklist w, String data) {
if (w.type == Worklist.STACK) {
Node n = new Node();
n.data = data;
n.link = w.front;
w.front = n;
}
else if (w.type == Worklist.QUEUE) {
η υλοποίηση της add για ουρές
}
else if (w.type == Worklist.PRIORITYQUEUE) {
η υλοποίηση της add για ουρές με προτεραιότητα
}
}

Κάθε μέθοδος που χρησιμοποιεί μια Worklist


πρέπει να έχει κάποια αντίστοιχη διακλάδωση τύπου
Αντικειμενοστρέφεια 18
Μειονεκτήματα

• Η επανάληψη του κώδικα που υλοποιεί τη διακλάδωση


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

Αντικειμενοστρέφεια 19

Πλεονεκτήματα αντικειμενοστρέφειας

• Όταν καλούμε μια μέθοδο κάποιας διαπροσωπείας, η


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

Αντικειμενοστρέφεια 20
Κάποιες σκέψεις

• Ο αντικειμενοστρεφής προγραμματισμός δεν είναι το


ίδιο πράγμα με τον προγραμματισμό σε μια
αντικειμενοστρεφή γλώσσα
– Μπορεί να γίνει σε μια μη αντικειμενοστρεφή γλώσσα
– Μπορεί να μη γίνει σε μια αντικειμενοστρεφή γλώσσα

• Συνήθως, οι αντικειμενοστρεφείς γλώσσες συνδυάζονται


με τα αντικειμενοστρεφή στυλ προγραμματισμού
– Συνήθως ένα πρόγραμμα σε ML που έχει γραφεί με χρήση
αντικειμενοστρεφούς στυλ προγραμματισμού δεν είναι ότι
καλύτερο υπάρχει από πλευράς σχεδιασμού
– Επίσης συνήθως ένα πρόγραμμα σε Java που έχει γραφεί με
χρήση του αντικειμενοστρεφούς στυλ προγραμματισμού έχει
καλύτερο σχεδιασμό από ότι ένα που έχει γραφεί χωρίς να
ακολουθηθεί το συγκεκριμένο στυλ
Αντικειμενοστρέφεια 21

Χαρακτηριστικά αντικειμενοστρέφειας

Αντικειμενοστρέφεια 22
Κλάσεις

• Οι περισσότερες αντικειμενοστρεφείς γλώσσες έχουν


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

Αντικειμενοστρέφεια χωρίς κλάσεις

• Σε μια γλώσσα με κλάσεις δημιουργούμε αντικείμενα με


το να φτιάχνουμε στιγμιότυπα των κλάσεων

• Σε μια γλώσσα χωρίς κλάσεις δημιουργούμε αντικείμενα


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

Αντικειμενοστρέφεια 24
Με κλάσεις: 
x = new Stack(); δημιουργία
στιγμιότυπου
x = {
private Node top = null;
public boolean hasMore() {
return (top != null); Χωρίς κλάσεις: 
}
public String remove() { δημιουργία
Node n = top; αντικειμένου
top = n.getLink(); από το μηδέν
return n.getData();
}

}

x = y.clone();
Χωρίς κλάσεις: 
x.top = null; κλωνοποίηση
πρωτοτύπου
Αντικειμενοστρέφεια 25

Πρωτότυπα

• Ένα πρωτότυπο (prototype) είναι ένα αντικείμενο το


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

Αντικειμενοστρέφεια 26
Χωρίς κλάσεις αλλά με πρωτότυπα

• Η εύκολη δημιουργία στιγμιότυπων είναι μία από τις


βασικές χρησιμότητες των κλάσεων
• Κάποια άλλα χαρακτηριστικά που πρέπει να
αποχωριστούν οι γλώσσες που βασίζονται σε
πρωτότυπα:
– Τις κλάσεις ως τύπους: οι περισσότερες γλώσσες που
βασίζονται σε πρωτότυπα έχουν δυναμικούς τύπους
– Την κληρονομικότητα: οι γλώσσες που βασίζονται σε πρωτότυπα
χρησιμοποιούν μια δυναμική τεχνική η οποία είναι σχετική με την
κληρονομικότητα και λέγεται αντιπροσωπεία (delegation)

Αντικειμενοστρέφεια 27

Κληρονομικότητα

• Σε επιφανειακό επίπεδο, η έννοια της κληρονομικότητας


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

• Αλλά το τι κληρονομείται από τη βασική κλάση (ή


κλάσεις) καθορίζεται από τη γλώσσα
• Θα δούμε ότι διαφορετικές γλώσσες έχουν διαφορετικές
προσεγγίσεις πάνω σε θέματα κληρονομικότητας

Αντικειμενοστρέφεια 28
Ερωτήσεις κληρονομικότητας

• Επιτρέπονται περισσότερες από μία βασικές κλάσεις;


– Απλή κληρονομικότητα: Smalltalk, Java
– Πολλαπλή κληρονομικότητα : C++, CLOS, Eiffel

• Οι υποκλάσεις κληρονομούν τα πάντα;


– Στη Java: η παραγόμενη κλάση κληρονομεί όλες τις μεθόδους
και τα πεδία
– Στη Sather: η παραγόμενη κλάση μπορεί να μετονομάσει τις
κληρονομημένες μεθόδους της (κάτι που είναι χρήσιμο όταν
υπάρχει πολλαπλή κληρονομικότητα), ή απλώς να τις ξε-ορίσει

• Υπάρχει κάποια καθολική βασική κλάση;


– Μια κλάση από την οποία όλες οι κλάσεις κληρονομούν: η κλάση
Object της Java
– Δεν υπάρχει κάποια τέτοια κλάση στη C++

Αντικειμενοστρέφεια 29

Ερωτήσεις κληρονομικότητας

• Κληρονομούνται οι προδιαγραφές;
– Ως υποχρεώσεις των μεθόδων για υλοποίηση, όπως στη Java
– Ως επιπλέον προδιαγραφές: invariants, όπως στην Eiffel

• Κληρονομούνται οι τύποι;
– Στη Java κληρονομούνται όλοι οι τύποι της βασικής κλάσης
• Υποστηρίζεται υπερκάλυψη, απόκρυψη, κ.λπ.;
– Τι συμβαίνει στη Java, στο περίπου (χωρίς πολλές λεπτομέρειες):
• Οι κατασκευαστές μπορούν να προσπελάσουν τους κατασκευαστές
των βασικών τους κλάσεων (Αυτό γίνεται με χρήση του super που
καλεί τον κατασκευαστή της άμεσης υπερκλάσης.)
• Μια νέα μέθοδος με το ίδιο όνομα και τύπο με την κληρονομημένη
μέθοδο την υπερκαλύπτει (Η υπερκαλυμμένη μέθοδος μπορεί να
κληθεί με χρήση του προσδιοριστή super.)
• Ένα νέο πεδίο ή στατική μέθοδος αποκρύπτει τις κληρονομημένες, οι
οποίες όμως μπορούν να προσπελαστούν με χρήση του super ή με
στατικούς τύπους της βασικής κλάσης
Αντικειμενοστρέφεια 30
Ενθυλάκωση (encapsulation)

• Απαντάται σχεδόν σε όλες τις μοντέρνες γλώσσες


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

• Στις αντικειμενοστρεφείς γλώσσες, τα αντικείμενα


ενθυλακώνονται
• (Διαφορετικές γλώσσες υλοποιούν την ενθυλάκωση με
διαφορετικούς τρόπους)

Αντικειμενοστρέφεια 31

Ορατότητα των πεδίων και των μεθόδων

• Στη Java υπάρχουν τέσσερα επίπεδα ορατότητας


– private: ορατά μόνο μέσα στην κλάση
– (default): ορατά μόνο εντός του πακέτου (package)
– protected: ορατά μόνο στο ίδιο πακέτο και στις παραγόμενες
κλάσεις
– public: παντού

• Κάποιες αντικειμενοστρεφείς γλώσσες (Smalltalk, Self)


έχουν λιγότερα επίπεδα ελέγχου ορατότητας: όλα κοινά
(public)
• Άλλες έχουν περισσότερα: στην Eiffel, πεδία και μέθοδοι
μπορεί να γίνουν ορατά μόνο σε ένα συγκεκριμένο
σύνολο από κλάσεις-πελάτες
Αντικειμενοστρέφεια 32
Πολυμορφισμός

• Συναντιέται σε πολλές γλώσσες, όχι μόνο στις


αντικειμενοστρεφείς
• Κάποιες από τις βασικότερες εκφράσεις του
πολυμορφισμού στις αντικειμενοστρεφείς γλώσσες:
– Όταν διαφορετικές κλάσεις έχουν μεθόδους του ίδιου ονόματος
και τύπου
• Π.χ. μια κλάση στοίβας και μια κλάση ουράς μπορούν και οι
δύο να έχουν μια μέθοδο ονόματι add
– Όταν η γλώσσα επιτρέπει μια κλήση μεθόδου σε στιγμές που η
κλάση του αντικειμένου δεν είναι γνωστή στατικά

Αντικειμενοστρέφεια 33

Παράδειγμα σε Java

public static void flashoff(Drawable d, int k) {


for (int i = 0; i < k; i++) {
d.show(0,0);
d.hide();
}
}

• Εδώ, η Drawable είναι μια διαπροσωπεία


• Η κλάση του αντικειμένου στην οποία αναφέρεται η
αναφορά d δεν είναι γνωστή στο χρόνο μεταγλώττισης

Αντικειμενοστρέφεια 34
Δυναμική αποστολή (dynamic dispatch)

• Στη Java, ο στατικός τύπος μιας αναφοράς μπορεί να


είναι μια υπερκλάση ή μια διαπροσωπεία κάποιας κλάσης
• Στο χρόνο εκτέλεσης, το σύστημα υλοποίησης της
γλώσσας θα πρέπει κάπως να βρει και να καλέσει τη
σωστή μέθοδο της πραγματικής κλάσης
• Αυτό αναφέρεται ως δυναμική αποστολή (dynamic
dispatch): η κρυφή και έμμεση διακλάδωση πάνω στην
κλάση κατά την κλήση των μεθόδων
– Η δυναμική αποστολή είναι προαιρετική στη C++
– Χρησιμοποιείται πάντα στη Java και στις περισσότερες άλλες
αντικειμενοστρεφείς γλώσσες

Αντικειμενοστρέφεια 35

Υλοποιήσεις και τύποι

• Στη Java υπάρχουν δύο μηχανισμοί:


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

• Το παραπάνω μερικώς διαχωρίζει την κληρονομικότητα


της υλοποίησης από την κληρονομικότητα του τύπου
• Άλλες αντικειμενοστρεφείς γλώσσες διαφέρουν στο
κατά πόσο ξεχωρίζουν τα δύο παραπάνω

Αντικειμενοστρέφεια 36
Υλοποιήσεις και τύποι

• Στη C++ δεν υπάρχει κάποιος αντίστοιχος διαχωρισμός


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

• Από την άλλη μεριά στη Sather ο διαχωρισμός


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

Αντικειμενοστρέφεια 37

Χρήση δυναμικού συστήματος τύπων

• Κάποιες αντικειμενοστρεφείς γλώσσες χρησιμοποιούν


δυναμικό σύστημα τύπων: π.χ. η Smalltalk και η Self
• Ένα αντικείμενο μπορεί να είναι ή να μην είναι σε θέση
να απαντήσει σε ένα συγκεκριμένο μήνυμα—και αυτό
μπορεί να μην μπορεί να ελεγχθεί κατά το χρόνο
μετάφρασης (όπως π.χ. έγινε στο ML τρικ μας)
• Αυτό δίνει απόλυτη ελευθερία: το πρόγραμμα μπορεί να
δοκιμάσει τη χρησιμοποίηση οποιασδήποτε μεθόδου σε
οποιοδήποτε αντικείμενο
• (Ο πολυμορφισμός δεν έχει σχέση με τη συγκεκριμένη ελευθερία)

Αντικειμενοστρέφεια 38
Συμπερασματικά

• Σήμερα, ακολουθήσαμε μια κοσμοπολίτικη προσέγγιση:


– Ο αντικειμενοστρεφής προγραμματισμός δεν είναι το ίδιο
πράγμα με τον προγραμματισμό με μια αντικειμενοστρεφή
γλώσσα
– Οι αντικειμενοστρεφείς γλώσσες δεν είναι όλες σαν τη Java

• Το τι είναι αντικειμενοστρεφές στυλ προγραμματισμού


δεν είναι μονοσήμαντα ορισμένο: υπάρχει αρκετή
διαφωνία ως προς το ακριβές σύνολο των
χαρακτηριστικών της αντικειμενοστρέφειας και τα
χαρακτηριστικά αυτά συνεχώς εξελίσσονται
• Δείξτε σκεπτικισμό στους ορισμούς!

Αντικειμενοστρέφεια 39

Εξαιρέσεις (στη Java)


Εξαιρέσεις στη Java
public class Test {
public static void main(String[] args) {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
System.out.println(i/j);
}
}

> javac Test.java


> java Test 4 2
2
> java Test
Exception in thread "main"
java.lang.ArrayIndexOutOfBoundsException: 0
at Test.main(Test.java:3)
> java Test 42 0
Exception in thread "main"
java.lang.ArithmeticException: / by zero
at Test.main(Test.java:5)
Εξαιρέσεις 2

Περιεχόμενα

• Ριπτόμενες κλάσεις (throwable classes)


• Πιάσιμο εξαιρέσεων (catching exceptions)
• Ρίψη εξαιρέσεων (throwing exceptions)
• Ελεγχόμενες εξαιρέσεις (checked exceptions)
• Χειρισμός σφαλμάτων (error handling)
• Η πρόταση finally

Εξαιρέσεις 3
Κάποιες προκαθορισμένες εξαιρέσεις

Εξαίρεση της Java Κώδικας που την εγείρει


NullPointerException String s = null;
s.length();
ArithmeticException int a = 3;
int b = 0;
int q = a/b;
ArrayIndexOutOfBoundsException int[] a = new int[10];
a[11];
ClassCastException Object x =
new Integer(1);
String s = (String) x;
StringIndexOutOfBoundsException String s = "Hello";
s.charAt(8);

Εξαιρέσεις 4

Μια εξαίρεση είναι ένα αντικείμενο

• Τα ονόματα των εξαιρέσεων είναι ονόματα κλάσεων,


όπως π.χ. NullPointerException
• Οι εξαιρέσεις είναι αντικείμενα των συγκεκριμένων
κλάσεων
• Στα προηγούμενα παραδείγματα, η υλοποίηση της Java
δημιουργεί αυτόματα ένα αντικείμενο της
συγκεκριμένης κλάσης εξαίρεσης και το ρίχνει (throws)
• Αν το πρόγραμμα δεν πιάσει (catch) αυτό το αντικείμενο,
η εκτέλεση του προγράμματος τερματίζεται με ένα
μήνυμα λάθους

Εξαιρέσεις 5
Ριπτόμενες κλάσεις

• Για να ριχθεί ως εξαίρεση, ένα αντικείμενο πρέπει να


είναι κάποιας κλάσης η οποία κληρονομεί από την
προκαθορισμένη κλάση Throwable
• Στο συγκεκριμένο μέρος της ιεραρχίας των κλάσεων της
Java υπάρχουν τέσσερις σημαντικές προκαθορισμένες
κλάσεις:
– Throwable
– Error
– Exception
– RuntimeException

Εξαιρέσεις 6

Οι κλάσεις που
παράγονται από την Object
Error χρησιμοποιούνται
Η Java ρίχνει αντικείμενα
για σημαντικά λάθη του
κλάσεων που είναι
συστήματος, όπως π.χ. το Throwable
υποκλάσεις της Throwable
OutOfMemoryError,
από τα οποία συνήθως
δεν μπορούμε να Error Exception
ανακάμψουμε
...

RuntimeException ...
Οι κλάσεις που παράγονται
Οι κλάσεις που παράγονται από από την Exception
τη RuntimeException ... χρησιμοποιούνται για
χρησιμοποιούνται για συνήθη συνήθη λάθη τα οποία το
λάθη του συστήματος, όπως πρόγραμμα μπορεί να
π.χ. ArithmeticException θέλει να πιάσει και να
ανακάμψει από αυτά
Εξαιρέσεις 7
Πιάσιμο εξαιρέσεων

Εξαιρέσεις 8

Η εντολή try

<try-statement> ::= <try-part> <catch-part>


<try-part> ::= try <compound-statement>
<catch-part> ::= catch (<type> <variable-name>)
<compound-statement>

• Η παραπάνω σύνταξη είναι απλοποιημένη… η πλήρης


σύνταξη θα δοθεί αργότερα
• Το <type> είναι το όνομα μιας ριπτόμενης κλάσης
• Η εντολή εκτελεί το σώμα της try
• Εκτελεί το catch μέρος μόνο εάν το try μέρος ρίξει
μια εξαίρεση του συγκεκριμένου τύπου <type>

Εξαιρέσεις 9
Παράδειγμα

public class Test {


public static void main(String[] args) {
try {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
System.out.println(i/j);
}
catch (ArithmeticException a) {
System.out.println("You're dividing by zero!");
}
}
}

Ο παραπάνω κώδικας θα πιάσει και θα χειριστεί οποιαδήποτε


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

Εξαιρέσεις 10

Παράδειγμα

> java Test 4 2


2
> java Test 42 0
You're dividing by zero!
> java Test
Exception in thread "main"
java.lang.ArrayIndexOutOfBoundsException: 0
at Test.main(Test.java:3)

• Ο τύπος του ορίσματος του catch επιλέγει το τι


εξαίρεση θα πιαστεί από τον κώδικα:
– Ο τύπος ArithmeticException θα πιάσει μόνο κάποια
αριθμητική εξαίρεση (π.χ. διαίρεση με το μηδέν)
– Ο τύπος RuntimeException θα πιάσει και τα δύο παραπάνω
παραδείγματα λάθους χρήσης
– Ο τύπος Throwable θα πιάσει όλες τις εξαιρέσεις
Εξαιρέσεις 11
Έλεγχος ροής μετά την εντολή try

• Η εντολή try μπορεί να είναι κάποια από τις εντολές σε


μια ακολουθία από εντολές
• Εάν δε συμβεί κάποια εξαίρεση στο try μέρος, το
catch μέρος δεν εκτελείται
• Εάν δε συμβεί κάποια εξαίρεση στο try μέρος, ή εάν
συμβεί κάποια εξαίρεση την οποία το catch μέρος
πιάνει, η εκτέλεση θα συνεχίσει με την εντολή που είναι
η αμέσως επόμενη από το catch μέρος της εντολής
try

Εξαιρέσεις 12

Χειρισμός της εξαίρεσης

System.out.print("1, ");
try {
String s = null;
s.length();
}
catch (NullPointerException e) {
System.out.print("2, ");
}
System.out.println("3");

Απλώς τυπώνει τη γραμμή


1, 2, 3

Εξαιρέσεις 13
Ρίψη εξαίρεσης από κληθείσα μέθοδο

• Η εντολή try έχει την ευκαιρία να πιάσει εξαιρέσεις που


πιθανώς να εγερθούν από την εκτέλεση του try μέρους
• Αυτό περιλαμβάνει όλες τις εξαιρέσεις που ρίχνονται
από μεθόδους που καλούνται (αμέσως ή εμμέσως) από
το σώμα του try

Εξαιρέσεις 14

Παράδειγμα
void f() {
try {
g();
}
catch (ArithmeticException a) {
… // some action
}
}
• Εάν η g ρίξει μια ArithmeticException και δεν την
πιάσει η ίδια, η εξαίρεση θα προωθηθεί στην f
• Γενικά, ένα throw που θα ρίξει μια εξαίρεση και το
catch που θα την πιάσει μπορεί να διαχωρίζονται από
έναν απροσδιόριστο αριθμό δραστηριοποιήσεων μεθόδων
Εξαιρέσεις 15
• Εάν η z ρίξει μια εξαίρεση που z’s activation
δεν πιάνει, η δραστηριοποίηση record

της z σταματάει… y’s activation


• …τότε η y έχει την ευκαιρία να record

πιάσει την εξαίρεση… εάν δε


την πιάσει, η δραστηριοποίηση ...

της y επίσης σταματάει…


g’s activation
• … κοκ … record

• … μέχρι την εγγραφή


f’s activation
δραστηριοποίησης της πρώτης record

κλήσης συνάρτησης (f)


Εξαιρέσεις 16

Ρίψεις μεγάλου μήκους

• Οι εξαιρέσεις είναι δομές ελέγχου ροής


• Ένα από τα μεγαλύτερα πλεονεκτήματα του χειρισμού
εξαιρέσεων είναι η δυνατότητα για ρίψεις μεγάλου
μήκους
• Όλες οι δραστηριοποιήσεις που βρίσκονται μεταξύ του
throw και του catch σταματούν την εκτέλεσή τους και
απομακρύνονται από τη στοίβα
• Εάν δεν υπάρχει ρίψη ή πιάσιμο εξαιρέσεων, οι
δραστηριοποιήσεις δε χρειάζεται να ξέρουν τίποτε για
τις εξαιρέσεις

Εξαιρέσεις 17
Πολλαπλά catch

<try-statement> ::= <try-part> <catch-parts>


<try-part> ::= try <compound-statement>
<catch-parts> ::= <catch-part> <catch-parts>
| <catch-part>
<catch-part> ::= catch (<type> <variable-name>)
<compound-statement>

• Για να πιάσουμε περισσότερα είδη εξαιρέσεων, ένα


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

Παράδειγμα

public static void main(String[] args) {


try {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
System.out.println(i/j);
}
catch (ArithmeticException a) {
System.out.println("You're dividing by zero!");
}
catch (ArrayIndexOutOfBoundsException a) {
System.out.println("Requires two parameters.");
}
}

Ο κώδικας θα πιάσει και θα χειριστεί τόσο


ArithmeticException όσο και
ArrayIndexOutOfBoundsException

Εξαιρέσεις 19
Επικαλυπτόμενες προτάσεις catch

• Εάν μια εξαίρεση από το σώμα του try ταιριάζει με


περισσότερα από ένα από τα catch, μόνο το πρώτο που
θα ταιριάξει εκτελείται
• Άρα προγραμματίζουμε ως εξής: γράφουμε catch
προτάσεις για τις ειδικές περιπτώσεις πρώτα και
βάζουμε τις πιο γενικές στο τέλος

Παρατήρηση: Η Java δεν επιτρέπει απρόσιτες προτάσεις


catch, ή πιο γενικά την ύπαρξη απρόσιτου κώδικα
Εξαιρέσεις 20

Παράδειγμα

public static void main(String[] args) {


try {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
System.out.println(i/j);
}
catch (ArithmeticException a) {
System.out.println("You're dividing by zero!");
}
catch (ArrayIndexOutOfBoundsException a) {
System.out.println("Requires two parameters.");
}
// last the superclass of all thrown exceptions
catch (RuntimeException a) {
System.out.println("Runtime exception.");
}
}

Εξαιρέσεις 21
Ρίψη εξαιρέσεων

Εξαιρέσεις 22

Η εντολή throw

<throw-statement> ::= throw <expression> ;

• Οι περισσότερες εξαιρέσεις εγείρονται αυτόματα από το


σύστημα υλοποίησης της γλώσσας
• Πολλές φορές όμως θέλουμε να εγείρουμε δικές μας
εξαιρέσεις και τότε χρησιμοποιούμε την εντολή throw
• Η έκφραση <expression> είναι μια αναφορά σε ένα
ριπτόμενο αντικείμενο, συνήθως ένα νέο αντικείμενο
κάποιας κλάσης εξαίρεσης:
throw new NullPointerException();

Εξαιρέσεις 23
Ριπτόμενες εξαιρέσεις ορισμένες από το χρήστη

public class OutOfGas extends Exception {


}

System.out.print("1, ");
try {
throw new OutOfGas();
}
catch (OutOfGas e) {
System.out.print("2, ");
}
System.out.println("3");

Εξαιρέσεις 24

Χρήση των αντικειμένων εξαιρέσεων

• Η ριφθείσα εξαίρεση είναι διαθέσιμη στο μπλοκ του


catch—με τη μορφή παραμέτρου
• Μπορεί να χρησιμοποιηθεί για να περάσει πληροφορία
από τον “ρίπτη” (thrower) στον “πιάνοντα” (catcher)
• Όλες οι κλάσεις που παράγονται από τη Throwable
κληρονομούν μια μέθοδο printStackTrace
• Κληρονομούν επίσης ένα πεδίο τύπου String στο οποίο
υπάρχει ένα λεπτομερές μήνυμα λάθους, όπως και μία
μέθοδο getMessage με την οποία μπορούμε να
προσπελάσουμε αυτό το μήνυμα

Εξαιρέσεις 25
Παράδειγμα χρήσης

public class OutOfGas extends Exception {


public OutOfGas(String details) {
super(details);
}
}

Καλεί τον κατασκευαστή της βασικής


κλάσης για να αρχικοποιήσει το πεδίο που
επιστρέφεται από τη getMessage()

try {
throw new OutOfGas("You have run out of gas.");
}
catch (OutOfGas e) {
System.out.println(e.getMessage());
}

Εξαιρέσεις 26

Σχετικά με το super στους κατασκευαστές

• Η πρώτη εντολή σε έναν κατασκευαστή μπορεί να είναι


μια κλήση στον κατασκευαστή της υπερκλάσης με χρήση
του super (με παραμέτρους, εάν χρειάζεται)
• Η συγκεκριμένη κλήση χρησιμοποιείται για να
αρχικοποιήσει τα κληρονομημένα πεδία

• Όλοι οι κατασκευαστές (εκτός φυσικά από αυτούς της


κλάσης Object) αρχίζουν με μια κλήση σε έναν άλλο
κατασκευαστή—εάν δεν περιλαμβάνουν μια τέτοια
κλήση, η Java προσθέτει τη super() κλήση αυτόματα

Εξαιρέσεις 27
Περισσότερα για τους κατασκευαστές

• Επίσης, όλες οι κλάσεις έχουν τουλάχιστον έναν


κατασκευαστή—εάν δεν περιλαμβάνουν έναν, η Java
έμμεσα παρέχει έναν κατασκευαστή χωρίς ορίσματα
• Οι δύο παρακάτω ορισμοί κλάσεων είναι ισοδύναμοι:
public class OutOfGas extends Exception {
}

public class OutOfGas extends Exception {


public OutOfGas() {
super();
}
}

Εξαιρέσεις 28

public class OutOfGas extends Exception {


private int miles;
public OutOfGas(String details, int m) {
super(details);
miles = m;
}
public int getMiles() {
return miles;
}
}

try {
throw new OutOfGas("You have run out of gas.", 42);
}
catch (OutOfGas e) {
System.out.println(e.getMessage());
System.out.println("Odometer: " + e.getMiles());
}

Εξαιρέσεις 29
Ελεγχόμενες εξαιρέσεις

Εξαιρέσεις 30

Ελεγχόμενες εξαιρέσεις

void z() {
throw new OutOfGas("You have run out of gas.", 42);
}

• Ο μεταγλωττιστής της Java βγάζει μήνυμα λάθους για


την παραπάνω μέθοδο: “The exception OutOfGas is not
handled”
• Η Java δεν παραπονέθηκε μέχρι στιγμής για κάτι
ανάλογο σε προηγούμενα παραδείγματα—γιατί τώρα;
• Αυτό συμβαίνει διότι η Java διαχωρίζει τις εξαιρέσεις σε
δύο είδη: τις ελεγχόμενες και τις μη ελεγχόμενες

Εξαιρέσεις 31
Ελεγχόμενες εξαιρέσεις

Throwable

Error Exception

Μη ...
ελεγχόμενες RuntimeException ...
εξαιρέσεις
...

Οι κλάσεις των μη ελεγχόμενων εξαιρέσεων


είναι η RuntimeException και οι απόγονοί
της και η κλάση Error και οι απόγονοι της.  
Όλες οι άλλες κλάσεις εξαιρέσεων είναι κλάσεις
ελεγχόμενων εξαιρέσεων.
Εξαιρέσεις 32

Τι είναι αυτό που ελέγχεται;

• Μια μέθοδος που μπορεί να δεχθεί μια ελεγχόμενη


εξαίρεση δεν επιτρέπεται να την αγνοήσει
• Αυτό που πρέπει να κάνει είναι είτε να την πιάσει
– Με άλλα λόγια, ο κώδικας που παράγει την εξαίρεση μπορεί να
είναι μέσα σε μια εντολή try η οποία πρέπει έχει ένα catch το
οποίο να πιάνει την ελεγχόμενη εξαίρεση

• Ή να δηλώσει ότι δεν την πιάνει


– Χρησιμοποιώντας μια πρόταση throws

Εξαιρέσεις 33
Η πρόταση throws

void z() throws OutOfGas {


throw new OutOfGas("You have run out of gas.", 42);
}

• Μια πρόταση throws δηλώνει μια ή περισσότερες


ελεγχόμενες κλάσεις που μπορεί να ρίξει η μέθοδος
• Αυτό σημαίνει ότι οι μέθοδοι που καλούν τη z είτε πρέπει
να πιάσουν την εξαίρεση OutOfGas ή πρέπει επίσης να
τη δηλώσουν στη δική τους πρόταση throws

Εξαιρέσεις 34

• Εάν η μέθοδος z δηλώνει ότι z’s activation


record
throws OutOfGas…
• …τότε η μέθοδος y πρέπει να y’s activation
record
είτε να την πιάσει, ή να δηλώσει
μέσω μιας throws πρότασης
...
ότι επίσης την ρίχνει…
• …κοκ… g’s activation
record
• σε όλες τις κλήσεις μέχρι την f
f’s activation
record

Εξαιρέσεις 35
Για ποιο λόγο θέλουμε ελεγχόμενες εξαιρέσεις;

• Η πρόταση throws προσφέρει τεκμηρίωση της μεθόδου:


λέει στον αναγνώστη ότι η συγκεκριμένη εξαίρεση
μπορεί να είναι το αποτέλεσμα μιας κλήσης της μεθόδου
• Αλλά είναι μια επικυρωμένη (verified) τεκμηρίωση: εάν το
αποτέλεσμα μιας κλήσης κάποιας μεθόδου ενδέχεται να
είναι μια ελεγχόμενη εξαίρεση, ο compiler θα επιμείνει
ότι η εξαίρεση αυτή πρέπει να δηλωθεί
• Άρα οι δηλώσεις των εξαιρέσεων μπορούν να κάνουν πιο
εύκολη τόσο την κατανόηση όσο και τη συντήρηση των
προγραμμάτων

Εξαιρέσεις 36

Παράκαμψη των ελεγχόμενων εξαιρέσεων

• Αν δε θέλουμε ελεγχόμενες εξαιρέσεις, μπορούμε να


χρησιμοποιήσουμε εξαιρέσεις οι οποίες είναι αντικείμενα
κλάσεων που είναι επεκτάσεις της κλάσης Error ή της
Throwable
• Οι εξαιρέσεις αυτές θα είναι μη ελεγχόμενες
• Όμως, θα πρέπει να λάβουμε υπόψη τα πλεονεκτήματα
και τα μειονεκτήματα μιας τέτοιας κίνησης

Εξαιρέσεις 37
Χειρισμός σφαλμάτων

Εξαιρέσεις 38

Χειρισμός σφαλμάτων

• Παράδειγμα σφάλματος: απόπειρα εξαγωγής στοιχείου


από μια κενή λίστα
• Τεχνικές:
– Χρήση προϋποθέσεων (preconditions)
– Χρήση καθολικών ορισμών (total definitions)
– Θανατηφόρα λάθη (fatal errors)
– Ένδειξη του σφάλματος (error flagging)
– Χρήση εξαιρέσεων

Εξαιρέσεις 39
Χρήση προϋποθέσεων

• Τεκμηριώνουμε (με τη μορφή σχολίων) όλες τις


αναγκαίες προϋποθέσεις για την αποφυγή λαθών
/**
* Pop the top int from this stack and return it.
* This should be called only if the stack is not empty.
* @return the popped int
*/
public int pop() {
Node n = top;
top = n.getLink();
return n.getData();
}
• Η καλούσα μέθοδος πρέπει είτε να εξασφαλίσει ότι οι
προϋποθέσεις ισχύουν, ή να τις ελέγξει εάν δεν είναι
βέβαιη ότι ισχύουν if (s.hasMore()) x = s.pop();
else …
Εξαιρέσεις 40

Μειονεκτήματα της χρήσης προϋποθέσεων

• Εάν κάποια κλήση ξεχάσει τον έλεγχο, το πρόγραμμα θα


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

Εξαιρέσεις 41
Καθολικός ορισμός

• Μπορούμε να αλλάξουμε τον ορισμό της pop έτσι ώστε


να δουλεύει σε κάθε περίπτωση
• Δηλαδή να ορίσουμε κάποια “λογική” συμπεριφορά για το
τι σημαίνει pop σε μια κενή στοίβα
• Κάτι αντίστοιχο συμβαίνει και σε άλλες περιπτώσεις, π.χ.
– Στις συναρτήσεις για ανάγνωση χαρακτήρων από ένα αρχείο στη
C που επιστρέφουν τον χαρακτήρα EOF εάν η ανάγνωση φτάσει
στο τέλος του αρχείου
– Στους αριθμούς κινητής υποδιαστολής κατά IEEE που
επιστρέφουν NaN (για αριθμούς που δεν αναπαρίστανται) και
συν/πλην άπειρο για πολύ μεγάλα/μικρά αποτελέσματα

Εξαιρέσεις 42

/**
* Pop the top int from this stack and return it.
* If the stack is empty we return 0 and leave the
* stack empty.
* @return the popped int, or 0 if the stack is empty
*/
public int pop() {
Node n = top;
if (n == null) return 0;
top = n.getLink();
return n.getData();
}

Εξαιρέσεις 43
Μειονεκτήματα των καθολικών ορισμών

• Μπορεί να κρύψουν σημαντικά προβλήματα του


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

Εξαιρέσεις 44

Θανατηφόρα λάθη

• Ελέγχουμε κάποιες προϋποθέσεις και εάν δεν ισχύουν


σταματάμε την εκτέλεση του προγράμματος
/**
* Pop the top int from this stack and return it.
* This should be called only if the stack is
* not empty. If called when the stack is empty,
* we print an error message and exit the program.
* @return the popped int
*/
public int pop() {
Node n = top;
if (n == null) {
System.out.println("Popping an empty stack!");
System.exit(-1);
}
top = n.getLink();
return n.getData();
}
Εξαιρέσεις 45
Μειονεκτήματα

• Το καλό με το συγκεκριμένο χειρισμό είναι ότι


τουλάχιστον δεν κρύβουμε το πρόβλημα…
• Αλλά ο χειρισμός δεν είναι συμβατός με το στυλ του
αντικειμενοστρεφούς προγραμματισμού: ένα αντικείμενο
κάνει πράγματα μόνο στον εαυτό του, όχι σε ολόκληρο
το πρόγραμμα
• Επιπλέον είναι κάπως άκαμπτος: διαφορετικές κλήσεις
μπορεί να θέλουν να χειριστούν το σφάλμα διαφορετικά
– Με τερματισμό
– Με κάποια ενέργεια καθαρισμού των συνεπειών και τερματισμό
– Με επιδιόρθωση και συνέχιση της εκτέλεσης
– Με αγνόηση του σφάλματος
Εξαιρέσεις 46

Ένδειξη του σφάλματος (error flagging)

• Η μέθοδος που ανιχνεύει κάποιο σφάλμα πρέπει να


επιστρέψει μια ένδειξη για αυτό:
– Επιστροφή μιας ειδικής τιμής (όπως κάνει π.χ. η malloc στη C)
– Ανάθεση κάποιας τιμής σε μια καθολική μεταβλητή (όπως π.χ. η
errno στη C)
– Ανάθεση κάποιας μεταβλητής που ελέγχεται με κλήση μιας
κατάλληλης μεθόδου (όπως π.χ. η ferror(f) στη C)

• Η καλούσα μέθοδος πρέπει να ελέγξει για την ύπαρξη


σφάλματος

Εξαιρέσεις 47
/**
* Pop the top int from this stack and return it.
* This should be called only if the stack is
* not empty. If called when the stack is empty,
* we set the error flag and return an undefined
* value.
* @return the popped int if stack not empty
*/
public int pop() {
Node n = top;
if (n == null) {
error = true;
return 0;
}
top = n.getLink();
return n.getData();
}

Εξαιρέσεις 48

/**
* Return the error flag for this stack. The error
* flag is set true if an empty stack is ever popped.
* It can be reset to false by calling resetError().
* @return the error flag
*/
public boolean getError() {
return error;
}

/**
* Reset the error flag. We set it to false.
*/
public void resetError() {
error = false;
}

Εξαιρέσεις 49
/**
* Pop the two top integers from the stack, divide
* them, and push their integer quotient. There
* should be at least two integers on the stack
* when we are called. If not, we leave the stack
* empty and set the error flag.
*/
public void divide() {
int i = pop();
int j = pop();
if (getError()) return;
push(i/j);
}

Όλες οι τεχνικές ένδειξης σφαλμάτων απαιτούν κάποιον


ανάλογο έλεγχο για την ύπαρξη ή όχι σφάλματος.
Παρατηρήστε ότι οι μέθοδοι που καλούν την divide πρέπει
επίσης να ελέγξουν για σφάλμα, όπως και οι μέθοδοι που
καλούν τις μεθόδους με κλήσεις της divide, κοκ…
Εξαιρέσεις 50

Χρήση εξαιρέσεων

• Με χρήση εξαιρέσεων, η μέθοδος που ανιχνεύει πρώτη


το σφάλμα εγείρει κάποια εξαίρεση
• Η εξαίρεση μπορεί να είναι ελεγχόμενη ή μη ελεγχόμενη
• Οι εξαιρέσεις είναι μέρος της τεκμηριωμένης
συμπεριφοράς της μεθόδου

Εξαιρέσεις 51
/**
* Pop the top int from this stack and return it.
* @return the popped int
* @exception EmptyStack if stack is empty
*/
public int pop() throws EmptyStack {
Node n = top;
if (n == null) throw new EmptyStack();
top = n.getLink();
return n.getData();
}

/**
* Pop the two top integers from the stack,
* divide them, and push their integer quotient.
* @exception EmptyStack if stack runs out
*/
public void divide() throws EmptyStack {
int i = pop();
int j = pop();
push(i/j); Η καλούσα μέθοδος δεν ελέγχει
για
}
σφάλμα—απλώς προωθεί την εξαίρεση
Εξαιρέσεις 52

Πλεονεκτήματα

• Έχουμε διευκρινιστικά μηνύματα λάθους ακόμα και εάν


δεν πιάσουμε την εξαίρεση
• Οι εξαιρέσεις είναι μέρος της τεκμηριωμένης
διαπροσωπείας των μεθόδων
• Σφάλματα εκτέλεσης πιάνονται άμεσα και δεν
αποκρύπτονται
• Η καλούσα μέθοδος δε χρειάζεται να ελέγξει για σφάλμα
• Ανάλογα με την περίπτωση, έχουμε τη δυνατότητα είτε
να αγνοήσουμε είτε να χειριστούμε κατάλληλα το
σφάλμα
Εξαιρέσεις 53
Ολόκληρη η σύνταξη του try

<try-statement> ::= <try-part> <catch-parts>


| <try-part> <catch-parts> <finally-part>
| <try-part> <finally-part>
<try-part> ::= try <compound-statement>
<catch-parts> ::= <catch-part> <catch-parts> | <catch-part>
<catch-part> ::= catch (<type> <variable-name>)
<compound-statement>
<finally-part> ::= finally <compound-statement>

• Ένα try έχει ένα προαιρετικό μέρος finally


• Το σώμα του finally εκτελείται πάντα στο τέλος της
εντολής try, ότι και αν συμβεί

Εξαιρέσεις 54

Χρήση του finally

• Το μέρος του finally συνήθως χρησιμοποιείται για


κάποιες λειτουργίες καθαρισμού (που είναι απαραίτητες
να γίνουν)
• Για παράδειγμα, ο παρακάτω κώδικας κλείνει το αρχείο
ανεξάρτητα του εάν εγερθεί κάποια εξαίρεση ή όχι
file.open();
try {
workWith(file);
}
finally {
file.close();
}
Εξαιρέσεις 55
Άλλο ένα παράδειγμα

System.out.print("1");
try {
System.out.print("2");
if (true) throw new Exception();
System.out.print("3");
}
catch (Exception e) {
System.out.print("4");
}
finally {
System.out.print("5");
}
Τι τυπώνεται;
System.out.println("6");

Τι θα συμβεί εάν αλλάξουμε


το new Exception() σε
new Throwable();
Εξαιρέσεις 56

Αποχαιρετισμός στη Java

Εξαιρέσεις 57
Μέρη της Java που δεν εξετάσαμε (1)

• Θεμελιώδεις εντολές της γλώσσας


– do, for, break, continue, switch

• Εκλεπτύνσεις
– Εσωτερικές κλάσεις που ορίζουν κλάσεις με εμβέλεια: μέσα σε
άλλες κλάσεις, σε μπλοκ, σε εκφράσεις
– Την εντολή assert (Java 1.4)

• Πακέτα (packages)
– Οι κλάσεις ομαδοποιούνται σε πακέτα
– Σε πολλές υλοποιήσεις της Java, όλα τα αρχεία πηγαίου κώδικα
σε κάποιο directory αντιστοιχούν σε ένα πακέτο
– Η προκαθορισμένη προσπέλαση (χωρίς public, private ή
protected) έχει εύρος πακέτου

Εξαιρέσεις 58

Μέρη της Java που δεν εξετάσαμε (2)

• Δομές ταυτοχρονισμού
– Γλωσσικές δομές συγχρονισμού (synchronization constructs) για
πολλαπλά νήματα εκτέλεσης
– Μέρη του API για τη δημιουργία νημάτων

• Το πολύ μεγάλο σε έκταση API


– containers (stacks, queues, hash tables, etc.)
– graphical user interfaces
– 2D and 3D graphics
– math
– ταίριασμα προτύπων με κανονικές εκφράσεις
– file I/O, network I/O και XML
– encryption και ασφάλεια
– remote method invocation (RMI)
– interfacing to databases and other tools
Εξαιρέσεις 59
Παράμετροι

Πέρασμα παραμέτρων

Τυπικές παράμετροι
int plus(int a, int b)
{
return a+b; Σώμα μεθόδου
}
Πραγματικές
int x = plus(1, 2); παράμετροι

Κλήση μεθόδου

• Πώς περνιούνται οι παράμετροι;


• Φαίνεται αρκετά «προφανής» διαδικασία
• Αλλά θα δούμε επτά διαφορετικές μεταξύ τους τεχνικές!

Παράμετροι 2
Περιεχόμενα

• Αντιστοιχία παραμέτρων
• Πέρασμα παραμέτρων
– Κλήση κατά τιμή (call by value)
– Κλήση κατ’ αποτέλεσμα (call by result)
– Κλήση κατά τιμή-αποτέλεσμα (call by value-result)
– Κλήση κατ’ αναφορά (call by reference)
– Κλήση με επέκταση μακροεντολών (call by macro expansion)
– Κλήση κατ’ όνομα (call by name)
– Κλήση κατ’ ανάγκη (call by need)

• Θέματα σχεδιασμού γλωσσών

Παράμετροι 3

Αντιστοιχία παραμέτρων

• Μια προκαταρκτική ερώτηση: με ποιο τρόπο ταιριάζει η


γλώσσα τις τυπικές με τις πραγματικές παραμέτρους;

• Η πιο συνηθισμένη απάντηση στο παραπάνω ερώτημα


είναι:
– Η αντιστοιχία καθορίζεται από τις θέσεις των παραμέτρων
(positional parameters)
– Για παράδειγμα, η ν-οστή τυπική παράμετρος ταιριάζει με τη
ν-οστή πραγματική παράμετρο

Παράμετροι 4
Ταίριασμα παραμέτρων μέσω ονομάτων

• Η αντιστοιχία μπορεί επίσης να καθοριστεί με χρήση


ταιριάσματος ονομάτων παραμέτρων

• Για παράδειγμα στην Ada:


DIVIDE(DIVIDEND => X, DIVISOR => Y);
• Ταιριάζει την πραγματική παράμετρο X με την τυπική
παράμετρο DIVIDEND, και την Y με την DIVISOR
• Στην περίπτωση αυτή η σειρά εμφάνισης των
παραμέτρων δεν παίζει κάποιο ρόλο

Παράμετροι 5

Θέσεις και ονόματα στο ταίριασμα παραμέτρων

• Οι περισσότερες γλώσσες που επιτρέπουν ταίριασμα


παραμέτρων μέσω ονομάτων (π.χ. Ada, Fortran, Dylan,
Python, Objective C, Smalltalk) επιτρέπουν επίσης και
παραμέτρους θέσεων
• Οι πρώτες παράμετροι συνήθως καθορίζονται μέσω
θέσεων, ενώ οι υπόλοιπες είναι παράμετροι που
καθορίζονται μέσω ονομάτων

Παράμετροι 6
Προαιρετικές παράμετροι

• Προαιρετικές παράμετροι με χρήση default τιμών: η


ακολουθία των τυπικών παραμέτρων περιλαμβάνει
default τιμές που χρησιμοποιούνται εάν οι αντίστοιχες
πραγματικές παράμετροι λείπουν
– Μέσω προαιρετικών παραμέτρων μπορούμε να γράψουμε εύκολα
κάποιους υπερφορτωμένους ορισμούς συναρτήσεων, όπως π.χ.
int f(int a=1, int b=2, int c=3) { body }
– Η παραπάνω έκφραση στη C++ είναι ισοδύναμη με:
int f() {f(1,2,3);}
int f(int a) {f(a,2,3);}
int f(int a, int b) {f(a,b,3);}
int f(int a, int b, int c) { body }
Παράμετροι 7

Μη περιορισμένες ακολουθίες παραμέτρων

• Κάποιες γλώσσες επιτρέπουν ακολουθίες πραγματικών


μεταβλητών μη καθορισμένου μήκους: π.χ. η C, η C++,
και γλώσσες σεναρίων όπως η JavaScript, η Python και η
Perl επιτρέπουν τον ορισμό
int printf(char *format, ...) { body }
• Οι επιπλέον παράμετροι προσπελάζονται μέσω ειδικών
μεθόδων της βιβλιοθήκης
• Οι μη περιορισμένες ακολουθίες παραμέτρων αποτελούν
«τρύπα» των στατικών συστημάτων τύπων
– Διότι οι τύποι των επιπλέον παραμέτρων δεν είναι εύκολο να
ελεγχθούν κατά το χρόνο μεταγλώττισης του προγράμματος
Παράμετροι 8
Πέρασμα Παραμέτρων

Παράμετροι 9

Κλήση κατά τιμή (call by value)

Στο πέρασμα παραμέτρων κατά τιμή, η τυπική


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

• Η απλούστερη μέθοδος περάσματος παραμέτρων


• Χρησιμοποιείται ευρέως
• Η μόνη μέθοδος στη Java

Παράμετροι 10
int plus(int a, int b) {
a += b; Η συνάρτηση f
return a; επιστρέφει 42
}
int f() {
int x = 3;
int y = 4; current
int z = plus(x, y); activation record
return 6*z;
}
a: 3 x: 3

b: 4 y: 4

Όταν η plus return address z: ?

αρχίζει την previous


return address
εκτέλεσή της activation record
previous
result: ?
activation record

Παράμετροι 11

Ορατότητα αλλαγών

• Όταν οι παράμετροι περνιούνται κατά τιμή, οι αλλαγές


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

Παράμετροι 12
void f() {
ConsCell x = new ConsCell(0, null);
alter(3, x);
}

void alter(int newHead, ConsCell c) {


c.setHead(newHead);
c = null;
}
head: 0
tail: null
curre nt
activa tion reco rd

newHead: 3

Όταν η alter c: x:

αρχίζει την return address return address


εκτέλεσή της previo us previo us
activa tion reco rd activa tion reco rd

Παράμετροι 13

void f() {
ConsCell x = new ConsCell(0, null);
alter(3, x);
}

void alter(int newHead, ConsCell c) {


c.setHead(newHead);
c = null;
}
head: 3
tail: null
current
activation record

newHead: 3

Όταν η alter c: null x:

τελειώνει την return address return address


εκτέλεσή της previous previous
activation record activation record

Παράμετροι 14
Κλήση κατ’ αποτέλεσμα (call by result)

Στο πέρασμα παραμέτρων κατ’ αποτέλεσμα, η τυπική


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

• Ονομάζεται επίσης copy-out


• Η πραγματική παράμετρος πρέπει να είναι μια lvalue
• Η κλήση κατ΄ αποτέλεσμα εισήχθη στην Algol 68 και
χρησιμοποιήθηκε και στην Ada

Παράμετροι 15

void plus(int a, int b, by-result int c) {


c = a+b;
}
int f() {
int x = 3;
int y = 4;
int z;
plus(x, y, z);
return 2*x*z;
} curre nt
a ctiva tio n re co rd

a: 3 x: 3

b: 4 y: 4

Όταν η plus c: ? z: ?
αρχίζει την re tur n a d d re s s retur n address
εκτέλεσή της previo us previo us
a ctiva tio n re co rd activatio n re co rd

Παράμετροι 16
void plus(int a, int b, by-result int c) {
c = a+b;
}
int f() {
int x = 3;
int y = 4;
int z;
plus(x, y, z);
return 2*x*z;
} c urre nt
a c tiva tio n re c o rd

a: 3 x: 3

b: 4 y: 4

Όταν η plus c: 7 z: ?
είναι έτοιμη να re tur n a d d re ss re tur n a d d re s s
επιστρέψει p re vio us p re v io us
a c tiva tio n re c o rd a ctiva tio n re co rd

Παράμετροι 17

void plus(int a, int b, by-result int c) {


c = a+b;
}
int f() {
int x = 3; Η συνάρτηση f
int y = 4; επιστρέφει 42
int z;
plus(x, y, z);
return 2*x*z;
} curre nt
activatio n record

a: 3 x: 3

b: 4 y: 4
Αφότου η plus c: 7 z: 7
επιστρέψει
retur n address retur n address
previo us previo us
activatio n record activatio n record

Παράμετροι 18
Κλήση κατά τιμή-αποτέλεσμα (call by value-result)

Στο πέρασμα παραμέτρων κατά τιμή‐αποτέλεσμα, η τυπική


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

• Ονομάζεται επίσης copy-in/copy-out


• Η πραγματική παράμετρος πρέπει να είναι μια lvalue

Παράμετροι 19

void plus(int a, by-value-result int b) {


b += a;
}
int f() {
int x = 3;
plus(4, x);
return x*x - x;
}

current
activation record

a: 4 x: 3

b: 3 return address
Όταν η plus
previous
αρχίζει την return address
activation record
εκτέλεσή της previous
activation record

Παράμετροι 20
void plus(int a, by-value-result int b) {
b += a;
}
int f() {
int x = 3;
plus(4, x);
return x*x - x;
}

current
activation record

a: 4 x: 3

b: 7 return address
Όταν η plus
previous
είναι έτοιμη return address
activation record
για επιστροφή previous
activation record

Παράμετροι 21

void plus(int a, by-value-result int b) {


b += a;
}
int f() {
int x = 3; Η συνάρτηση f
plus(4, x); επιστρέφει 42
return x*x - x;
}

current
activation record

a: 4 x: 7

b: 7 return address
Όταν η plus
previous
επιστρέψει return address
activation record
previous
activation record

Παράμετροι 22
Κλήση κατ’ αναφορά (call by reference)

Στο πέρασμα παραμέτρων κατ’ αναφορά, η lvalue της


παραμέτρου υπολογίζεται πριν να αρχίσει η εκτέλεση
της κληθείσας συνάρτησης.  Μέσα στη συνάρτηση, η
lvalue χρησιμοποιείται ως lvalue της αντίστοιχης τυπικής
παραμέτρου.  Κατά συνέπεια, η τυπική παράμετρος είναι
ένα επιπλέον όνομα (δηλαδή ένα συνώνυμο ‐ alias) για
τη θέση μνήμης της πραγματικής παραμέτρου.

• Ένας από τους παλαιότερους τρόπους κλήσης


• Χρησιμοποιήθηκε στη Fortran
• Αποδοτικός για μεγάλα αντικείμενα
• Χρησιμοποιείται ευρέως και σήμερα
Παράμετροι 23

void plus(int a, by-reference int b) {


b += a;
}
void f() {
int x = 3;
plus(4, x);
}

current
activation record

a: 4 x: 3

b: return address
Όταν η plus previous
αρχίζει την return address
activation record
εκτέλεσή της previous
activation record

Παράμετροι 24
void plus(int a, by-reference int b) {
b += a;
}
void f() {
int x = 3;
plus(4, x);
}

current
activation record

a: 4 x: 7

b: return address
Όταν η plus previous
εκτελέσει την return address
activation record
ανάθεση previous
activation record

Παράμετροι 25

Υλοποίηση κλήσης κατ’ αναφορά

void plus(int a, by-reference int b) {


b += a;
}
void f() { Το
int x = 3; προηγούμενο
plus(4, x); παράδειγμα
}

void plus(int a, int *b) {


*b += a;
}
void f() { Η υλοποίησή του σε C
int x = 3;
plus(4, &x);
}

κατ’ αναφορά = διεύθυνση κατά τιμή


Παράμετροι 26
Συνωνυμία (aliasing)

• Όταν δύο εκφράσεις έχουν την ίδια lvalue, τότε λέμε ότι
είναι συνώνυμα (aliases) η μία της άλλης
• Κάποιες προφανείς περιπτώσεις aliasing
ConsCell x = new ConsCell(0,null);
ConsCell y = x;

A[i] = A[j] + A[k];

• Όπως θα δούμε στην επόμενη διαφάνεια, η κλήση κατ΄


αναφορά οδηγεί σε πιο περίπλοκες περιπτώσεις aliasing…

Παράμετροι 27

Παράδειγμα

void sigsum(by-reference int n,


by-reference int ans) {
ans = 0;
int i = 1;
while (i <= n) ans += i++;
}

int f() { int g() {


int x, y; int x;
x = 10; x = 10;
sigsum(x,y); sigsum(x,x);
return y; return x;
} }

Παράμετροι 28
void sigsum(by-reference int n,
by-reference int ans) {
ans = 0;
int i = 1;
while (i <= n) ans += i++;
}

int g() { current


int x = 10; activation record
sigsum(x,x);
return x;
} n: x: 10

ans: return address


previous
i: ?
activation record
return address result: ?
previous
activation record
Παράμετροι 29

Επέκταση μακροεντολών (macro expansion)

Στην επέκταση μακροεντολών, το σώμα της μακροεντολής


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

• Με άλλα λόγια, όπως δουλεύουν οι μακροεντολές της C


• Φυσική υλοποίηση: αντικατάσταση κειμένου πριν τη
μεταγλώττιση

Παράμετροι 30
Επέκταση μακροεντολών στη C

assembly-
source pre-processor expanded compiler
file language
source
file

• Η προεπεξεργασία είναι μία επιπλέον φάση της


μεταγλώττισης
• Επεκτείνει τις μακροεντολές πριν τη μετάφραση
• Παράδειγμα
#define MIN(X,Y) ((X)<(Y)?(X):(Y))
a = MIN(b,c);

a = ((b)<(c)?(b):(c))

Παράμετροι 31

Προεπεξεργασία (preprocessing)

• Αντικαθιστούμε κάθε χρήση της μακροεντολής με ένα


αντίγραφο του σώματος της μακροεντολής, όπου οι
τυπικές παράμετροι έχουν αντικατασταθεί από τις
πραγματικές
• Αποτελεί μια παλιά μέθοδο: χρησιμοποιήθηκε στους
assemblers πριν από την ύπαρξη των πρώτων
«ανώτερων» γλωσσών προγραμματισμού
• Έχει κάποιες περίεργες παρενέργειες
– Επανεκτίμηση
– Πιάσιμο ονομάτων (name capture)

Παράμετροι 32
Επανεκτίμηση (repeated evaluation)

• Κάθε πραγματική παράμετρος επανεκτιμάται κάθε φορά


που χρησιμοποιείται

πηγαίος #define MIN(X,Y) ((X)<(Y)?(X):(Y))


κώδικας: a = MIN(b++,c++);

κώδικας
μετά την a = ((b++)<(c++)?(b++):(c++))
επέκταση:

Παράμετροι 33

Παράδειγμα πιασίματος ονομάτων

#define intswap(X,Y) {int temp=X; X=Y; Y=temp;}


int main() {
πηγαίος int temp = 1, b = 2;
κώδικας: intswap(temp, b);
printf("%d, %d\n", temp, b);
}

int main() {
κώδικας int temp = 1, b = 2;
μετά την {int temp = temp; temp = b; b = temp;} ;
επέκταση: printf("%d, %d\n", temp, b);
}

Παράμετροι 34
Πιάσιμο ονομάτων (name capture)

• Σ’ ένα κομμάτι κώδικα, κάθε εμφάνιση μιας μεταβλητής η


οποία δεν είναι δεσμευμένη στατικά ονομάζεται
ελεύθερη (free)
• Όταν κάποιο κομμάτι κώδικα μετακινηθεί σε ένα
διαφορετικό περιβάλλον, οι ελεύθερες μεταβλητές του
μπορεί να δεσμευθούν (become bound)
• Το φαινόμενο αυτό ονομάζεται capture:
– Οι ελεύθερες μεταβλητές των πραγματικών παραμέτρων μπορεί
να «πιαστούν» (be captured) από ορισμούς μεταβλητών στο
σώμα της μακροεντολής
– Επίσης, μεταβλητές που είναι ελεύθερες στο σώμα της
μακροεντολής μπορεί να «πιαστούν» από ορισμούς μεταβλητών
της καλούσας μεθόδου
Παράμετροι 35

Κλήση κατ’ όνομα (call by name)

Στο πέρασμα παραμέτρων κατ’ όνομα, κάθε παράμετρος


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

• Δουλεύει όπως η επέκταση μακροεντολών αλλά


αποφεύγει το πρόβλημα του πιασίματος ονομάτων
• Χρησιμοποιήθηκε στην Algol 60 και στις επεκτάσεις της
• Σήμερα είναι μη δημοφιλής μέθοδος

Παράμετροι 36
Υλοποίηση της κλήσης κατ’ όνομα

• Χειριζόμαστε την πραγματική παράμετρο ως μια μικρή


ανώνυμη συνάρτηση
• Όταν η καλούσα μέθοδος χρειαστεί την τιμή της τυπικής
παραμέτρου (είτε την rvalue της ή την lvalue της) καλεί
τη συνάρτηση για να της την επιστρέψει
• Η συνάρτηση πρέπει να περαστεί μαζί με το σύνδεσμο
φωλιάσματός της (its nesting link), έτσι ώστε να μπορεί
να αποτιμηθεί στο περιβάλλον της καλούσας μεθόδου

Παράμετροι 37

void f(by-name int a, by-name int b) { Η συνάρτηση g


b = 6; επιστρέφει 7
b = a;
}
int g() {
current
int i = 3; activation record
i+1
f(i+1, i);
return i;
}
i

a: i: 3

Όταν η f αρχίζει b: return address

την εκτέλεσή της return address


previous
activation record
previous
result: ?
activation record

Παράμετροι 38
Σύγκριση

• Όπως και στην επέκταση μακροεντολών, οι παράμετροι


που περνιούνται κατ’ όνομα επανεκτιμώνται κάθε φορά
που χρησιμοποιούνται
• Η παραπάνω ιδιότητα μπορεί να φανεί αρκετά χρήσιμη
σε κάποιες περιπτώσεις, όμως στις περισσότερες
περιπτώσεις αποτελεί χάσιμο χρόνου
• Όμως, αντίθετα με την επέκταση μακροεντολών, η
κλήση κατ΄ όνομα αποφεύγει το πρόβλημα του
πιασίματος ονομάτων

Παράμετροι 39

Κλήση κατ’ ανάγκη (call by need)

Στο πέρασμα παραμέτρων κατ’ ανάγκη, κάθε παράμετρος


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

• Χρησιμοποιείται στις οκνηρές (lazy) συναρτησιακές


γλώσσες προγραμματισμού (Miranda, Haskell, Clean)
• Αποφεύγει το χάσιμο χρόνου λόγω της επανεκτίμησης
την οποία πολλές φορές κάνει η κλήση κατ΄ όνομα

Παράμετροι 40
int f(by-need int a, by-need int b) { Η συνάρτηση g
b = a; επιστρέφει 4
b = a;
return b;
}
current
int g() { activation record
i+1
int i = 3;
return f(i+1, i);
}
i

a: i: 3

Όταν η f αρχίζει b: return address


την εκτέλεσή της previous
return address
activation record
previous
result: ?
activation record
Παράμετροι 41

Οκνηρία (laziness)

boolean andand(by-need boolean a,


by-need boolean b) {
if (!a) return false;
else return b;
}
Η συνάρτηση andand
βραχυκυκλώνει όπως η
boolean g() { andalso της ML και ο
while (true) { τελεστής && της Java.
}
return true; Με κλήση κατ΄ ανάγκη η
} μέθοδος f θα τερματίσει.
Το αποτέλεσμα θα ήταν
void f() { το ίδιο τόσο με κλήση
andand(false, g()); κατ΄ όνομα όσο και με
} επέκταση μακροεντολών.

Παράμετροι 42
Θέματα Σχεδιασμού Γλωσσών

Παράμετροι 43

Θέματα σχεδιασμού γλωσσών

Ερώτηση: Αυτά που εξετάσαμε σχετικά με το πέρασμα


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

Παράμετροι 44
Γλώσσες χωρίς παρενέργειες

Ερώτηση: πρέπει οι παράμετροι πάντα να αποτιμώνται


(eager evaluation), ή αυτό συμβαίνει μόνο όταν υπάρχει
ανάγκη χρήσης των τιμών τους (lazy evaluation);

• Το παραπάνω είναι μέρος του μοντέλου κόστους της


γλώσσας και μπορεί επίσης να χρησιμοποιηθεί από τον
προγραμματιστή:
– Πόσο στοιχίζει η επανεκτίμηση κάποιας τυπικής παραμέτρου;
– Έχει το πέρασμα παραμέτρων κόστος ανάλογο με το μέγεθος
των αντικειμένων (που περιλαμβάνονται στις παραμέτρους);

Παράμετροι 45

Γλώσσες με παρενέργειες

• Στις γλώσσες αυτής της κατηγορίας, ένα πρόγραμμα


μπορεί να ανιχνεύσει ποια τεχνική περάσματος
παραμέτρων χρησιμοποιείται από την υλοποίηση της
γλώσσας
• Όμως αυτό μπορεί να είναι μια λεπτομέρεια υλοποίησης
στην οποία τα προγράμματα δεν πρέπει να μπορούν να
βασιστούν — διότι δεν είναι μέρος της προδιαγραφής της
γλώσσας
• Παράδειγμα τέτοιας γλώσσας είναι η Ada

Παράμετροι 46
Ada modes

• Τρεις τρόποι (modes) περάσματος παραμέτρων:


– in: μπορούν να διαβαστούν από την κληθείσα συνάρτηση αλλά
δε μπορεί να τους γίνει κάποια ανάθεση — με άλλα λόγια
συμπεριφέρονται σαν σταθερές
– out: μπορεί να τους γίνει κάποια ανάθεση αλλά δε μπορούν να
διαβαστούν
– in out: μπορούν τόσο να διαβαστούν όσο και να τους γίνει
κάποια ανάθεση

• Η προδιαγραφή της Ada επίτηδες αφήνει κάποιο


περιθώριο για ευελιξία στις υλοποιήσεις της γλώσσας

Παράμετροι 47

Υλοποιήσεις της Ada

• Για βαθμωτές παραμέτρους η υλοποίηση γίνεται μέσω


αντιγραφής:
– in = value, out = result, in out = value/result

• Οι συλλογές αντικειμένων (π.χ. arrays και records)


μπορεί να περαστούν κατ΄ αναφορά αντί με αντιγραφή
• Κάθε πρόγραμμα που μπορεί να ανιχνεύσει τη διαφορά
της υλοποίησης (όπως προγράμματα αντίστοιχα με αυτά
της σημερινής διάλεξης) είναι μη νόμιμο πρόγραμμα Ada

Παράμετροι 48
Συμπερασματικά

• Σήμερα είδαμε:
– Πώς ταιριάζουμε τυπικές και πραγματικές παραμέτρους
– Επτά διαφορετικές τεχνικές για πέρασμα παραμέτρων
– Ιδέες για το πού βρίσκεται η διαχωριστική γραμμή μεταξύ
ορισμού γλώσσας και λεπτομέρειας υλοποίησης

• Αυτές δεν είναι οι μόνες τεχνικές που έχουν δοκιμαστεί,


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

Παράμετροι 49

Εισαγωγή στη Γλώσσα


Prolog
Περιεχόμενα

• Λογικός προγραμματισμός
• Όροι (terms)
• Χρήση ενός συστήματος Prolog
• Κανόνες (rules)
• Οι δύο όψεις της Prolog
• Τελεστές (operators)
• Λίστες (lists)
• Άρνηση ως αποτυχία (negation as failure)
• Σε τί είναι καλή η Prolog;

Εισαγωγή στη γλώσσα Prolog 2

Λογικός προγραμματισμός

• Η χρήση της μαθηματικής λογικής στον προγραμματισμό


• Η λογική χρησιμοποιείται ως μια αγνή δηλωτική γλώσσα
για την αναπαράσταση του προβλήματος
• Το έργο του προγραμματιστή είναι να γράφει
προγράμματα που να είναι «αληθείς» αναπαραστάσεις
του προβλήματος στη λογική
• Το σύστημα εκτέλεσης φροντίζει για την επίλυση του
προβλήματος, με αποδοτικό τρόπο
– αποδείκτες θεωρημάτων (theorem provers)
– γεννήτριες μοντέλων (model generators)
Όροι (Terms)

• Κάθε τι στην Prolog κατασκευάζεται από όρους:


– Τα προγράμματα σε Prolog
– Τα δεδομένα που χειρίζονται τα προγράμματα σε Prolog

• Τρεις κατηγορίες όρων:


– Μεταβλητές
– Σταθερές: ακέραιοι (integers), πραγματικοί αριθμοί (reals), και
άτομα (atoms)
– Σύνθετοι όροι: λίστες (lists) και δομημένα δεδομένα (structures)

Εισαγωγή στη γλώσσα Prolog 4

Άτομα (Atoms)

• Ένα άτομο είναι μια σταθερά που αποτελείται από


αλφαριθμητικούς χαρακτήρες:
– an, atom, has, many_characters
• Εάν ο αρχικός χαρακτήρας δεν είναι ένα μικρό γράμμα ή
υπάρχουν «περίεργοι» χαρακτήρες, το άτομο πρέπει να
βρίσκεται μέσα σε αποστρόφους
– ’Kostis’, ’40 παλικάρια’, ’$^%(@(&$#*&$’
• Κάθε άτομο έχει μια τιμή (τους χαρακτήρες του) και
είναι ίσο μόνο με άλλα άτομα που έχουν ακριβώς τους
ίδιους χαρακτήρες ως τιμή
• Σκεφτείτε τα άτομα κάτι σαν strings σε άλλες γλώσσες
Εισαγωγή στη γλώσσα Prolog 5
Μεταβλητές

• Μεταβλητή είναι κάθε τι που αρχίζει από ένα κεφαλαίο


γράμμα ή underscore, και ακολουθείται από γράμματα,
αριθμούς ή underscores:
_ , X, Variable, Kostis, _123

• Οι μεταβλητές έχουν δύο καταστάσεις:


– Μπορεί να μην έχουν ακόμα δεθεί με κάποια τιμή (unbound)
– Μπορεί να έχουν κάποια τιμή, δηλαδή να είναι δεμένες (bound)

• Όταν όμως μια μεταβλητή είναι σε κατάσταση «με τιμή»,


η τιμή αυτή δεν αλλάζει

Εισαγωγή στη γλώσσα Prolog 6

Σύνθετοι όροι (Compound terms)

• Αρχίζουν με ένα άτομο και ακολουθούνται από μια


ακολουθία όρων που διαχωρίζονται με κόμματα και
περικλείονται από παρενθέσεις:
– university('Ε.Μ.Π.')
– '+'(2,3.14)
– couple(adam,eve)
– tree(V,tree(42,Y,Z),R)
• Οι λίστες είναι και αυτές σύνθετοι όροι αλλά όπως θα
δούμε έχουν ειδική σύνταξη
• Σκεφτείτε τους σύνθετους όρους σαν δομές δεδομένων

Εισαγωγή στη γλώσσα Prolog 7


Σύνταξη των όρων

<term> ::= <constant> | <variable> | <compound-term>


<constant> ::= <integer> | <real number> | <atom>
<compound-term> ::= <atom> ( <termlist> )
<termlist> ::= <term> | <term> , <termlist>

• Όλα τα προγράμματα και τα δεδομένα της Prolog


κατασκευάζονται από τέτοιους όρους
• Σε λίγο θα δούμε ότι, για παράδειγμα, ο σύνθετος όρος
'+'(1,2) συνήθως γράφεται ως 1+2
• Αλλά αυτή είναι απλά μια συντομογραφία του ίδιου όρου

Εισαγωγή στη γλώσσα Prolog 8

Ενοποίηση (Unification)

• Ταίριασμα προτύπων σε όρους της Prolog


• Δύο όροι ενοποιούνται εάν υπάρχει κάποιος τρόπος να
βρεθούν τιμές για τις μεταβλητές τους που να τους
κάνουν ακριβώς ίδιους
• Για παράδειγμα, οι όροι couple(adam,eve) και
couple(Who,WithWhom) ενοποιούνται αν η μεταβλητή
Who πάρει την τιμή adam και η μεταβλητή WithWhom
πάρει την τιμή eve

• (Περισσότερες λεπτομέρειες στο επόμενο μάθημα…)


Εισαγωγή στη γλώσσα Prolog 9
Η βάση δεδομένων της Prolog

• Η υλοποίηση της Prolog διατηρεί μια αναπαράσταση των


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

Εισαγωγή στη γλώσσα Prolog 10

Παράδειγμα κατηγορήματος με γεγονότα

parent(kim, holly).
parent(margaret, kim).
parent(margaret, kent).
parent(esther, margaret).
parent(herbert, margaret).
parent(herbert, jean).

• Ένα πρόγραμμα Prolog με έξι γεγονότα


• Ορίζει ένα κατηγόρημα (predicate) parent/2
• Αποτελούμενο από γεγονότα που έχουν την «προφανή»
ερμηνεία: ο kim είναι γονιός της holly, η margaret
είναι γονιός του kim, κ.ο.κ.
Εισαγωγή στη γλώσσα Prolog 11
Ο διερμηνέας της Prolog

% swipl
Welcome to SWI-Prolog (Multi-threaded, Version 5.2.13)
Copyright (c) 1990-2003 University of Amsterdam.
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. ...
Please visit http://www.swi-prolog.org for details

For help, use ?- help(Topic). or ?- apropos(Word).

?-

• Το ?- είναι η προτροπή της Prolog για μια ερώτηση


• Ο διερμηνέας είναι διαδραστικός: δέχεται μια ερώτηση,
προσπαθεί να την απαντήσει, τυπώνει την απάντησή της,
και επαναλαμβάνει αυτή τη διαδικασία
Εισαγωγή στη γλώσσα Prolog 12

Το κατηγόρημα consult/1

?- consult(relations).
% relations compiled 0.00 sec, ΧΧ bytes

Yes
?-

• Κατηγόρημα του συστήματος με το οποίο φορτώνουμε


ένα πρόγραμμα από ένα αρχείο στη βάση της Prolog
• Το αρχείο relations (ή relations.pl) περιλαμβάνει
τα γεγονότα του κατηγορήματος parent/2

?- [relations].
% relations compiled 0.00 sec, ΧΧ bytes

Yes
Εισαγωγή στη γλώσσα Prolog 13
Απλές ερωτήσεις
?- parent(margaret, kent).

Yes
?- parent(fred, pebbles).

No
?-

• Μια ερώτηση προκαλεί την Prolog να αποδείξει κάτι


• Η απάντηση των απλών ερωτήσεων είναι ένα Yes ή No

(Κάποιες ερωτήσεις, όπως η ερώτηση στο κατηγόρημα


consult/1, εκτελούνται μόνο για τις παρενέργειές τους)

Εισαγωγή στη γλώσσα Prolog 14

Η τελεία

?- parent(margaret, kent)
| .

Yes
?-

• Οι ερωτήσεις μπορούν να εκτείνονται σε πολλές


γραμμές
• Εάν ξεχάσετε τη τελεία (το χαρακτήρα τέλους της
ερώτησης), ο διερμηνέας της Prolog θα σας ζητήσει
περισσότερη είσοδο με το χαρακτήρα |

Εισαγωγή στη γλώσσα Prolog 15


Ερωτήσεις με μεταβλητές

• Οι ερωτήσεις μπορεί να είναι οποιοσδήποτε όρος Prolog,


συμπεριλαμβανομένων όρων με μεταβλητές
• Ο διερμηνέας της Prolog επιστρέφει ως απάντηση τις
τιμές των μεταβλητών για τις οποίες η ερώτηση μπορεί
να απαντηθεί
?- parent(P, jean). Στο σημείο αυτό, ο
διερμηνέας περιμένει
P = herbert
κάποια είσοδο.
Yes Δίνουμε enter για να
?- parent(P, esther). σταματήσει η εκτέλεση
της ερώτησης.
No

Εισαγωγή στη γλώσσα Prolog 16

Ευελιξία στις ερωτήσεις

• Οι μεταβλητές μπορούν φυσικά να βρίσκονται σε


οποιαδήποτε θέση κάποιας ερώτησης:
– parent(Parent, jean)
– parent(esther, Child)
– parent(Parent, Child)
– parent(Person, Person)

Εισαγωγή στη γλώσσα Prolog 17


Συζεύξεις (Conjunctions)

?- parent(margaret, X), parent(X, holly).

X = kim

Yes

• Μια σύζευξη ερωτήσεων είναι μια ακολουθία από όρους


που διαχωρίζονται από κόμματα (and)
• Ο διερμηνέας της Prolog προσπαθεί να τις αποδείξει
όλες (χρησιμοποιώντας το ίδιο σύνολο «δεσιμάτων» για
τις μεταβλητές της ερώτησης)

Εισαγωγή στη γλώσσα Prolog 18

Πολλαπλές λύσεις

?- parent(margaret, Child).

Child = kim ;

Child = kent ;

No

• Μπορεί να υπάρχουν περισσότεροι από ένας τρόποι για


να απαντηθεί μια ερώτηση
• Με το να πληκτρολογήσουμε ; αντί για enter,
προτρέπουμε το σύστημα Prolog να βρει και να μας
δώσει περισσότερες απαντήσεις
Εισαγωγή στη γλώσσα Prolog 19
?- parent(Parent,kim), parent(Grandparent,Parent).

Parent = margaret
Grandparent = esther ;

Parent = margaret
Grandparent = herbert ;

No
?- parent(esther, Child),
| parent(Child, Grandchild),
| parent(Grandchild, GreatGrandchild).

Child = margaret
Grandchild = kim
GreatGrandchild = holly

Yes

Εισαγωγή στη γλώσσα Prolog 20

Ανάγκη για κανόνες

• Στο προηγούμενο παράδειγμα χρησιμοποιήσαμε μια


μακροσκελή ερώτηση για τα δισέγγονα της esther
• Θα ήταν καλύτερο εάν μπορούσαμε να γράψουμε κατ’
ευθείαν την ερώτηση:
greatgrandparent(esther, GGC)
• Αλλά δε θέλουμε να εισαγάγουμε ξεχωριστά γεγονότα
αυτού του κατηγορήματος στη βάση των δεδομένων
διότι η σχέση greatgrandparent/2 προκύπτει (και
πρέπει να προκύπτει) από την ήδη ορισμένη βασική
σχέση parent/2

Εισαγωγή στη γλώσσα Prolog 21


Ένα παράδειγμα κανόνα
κεφαλή

greatgrandparent(GGP,GGC):-
parent(GGP,GP),
parent(GP,P), συνθήκες
parent(P,GGC).

• Ένας κανόνας καθορίζει πώς αποδεικνύουμε κάτι: για να


αποδείξουμε την κεφαλή του κανόνα πρέπει να αποδείξουμε
τις συνθήκες του
• Για να αποδείξουμε greatgrandparent(GGP,GGC), πρέπει
να βρούμε τιμές GP και P για τις οποίες μπορούμε να
αποδείξουμε ότι parent(GGP,GP), μετά ότι parent(GP,P)
και τελικά ότι parent(P,GGC)
Εισαγωγή στη γλώσσα Prolog 22

Πρόγραμμα με χρήση του κανόνα


parent(kim,holly).
parent(margaret,kim).
parent(margaret,kent).
parent(esther,margaret).
parent(herbert,margaret).
parent(herbert,jean).

greatgrandparent(GGP,GGC) :-
parent(GGP,GP),
parent(GP,P), parent(P,GGC).

• Ένα πρόγραμμα αποτελείται από μια ακολουθία από


προτάσεις (clauses)
• Μια πρόταση είναι είτε ένα γεγονός ή ένας κανόνας
Εισαγωγή στη γλώσσα Prolog 23
Παράδειγμα

?- greatgrandparent(esther, GreatGrandchild).

GreatGrandchild = holly ;

No

• Για την απάντηση της παραπάνω ερώτησης, εσωτερικά


εξετάζονται διάφοροι ενδιάμεσοι στόχοι (goals):
– Ο πρώτος (υπο)στόχος είναι η αρχική ερώτηση
– Ο επόμενος είναι ό,τι απομένει προς απόδειξη μετά την απόδειξη
του πρώτου (υπο)στόχου με χρήση κάποιας πρότασης του
προγράμματος (στη συγκεκριμένη περίπτωση, με χρήση του
κανόνα για το κατηγόρημα greatgrandparent/2)
– Κοκ., μέχρι να μη μείνει τίποτα προς απόδειξη

Εισαγωγή στη γλώσσα Prolog 24

1. parent(kim,holly).
2. parent(margaret,kim).
3. parent(margaret,kent).
4. parent(esther,margaret).
5. parent(herbert,margaret).
6. parent(herbert,jean).
7. greatgrandparent(GGP,GGC) :-
parent(GGP,GP), parent(GP,P), parent(P,GGC).

greatgrandparent(esther,GreatGrandchild)
Clause 7, binding GGP to esther and GGC to GreatGrandChild
parent(esther,GP), parent(GP,P), parent(P,GreatGrandchild)
Clause 4, binding GP to margaret
parent(margaret,P), parent(P,GreatGrandchild)

Clause 2, binding P to kim


parent(kim,GreatGrandchild)
Clause 1, binding GreatGrandchild to holly
Εισαγωγή στη γλώσσα Prolog 25
Κανόνες με χρήση άλλων κανόνων

grandparent(GP,GC) :-
parent(GP,P), parent(P,GC).

greatgrandparent(GGP,GGC) :-
grandparent(GGP,P), parent(P,GGC).

• Η ίδια σχέση με την προηγούμενη, ορισμένη σε στρώσεις


• Παρατηρήστε ότι και οι δύο κανόνες χρησιμοποιούν
μεταβλητές με όνομα P
• Η εμβέλεια κάποιας μεταβλητής είναι μόνο ο κανόνας
που την περιέχει
• Οι δύο μεταβλητές με όνομα P είναι ανεξάρτητες
Εισαγωγή στη γλώσσα Prolog 26

Αναδρομικοί κανόνες

ancestor(X,Y) :- parent(X,Y).
ancestor(X,Y) :-
parent(Z,Y),
ancestor(X,Z).

• Ένας X είναι πρόγονος κάποιου Y:


– Εάν ο X είναι γονιός του Y, ή
– Εάν υπάρχει κάποιος Z τέτοιος ώστε ο Z να είναι ο γονιός του Y,
και ο X να είναι πρόγονος του Z

• Η Prolog δοκιμάζει τους κανόνες με τη σειρά που αυτοί


εμφανίζονται, οπότε είναι καλό (και μάλιστα πολλές
φορές αναγκαίο) τα γεγονότα και οι μη αναδρομικοί
κανόνες να εμφανίζονται πριν από τους αναδρομικούς
Εισαγωγή στη γλώσσα Prolog 27
?- ancestor(jean, jean).
No

?- ancestor(kim, holly).
Yes

?- ancestor(A, holly).
A = kim ;
A = margaret ;
A = esther ;
A = herbert ;
No

Εισαγωγή στη γλώσσα Prolog 28

Ο πυρήνας της σύνταξης της Prolog

• Ολόκληρη η σύνταξη του πυρήνα της Prolog:


<clause> ::= <fact> | <rule>
<fact> ::= <term> .
<rule> ::= <term> :- <termlist> .
<termlist> ::= <term> | <term> , <termlist>

• Εάν εξαιρεθεί η δυνατότητα ορισμού νέων τελεστών από


το χρήστη, συντακτικά η Prolog είναι πολύ απλή γλώσσα
• Αυτό ήταν όλο!

Εισαγωγή στη γλώσσα Prolog 29


Η διαδικαστική όψη της Prolog

greatgrandparent(GGP,GGC) :-
parent(GGP,GP),
parent(GP,P),
parent(P,GGC).

• Ένας κανόνας λέει πώς μπορούμε να αποδείξουμε κάτι:


– Για να αποδείξουμε greatgrandparent(GGP,GGC), πρέπει να
βρούμε τιμές GP και P για τις οποίες μπορούμε να αποδείξουμε
ότι parent(GGP,GP), μετά ότι parent(GP,P) και τελικά ότι
parent(P,GGC)

• Ένα πρόγραμμα Prolog προσδιορίζει διαδικασίες


εύρεσης απαντήσεων σε ερωτήσεις

Εισαγωγή στη γλώσσα Prolog 30

Η δηλωτική όψη της Prolog

• Ένας κανόνας είναι μια λογική δήλωση:


– Για όλες τις τιμές των GGP, GP, P, και GGC, αν ισχύει ότι
parent(GGP,GP) και parent(GP,P) και parent(P,GGC),
τότε επίσης ισχύει και greatgrandparent(GGP,GGC)

• Με άλλα λόγια, ένας κανόνας είναι απλώς μια φόρμουλα


– δε λέει πώς θα γίνει κάτι αλλά απλώς ορίζει τις προϋποθέσεις
κάτω από τις οποίες μια πρόταση είναι αληθής:

∀GGP, GP, P, GGC . parent (GGP, GP ) ∧ parent (GP, P ) ∧ parent (P, GGC )
⇒ greatgrandparent (GGP, GGC )

Εισαγωγή στη γλώσσα Prolog 31


Δηλωτικές γλώσσες προγραμματισμού

• Κάθε κομμάτι του προγράμματος αντιστοιχεί σε μια


απλή μαθηματική έννοια:
– Προτάσεις της Prolog – σε φόρμουλες της κατηγορηματικής
λογικής πρώτης τάξης (first-order predicate logic)
– Οι ορισμοί συναρτήσεων στην ML – σε μαθηματικές συναρτήσεις

• Πολλές φορές ο όρος δηλωτικές (declarative) γλώσσες


προγραμματισμού χρησιμοποιείται για να δείξει την
αντιδιαστολή των γλωσσών αυτών σε σχέση με τις
προστακτικές (imperative) γλώσσες
• Ως δηλωτικές γλώσσες συνήθως θεωρούμε τις λογικές
και τις συναρτησιακές

Εισαγωγή στη γλώσσα Prolog 32

Πλεονεκτήματα των δηλωτικών γλωσσών

• Τα προγράμματα σε προστακτικές γλώσσες πολλές


φορές έχουν προβλήματα λόγω παρενεργειών και
αλληλεξαρτήσεων
• Επειδή οι δηλωτικές γλώσσες έχουν σχετικά απλή και
«καθαρότερη» σημασιολογία, αποφεύγονται κάποιου
είδους προγραμματιστικά λάθη με συνέπεια η ανάπτυξη
και η συντήρηση προγραμμάτων να γίνεται πιο εύκολα
• Ο προγραμματισμός είναι ανώτερου επιπέδου και πιο
αυτόματος: ο προγραμματιστής απλώς περιγράφει την
προδιαγραφή του προβλήματος και κάποια κομμάτια της
εκτέλεσής του παρέχονται από το σύστημα υλοποίησης
Εισαγωγή στη γλώσσα Prolog 33
Η Prolog έχει και τα δύο χαρακτηριστικά

• Έχει δηλωτική σημασιολογία


– Ένα πρόγραμμα Prolog έχει ένα λογικό περιεχόμενο
• Έχει διαδικαστική σημασιολογία
– Ένα πρόγραμμα Prolog έχει και καθαρά διαδικαστικά
χαρακτηριστικά: τη σειρά με την οποία εμφανίζονται οι
κανόνες, τη σειρά εμφάνισης των υποστόχων σ’ ένα
κανόνα, κατηγορήματα με παρενέργειες (π.χ. Ι/Ο), κ.λπ.
• Όμως είναι σημαντικό ο προγραμματιστής να είναι
ενήμερος και να παίρνει υπόψη του και τις δύο όψεις της
γλώσσας κατά τον προγραμματισμό

Εισαγωγή στη γλώσσα Prolog 34

Τελεστές της Prolog

• Η Prolog έχει κάποιους προκαθορισμένους τελεστές


(και επιτρέπει τον ορισμό νέων τελεστών από το χρήστη)
• Ένας τελεστής είναι απλώς ένα κατηγόρημα για το
οποίο είναι δυνατή η χρήση κάποιας ειδικής συντακτικής
συντομογραφίας στις χρήσεις του

Εισαγωγή στη γλώσσα Prolog 35


Το κατηγόρημα =/2

• Ο στόχος =(X,Y) επιτυγχάνει αν και μόνον αν οι όροι X


και Y μπορούν να ενοποιηθούν:
?- =(parent(adam,seth), parent(adam,X)).

X = seth ;

No

• Επειδή το άτομο '=' είναι ορισμένο ως δυαδικός


τελεστής, συνήθως γράφουμε το παραπάνω ως:
?- parent(adam,seth) = parent(adam,X).

X = seth ;

No
Εισαγωγή στη γλώσσα Prolog 36

Αριθμητικοί τελεστές

• Τα άτομα '+', '-', '*' και '/' είναι ορισμένα ως


τελεστές, με τη συνήθη προτεραιότητα και
προσεταιριστικότητα
?- X = +(1,*(2,3)). Η Prolog μας επιτρέπει να
X = 1+2*3 ;
χρησιμοποιήσουμε σύνταξη
τελεστών στην είσοδο, και
No χρησιμοποιεί αυτή τη μορφή
?- X = 1+2*3. στην έξοδο των όρων, αλλά
και στις δύο περιπτώσεις ο
X = 1+2*3 ;
υποκείμενος όρος είναι ο
No +(1,*(2,3))

Εισαγωγή στη γλώσσα Prolog 37


Μη αποτιμημένοι όροι

?- +(X,Y) = 1+2*3.

X = 1
Y = 2*3 ;

No
?- 7 = 1+2*3.

No

• Μετά την ενοποίηση, ο όρος παραμένει +(1,*(2,3))


• Με άλλα λόγια, ο όρος είναι μη αποτιμημένος

(Σε επόμενη διάλεξη θα δούμε ότι υπάρχει τρόπος στην


Prolog να αποτιμήσουμε τέτοιους όρους)
Εισαγωγή στη γλώσσα Prolog 38

Λίστες στην Prolog

• Συντακτικά είναι περίπου σαν τις λίστες στην ML


• Το άτομο [] αναπαριστά την κενή λίστα
• Το συναρτησιακό σύμβολο '.'/2 αντιστοιχεί στον
τελεστή :: της ML

Έκφραση στην ML Όρος στην Prolog


[] []
1::[] .(1,[])
1::2::3::[] .(1,.(2,.(3,[])))
Δεν υπάρχει αντίστοιχο. .(1,.(2,.(3.14),[]))

Εισαγωγή στη γλώσσα Prolog 39


Οι αναπαραστάσεις των λιστών στην Prolog

Λίστα Όρος στον οποίο αντιστοιχεί


[] []
[1] .(1,[])
[1,2,3] .(1,.(2,.(3,[])))
[1,2,3.14] .(1,.(2,.(3.14,[]))

• Η μορφή που συνήθως χρησιμοποιείται είναι η αριστερή


• Αλλά είναι απλώς μια συντομογραφία της μορφής που
φαίνεται δεξιά στον παραπάνω πίνακα
• Η Prolog τυπώνει τις λίστες με χρήση της μορφής στο
αριστερό μέρος του πίνακα
Εισαγωγή στη γλώσσα Prolog 40

Παράδειγμα

?- X = .(1,.(2,.(3,[]))).

X = [1, 2, 3] ;

No
?- .(X,Y) = [1,2,3].

X = 1
Y = [2, 3] ;

No

Εισαγωγή στη γλώσσα Prolog 41


Αναπαραστάσεις λιστών με ουρά

Λίστα Όρος στον οποίο αντιστοιχεί


[1|X] .(1,X)
[1,2|X] .(1,.(2,X)))
[1,2|[3,4]] Το ίδιο με [1,2,3,4]

• Πολλές φορές το τέλος της λίστας είναι το σύμβολο |


ακολουθούμενο από την ουρά της λίστας
• Χρησιμοποιείται σε πρότυπα: ο όρος [1,2|X] ενοποιείται
με κάθε λίστα που αρχίζει με 1,2 και έχει έναν όρο X ως
ουρά
?- [1,2|X] = [1,2,3,4,5].
X = [3, 4, 5] ;
No
Εισαγωγή στη γλώσσα Prolog 42

Το κατηγόρημα append/3

• Το προκαθορισμένο κατηγόρημα append(X,Y,Z)


επιτυγχάνει αν και μόνο αν η λίστα Z είναι το αποτέλεσμα
της συνένωσης της λίστας Y στο τέλος της λίστας X
?- append([1,2],[3,4],Z).

Z = [1, 2, 3, 4] ;

No

Εισαγωγή στη γλώσσα Prolog 43


Όχι απλώς μια συνάρτηση!

?- append(X, [3,4], [1,2,3,4]).

X = [1, 2] ;

No

• Το κατηγόρημα append μπορεί να χρησιμοποιηθεί με


κάθε όρο της Prolog (με άλλα λόγια, μπορεί να κληθεί και
με μεταβλητές σε κάθε όρισμα)

Εισαγωγή στη γλώσσα Prolog 44

Όχι απλώς μια συνάρτηση!

?- append(X, Y, [1,2,3]).

X = []
Y = [1, 2, 3] ;

X = [1]
Y = [2, 3] ;

X = [1, 2]
Y = [3] ;

X = [1, 2, 3]
Y = [] ;

No

Εισαγωγή στη γλώσσα Prolog 45


Η υλοποίηση του κατηγορήματος append/3

append([], L, L).
append([H|L1], L2, [H|L3]) :-
append(L1, L2, L3).

Εισαγωγή στη γλώσσα Prolog 46

Άλλα κατηγορήματα για λίστες

Κατηγόρημα Περιγραφή
member(X,Y) Επιτυγχάνει εάν η λίστα Y έχει ως μέλος τον όρο X.
Επιτυγχάνει εάν η λίστα Y περιέχει το X ως μέλος, και
select(X,Y,Z) ο όρος Z είναι ο ίδιος με τη λίστα Y αλλά χωρίς ένα
στιγμιότυπο του όρου X.
nth0(N,L,X) Επιτυχάνει εάν ο N είναι ένας μη αρνητικός ακέραιος,
L μια λίστα, και το X είναι το N-οστό στοιχείο της
λίστας L, αρχίζοντας την αρίθμηση από το 0.
length(L,N) Επιτυγχάνει εάν ο όρος L είναι μια λίστα με μήκος N.

• Όλα τα παραπάνω είναι ευέλικτα, σαν το append/3


• Με άλλα λόγια οι ερωτήσεις στα κατηγορήματα μπορούν
να περιέχουν μεταβλητές σε οποιαδήποτε όρισμά τους
Εισαγωγή στη γλώσσα Prolog 47
Χρήση του κατηγορήματος select/3

?- select(2, [1,2,3], Z). ?- select(2, [1,2,3,2], Z).

Z = [1, 3] ; Z = [1, 3, 2] ;
Z = [1, 2, 3] ;
No
No
?- select(2, Y, [1,3]).
?- select(2, [Χ,Y], [1]).
Y = [2, 1, 3] ;
X = 2
Y = [1, 2, 3] ; Y = 1 ;

Y = [1, 3, 2] ; X = 1
Y = 2 ;
No
No

Εισαγωγή στη γλώσσα Prolog 48

Το κατηγόρημα reverse/2

• reverse(X,Y) είναι αληθές εάν ο όρος Y ενοποιείται


με την αναστροφή της λίστας X

?- reverse([1,2,3,4], Y).

Y = [4, 3, 2, 1] ;

No

Εισαγωγή στη γλώσσα Prolog 49


Η υλοποίηση του κατηγορήματος reverse/2

reverse([], []).
reverse([Head|Tail], Reversed) :-
reverse(Tail, RevTail),
append(RevTail, [Head], Reversed).

• Δεν είναι ένας αποδοτικός τρόπος να αντιστρέψουμε μια


λίστα
• Υπάρχουν πιο αποδοτικοί τρόποι αντιστροφής λιστών
(όπως ακριβώς είδαμε και στην ML)

Εισαγωγή στη γλώσσα Prolog 50

Όταν δεν πάνε όλα καλά…

?- reverse(L, [1,2,3,4]).

L = [4, 3, 2, 1] ;

Action (h for help) ? a


% Execution Aborted
?-

• Ζητήσαμε μια ακόμα λύση και βρεθήκαμε σ’ έναν


ατέρμονο βρόχο
• Μπορούμε να παύσουμε την εκτέλεση πατώντας Ctrl-C,
και στη συνέχεια πατάμε a για το οριστικό σταμάτημά της
• Το reverse/2 δεν είναι τόσο ευέλικτο όσο το append/3
Εισαγωγή στη γλώσσα Prolog 51
Αναστρέψιμα και μη αναστρέψιμα κατηγορήματα

• Σ’ έναν ιδεατό κόσμο, τα κατηγορήματα είναι όλα τόσο


ευέλικτα όσο το append/3
• Είναι πιο δηλωτικά και με λιγότερους περιορισμούς στη
χρήση τους
• Αλλά πολλές φορές μη ευέλικτες υλοποιήσεις είναι
προτιμότερες, για λόγους απόδοσης ή/και απλότητας
• Ένα παράδειγμα του τι σημαίνει η παραπάνω φράση μας
δείχνει το ενσωματωμένο κατηγόρημα sort/2

Εισαγωγή στη γλώσσα Prolog 52

Το κατηγόρημα sort/2

?- sort([2,3,1,4], X).

X = [1, 2, 3, 4] ;

No
?- sort(X, [1,2,3,4]).
ERROR: Arguments are not sufficiently instantiated

• Ένα αναστρέψιμο κατηγόρημα sort/2 θα έπρεπε να


μπορεί να «αντι-ταξινομήσει» μια ήδη ταξινομημένη
λίστα — με άλλα λόγια θα έπρεπε να επιστρέφει όλες τις
αναδιατάξεις των στοιχείων της — που είναι εκθετικό
• Θέλουμε κάτι σαν το παραπάνω να είναι ενσωματωμένο
στη γλώσσα;
Εισαγωγή στη γλώσσα Prolog 53
Ανώνυμες μεταβλητές

• Η μεταβλητή _ είναι μια μεταβλητή χωρίς όνομα


• Κάθε εμφάνισή της είναι ανεξάρτητη από όλες τις
υπόλοιπες
• Ενοποιείται με κάθε όρο
• Με άλλα λόγια, συμπεριφέρεται σαν τις μεταβλητές _
της ML

Εισαγωγή στη γλώσσα Prolog 54

Το κατηγόρημα member/2

member(X, [X|_]).
member(X, [_|T]) :-
member(X, T).

?- member(b, [a,b,c]).
Yes
?- member(d, [a,b,c]).
No
?- member(X, [a,b]).

X = a ;
X = b ;
No
Εισαγωγή στη γλώσσα Prolog 55
Προσοχή στις προειδοποιήσεις!

append([], L, L).
append([H|L1], L2, [H|L3]) :-
append(LI, L2, L3).

Warning: Singleton variables [L1,LI]

• Μην αγνοείτε τα προειδοποιητικά μηνύματα ότι


μεταβλητές εμφανίζονται μόνο μια φορά
• Όπως και στην ML, είναι κακό στυλ προγραμματισμού να
ονομάζουμε μεταβλητές που δεν χρησιμοποιούμε – ειδικά
επειδή είναι το μόνο μήνυμα που ο διερμηνέας θα βγάλει
Εισαγωγή στη γλώσσα Prolog 56

Το κατηγόρημα \+/1

?- member(1, [1,2,3]).

Yes
?- \+ member(4, [1,2,3]).

Yes

• Σε απλές εφαρμογές, συνήθως λειτουργεί σαν τον


τελεστή λογικής άρνησης
• Αλλά έχει και μια σημαντική διαδικαστική πλευρά…

Εισαγωγή στη γλώσσα Prolog 57


Άρνηση ως αποτυχία (negation as failure)

• Για να αποδείξει το \+ X, η Prolog προσπαθεί να


αποδείξει το X
• \+ X επιτυγχάνει εάν ο στόχος X αποτυγχάνει
• Οι δύο όψεις ξανά:
– Δηλωτική: \+ X = ¬X
– Διαδικαστική: \+ X επιτυγχάνει εάν το X αποτυγχάνει, και
αποτυγχάνει εάν το X επιτυγχάνει, και δεν τερματίζει εάν
το X δεν τερματίζει

Εισαγωγή στη γλώσσα Prolog 58

sibling(X, Y) :-
Παράδειγμα parent(P, X),
parent(P, Y),
\+ (X = Y).
sibling(X, Y) :-
\+ (X = Y),
parent(P, X), ?- sibling(X, Y).
parent(P, Y).
X = kim
Y = kent ;
?- sibling(kim, kent).
X = kent
Yes Y = kim ;
?- sibling(kim, kim). X = margaret
Y = jean ;
No
X = jean
?- sibling(X, Y).
Y = margaret ;
No
No

Εισαγωγή στη γλώσσα Prolog 59


Ένα μεγαλύτερο παράδειγμα σε Prolog

Εισαγωγή στη γλώσσα Prolog 60

Μια κλασική σπαζοκεφαλιά

• Ένας άνθρωπος (man) ταξιδεύει μαζί μ’ ένα λύκο (wolf),


μια κατσίκα (goat) και ένα λάχανο (cabbage)
• Ο άνθρωπος θέλει να διασχίσει ένα ποτάμι από τη δυτική
όχθη στην ανατολική
• Στην αρχική όχθη υπάρχει μια βάρκα, η οποία όμως
χωράει τον άνθρωπο και το πολύ ένα άλλο αντικείμενο
• Ο λύκος τρώει την κατσίκα εάν βρεθεί μόνος μαζί της
• Η κατσίκα τρώει το λάχανο εάν βρεθεί μόνη μαζί του
• Με ποιο τρόπο μπορεί ο άνθρωπος να περάσει στην
απέναντι όχθη με όλα τα υπάρχοντά του;
Εισαγωγή στη γλώσσα Prolog 61
Δομές δεδομένων της λύσης

• Θα αναπαραστήσουμε τους σχηματισμούς του


συστήματος με μια λίστα που δείχνει την όχθη στην
οποία είναι το κάθε τι με την εξής σειρά:
man, wolf, goat, cabbage
• Ο αρχικός σχηματισμός: [w,w,w,w]
• Εάν για παράδειγμα ο άνθρωπος με το λύκο μπουν στη
βάρκα, ο νέος σχηματισμός είναι ο [e,e,w,w] – αλλά
τότε η κατσίκα θα φάει το λάχανο,οπότε ο παραπάνω
σχηματισμός δεν είναι επιτρεπτός ως ενδιάμεσος
• Ο επιθυμητός τελικός σχηματισμός: [e,e,e,e]

Εισαγωγή στη γλώσσα Prolog 62

Κινήσεις

• Σε κάθε κίνηση, ο άνθρωπος διασχίζει το ποτάμι με το


πολύ ένα από τα υπάρχοντά του
• Θα αναπαραστήσουμε τις τέσσερις επιτρεπτές κινήσεις
με τέσσερα άτομα: wolf, goat, cabbage, nothing
• (Όπου, nothing σημαίνει ότι ο άνθρωπος διασχίζει το
ποτάμι μόνος του μέσα στη βάρκα)

Εισαγωγή στη γλώσσα Prolog 63


Οι κινήσεις τροποποιούν τον σχηματισμό

• Κάθε κίνηση μεταμορφώνει ένα σχηματισμό σ’ έναν άλλο


• Στην Prolog, θα προγραμματίσουμε τη λύση με ένα
κατηγόρημα: move(Config, Move, NextConfig)
– Config είναι ένας σχηματισμός (π.χ. [w,w,w,w])
– Move είναι μια κίνηση (π.χ. wolf)
– NextConfig είναι ο σχηματισμός-αποτέλεσμα (στο συγκεκριμένο
παράδειγμα ο [e,e,w,w])

Εισαγωγή στη γλώσσα Prolog 64

Το κατηγόρημα move/2

change(e, w).
change(w, e).

move([X,X,Goat,Cabbage], wolf, [Y,Y,Goat,Cabbage]) :-


change(X, Y).
move([X,Wolf,X,Cabbage], goat, [Y,Wolf,Y,Cabbage]) :-
change(X, Y).
move([X,Wolf,Goat,X], cabbage, [Y,Wolf,Goat,Y]) :-
change(X, Y).
move([X,Wolf,Goat,C], nothing, [Y,Wolf,Goat,C]) :-
change(X, Y).

Εισαγωγή στη γλώσσα Prolog 65


Ασφαλείς σχηματισμοί

• Ένας σχηματισμός είναι ασφαλής εάν


– Ο λύκος και ή κατσίκα είτε φυλάσσονται από τον άνθρωπο
ή είναι σε διαφορετικές όχθες, και
– Η κατσίκα και το λάχανο είτε φυλάσσονται από τον άνθρωπο
ή είναι σε διαφορετικές όχθες.

guarded_or_separated(X, X, X).
guarded_or_separated(_, Y, Z) :- Y \= Z.

safe([Man, Wolf, Goat, Cabbage]) :-


guarded_or_separated(Man, Goat, Wolf),
guarded_or_separated(Man, Goat, Cabbage).

Εισαγωγή στη γλώσσα Prolog 66

Λύσεις

• Μια λύση αρχίζει από τον αρχικό σχηματισμό και


δημιουργεί μια λίστα από κινήσεις που οδηγούν στο
σχηματισμό [e,e,e,e], έτσι ώστε όλοι οι ενδιάμεσοι
σχηματισμοί να είναι ασφαλείς

solution([e,e,e,e], []).
solution(Config, [Move|Moves]) :-
move(Config, Move, NextConfig),
safe(NextConfig),
solution(NextConfig, Moves).

Εισαγωγή στη γλώσσα Prolog 67


Το κατηγόρημα length/2
?- length(L, N).

L = []
N = 0 ;

L = [_]
N = 1 ;

L = [_, _]
N = 2 ;

L = [_, _, _]
N = 3 ;

L = [_, _, _, _]
N = 4

Yes
Εισαγωγή στη γλώσσα Prolog 68

Η Prolog βρίσκει τη λύση

?- length(L, Ν), solution([w,w,w,w], L).

L = [goat, nothing, wolf, goat, cabbage, nothing, goat]


Ν = 7

Yes

Σημείωση: χωρίς τη χρήση του length/2, η Prolog δε θα


έβρισκε κάποια λύση (με το συγκεκριμένο πρόγραμμα).
Θα μπερδευόταν ψάχνοντας για λύσεις της μορφής:
[goat,goat,goat,goat,goat…]

Εισαγωγή στη γλώσσα Prolog 69


Κάποια καλά χαρακτηριστικά της Prolog

• Το πρόγραμμά μας απλώς κατέγραψε την εκφώνηση του


προβλήματος προς επίλυση
• Δεν προσδιορίσαμε κάποιο τρόπο αναζήτησης της λύσης
του προβλήματος – η ίδια η Prolog έκανε την αναζήτηση
• Αυτού του είδους τα προβλήματα είναι ακριβώς τα
προβλήματα που είναι κατάλληλα προς επίλυση σε
Prolog

• Περισσότερα παραδείγματα σε επόμενες διαλέξεις…

Εισαγωγή στη γλώσσα Prolog 70

Περισσότερη Prolog
Περιεχόμενα

• Ενοποίηση
• Μοντέλα εκτέλεσης της Prolog
– Το διαδικαστικό μοντέλο
– Το μοντέλο υλοποίησης
– Το αφηρημένο μοντέλο

• Περισσότερη Prolog
– Κατηγορήματα εισόδου και εξόδου
– Κατηγορήματα διαχείρισης της βάσης της Prolog
– Αριθμητικοί υπολογισμοί και συγκρίσεις στην Prolog

• Παραδείγματα προγραμματισμού σε Prolog


• Αποχαιρετισμός στην Prolog

Περισσότερη Prolog 2

Αντικαταστάσεις (Substitutions)

• Μια αντικατάσταση είναι μια συνάρτηση που απεικονίζει


μεταβλητές σε όρους:
σ = {X → a, Y → f(a,b)}
• Η αντικατάσταση σ αντιστοιχεί τη μεταβλητή X στο a και
τη Y στο f(a,b)
• Το αποτέλεσμα της εφαρμογής μιας αντικατάστασης σε
έναν όρο δημιουργεί ένα στιγμιότυπο του όρου
• Για παράδειγμα σ(g(X,Y)) = g(a,f(a,b))
• Ο όρος g(a,f(a,b)) είναι στιγμιότυπο του g(X,Y)

Περισσότερη Prolog 3
Ενοποίηση

• Δύο όροι της Prolog t1 και t2 ενοποιούνται εάν υπάρχει


κάποια αντικατάσταση σ (ο ενοποιητής τους) που κάνει
τους δύο όρους ακριβώς τους ίδιους: σ(t1) = σ(t2)
– Οι όροι a και b δεν ενοποιούνται
– Οι όροι f(X,b) και f(a,Y) ενοποιούνται: ένας ενοποιητής
είναι ο {X → a, Y → b}
– Οι όροι f(X,b) και g(Y,b) δεν ενοποιούνται
– Οι όροι a(X,X,b) και a(b,Y,Y) ενοποιούνται: ένας
ενοποιητής είναι ο {X → b, Y → b}
– Οι όροι a(X,X,b) και a(c,Y,Y) δεν ενοποιούνται
– Οι όροι a(X,f) και a(Y,f) ενοποιούνται: ένας ενοποιητής
είναι ο {X → 42, Y → 42}

Περισσότερη Prolog 4

Πολλαπλοί ενοποιητές

• parent(X,Y) και parent(fred,Z):


– Ένας ενοποιητής είναι ο σ1 = {X → fred, Y → Z}
– Άλλος ένας είναι ο σ2 = {X → fred, Y → mary, Z → mary}
– Άλλος ένας είναι ο σ3 = {X → fred, Y → foo(42), Z → foo(42)}

• Η Prolog επιλέγει ενοποιητές όπως ο σ1 οι οποίοι


καθορίζουν ακριβώς τις αντικαταστάσεις που είναι
αναγκαίες για την ενοποίηση των δύο όρων
• Με άλλα λόγια, η Prolog επιλέγει τον πιο γενικό
ενοποιητή των δύο όρων (MGU — Most General Unifier)

Περισσότερη Prolog 5
Ο πιο γενικός ενοποιητής (ΠΓΕ)

• Ο όρος t1 είναι πιο γενικός από τον όρο t2 εάν ο t2 είναι


στιγμιότυπο του t1 αλλά ο t1 δεν είναι στιγμιότυπο του t2
– Παράδειγμα: ο όρος parent(fred,Y) είναι πιο γενικός από τον
όρο parent(fred,mary)

• Ένας ενοποιητής σ1 δύο όρων t1 και t2 είναι ο πιο γενικός


ενοποιητής εάν δεν υπάρχει άλλος ενοποιητής σ2 τέτοιος
ώστε ο όρος σ2(t1) να είναι πιο γενικός από τον όρο σ1(t1)
• Μπορεί να αποδειχθεί ότι ο πιο γενικός ενοποιητής είναι
μοναδικός (αν αγνοήσουμε τα ονόματα των μεταβλητών)

Περισσότερη Prolog 6

Η Prolog χρησιμοποιεί ενοποίηση για τα πάντα

• Πέρασμα παραμέτρων
– reverse([1,2,3],X)

• Δέσιμο μεταβλητών
– X = 0

• Κατασκευή δεδομένων
– X = .(1,[2,3])

• Επιλογή δεδομένων
– [1,2,3] = .(X,Y)

Περισσότερη Prolog 7
Έλεγχος εμφάνισης (Occurs check)

• Μια μεταβλητή X και ένας όρος t ενοποιούνται με την


αντικατάσταση {X → t }:
– X και b ενοποιούνται: ο ΠΓΕ είναι {X → b}
– X και f(a,g(b)) ενοποιούνται: ο ΠΓΕ είναι {X → f(a,g(b))}
– X και f(a,Y) ενοποιούνται: ο ΠΓΕ είναι {X → f(a,Y)}
• Εκτός εάν η μεταβλητή X περιλαμβάνεται στον όρο t:
– Οι όροι X και f(a,g(X)) δεν ενοποιούνται: η αντικατάσταση
{X → f(a,g(X))} δεν είναι ενοποιητής

• Με άλλα λόγια, τουλάχιστον στη θεωρία, η ενοποίηση


πρέπει να είναι καλά θεμελιωμένη (well-founded)

Περισσότερη Prolog 8

Ενοποίηση χωρίς έλεγχο εμφάνισης

• Η default ενοποίηση της Prolog δεν περιλαμβάνει έλεγχο


εμφάνισης
append([], L, L).
append([H|L1], L2, [H|L3]) :-
append(L1, L2, L3).

?- append([], X, [a | X]).

X = [a, a, a, a, a, a, a, a, a|...] ;

No

• Αλλά το ISO Prolog standard περιλαμβάνει ένα


κατηγόρημα με όνομα unify_with_occurs_check/2

Περισσότερη Prolog 9
Μοντέλα Εκτέλεσης της Prolog

Περισσότερη Prolog 10

Το διαδικαστικό μοντέλο εκτέλεσης της Prolog

• Κάθε κατηγόρημα είναι μια διαδικασία για την απόδειξη


στόχων
– p :- q, r.
• Για την απόδειξη του στόχου p, πρώτα ενοποίησε το στόχο
με την κεφαλή του κανόνα p, μετά απόδειξε το q, και μετά
απόδειξε το r
– s.
• Για την απόδειξη του στόχου s, ενοποίησε το στόχο με το s

• Η κάθε πρόταση (γεγονός ή κανόνας) αποτελεί ένα


διαφορετικό τρόπο απόδειξης του στόχου
• Η απόδειξη μπορεί να περιλαμβάνει κλήσεις σε άλλα
κατηγορήματα-διαδικασίες
Περισσότερη Prolog 11
Παραδείγματα

p :- q, r. boolean p() {return q() && r();}


q :- s. boolean q() {return s();}
r :- s. boolean r() {return s();}
s. boolean s() {return true;}

p :- p. boolean p() {return p();}

Περισσότερη Prolog 12

Οπισθοδρόμηση (Backtracking)

• Μια περιπλοκή: οπισθοδρόμηση


• Η Prolog κρατάει μια λίστα με όλους τους δυνατούς
τρόπους με τους οποίους μπορεί να ικανοποιηθεί
κάποιος στόχος και τους δοκιμάζει με τη σειρά μέχρι να
ικανοποιήσει πλήρως το στόχο ή μέχρι να εξαντλήσει
όλες τις δυνατότητες ικανοποίησής του
• Έστω ο στόχος p στο παρακάτω πρόγραμμα: ο στόχος
ικανοποιείται αλλά μόνο με χρήση οπισθοδρόμησης
1. p :- q, r.
2. q :- s.
3. q.
4. r.
Περισσότερη Prolog
5. s :- 0 = 1. 13
Αντικατάσταση

• Άλλη μια περιπλοκή: αντικατάσταση μεταβλητών


• Μια κρυμμένη ροή πληροφορίας Η αντικατάσταση σ3 προκύπτει
από την απόδειξη του όρου
σ2(σ1(r(Y)))
Η αντικατάσταση σ1 = MGU(p(f(Y)),t)
Η σύνθεση των αντικαταστάσεων
εφαρμόζεται σε όλες τις μεθεπόμενες επιστρέφεται στον καλούντα
συνθήκες προς ικανοποίηση της πρότασης
Ο όρος που αποδείχθηκε είναι ο
σ3(σ2(σ1(t)))
p(f(Y)) :- q(Y) , r(Y) .

Ο αρχικός
όρος προς
Το ίδιο συμβαίνει και με την
απόδειξη t
αντικατάσταση σ2 η οποία
προκύπτει από την διαδικασία
απόδειξης του στόχου σ1(q(Y))
Περισσότερη Prolog 14

Το μοντέλο υλοποίησης: Επίλυση (Resolution)

• Το βασικό βήμα συμπερασμού


• Κάθε κανόνας αναπαριστάται με μια λίστα από όρους
(κάθε γεγονός αναπαριστάται από λίστα ενός στοιχείου)
• Κάθε βήμα επίλυσης χρησιμοποιεί μια από αυτές τις
λίστες, μια φορά, για να επιτύχει κάποια πρόοδο στην
απόδειξη μιας λίστας από όρους που είναι στόχοι προς
απόδειξη για την απάντηση κάποιας ερώτησης
function resolution(clause, goals):
let sub = the MGU of head(clause) and head(goals)
return sub(body(clause) concatenated with tail(goals))

Περισσότερη Prolog 15
Παράδειγμα επίλυσης

Για την παρακάτω λίστα όρων προς απόδειξη:


[p(X),s(X)]
και τον κανόνα:
p(f(Y)) :- q(Y), r(Y).
Ο ΠΓΕ είναι ο {X → f(Y)}, και
στο επόμενο βήμα προκύπτει η
λίστα αποτελούμενη από τους όρους:
resolution([p(f(Y)),q(Y),r(Y)], [p(X),s(X)])
= [q(Y),r(Y),s(f(Y))]

function resolution(clause, goals):


let sub = the MGU of head(clause) and head(goals)
return sub(body(clause) concatenated with tail(goals))

Περισσότερη Prolog 16

Ένας διερμηνέας της Prolog

function solve(goals)
if goals is empty then succeed()
else for each clause c in the program, in order
if head(c) does not unify with head(goals) then do nothing
else solve(resolution(c, goals))

Περισσότερη Prolog 17
Παράδειγμα

Μια μερική επίλυση του p(X):


1. p(f(Y)) :- solve([p(X)])
q(Y),r(Y). 1. solve([q(Y),r(Y)])
2. q(g(Z)). …
3. q(h(Z)). 2. nothing
4. r(h(a)). 3. nothing
4. nothing

• Η συνάρτηση solve δοκιμάζει τις τέσσερις προτάσεις


τη μία μετά την άλλη
– Η πρώτη πρόταση ταιριάζει, και η συνάρτηση solve καλεί τον
εαυτό της αναδρομικά με το αποτέλεσμα της διάλυσης
– Οι άλλες τρεις προτάσεις δεν ταιριάζουν: οι κεφαλές τους δεν
ενοποιούνται με τον όρο της λίστας

Περισσότερη Prolog 18

Παραδείγματος συνέχεια

Μια μερική επίλυση του p(X), επεκταμένη:


1. p(f(Y)) :- solve([p(X)])
q(Y),r(Y). 1. solve([q(Y),r(Y)])
2. q(g(Z)). 1. nothing
3. q(h(Z)). 2. solve([r(g(Z))])
4. r(h(a)). …
3. solve([r(h(Z))])

4. nothing
2. nothing
3. nothing
4. nothing

Περισσότερη Prolog 19
Το παράδειγμα ολοκληρωμένο

Η ολική επίλυση της ερώτησης p(X):


1. p(f(Y)) :- solve([p(X)])
q(Y),r(Y). 1. solve([q(Y),r(Y)])
2. q(g(Z)). 1. nothing
3. q(h(Z)). 2. solve([r(g(Z))])
4. r(h(a)). 1. nothing
2. nothing
3. nothing
4. nothing
3. solve([r(h(Z))])
1. nothing
2. nothing
3. nothing
4. solve([]) —success!
4. nothing
2. nothing
3. nothing
4. nothing
Περισσότερη Prolog 20

Συλλογή των αντικαταστάσεων

function resolution(clause, goals, query):


let sub = the MGU of head(clause) and head(goals)
return (sub(tail(clause) concatenated with tail(goals)), sub(query))
function solve(goals, query)
if goals is empty then succeed(query)
else for each clause c in the program, in order
if head(c) does not unify with head(goals) then do nothing
else solve(resolution(c, goals, query))

• Τροποποιημένη συνάρτηση που δέχεται ως όρισμα την


αρχική ερώτηση και εφαρμόζει όλες τις αντικαταστάσεις
πάνω της
• Στο τέλος της επίλυσης, το αποδειχθέν στιγμιότυπο
περνιέται ως όρισμα στη συνάρτηση succeed
Περισσότερη Prolog 21
Η ολική επίλυση της ερώτησης p(X):
1. p(f(Y)) :- solve([p(X)],p(X))
q(Y),r(Y). 1. solve([q(Y),r(Y)],p(f(Y)))
2. q(g(Z)). 1. nothing
3. q(h(Z)). 2. solve([r(g(Z))],p(f(g(Z))))
4. r(h(a)). 1. nothing
2. nothing
3. nothing
4. nothing
3. solve([r(h(Z))],p(f(h(Z))))
1. nothing
2. nothing
3. nothing
4. solve([],p(f(h(a))))
4. nothing
2. nothing
3. nothing
4. nothing

Περισσότερη Prolog 22

Διερμηνείς της Prolog

• Ο διερμηνέας που μόλις είδαμε είναι λειτουργικός αλλά


δεν χρησιμοποιείται από τις υλοποιήσεις της Prolog
• Όλες οι υλοποιήσεις της Prolog πρέπει μεν να
λειτουργήσουν με τον τρόπο που μόλις περιγράψαμε
αλλά συνήθως χρησιμοποιούν εντελώς διαφορετικές
τεχνικές υλοποίησης: μεταφράζουν τον κώδικα σε
γλώσσα κάποιας αφηρημένης μηχανής
• Η πιο συνηθισμένη τέτοια μηχανή είναι η Αφηρημένη
Μηχανή του Warren (Warren Abstract Machine)

Περισσότερη Prolog 23
Το αφηρημένο μοντέλο: Δένδρα απόδειξης

• Θέλουμε να αναπαραστήσουμε με κάποιο τρόπο τη σειρά


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

Περισσότερη Prolog 24

Παράδειγμα

solve [p(X)]

solve [q(Y),r(Y)] nothing nothing nothing

nothing solve [r(g(Z))] solve [r(h(Z))] nothing

nothing nothing nothing nothing

nothing nothing nothing solve []

Περισσότερη Prolog 25
Απλοποίηση των δένδρων απόδειξης

• Τα παιδιά κάθε κόμβου αντιστοιχούν στις προτάσεις του


προγράμματος
• Και η σειρά τους είναι ίδια με τη σειρά εμφάνισής τους
στο πρόγραμμα
• Μπορούμε να απλοποιήσουμε το δένδρο με το να
απαλείψουμε όλους τους nothing κόμβους
• Οι κόμβοι αυτοί αντιστοιχούν σε προτάσεις οι οποίες δεν
ενοποιούνται με το πρώτο στοιχείο της λίστας του
κόμβου πατέρα

Περισσότερη Prolog 26

Παράδειγμα απλοποιημένου δένδρου απόδειξης

solve [p(X)]

solve [q(Y),r(Y)]

solve [r(g(Z))] solve [r(h(Z))]

solve []

Περισσότερη Prolog 27
Σημασιολογία της Prolog

• Δοθέντος ενός προγράμματος και μιας ερώτησης, ένα


σύστημα Prolog πρέπει να λειτουργήσει με τρόπο που
καθορίζεται από μια πρώτα κατά βάθος, αριστερά προς
τα δεξιά διάσχιση (depth-first, left-to-right traversal) του
δένδρου απόδειξης
• Ένας τρόπος υλοποίησης είναι μέσω κάποιου διερμηνέα
όπως αυτός ορίζεται από τη συνάρτηση solve
• Στην πράξη συνήθως χρησιμοποιούνται άλλοι, πιο
αποδοτικοί, τρόποι υλοποίησης

Περισσότερη Prolog 28

Κατηγορήματα εισόδου και εξόδου

• Η είσοδος και η έξοδος όρων γίνεται σχετικά απλά


?- write('Hello world').
Hello world

Yes
?- read(X).
|: hello(world).

X = hello(world) ;

No
• Οποιοσδήποτε όρος της Prolog μπορεί να διαβαστεί
• Υπάρχει επίσης το κατηγόρημα nl/0 το οποίο είναι
ισοδύναμο με την κλήση write('\n')
Περισσότερη Prolog 29
Το κατηγόρημα assert/1

• Προσθέτει ένα γεγονός ή έναν κανόνα στην εσωτερική


βάση δεδομένων της Prolog (στο τέλος της βάσης)
?- dynamic parent/2.

Yes
?- parent(X,Y).

No
?- assert(parent(joe,mary)).

Yes
?- parent(X,Y).

X = joe
Y = mary ;

No
Περισσότερη Prolog 30

Το κατηγόρημα retract/1

• Απομακρύνει την πρώτη πρόταση (κανόνα ή γεγονός) που


ενοποιείται με το όρισμά του από τη βάση των δεδομένων
?- parent(joe, mary).

Yes
?- retract(parent(joe, mary)).

Yes
?- parent(joe, mary).

No

• Υπάρχει επίσης και το κατηγόρημα retractall/1 το


οποίο απομακρύνει όλες τις προτάσεις που ενοποιούνται
με το όρισμά του
Περισσότερη Prolog 31
Τα κατηγορήματα listing/0 και listing/1

• Τυπώνουν την εσωτερική βάση δεδομένων της Prolog:


– όλη (listing/0) ή
– μόνο κάποιο συγκεκριμένο κατηγόρημα (listing/1)

?- dynamic parent/2.

Yes
?- assert(parent(joe,mary)).

Yes
?- listing(parent/2).
:- dynamic parent/2.

parent(joe, mary).

Yes

Περισσότερη Prolog 32

Μη αποτιμημένοι όροι

• Οι τελεστές της Prolog επιτρέπουν πιο συμπυκνωμένη


γραφή των όρων, αλλά οι όροι δεν αποτιμώνται
• Όλες οι παρακάτω γραφές είναι ο ίδιος όρος Prolog:
1+ *(2,3)
+(1,2*3)
(1+(2*3))
1+2*3
• Ο παραπάνω όρος δεν ενοποιείται με τον όρο 7

Περισσότερη Prolog 33
Αποτίμηση αριθμητικών εκφράσεων

?- X is 1+2*3.

X = 7 ;

No

• Το προκαθορισμένο κατηγόρημα is/2 μπορεί να


χρησιμοποιηθεί για την αποτίμηση ενός όρου που είναι
μια αριθμητική έκφραση
• is(X,Y) αποτιμά τον όρο Y και ενοποιεί τον όρο X με το
αποτέλεσμα της αποτίμησης
• Συνήθως χρησιμοποιείται ως δυαδικός τελεστής
Περισσότερη Prolog 34

Η αποτίμηση προϋποθέτει τιμές…

?- Y = X+2, X = 1.

Y = 1+2
X = 1 ;

No
?- X = 1, Y is X+2.

X = 1
Y = 3 ;

No
?- Y is X+2, X = 1.

ERROR: Arguments are not sufficiently instantiated

Περισσότερη Prolog 35
Αριθμητικές εκφράσεις και συναρτήσεις

• Σε έναν όρο X is Y, οι υπό-όροι του Y πρέπει να είναι


αριθμοί ή αποτιμώμενες συναρτήσεις
• Οι συναρτήσεις αυτές περιλαμβάνουν τους
προκαθορισμένους αριθμητικούς τελεστές +, -, * και /
• Όπως και προκαθορισμένες αριθμητικές συναρτήσεις,
π.χ. abs(Χ), sqrt(Χ), floor(Χ), …

Περισσότερη Prolog 36

Πραγματικές και ακέραιες τιμές

?- X is 1/2. Δύο αριθμητικοί τύποι:


X = 0.5 ; ακέραιοι και πραγματικοί.
No Οι περισσότερες αριθμητικές
?- X is 4.0/2.0. συναρτήσεις είναι
X = 2.0 ; υπερφορτωμένες για όλους
No τους συνδυασμούς ορισμάτων.
?- X is 5 // 2.
Η Prolog έχει δυναμικούς
X = 2 ; τύπους – οι τύποι
No χρησιμοποιούνται κατά το
?- X is 4.0/2. χρόνο εκτέλεσης για την
X = 2.0 ; επίλυση της υπερφόρτωσης.
No Παρατηρείστε όμως ότι ο
στόχος 2 = 2.0 αποτυγχάνει.
Περισσότερη Prolog 37
Αριθμητικές συγκρίσεις

• Τελεστές αριθμητικής σύγκρισης:


<, >, =<, >=, =:=, =\=
• Σε μια αριθμητική σύγκριση, η Prolog αποτιμά και τα δύο
ορίσματα του τελεστή και συγκρίνει τις τιμές που
προκύπτουν
• Άρα και τα δύο ορίσματα πρέπει να έχουν τιμές για όλες
τις μεταβλητές τους

Περισσότερη Prolog 38

Αριθμητικές συγκρίσεις

?- 1+2 < 1*2.

No
?- 1 < 2.0.

Yes
?- 1+2 >= 1+3.

No
?- X is 1-3, Y is 0-2.0, X =:= Y.

X = -2
Y = -2.0 ;

No

Περισσότερη Prolog 39
Συγκρίσεις ισότητας στην Prolog

• Μέχρι στιγμής έχουμε χρησιμοποιήσει τρεις


διαφορετικούς τελεστές σύγκρισης ισότητας:
X is Y αποτιμά τον όρο Y και ενοποιεί το αποτέλεσμα με το X
π.χ. 3 is 1+2 επιτυγχάνει, αλλά 1+2 is 3 αποτυγχάνει
X = Y ενοποιεί τους όρους X και Y, αλλά δεν τους αποτιμά
π.χ. τόσο ο στόχος 3 = 1+2 όσο και ο 1+2 = 3 αποτυγχάνουν
X =:= Y αποτιμά τους δύο όρους και τους συγκρίνει αριθμητικά
π.χ. τόσο ο στόχος 3 =:= 1+2 και ο 1+2 =:= 3 επιτυγχάνουν

Περισσότερη Prolog 40

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

mylength1([],0). mylength2([],0).
mylength1([_|T], Len) :- mylength2([_|T], Len) :-
mylength1(T, LenT), mylength2(T, LenT),
Len = LenT + 1. Len is LenT + 1.

?- mylength1([a,b,c],X). ?- mylength2([a,b,c],X).

X = 0+1+1+1 ; X = 3 ;

No No
?- mylength2(X,3).

X = [_, _, _] ;

No

Περισσότερη Prolog 41
Παράδειγμα: sum

sum([], 0).
sum([Head|Tail],Sum) :-
sum(Tail,TailSum),
Sum is Head + TailSum.

?- sum([1, 2, 3], X).

X = 6 ;

No
?- sum([1, 2.5, 3], X).

X = 6.5 ;

No

Περισσότερη Prolog 42

Παράδειγμα: gcd

gcd(X,Y,GCD) :- Προσοχή: όχι απλά


X =:= Y,
GCD is X. gcd(X,X,X)
gcd(X,Y,GCD) :-
X < Y,
NewY is Y - X,
gcd(X,NewY,GCD).
gcd(X,Y,GCD) :-
X > Y,
NewX is X - Y,
gcd(NewX,Y,GCD).

Περισσότερη Prolog 43
Παράδειγμα: χρήση του κατηγορήματος gcd

?- gcd(5,5,X).
X = 5 ;
No
?- gcd(12,21,X).
X = 3 ;
No
?- gcd(91,105,X).
X = 7 ;
No
?- gcd(91,21*5,7).
Yes
?- gcd(91,X,7).
ERROR: Arguments are not sufficiently instantiated

Περισσότερη Prolog 44

Παράδειγμα: factorial

factorial(X,Fact) :-
X =:= 1, Fact is 1.
factorial(X,Fact) :-
X > 1,
NewX is X - 1,
factorial(NewX,NF),
Fact is X * NF.

?- factorial(5,F).
F = 120 ;
No
?- factorial(2*5,F).
F = 3628800 ;
No
?- factorial(-2,F).
No
Περισσότερη Prolog 45
Διάζευξη και if-then-else στην Prolog

• Ο δυαδικός τελεστής ;/2 υλοποιεί τη διάζευξη (or)


• Ο ορισμός του είναι:
';'(X, _) :- X.
';'(_, Y) :- Y.

• Το if-then-else γράφεται με χρήση διάζευξης και του


δυαδικού τελεστή ->/2
• Ο ορισμός του είναι:
(Cond -> Then ; _) :- Cond, Then.
(Cond -> _ ; Else) :- \+ Cond, Else.

Περισσότερη Prolog 46

Παράδειγμα χρήσης διάζευξης και if-then-else

factorial(X,Fact) :- factorial(X,Fact) :-
X =:= 1, Fact is 1. ( X =:= 1, Fact is 1
factorial(X,Fact) :- ; X > 1,
X > 1, ⇔ NewX is X - 1,
NewX is X - 1, factorial(NewX,NF),
factorial(NewX,NF), Fact is X * NF
Fact is X * NF. ).

factorial(X,Fact) :-
( X =:= 1 -> Fact is 1
; X > 1,
NewX is X - 1,
factorial(NewX,NF),
Fact is X * NF
).

Περισσότερη Prolog 47
Παράδειγμα χρήσης if-then-else

gcd(X,Y,GCD) :- gcd(X,Y,GCD) :-
X =:= Y, ( X =:= Y ->
GCD is X. GCD is X
gcd(X,Y,GCD) :- ⇔ ; X < Y ->
X < Y, NewY is Y - X,
NewY is Y - X, gcd(X,NewY,GCD)
gcd(X,NewY,GCD). ; X > Y ->
gcd(X,Y,GCD) :- NewX is X - Y,
X > Y, gcd(NewX,Y,GCD)
NewX is X - Y, ).
gcd(NewX,Y,GCD).
Από τον παραπάνω κώδικα
μπορούμε να παραλείψουμε
τον έλεγχο της συνθήκης
X > Y (και φυσικά το ->)

Περισσότερη Prolog 48

Συμπεριφορά του if-then χωρίς το else

Προσοχή: Ένα if-then χωρίς το else κομμάτι θα αποτύχει


εάν η συνθήκη του if δεν είναι αληθής

• Αν θέλουμε να συνεχιστεί η εκτέλεση με τους στόχους


μετά το if-then, πρέπει να χρησιμοποιήσουμε στο else
κομμάτι το κατηγόρημα true/0

Περισσότερη Prolog 49
Δύο Παραδείγματα Προγραμμάτων

Περισσότερη Prolog 50

Προβλήματα αναζήτησης λύσης

• Η Prolog δε σχεδιάστηκε για προγραμματισμό


αριθμητικών εφαρμογών (προφανώς)
• Τα προβλήματα στα οποία η Prolog δείχνει τις
ικανότητές της είναι προβλήματα που χρησιμοποιούν
αναζήτηση σε ένα χώρο λύσεων και όπου
– Προσδιορίζουμε έναν ορισμό της λύσης με χρήση λογικής και
– Αφήνουμε την Prolog να βρει αυτή τη λύση

• Θα εξετάσουμε τις λύσεις δύο προβλημάτων:


– Το πρόβλημα του σακιδίου
– Το πρόβλημα των οκτώ βασιλισσών

Περισσότερη Prolog 51
Το πρόβλημα του σακιδίου (Knapsack problem)

• Ετοιμάζουμε το σακίδιό μας για μια εκδρομή


• Στα ντουλάπια υπάρχουν τα εξής τρόφιμα:
Item Weight in kilograms Calories
bread 4 9200
pasta 2 4600
peanut butter 1 6700
baby food 3 6900

• Το σακίδιο χωράει πράγματα συνολικού βάρους 4 kg.


• Ποια επιλογή πραγμάτων βάρους ≤ 4 kg. μεγιστοποιεί το
ποσό των θερμίδων;

Περισσότερη Prolog 52

Οι άπληστες μέθοδοι δε δουλεύουν

Item Weight in kilograms Calories


bread 4 9200
pasta 2 4600
peanut butter 1 6700
baby food 3 6900

• Πρώτα τις περισσότερες θερμίδες: bread = 9200


• Πρώτα τα πιο ελαφριά: peanut butter + pasta = 11300
• (Η βέλτιστη επιλογή: peanut butter + baby food = 13600)

Περισσότερη Prolog 53
Αναζήτηση

• Δεν υπάρχει γνωστός αλγόριθμος για το συγκεκριμένο


πρόβλημα που
– Πάντα δίνει τη βέλτιστη λύση για το πρόβλημα, και
– Χρειάζεται λιγότερο από εκθετικό χρόνο για να τη βρει

• Κατά συνέπεια, δε θα πρέπει να ντρεπόμαστε αν


χρησιμοποιήσουμε εξαντλητική αναζήτηση
• Το αντίθετο μάλιστα, επειδή η αναζήτηση είναι κάτι που
η Prolog κάνει καλά

Περισσότερη Prolog 54

Αναπαράσταση

• Θα αναπαραστήσουμε κάθε είδος φαγητού με τον όρο


food(N,W,C)
• Το ντουλάπι του παραδείγματός μας αναπαριστάται ως
μια λίστα από τέτοιους όρους:
[food(bread,4,9200),
food(pasta,2,4500),
food(peanutButter,1,6700),
food(babyFood,3,6900)]
• Θα χρησιμοποιήσουμε την ίδια αναπαράσταση για τα
περιεχόμενα του σακιδίου

Περισσότερη Prolog 55
Ακολουθίες και υπακολουθίες

/*
subseq(X, Y) succeeds when list X is the same as
list Y, but with zero or more elements omitted.
This can be used with any pattern of instantiations.
*/
subseq([], []).
subseq([Item | RestX], [Item | RestY]) :-
subseq(RestX, RestY).
subseq(X, [_ | RestY]) :-
subseq(X, RestY).

• Μια υπακολουθία μιας λίστας είναι ένα αντίγραφο της


λίστας όπου κάποια στοιχεία της λίστας έχουν
παραλειφθεί – στο παράδειγμά μας τα περιεχόμενα του
σακιδίου είναι μια υπακολουθία του ντουλαπιού
Περισσότερη Prolog 56

Κάποια παραδείγματα
?- subseq([1,3],[1,2,3,4]).

Yes
?- subseq(X,[1,2,3]).

X = [1, 2, 3] ;
X = [1, 2] ;
Παρατηρείστε ότι το κατηγόρημα
X = [1, 3] ; subseq/2 όχι μόνο μπορεί να
X = [1] ; ελέγξει κατά πόσο μια λίστα είναι
μια υπακολουθία κάποιας άλλης
X = [2, 3] ; αλλά μπορεί επίσης να παραγάγει
X = [2] ; υπακολουθίες, τις οποίες και θα
χρησιμοποιήσουμε για τη λύση του
X = [3] ; προβλήματος του σακιδίου.
X = [] ;
No
Περισσότερη Prolog 57
/*
knapsackDecision(Items, Capacity, Goal, Knapsack)
takes a list Items of food terms, a positive number
Capacity, and a positive number Goal. We unify
Knapsack with a subsequence of Items representing
a knapsack with total calories >= Goal, subject to
the constraint that the total weight is =< Capacity.
*/
knapsackDecision(Items, Capacity, Goal, Knapsack) :-
subseq(Knapsack, Items),
weight(Knapsack, Weight),
Weight =< Capacity,
calories(Knapsack, Calories),
Calories >= Goal.

Περισσότερη Prolog 58

Για να δούμε που βρισκόμαστε…


?- knapsackDecision(
| [food(bread,4,9200),
| food(pasta,2,4500),
| food(peanutButter,1,6700),
| food(babyFood,3,6900)],
| 4,
| 10000,
| X).

X = [food(pasta,2,4500), food(peanutButter,1,6700)]

Yes

• Η παραπάνω κλήση αποφασίζει εάν υπάρχει λύση που να


ικανοποιεί το στόχο των ελάχιστων θερμίδων (10000)
• Όχι όμως υποχρεωτικά τη λύση που ζητάμε εμείς…
Περισσότερη Prolog 59
Αποφασισιμότητα και βελτιστοποίηση

• Μέχρι στιγμής λύσαμε το πρόβλημα της απόφασης για


το πρόβλημα του σακιδίου
• Αυτό που θέλουμε να λύσουμε είναι το πρόβλημα της
βελτιστοποίησης της λύσης του συγκεκριμένου
προβλήματος
• Για να το επιτύχουμε, θα χρησιμοποιήσουμε ένα άλλο
προκαθορισμένο κατηγόρημα της Prolog: findall/3

Περισσότερη Prolog 60

Το κατηγόρημα findall/3

• findall(X, Goal, L)
– Βρίσκει όλους τους τρόπους με τους οποίους ο στόχος Goal
ικανοποιείται
– Για τον καθένα από αυτούς, εφαρμόζει στον όρο X την ίδια
αντικατάσταση με την οποία ο στόχος Goal ικανοποιήθηκε
– Ενοποιεί τη λίστα L με τη λίστα όλων αυτών των X
?- findall(42, subseq(_,[1,2]), L).

L = [42, 42, 42, 42] ;

No

• Υπάρχουν τέσσερις λύσεις για το subseq(_,[1,2])


• Συλλέξαμε μια λίστα από 42, ένα για κάθε λύση

Περισσότερη Prolog 61
Συλλογή των απαντήσεων που θέλουμε

• Συνήθως, η πρώτη παράμετρος του findall/3 είναι


ένας όρος με τις μεταβλητές που υπάρχουν στο στόχο
προς επίλυση
?- findall(X, subseq(X,[1,2]), L).

X = _G396
L = [[1, 2], [1], [2], []] ;

No

• Η παραπάνω ερώτηση συλλέγει όλες τις απαντήσεις της


ερώτησης-στόχου subseq(X,[1,2])

Περισσότερη Prolog 62

/*
knapsackOptimization(Items,Capacity,Knapsack) takes
a list Items of food items and a positive integer
Capacity. We unify Knapsack with a subsequence of
Items representing a knapsack of maximum total
calories, subject to the constraint that the total
weight is =< Capacity.
*/
knapsackOptimization(Items, Capacity, Knapsack) :-
findall(K, legalKnapsack(Items,Capacity,K), L),
maxCalories(L, Knapsack).

Περισσότερη Prolog 63
/*
legalKnapsack(Items,Capacity,Knapsack) takes a list
Items of food terms and a positive number Capacity.
We unify Knapsack with a subsequence of Items whose
total weight is =< Capacity.
*/
legalKnapsack(Items, Capacity, Knapsack):-
subseq(Knapsack, Items),
weight(Knapsack, W),
W =< Capacity.

Περισσότερη Prolog 64

/*
maxCalories(List, Result) takes a non-empty List of
lists of food terms. We unify Result with an element
from the list that maximizes the total calories.
We use a helper predicate maxC that takes four
parameters: the remaining list of lists of food
terms, the best list of food terms seen so far, its
total calories, and the final result.
*/
maxCalories([First | Rest], Result) :-
calories(First, FirstC),
maxC(Rest, First, FirstC, Result).
maxC([], Sofar, _, Sofar).
maxC([First | Rest], _, MC, Result) :-
calories(First, FirstC),
MC =< FirstC,
maxC(Rest, First, FirstC, Result).
maxC([First | Rest], Sofar, MC, Result) :-
calories(First, FirstC),
MC > FirstC,
maxC(Rest, Sofar, MC, Result).

Περισσότερη Prolog 65
/*
maxCalories(List, Result) takes a non-empty List of
lists of food terms. We unify Result with an element
from the list that maximizes the total calories.
We use a helper predicate maxC that takes four
parameters: the remaining list of lists of food
terms, the best list of food terms seen so far, its
total calories, and the final result.
*/
maxCalories([First | Rest], Result) :-
calories(First, FirstC),
maxC(Rest, First, FirstC, Result).
maxC([], Sofar, _, Sofar).
maxC([First | Rest], Sofar, MC, Result) :-
calories(First, FirstC),
( MC =< FirstC ->
NSofar = First, NMC = FirstC Το ίδιο με χρήση
;
NSofar = Sofar, NMC = MC
if-then-else
),
maxC(Rest, NSofar, NMC, Result).

Περισσότερη Prolog 66

?- knapsackOptimization(
| [food(bread,4,9200),
| food(pasta,2,4500),
| food(peanutButter,1,6700),
| food(babyFood,3,6900)],
| 4,
| Knapsack).

Knapsack = [food(peanutButter,1,6700),
food(babyFood,3,6900)] ;

Νο

Περισσότερη Prolog 67
Το πρόβλημα των οκτώ βασιλισσών (8-queens)

• Κάποιες πληροφορίες για το σκάκι:


– Παίζεται σ’ ένα τετράγωνο 8×8
– Μια βασίλισσα μπορεί να κινηθεί οριζόντια, κάθετα ή διαγώνια
για όσα τετράγωνα θελήσει
– Δύο βασίλισσες απειλούν η μία την άλλη εάν είναι στην ίδια
γραμμή, στήλη ή διαγώνιο (και η μία μπορεί να κινηθεί άμεσα στο
τετράγωνο της άλλης)

• Το πρόβλημα: βρες ένα τρόπο να


τοποθετηθούν οκτώ βασίλισσες
σε μια κενή σκακιέρα έτσι ώστε
καμία βασίλισσα να μην
απειλείται από κάποια άλλη

Περισσότερη Prolog 68

Αναπαράσταση

• Θα μπορούσαμε να αναπαραστήσουμε τη βασίλισσα στη


στήλη 2, σειρά 5 με τον όρο queen(2,5)
• Αλλά αφού δεν υπάρχουν άλλα κομμάτια στη σκακιέρα—
π.χ. δε θα έχουμε κάποιο pawn(X,Y) ή king(X,Y)—θα
χρησιμοποιήσουμε απλώς έναν όρο της μορφής X/Y
• (Δε θα τον αποτιμήσουμε ως διαίρεση)

Περισσότερη Prolog 69
Παράδειγμα

• Ένας σχηματισμός σκακιέρας είναι μια λίστα από


βασίλισσες
• Ο παρακάτω είναι για τις βασίλισσες [2/5,3/7,6/1]

7 Q

6
5 Q

1 Q

1 2 3 4 5 6 7 8
Περισσότερη Prolog 70

/*
nocheck(L, X/Y) takes a queen X/Y and a list
of queens. It succeeds if and only if the X/Y
queen holds none of the others in check.
*/
nocheck([], _).
nocheck([X1/Y1 | Rest], X/Y) :-
X =\= X1,
Y =\= Y1,
abs(Y1-Y) =\= abs(X1-X),
nocheck(Rest, X/Y).
/*
legal(L) succeeds if L is a legal placement of
queens: all coordinates in range and no queen
in check.
*/
legal([]).
legal([X/Y | Rest]) :-
legal(Rest),
member(X, [1,2,3,4,5,6,7,8]),
member(Y, [1,2,3,4,5,6,7,8]),
Περισσότερη Prolog
nocheck(Rest, X/Y). 71
Επάρκεια

• Έχουμε ήδη αρκετά συστατικά για να λύσουμε το


πρόβλημα: η ερώτηση legal(X) βρίσκει όλους τους
επιτρεπτούς σχηματισμούς:
?- legal(X).

X = [] ;

X = [1/1] ;

X = [1/2] ;

X = [1/3]

Περισσότερη Prolog 72

Λύση για τις οκτώ βασίλισσες

• Φυσικά, η παραπάνω «λύση» θα πάρει πολύ χρόνο: θα


αρχίσει με το να βρει όλες τις 64 λύσεις με μια βασίλισσα,
και μετά θα αρχίσει με όλες τις λύσεις με δύο, κοκ.
• Μπορούμε να «εκβιάσουμε» μια λύση με 8 βασίλισσες με
την ερώτηση: 8 Q
?- X = [_,_,_,_,_,_,_,_], 7 Q

| legal(X). 6 Q

5 Q
X = [8/4, 7/2, 6/7, 5/3, 4 Q
4/6, 3/8, 2/5, 1/1] 3 Q

2 Q
Yes
1 Q

1 2 3 4 5 6 7 8
Περισσότερη Prolog 73
Υπάρχει δυνατότητα για βελτίωση;

• Η εύρεση της λύσης με αυτόν τον τρόπο παίρνει πολύ


χρόνο
• Στη συνέχεια, η Prolog βρίσκει απλές αναδιατάξεις της
πρώτης λύσης:
?- X = [_,_,_,_,_,_,_,_], legal(X).

X = [8/4, 7/2, 6/7, 5/3, 4/6, 3/8, 2/5, 1/1] ;

X = [7/2, 8/4, 6/7, 5/3, 4/6, 3/8, 2/5, 1/1] ;

X = [8/4, 6/7, 7/2, 5/3, 4/6, 3/8, 2/5, 1/1] ;

X = [6/7, 8/4, 7/2, 5/3, 4/6, 3/8, 2/5, 1/1]

Περισσότερη Prolog 74

Βελτίωση του προγράμματος

• Προφανώς κάθε λύση έχει μία βασίλισσα σε κάθε στήλη


• Άρα, κάθε λύση μπορεί να γραφεί με τη μορφή:
X = [1/_,2/_,3/_,4/_,5/_,6/_,7/_,8/_]
• Δίνοντας έναν όρο αυτής της μορφής περιορίζουμε την
αναζήτηση (επιταχύνοντάς την) και αποφεύγουμε την
εύρεση απλών αναδιατάξεων
/* eightqueens(X) succeeds if X is a legal
placement of eight queens, listed in order
of their X coordinates.
*/
eightqueens(X) :-
X = [1/_,2/_,3/_,4/_,5/_,6/_,7/_,8/_],
legal(X).
Περισσότερη Prolog 75
Περισσότερες βελτιώσεις

• Επειδή ξέρουμε ότι όλες οι X-συντεταγμένες είναι εντός


του διαστήματος και διαφορετικές μεταξύ τους, το
πρόγραμμα μπορεί λιγάκι να βελτιωθεί
nocheck([], _).
nocheck([X1/Y1 | Rest], X/Y) :-
% X =\= X1, assume the X's are distinct
Y =\= Y1,
abs(Y1-Y) =\= abs(X1-X),
nocheck(Rest, X/Y).

legal([]).
legal([X/Y | Rest]) :-
legal(Rest),
% member(X, [1,2,3,4,5,6,7,8]), assume X in range
member(Y, [1,2,3,4,5,6,7,8]),
nocheck(Rest, X/Y).
Περισσότερη Prolog 76

Βελτιωμένη λύση στο πρόβλημα

• Το πρόγραμμα τώρα τρέχει αρκετά πιο γρήγορα


• Για παράδειγμα, δε βρίσκει πλέον όλες τις αναδιατάξεις
?- eightqueens(X).

X = [1/4, 2/2, 3/7, 4/3, 5/6, 6/8, 7/5, 8/1] ;

X = [1/5, 2/2, 3/4, 4/7, 5/3, 6/8, 7/6, 8/1]

Περισσότερη Prolog 77
Ένα πείραμα

legal([]).
legal([X/Y | Rest]) :-
legal(Rest),
% member(X, [1,2,3,4,5,6,7,8]), assume X in range
1 =< Y, Y =< 8, % was member(Y,[1,2,3,4,5,6,7,8]),
nocheck(Rest, X/Y).

• Το παραπάνω κατηγόρημα δε δουλεύει


• Εγείρει εξαίρεση: “arguments not sufficiently instantiated”
• Η κλήση του κατηγορήματος member/2 δεν είναι απλώς
κάποιος έλεγχος ότι οι συντεταγμένες είναι εντός ορίων
αλλά είναι παραγωγή των συντεταγμένων

Περισσότερη Prolog 78

Άλλο ένα πείραμα

legal([]).
legal([X/Y | Rest]) :-
% member(X, [1,2,3,4,5,6,7,8]), assume X in range
member(Y, [1,2,3,4,5,6,7,8]),
nocheck(Rest, X/Y),
legal(Rest). % formerly the first condition

• Εγείρει εξαίρεση: “arguments not sufficiently instantiated”


• Η κλήση legal(Rest) πρέπει να προηγείται διότι
παράγει την μερική λύση που χρειάζεται η αναδρομική
κλήση του nocheck

Περισσότερη Prolog 79
Εύρεση μίας μόνο λύσης

• Αν θέλαμε να βρούμε μία μόνο λύση του προβλήματος


θα μπορούσαμε να χρησιμοποιήσουμε οποιοδήποτε από
τους δύο παρακάτω ορισμούς του σχετικού
κατηγορήματος
/* /*
eightqueens1(X) finds eightqueens1(X) finds
one solution to the one solution to the
eight queens problem. eight queens problem.
*/ */
eightqueens1(X) :- eightqueens1(X) :-
once(eightqueens(X)). eightqueens(X),
!.

Περισσότερη Prolog 80

Το cut (!) της Prolog

• Το προκαθορισμένο κατηγόρημα !/0 (διαβάζεται cut)


χρησιμοποιείται για να περιορίσει την οπισθοδρόμηση
• Η εκτέλεσή του πάντα επιτυγχάνει και «κλαδεύει»:
– Όλους τους εναλλακτικούς τρόπους ικανοποίησης των
υποστόχων που πιθανώς να υπάρχουν μεταξύ της κεφαλής
ενός κανόνα και του σημείου που το ! εμφανίζεται στον
κανόνα, και
– Όλους τους κανόνες που μπορεί να έπονται του κανόνα
που περιέχει το !
• Η χρήση του πρέπει να γίνεται με φειδώ και ύστερα από
αρκετή σκέψη

Περισσότερη Prolog 81
Παράδειγμα: Οπισθοδρόμηση χωρίς και με cut

p(X,Y) :- ?- p(X,Y). p(X,Y) :- ?- p(X,Y).


x(X), x(X),
X = 1 X = 1
y(Y). !,
Y = a ; Y = a ;
p(X,Y) :- y(Y).
z(X,Y). X = 1 p(X,Y) :- X = 1
p(4,d). Y = b ; z(X,Y). Y = b ;
p(4,d).
x(1). X = 2 No
x(2). Y = a ; x(1).
x(2).
y(a). X = 2
y(b). Y = b ; y(a).
y(b).
z(3,c). X = 3
Y = c ; z(3,c).
X = 4
Y = d ;
No
Περισσότερη Prolog 82

Μέρη της Prolog που δεν εξετάσαμε

• Τη δυνατότητα ορισμού νέων τελεστών


• Το χειρισμό εξαιρέσεων
– Εξαιρέσεις που εγείρονται από το σύστημα και από το χρήστη
– Τα σχετικά κατηγορήματα throw και catch

• Τις βιβλιοθήκες και πολλά από τα προκαθορισμένα


κατηγορήματα της γλώσσας

Περισσότερη Prolog 83
Σύντομη Εισαγωγή στην SML/NJ
Φυλλάδιο σημειώσεων για το 1ο εργαστήριο του μαθήματος

1 Εγκατάσταση και πρώτη γνωριμία


Η πιο πρόσφατη έκδοση της SML/NJ τη στιγμή συγγραφής αυτού του κειμένου υπάρχει
στο:
http://www.smlnj.org/dist/working/110.69/index.html

Windows
• http://www.smlnj.org/dist/working/110.69/NOTES/WININSTALL
• Start → Programs → SML of New Jersey
• Start → Run, ”cmd”, ”sml”

Unix
• Debian/Ubuntu: apt-get install smlnj smlnj-doc rlwrap
• Εκτέλεση από terminal με sml ή rlwrap sml
• Εναλλακτικά mlton (www.mlton.org)

2 Χρήση του toplevel


 
Standard ML of New Jersey v110 .67 [ built : Sun Oct 19 17:18:14 2008]
- print " Hello world !\ n " ;
Hello world !
val it = () : unit
- 13 + 29;
val it = 42 : int
- ~ it ;

 val it = ~42 : int



Ε: Τί είναι το it;
Α: Μια ειδική μεταβλητή του toplevel που περιέχει πάντα το αποτέλεσμα της τελευταίας
έκφρασης.

Ε: Πως θα αλλάξω την τιμή μιας μεταβλητής;


Α: Στην ML δεν υπάρχει πολλαπλή ανάθεση. Οι μεταβλητές απλά αντιστοιχούν ονόματα
σε τιμές και ο τελεστής = χωρίς χρήση του val ελέγχει ισότητα.
 
- val x = 3;
val x = 3 : int
- x = x + 1;

 val it = false : bool



1
Ε: Πώς θα γράψω ένα loop;
Α: Πολύ εύκολα, όπως σε όλες τις Turing complete γλώσσες!
 
- fun loop x = loop x ;

 val loop = fn : ’a -> ’b



Ε: Χα χα, δεν εννοούσα αυτό. Εννοούσα πως μπορώ να γράψω κάτι σαν τα for loops της
C;
Α: Εξίσου απλά, με χρήση αναδρομής:
 
- fun for 0 = ()
= | for n = for (n -1) ;

 val for = fn : int -> unit



Ε: Χμμμ... θα μου πάρει λίγο καιρό να συνηθίσω αυτό το στυλ προγραμματισμού. Τι στο
καλό είναι αυτό το unit;
Α: Σημαίνει ότι η συνάρτηση αυτή δεν επιστρέφει κάποια τιμή — είναι κάτι σαν το void
στη C. Με άλλα λόγια, η συνάρτηση θα εκτελεστεί μόνο για τις παρενέργειές της.

Ε: Παρενέργειες; Μα η παραπάνω συνάρτηση δεν κάνει πολλά πράγματα...


Α: Δίκιο έχεις. Ας γράψουμε λοιπόν μια συνάρτηση που να τυπώνει τα τετράγωνα όλων
των φυσικών από το 0 μέχρι το n, το κάθε ένα σε διαφορετική γραμμή, ξεκινώντας
από το μεγαλύτερο:
 
- fun fordown n =
= if n < 0 then ()
= else ( print ( Int . toString ( n * n ) ) ; print " \ n " ; fordown (n -1) ) ;
val fordown = fn : int -> unit
- fordown 4;
16
9
4
1
0

 val it = () : unit

Ε: Και αν ήθελα να τυπώσω τα τετράγωνα ξεκινώντας από το 0;
Α: Καλή ερώτηση. Αυτό μπορεί να γίνει με δύο τρόπους. Ο ένας είναι να χρησιμοποιήσεις
μια επιπλέον μεταβλητή για να αρχίσεις την επανάληψη από το μηδέν και μία για να
ξέρεις που πρέπει να σταματήσεις.
 
- fun forup n lim =
= if n > lim then ()
= else ( print ( Int . toString ( n * n ) ) ; print " \ n " ; forup ( n +1) lim ) ;
val forup = fn : int -> int -> unit
- forup 0 4;
0
1
4
9
16

 val it = () : unit

Ε: Και ο άλλος τρόπος;

2
Α: Ο δεύτερος τρόπος είναι να χρησιμοποιήσεις το σχήμα της αρχικής συνάρτησης fordown
αλλά με μια δομή δεδομένων (π.χ. μια λίστα) ως accumulator όπου θα βάζεις τα
τετράγωνα των αριθμών εκεί, όπως αυτά παράγονται.
 
- fun forlst n acc =
= if n < 0 then acc
= else forlst (n -1) (( n * n ) :: acc ) ;
val forlst = fn : int -> int list -> int list
- forlst 4 [];

 val it = [0 ,1 ,4 ,9 ,16] : int list



Ε: Ναι αλλά η παραπάνω συνάρτηση απλώς δημιουργεί τη λίστα, δεν την τυπώνει...
Α: Ε, κάνε και κάτι μόνος σου: γράψε αυτή τη συνάρτηση για εξάσκηση. Α, μια και θα
γράφεις, γράψε και τις παρακάτω συναρτήσεις.
 
fun make_list n =
let fun loop 0 result = result
| loop i result = loop (i -1) ( i :: result )
in
loop n []
end

fun len [] = 0
| len ( h :: t ) = 1 + len t

(* Tail recursive version of the len function below *)


fun len2 l =
let fun aux [] i = i
| aux ( h :: t ) i = aux t ( i +1)
in
aux l 0

 end

Ε: Πως θα σώσω και θα φορτώσω στον interpreter τον κώδικά μου;
Α: Γράφουμε με τον editor της επιλογής μας τον κώδικά μας και τον σώζουμε σε ένα
αρχείο. Στη συνέχεια, μέσα στον interpreter χρησιμοποιούμε τη συνάρτηση use που
παίρνει ως όρισμα ένα string με το όνομα του αρχείου που θέλουμε να φορτώσουμε.
 
- use " myfile . sml " ;
[ opening myfile . sml ]
val make_list = fn : int -> int list
val len = fn : ’a list -> int
val len2 = fn : ’a list -> int
val it = () : unit
- len [1 ,2 ,3];

 val it = 3 : int

Ε: Και αν το αρχείο είναι σε άλλο directory;
Α: Σε αυτήν την περίπτωση, είτε δίνουμε το απόλυτο όνομα του αρχείου, είτε χρησιμο-
ποιούμε τις συναρτήσεις OS.FileSys.getDir() και OS.FileSys.chDir("path"), για να
δούμε που βρισκόμαστε και να περιηγηθούμε στους καταλόγους του συστήματος.

Ε: Και αν βαριέμαι να γράφω use ... κάθε φορά που ξεκινάω την sml;
Α: Mπορείς να φορτώσεις κατ’ ευθείαν τον κώδικα στην SML κατά τη στιγμή της εκκί-
νησης καλώντας την (στο Linux) με την εντολή: sml myfile.sml

3
3 Ταίριασμα προτύπων (pattern matching)
Μπορούμε να κάνουμε pattern matching κατευθείαν στις παραμέτρους της συνάρτη-
σης, όπως φαίνεται στις παρακάτω συναρτήσεις:
 
fun factorial 0 = 1
| factorial n = n * factorial ( n - 1)

fun length [] = 0

 | length ( x :: xs ) = 1+ length xs

Επίσης, μπορούμε να κάνουμε pattern matching σε μεταβλητές με χρήση του case:
 
fun factorial n =
case n of 0 = > 1
| m = > m * factorial (m -1)

fun length l =
case l of [] => 0

 | x :: xs = > 1+ length xs

Σε κάθε pattern matching που κάνουμε πρέπει, στο μέτρο του δυνατού, να φροντίζουμε
να μην εμφανίζεται το μήνυμα Warning: match nonexhaustive, όπως συμβαίνει παρακάτω:
 
- fun hd [ a ] = a
= | hd ( h :: t ) = h ;
stdIn :1.5 -4.18 Warning : match nonexhaustive
a :: nil = > ...
h :: t = > ...

 val hd = fn : ’a list -> ’a



Σε αντίθεση με την παραπάνω συνάρτηση, οι συναρτήσεις που ορίζουμε είναι καλό να
είναι πλήρως ορισμένες, ούτως ώστε να αποφεύγεται η παραπάνω προειδοποίηση. Για το
σκοπό αυτό μπορούμε να χρησιμοποιήσουμε και εξαιρέσεις, όπου κρίνεται απαραίτητο:
 
- exception HdEmpty ;
exception HdEmpty
- fun hd [] = raise HdEmpty
= | hd [ a ] = a
= | hd ( h :: t ) = h ;

 val hd = fn : ’a list -> ’a



Το pattern matching μπορεί, τέλος, να χρησιμοποιηθεί για να επιλέξουμε ένα στοιχείο
από μια πλειάδα (tuple):
 
fun fst (x , _ ) = x ;
fun snd (_ , x ) = x ;
- fst (10 ,20) ;
val it = 10 : int
- snd (10 ,20) ;

 val it = 20 : int

Για την επιλογή ενός στοιχείου από μια πλειάδα με βάση τη θέση του, η SML παρέχει
έναν ειδικό τελεστή:
 
- #1 (10 ,20) ;

 val it = 10 : int

4
4 Υπερφόρτωση τελεστών
Μερικοί τελεστές της SML είναι υπερφορτωμένοι. Επιτελούν, δηλαδή, διαφορετική
λειτουργία ανάλογα με τον τύπο των τελουμένων τους. Ένας τέτοιος τελεστής είναι ο
γνωστός μας τελεστής + της πρόσθεσης:
 
- 1 + 2;
val it = 3 : int
- 1.0 + 2.0;

 val it = 3.0 : real



Όμως, η ML δεν επιτρέπει πρόσθεση μεταξύ αριθμών διαφορετικού τύπου ούτε κάνει
αυτόματη μετατροπή από έναν τύπο σε κάποιον άλλο:
 
- 1.0 + 2;
stdIn :1.1 -1.8 Error : operator and operand don ’ t agree [ literal ]
operator domain : real * real
operand : real * int
in expression :

 1.0 + 2

Για όσους δεν κατάλαβαν τις ακριβείς συνέπειες του παραπάνω για το τι επιτρέπεται και
τι δεν επιτρέπεται στην ML, δείχνουμε άλλο ένα παράδειγμα:1
 
- val x : Int64 . int = 1234567890123;
val x = 1234567890123 : Int64 . int
- val y = 42;
val y = 42 : int
- x + y;
stdIn :4.1 -4.7 Error : operator and operand don ’ t agree [ tycon mismatch ]
operator domain : Int64 . int * Int64 . int
operand : Int64 . int * int
in expression :

 x + 42

Κατά συνέπεια, πολλές φορές μπορεί να χρειαστεί να χρησιμοποιήσουμε κάποιες από
τις συναρτήσεις μετατροπής τύπων της βιβλιοθήκης:
 
- val x : Int64 . int = 1234567890123;
val x = 1234567890123 : Int64 . int
- val y = 42;
val y = 42 : int
- x + ( Int64 . fromInt y ) ;
val it = 1234567890165 : Int64 . int
- 3.14 * real 42;

 val it = 131.88 : real



Επίσης, όταν ορίζουμε συναρτήσεις που χρησιμοποιούν τους τελεστές οι οποίοι έχουν
έναν προεπιλεγμένο default τυπο, ίσως είναι απαραίτητο να δηλώσουμε τον επιθυμητό
τύπο των παραμέτρων τους έτσι ώστε να έχουμε το αναμενόμενο αποτέλεσμα:
 
- fun add x y = x+y;
val add = fn : int -> int -> int
- fun add ( x : real ) y = x + y ;

 val add = fn : real -> real -> real



1Η επανάληψη είναι μήτηρ μαθήσεως, ακόμα και στην ML που δεν υποστηρίζει επανάληψη!

5
5 Ορισμός datatypes
Στην SML μπορούμε να ορίσουμε νέους τύπους δεδομένων, π.χ. ένα δυαδικό δέντρο με
μια απλή δήλωση:
 
 datatype btree = Empty | Leaf | Node of btree * btree

Στη συνέχεια, μπορούμε να γράψουμε συναρτήσεις που επεξεργάζονται τους συγκεκρι-
μένους τύπους δεδομένων, όπως η παρακάτω συνάρτηση που υπολογίζει το πλήθος των
φύλλων ενός δυαδικού δέντρου:
 
- fun cntleaves Empty = 0
= | cntleaves Leaf = 1
= | cntleaves ( Node ( tree1 , tree2 ) ) =
= ( cntleaves tree1 ) + ( cntleaves tree2 ) ;
val cntleaves = fn : btree -> int
- val tree = Node ( Node ( Leaf , Leaf ) , Leaf ) ;
val tree = Node ( Node ( Leaf , Leaf ) , Leaf ) : btree
- cntleaves tree ;

 val it = 3 : int


6 If και βραχυκύκλωση
Η αποτίμηση των λογικών τελεστών στην SML γίνεται με την τεχνική της βραχυκύ-
κλωσης. Αυτό σημαίνει ότι η αποτίμηση σταματάει όταν πλέον το αποτέλεσμά της είναι
γνωστό, π.χ.:
 
if true orelse 2 div 0 = 1 then print " ok \ n " else print " bad \ n " ;

 if false andalso 2 div 0 = 1 then print " bad \ n " else print " ok \ n " ;

Στο παραπάνω παράδειγμα αν η αποτίμηση των συνθηκών δε χρησιμοποιούσε την εν λόγω
τεχνική, τότε θα εμφανιζόταν το μήνυμα uncaught exception Div [divide by zero].

7 Συνηθισμένα λάθη αρχαρίων


Έλλειψη κάποιου μέρους του if then else Στην ML το else είναι υποχρεωτικό. Το if
then else είναι έκφραση και επιστρέφει πάντα μια τιμή – σε αυτή την περίπτωση
αγνοούμε την τιμή που επιστρέφει, η οποία είναι τύπου unit.
 
(* Wrong way of doing this *)
fun times3 x =
( if x > 3 then
print " Greater than 3\ n " ;
x * 3 )

(* Right way of doing this *)


fun times3 x =
( if x > 3 then
print " Greater than 3\ n "
else () ;

 x * 3 )


6
Συναρτήσεις με παρενέργειες (π.χ., εκτύπωση με αλλαγή γραμμής)
 
(* Wrong way of doing this *)
fun println s = print s ; print " \ n " ;

(* Right way *)
fun println s = ( print s ; print " \ n " ) ;

 fun println s = print ( s ^ " \ n " ) ;



Διαφορές μεταξύ λιστών και πλειάδων Στην ML τα στοιχεία μιας λίστας πρέπει να
έχουν όλα τον ίδιο τύπο. Αντίθετα, οι πλειάδες μπορεί να έχουν στοιχεία οποιουδή-
ποτε τύπου σε κάθε θέση τους.
 
(* Right *)
[1 , 2 , 3 , 4]

(* Wrong *)
[1 , 2 , " Hello " ]

(* Right *)

 (1 , 2 , " Hello " )



Λάθη ταιριάσματος προτύπων (pattern matching errors) Η έκφραση [hd,tl] ται-
ριάζει μόνο με μια λίστα δύο στοιχείων και είναι ισοδύναμη με (hd::tl::[]).
 
(* Wrong *)
fun len [ hd , tl ] = 1 + len tl
| len [] = 0

(* Right *)
fun len ( hd :: tl ) = 1 + len tl

 | len [] = 0

Επίσης, η σειρά των εμφάνισης των προτύπων έχει σημασία. Η ML μας προειδοποιεί
όταν κάποιο πρότυπο δεν ταιριάζει ποτέ.
 
datatype ’a tree = Empty | Node of ’a * ’a tree * ’a tree

(* Wrong *)
fun is_leaf Empty = false
| is_leaf ( Node (v , l , r ) ) = false
| is_leaf ( Node (v , Empty , Empty ) ) = true

(* Right *)
fun is_leaf Empty = false
| is_leaf ( Node (v , Empty , Empty ) ) = true

 | is_leaf ( Node (v , l , r ) ) = false




7
8 Συχνές ερωτήσεις
Ε: Η ML έχει κάποιες βιβλιοθήκες που μπορώ να χρησιμοποιήσω;
Α: Βεβαίως. Τη στιγμή συγγραφής αυτού του φυλλαδίου περιγράφονται στις ιστοσελίδες:

• http://www.standardml.org/Basis/overview.html
• http://www.standardml.org/Basis/manpages.html
• http://www.smlnj.org/doc/smlnj-lib/Manual/toc.html

Ε: Υπάρχει κάποιος τρόπος στην ML, για να δω όλα τα στοιχεία μιας δομής δεδομένων
(π.χ. μιας λίστας); Όταν η δομή είναι μεγάλη μου βγάζει τελίτσες από κάποιο σημείο
και μετά...
Α: Ο ένας τρόπος είναι να υλοποιήσεις τη δική σου συνάρτηση ωραίου τυπώματος για
τη δομή δεδομένων που θες να δεις (μάλλον το έχεις κάνει ήδη στην προτροπή που
υπάρχει στη σελίδα 3). Ο άλλος τρόπος στο toplevel του SML/NJ interpreter είναι να
αναθέσεις σε μια αναφορά με όνομα Control.Print.printLength την τιμή που θέλεις.
 
- [1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17];
val it = [1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,...] : int list
- Control . Print . printLength := 42;
[ autoloading ]
[ library $smlnj / compiler / current . cm is stable ]
... (* many other lines supressed *)
[ autoloading done ]
val it = () : unit
- [1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17];

 val it = [1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17] : int list


Ασκήσεις εξάσκησης
1. Ταίριασμα προτύπων
Γράψτε πρότυπα που να ταιριάζουν μια μεταβλητή x με την τιμή 0 στις παρακάτω
λίστες και πλειάδα.

(αʹ) [~2, ~1, 0, 1, 2]


(βʹ) [(1,2), (0,1)]
(γʹ) ((1,2,0), (3,2,4))
2. Εύρεση λαθών
Να βρεθούν τα λάθη ή οι προειδοποιήσεις της ML στις παρακάτω περιπτώσεις:

(αʹ) print 42
(βʹ) fun foo = 42
(γʹ) fun inc x = x + 1
fun f n = inc true
(δʹ) fun divide_by_two x = x / 2
(εʹ) datatype ’a btree = Leaf of ’a | Node of ’a btree * ’a btree
fun preorder Leaf(v) = [v]
| preorder Node(l,r) = preorder l @ preorder r

8
(Ϛʹ) datatype ’a option = NONE | SOME of ’a
fun filter pred l =
let fun filterP (x::r, l) =
case (pred x) of
SOME y => filterP (r, y::l)
| NONE => filterP (r, l)
| filterP ([], l) = rev l
in
filterP (l, [])
end
(ζʹ) let val e = 5 and f = e + 1 in e + f end
(ηʹ) val (x, x) = (42, 42)
(θʹ) fun f (x, y) = x + y + 1
| f (x, y, z) = x + y + z + 2
| f _ = 3
(ιʹ) fun f _ = "nonzero"
| f 0 = "zero"
(ιαʹ) fun h (x::xs) = x + 1
(ιβʹ) val succ = op + 1
3. Διαχωρισμός λίστας
Να γράψετε μία συνάρτηση που να δέχεται μία λίστα και να τη χωρίζει σε δύο υπο-
λίστες εκ των οποίων η πρώτη θα περιέχει το πρώτο ήμισυ και η άλλη το δεύτερο.
(Αν η αρχική λίστα έχει περιττό μήκος, επιλέξτε εσείς σε ποιά λίστα θα καταλήξει
το μεσαίο στοιχείο.)
4. Τέλειοι αριθμοί
Ένας τέλειος αριθμός ισούται με το άθροισμα των παραγόντων του, συμπεριλαμβα-
νομένης της μονάδας (1) αλλά όχι του εαυτού του. Για παράδειγμα, ο 6 είναι ένας
τέλειος αριθμός γιατί 6 = 3 + 2 + 1. Να γράψετε μία συνάρτηση που να επιστρέφει
true αν ένας αριθμός είναι τέλειος και false αν δεν είναι.
5. Περίγραμμα δέντρου
Γράψτε μια συνάρτηση που παίρνει ένα δέντρο σαν όρισμα, και επιστρέφει μια λίστα
με όλα τα στοιχεία του που είναι φύλλα, από αριστερά προς τα δεξιά, για τον εξής
ορισμό της δομής των δέντρων:
 
 datatype ’a tree = Empty | Node of ’a * ’a tree * ’a tree

6. Δυναμοσύνολο
Να ορίσετε μία συνάρτηση στην οποία όταν δίνεται ένα σύνολο με τη μορφή λίστας
να επιστρέφει το σύνολο των υποσυνόλων του σε μορφή λίστας.
7. Ταξινόμηση
Να γράψετε μία συνάρτηση ταξινόμησης για τη μέθοδο Merge Sort χρησιμοποιώντας
τη συνάρτηση halve:
 
fun halve nil = ( nil , nil )
| halve [ a ] = ([ a ] , nil )
| halve ( a :: b :: cs ) =
let
val (x , y ) = halve cs
in
( a :: x , b :: y )

 end

9
8. Οι πύργοι του Hanoi2
Υποθέστε ότι σας δίνονται τρεις ράβδοι και δίσκοι διαφορετικού μεγέθους. Οι δίσκοι
μπορούν να στοιβαχθούν στις ράβδους σχηματίζοντας πύργους. Βρίσκονται αρχικά
στη ράβδο left με φθίνουσα τάξη μεγέθους. Πρέπει να μεταφερθούν στη ράβδο right
ώστε τελικά να βρεθούν τοποθετημένοι με την ίδια σειρά. Αυτό πρέπει να επιτευχθεί
με τους παρακάτω περιορισμούς:

• Σε κάθε βήμα μόνο ένας δίσκος μεταφέρεται από μία ράβδο σε άλλη.
• Ένας δίσκος απαγορεύεται να τοποθετηθεί πάνω από μικρότερο δίσκο.
• Η ράβδος middle μπορεί να χρησιμοποιηθεί για προσωρινή τοποθέτηση των δί-
σκων.

Να γράψετε μία συνάρτηση που να επιτυγχάνει το επιθυμητό αποτέλεσμα.

2 Ιστορική σημείωση: Το παιχνίδι ονομάσθηκε «Πύργος του Ανόι» γιατί το σχήμα του πύργου θυμίζει την

αρχιτεκτονική των ναών στις χώρες της Άπω Ανατολής. Το παιχνίδι πρωτοεμφανίστηκε από τον Γάλλο μαθη-
ματικό Édouard Lucas γύρω στα 1883. Η προέλευσή του είναι μάλλον από τις Ινδίες. Υπάρχει για το παιχνίδι ο
παρακάτω θρύλος με τίτλο «Πύργος του Βράχμα»:
Όταν ο Βράχμα δημιούργησε τον κόσμο, έστησε σε ένα ναό στην πόλη Μπενάρες 64 δακτυλίδια
χρυσού άνισου μεγέθους, όλα περασμένα σε μία ράβδο. Oι ιερείς του ναού έπρεπε να δουλεύουν
μέρα και νύχτα ασταμάτητα για να μεταφέρουν τα δακτυλίδια σε μία άλλη ράβδο, χρησιμοποιώντας
μία τρίτη σαν βοηθητική, έτσι ώστε να μην τοποθετήσουν μεγαλύτερο δακτυλίδι πάνω από μικρότερο
και μετακινώντας ένα μόνο δακτυλίδι σε κάθε κίνηση. Ο θρύλος λέει πως πριν προλάβουν οι ιερείς
να μεταφέρουν όλα τα δακτυλίδια στην άλλη ράβδο, ο ναός θα καταρρεύσει μέσα στην σκόνη και ο
κόσμος θα χαθεί μέσα σε τρομακτικό κρότο βροντής.

10
Σύντομη Εισαγωγή στη Java
Φυλλάδιο σημειώσεων για το 2ο εργαστήριο του μαθήματος

1 Χρήσιμες πληροφορίες και συνηθισμένα λάθη


Όνομα κλάσης και αρχείου Τα περισσότερα συστήματα Java απαιτούν ο κώδικας μίας
public κλάσης να βρίσκεται σε ένα .java αρχείο που έχει το ίδιο όνομα με την κλάση.
Αν αγνοήσουμε αυτή τη σύμβαση μπορεί να προκύψουν αρκετά προβλήματα κατά τη
μεταγλώττιση.
✞ ☎
// Wrong : File Lab2 . java
public class Airplane extends Vehicle {
int num_seats ;
public Airplane () {
num_seats = 42;
}
}
✝ ✆

> javac Lab2.java


Lab2.java:2: class Airplane is public, should be declared in a file named Airplane.java

Σχέσεις κλάσεων και αρχείων Τα αρχεία Java έχουν μία πολύ ειδική σχέση με τις
κλάσεις που βρίσκονται σε αυτά. Σε γενικές γραμμές, οι κανόνες είναι οι εξής:
1. Κάθε κλάση ορίζεται σε ακριβώς ένα αρχείο.
2. Το πολύ μία public κλάση μπορεί να οριστεί σε ένα αρχείο.
3. Αν μία public κλάση βρίσκεται σε ένα αρχείο, τότε το όνομα της κλάσης και το
όνομα του αρχείου πρέπει να είναι τα ίδια.
Σύγκριση δύο αντικειμένων Ο τελεστής ισότητας == χρησιμοποιείται για τη σύγκριση
δύο αναφορών σε αντικείμενα. Ελέγχει δηλαδή αν δύο αναφορές δείχνουν στο ίδιο
αντικείμενο. Για τη σύγκριση των ίδιων των αντικειμένων πρέπει να χρησιμοποιηθεί
η μέθοδος equals η οποία κληρονομείται σε όλες τις κλάσεις από την java.lang.Object.
Κατά συνέπεια κληρονομείται και στα strings καθότι και τα strings στη Java είναι
αντικείμενα της κλάσης java.lang.String!
✞ ☎
// Wrong way to find out if the first argument is " - a "
if ( args [0] == " -a " )
optionsAll = true ;

// Right way
if ( " -a " . equals ( args [0]) )
optionsAll = true ;
✝ ✆

1
Αρχικοποίηση πινάκων αντικειμένων Στη Java ένας πίνακας που δηλώνεται ότι εί-
ναι πίνακας σε αντικείμενα κάποιου τύπου, στην πραγματικότητα είναι ένας πίνα-
κας αναφορών σε αντικείμενα αυτού του τύπου. Έτσι, όλα τα στοιχεία του πίνακα
αρχικοποιούνται αυτόματα σε null. Είναι, λοιπόν, απαραίτητο να θέσουμε κάθε στοι-
χείο του πίνακα σε μία πραγματική αναφορά σε κάποιο αντικείμενο του σωστού τύ-
που.
✞ ☎
// Wrong way to create an array of data buffers
StringBuffer [] myTempBuffers ;
myTempBuffers = new StringBuffer [3];
myTempBuffers [0]. add ( data ) ;
✝ ✆
✞ ☎
// Right way to create an array of data buffers and initialize it
StringBuffer [] myTempBuffers ;
myTempBuffers = new StringBuffer [3];
for ( int ix = 0; ix < myTempBuffers . length ; ix ++)
myTempBuffers [ ix ] = new StringBuffer () ;
myTempBuffers [0]. add ( data ) ;
✝ ✆

Επισκίαση πεδίου από τοπική μεταβλητή Η Java επιτρέπει στον προγραμματιστή να


ορίσει μέσα σε μεθόδους τοπικές μεταβλητές με ονόματα ίδια με αυτά των πεδίων
της κλάσης. Σε αυτές τις περιπτώσεις οι τοπικές μεταβλητές έχουν προτεραιότητα
και, όπου αυτές εμφανίζονται, επισκιάζουν τα πεδία της κλάσης. Υπάρχουν δύο τρό-
ποι για την επίλυση αυτού του προβλήματος, η χρήση της δομής this.<πεδίο> όταν
αναφερόμαστε σε κάποιο πεδίο και η μετονομασία είτε των πεδίων είτε των τοπικών
μεταβλητών.
✞ ☎
// Wrong
public class ... {
int i = 0; int j = 0; int k = 0;
public boolean hits ( Point [] p2list ) {
for ( int i = 0; i < p2list . length ; i ++) {
Point p2 = p2list [ i ];
if ( p2 . x == i && p2 . y == j )
return true ;
}
return false ;
}
}
✝ ✆
✞ ☎
// One way to fix the problem
public class ... {
int i = 0; int j = 0; int k = 0;
public boolean hits ( Point [] p2list ) {
for ( int i = 0; i < p2list . length ; i ++) {
Point p2 = p2list [ i ];
if ( p2 . x == this . i && p2 . y == this . j )
return true ;
}
return false ;
}
}
✝ ✆

2
✞ ☎
// A better way to fix the problem
public class ... {
int x = 0; int y = 0; int z = 0;
public boolean hits ( Point [] p2list ) {
for ( int i = 0; i < p2list . length ; i ++) {
Point p2 = p2list [ i ];
if ( p2 . x == x && p2 . y == y )
return true ;
}
return false ;
}
}
✝ ✆

Κλήση του κατασκευαστή της υπερκλάσης Όταν μία κλάση επεκτείνει κάποια άλλη,
τότε ο κατασκευαστής της υποκλάσης πρέπει με κάποιο τρόπο να καλεί τον κατα-
σκευαστή της υπερκλάσης. Συνήθως αυτό επιτυγχάνεται βάζοντας μία κλήση στον
κατασκευαστή της υπερκλάσης στην πρώτη γραμμή του κατασκευαστή της υποκλά-
σης, γράφοντας δηλαδή κάτι σαν και το super(arg1, ..., argN). Αν η πρώτη γραμμή
του κατασκευαστή της υποκλάσης δεν είναι κάτι σαν και το παραπάνω, ο compiler
συνήθως εισάγει μία κλήση super() χωρίς ορίσματα. Αυτό όμως προκαλεί προβλή-
ματα αν η υπερκλάση δεν διαθέτει έναν κατασκευαστή που να μην δέχεται παραμέ-
τρους.
✞ ☎
// Wrong
public class JavaClassFile extends File {
String classname ;
public JavaClassFile ( String cl ) {
classname = cl ;
}
}
✝ ✆
✞ ☎
// Right
public class JavaClassFile extends File {
String classname ;
public JavaClassFile ( String cl ) {
super ( cl + " . class " ) ;
classname = cl ;
}
}
✝ ✆

Οι static και οι λοιπές Ορίζουμε μία μέθοδο ως static, όπως τη main, για να δηλώσουμε
ότι δεν χρειάζεται να δημιουργήσουμε ένα στιγμιότυπο της κλάσης που την περιέχει
για να την καλέσουμε. Όμως από τη στιγμή που δεν έχει δημιουργηθεί ένα στιγμιό-
τυπο της κλάσης, η main δεν μπορεί να χρησιμοποιήσει πεδία της κλάσης ούτε και
να καλέσει κάποιες από τις μεθόδους της, γιατί δεν υπάρχουν! Για την προσπέλαση
ενός πεδίου της κλάσης, θα πρέπει πρώτα να δημιουργήσουμε ένα στιγμιότυπό της.
✞ ☎
// Wrong
public class StaticDemo {
public String my_member_variable = " somedata " ;
public static void main ( String [] args ) {
// Access a non - static member from static method
System . out . println ( " This generates a compiler error " +
my_member_variable ) ;
}
}
✝ ✆

3
✞ ☎
// Right
public class NonStaticDemo {
public String my_member_variable = " somedata " ;
public static void main ( String [] args ) {
NonStaticDemo demo = new NonStaticDemo () ;
// Access member variable of demo
System . out . println ( " This WON ’T generate an error " +
demo . my_member_variable ) ;
}
}
✝ ✆

Όσον αφορά στις μεθόδους μπορούμε είτε να κάνουμε ό,τι κάναμε και για τα πεδία
είτε να ορίσουμε και τη μέθοδο που θέλουμε να καλέσουμε ως static. Αν η εν λόγω
μέθοδος καλεί και άλλες μεθόδους της κλάσης ή χρησιμοποιεί κάποια από τα πεδία
της, τότε ο πρώτος τρόπος συμφέρει περισσότερο.
✞ ☎
// Wrong
public class DivTest {
boolean divisible ( int x , int y ) {
return ( x % y == 0) ;
}
public static void main ( String [] args ) {
int v1 = 14;
int v2 = 9;
// next line causes a compiler error message
if ( divisible ( v1 , v2 ) )
System . out . println ( v1 + " is a multiple of " + v2 ) ;
else
System . out . println ( v2 + " does not divide " + v1 ) ;
}
}
✝ ✆
✞ ☎
// One way to fix the problem
public class DivTest {
int modulus ;
public DivTest ( int m ) { modulus = m ; }
boolean divisible ( int x ) {
return ( x % modulus == 0) ;
}
public static void main ( String [] args ) {
int v1 = 14;
int v2 = 9;
DivTest tester = new DivTest ( v2 ) ;
if ( tester . divisible ( v1 )
System . out . println ( v1 + " is a multiple of " + v2 ) ;
else
System . out . println ( v2 + " does not divide " + v1 ) ;
}
}
✝ ✆
✞ ☎
// Another way to fix the problem
public class DivTest {
static boolean divisible ( int x , int y ) {
return ( x % y == 0) ;
}
public static void main ( String [] args ) {

4
int v1 = 14;
int v2 = 9;
if ( divisible ( v1 , v2 ) )
System . out . println ( v1 + " is a multiple of " + v2 ) ;
else
System . out . println ( v2 + " does not divide " + v1 ) ;
}
}
✝ ✆

Μέθοδος ή κατασκευστής Οι κατασκευαστές αντικειμένων στην Java μοιάζουν πολύ


με μεθόδους. Οι μόνες εμφανείς συντακτικές διαφορές μεταξύ των δύο είναι ότι οι
κατασκευαστές δεν έχουν τύπο επιστροφής στον ορισμό τους και το όνομά τους είναι
ίδιο με το όνομα της κλάσης. Παρόλα αυτά, και οι μέθοδοι επιτρέπεται να έχουν το
ίδιο όνομα με την κλάση.
✞ ☎
// Wrong
public class IntList {
Vector list ;
// This may look like a constructor , but it is a method
public void IntList () {
list = new Vector () ;
}
public append ( int n ) {
list . addElement ( new Integer ( n ) ) ;
}
}
✝ ✆
✞ ☎
// Right
public class IntList {
Vector list ;
// This is a real constructor
public IntList () {
list = new Vector () ;
}
public append ( int n ) {
list . addElement ( new Integer ( n ) ) ;
}
}
✝ ✆

Oh strings! Τα strings στην Java είναι αντικείμενα που δεν επιδέχονται μετατροπή (immutable).
Αυτό σημαίνει ότι δεν τα μεταχειριζόμαστε ως πίνακες χαρακτήρων που περνούν
στις μεθόδους κατ’ αναφορά!
✞ ☎
// Wrong
public static void main ( String [] args ) {
String test1 = " Today is " ;
appendTodaysDate ( test1 ) ;
System . out . println ( test1 ) ;
}
public void appendTodaysDate ( String line ) {
line = line + ( new Date () ) . toString () ;
}
✝ ✆
✞ ☎
// One way to fix the problem
public static void main ( String [] args ) {

5
String test1 = " Today is " ;
test1 = appendTodaysDate ( test1 ) ;
System . out . println ( test1 ) ;
}
public String appendTodaysDate ( String line ) {
return ( line + ( new Date () ) . toString () ) ;
}
✝ ✆
✞ ☎
// Another way to fix the problem
public static void main ( String [] args ) {
StringBuffer test1 = new StringBuffer ( " Today is " ) ;
appendTodaysDate ( test1 ) ;
System . out . println ( test1 . toString () ) ;
}
public void appendTodaysDate ( StringBuffer line ) {
line . append (( new Date () ) . toString () ) ;
}
✝ ✆

Long strings:
✞ ☎
// Wrong
String s = " A very long string which just happens to go over the
end of a line and causes a problem with the compiler " ;

// Right
String s = " A very long string which just happens to go over " +
" the end of a line and causes a problem with the compiler " ;
✝ ✆

Κατά τιμή και κατ’ αναφορά Η Java χρησιμοποιεί πέρασμα παραμέτρων και κατά τιμή
και κατ’ αναφορά. Όταν περνάμε σαν παράμετρο έναν πρωτόγονο τύπο δεδομένων,
όπως char, int, float, double, τον περνάμε κατά τιμή. Αυτό σημαίνει ότι στη μέθοδο
που καλείται περνάει ένα αντίγραφο του τύπου δεδομένων το οποίο μπορεί μεν να
μεταβληθεί αλλά έχει διάρκεια ζωής μέχρι το τέλος της μεθόδου. Αντίθετα, όταν περ-
νάμε σαν παράμετρο ένα αντικείμενο, το περνάμε κατ’ αναφορά, δηλαδή οι αλλαγές
που θα γίνουν σε αυτό από τη μέθοδο που καλείται είναι μόνιμες.
Casting Η Java επιτρέπει στον προγραμματιστή να μεταχειριστεί ένα αντικείμενο μίας
υποκλάσης σαν αντικείμενο της υπερκλάσης της. Αυτό συμβαίνει γιατί το upcasting
στην Java γίνεται αυτόματα. Αντίθετα, το downcasting πρέπει να δηλώνεται ρητά.
✞ ☎
// Wrong
Object arr [] = new Object [10];
arr [0] = " m " ;
arr [1] = new Character ( ’m ’) ;
String arg = args [0];
if ( arr [0]. compareTo ( arg ) < 0)
System . out . println ( arg + " comes before " + arr [0]) ;
✝ ✆
✞ ☎
// Right
Object arr [] = new Object [10];
arr [0] = " m " ;
arr [1] = new Character ( ’m ’) ;
String arg = args [0];
if ( (( String ) arr [0]) . compareTo ( arg ) < 0)
System . out . println ( arg + " comes before " + arr [0]) ;
✝ ✆

6
✞ ☎
// File : Input . java
import java . io .*;

public class Input {


public static void main ( String [] args ) {
BufferedReader reader =
new BufferedReader ( new InputStreamReader ( System . in ) ) ;
String line = null ;
int sum = 0;
try {
while ( true ) {
line = reader . readLine () ;
if ( line == null || line . equals ( " stop " ) ) {
System . out . println ( " Sum : " + sum ) ;
return ;
}
sum += Integer . parseInt ( line ) ;
}
} catch ( Exception e ) { // Bad practice !
System . out . println ( " Something went wrong : " + e ) ;
}
}
}
✝ ✆

Σχήμα 1: Ανάγνωση από το standard input.

2 FAQ
Ε: Πως διαβάζω από το standard input;
Α: Με 3-4 *Reader κάτι μπορεί να γίνει, όπως φαίνεται και στο Σχήμα 1.
Η Java για να χειρίζεται και να διαβάζει αρχεία χρησιμοποιεί τα I/O Streams που ορί-
ζονται στο java.io package. Ένας εύκολος τρόπος για διάβασμα από αρχείο είναι
χρησιμοποιώντας την κλάση BufferedReader, όπως φαίνεται στο παραπάνω παρά-
δειγμα. Αρχικά, φτιάχνουμε ένα InputStream (FileReader) με το αρχείο εισόδου και
με αυτό δημιουργούμε ένα αντικείμενο κλάσης BufferedReader. Με το BufferedReader
που έχουμε ορίσει μπορούμε να διαβάσουμε ένα-ένα χαρακτήρα (read()) ή ολόκληρη
γραμμή από χαρακτήρες (String readLine()). Ένα χρήσιμο εργαλείο είναι το String[]
split(String), που παίρνει ως παράμετρο ένα String και επιστρέφει έναν πίνακα από
Strings. Η συνάρτηση αυτή χωρίζει το αρχικό String σε επιμέρους με βάση κάποιο
διαχωριστικό όπως το κενό, το κόμμα, την παύλα, κ.λπ.
Για παράδειγμα το παρακάτω κομμάτι κώδικα:

String a = "boo:and:foo";
String b = a.split(":");
String c = a.split("o");

δίνει το εξής αποτέλεσμα:

b = { "boo", "and", "foo" }


c = { "b", "", ":and:f" }

Ένα παράδειγμα χρήσης των παραπάνω δίνεται στο Σχήμα 2.


Εναλλακτικά, για την επεξεργασία της εισόδου του προγράμματος μπορεί να χρησι-
μοποιηθεί και η κλάση java.util.Scanner. Για παράδειγμα, για να διαβάσει κανείς

7
✞ ☎
// File : Input . java
import java . io .*;

public class Input {


public static void main ( String [] args ) {
try {
BufferedReader in = new BufferedReader ( new FileReader ( " input . txt " ) ) ;
String line = in . readLine () ;
String [] a = line . split ( " " ) ;
int n = Integer . parseInt ( a [0]) ;
int m = Integer . parseInt ( a [1]) ;
int [][] floor = new int [ m ][ n ];

for ( int i = 0; i < m ; i ++)


for ( int j = 0; j < n ; j ++)
floor [ i ][ j ] = in . read () ;
in . close () ;
}
catch ( IOException e ) {
e . printStackTrace () ;
}
}
}
✝ ✆

Σχήμα 2: Ανάγνωση από το standard input.

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

Ε: Πως διαβάζω command line arguments;


Α: Το διάβασμα command line arguments γίνεται απλά, περίπου όπως στη C. Το μόνο που
χρειάζεται προσοχή είναι ότι κάθε argument θεωρείται String και αν θέλουμε να το
χειριστούμε ως integer, ή κάποιο άλλο τύπο, πρέπει να το μετατρέψουμε.
Παράδειγμα (από την περσινή άσκηση για το zapping):
Η είσοδος δίνεται ως εξής: Πρώτο όρισμα είναι o μέγιστος αριθμός καναλιών και τα
υπόλοιπα ορίσματα είναι τα κανάλια που είναι προγραμματισμένες οι τηλεοράσεις
μια δεδομένη χρονική στιγμή. Δηλαδή θέλουμε να διαβάσουμε το πρώτο όρισμα και
να το αποθηκεύσουμε σε μια μεταβλητή και τα υπόλοιπα ορίσματα τα αποθηκεύουμε
σε ένα πίνακα.
✞ ☎
public static void main ( String [] args ) {
int i , max , temp ;
int [] channels ;

if ( args . length > 1) {


max = Integer . parseInt ( args [0]) ;
channels = new int [ args . length -1];
for ( i = 1; i < args . length ; i ++)
channels [i -1] = Integer . parseInt ( args [ i ]) ;
}
else
System . out . println ( " Wrong input \ n " ) ;
}
✝ ✆

8
✞ ☎
// File : Graph . java
import java . util . Scanner ;

public class Graph {


public static void main ( String [] args ) {
...
Scanner in = new Scanner ( System . in ) ;
n = in . nextInt () ;
m = in . nextInt () ;
for ( int i = 0 ; i < m ; i ++) {
int source = in . nextInt () ;
int target = in . nextInt () ;
int weight = in . nextInt () ;
edges . addEdge ( source , target , weight ) ;
}
in . close () ;
...
}
✝ ✆

Σχήμα 3: Ανάγνωση από το standard input με χρήση Scanner.

Ε: Τι είναι οι iterators και πώς χρησιμοποιούνται;


Α: Σε κάποια κλάση που υλοποιεί το interface Collection, όπως οι List, Vector, Set, για
να διατρέξουμε τα στοιχεία τους χρησιμοποιούμε τo interface iterator. Ειδικά για
λίστες υπάρχει και το subinterface Listterator, που επιτρέπει να διατρέξουμε τη
λίστα και προς την αντίθετη κατεύθυνση και να τροποποιήσουμε κάποιο στοιχείο
της.
✞ ☎
class IteratorDemo {
public static void main ( String args []) {
// create an array list
ArrayList al = new ArrayList () ;
// add elements to the array list
al . add ( " C " ) ;
al . add ( " A " ) ;
al . add ( " E " ) ;
al . add ( " B " ) ;
al . add ( " D " ) ;
al . add ( " F " ) ;

// use iterator to display contents of al


System . out . print ( " Original contents of al : " ) ;
Iterator itr = al . iterator () ;
while ( itr . hasNext () ) {
String element = ( String ) itr . next () ;
System . out . print ( element + " " ) ;
}
System . out . println () ;

// modify objects being iterated


ListIterator litr = al . listIterator () ;
while ( litr . hasNext () ) {
String element = ( String ) litr . next () ;
litr . set ( element + " + " ) ;
}
System . out . print ( " Modified contents of al : " ) ;
itr = al . iterator () ;
while ( itr . hasNext () ) {

9
String element = ( String ) itr . next () ;
System . out . print ( element + " " ) ;
}
System . out . println () ;

// now , display the list backwards


System . out . print ( " Modified list backwards : " ) ;
while ( litr . hasPrevious () ) {
String element = ( String ) litr . previous () ;
System . out . print ( element + " " ) ;
}
System . out . println () ;
}
}
✝ ✆

To output του παραπάνω κώδικα είναι:

Original contents of al: C A E B D F


Modified contents of al: C+ A+ E+ B+ D+ F+
Modified list backwards: F+ D+ B+ E+ A+ C+

Με τα generics, δε χρειάζεται να κάνουμε cast σε συγκεκριμένο τύπο αντικειμένου.


✞ ☎
ArrayList < String > al = new ArrayList < String >() ;
System . out . print ( " Original contents of al : " ) ;
Iterator itr = al . iterator () ;
while ( itr . hasNext () ) {
String element = itr . next () ;
System . out . print ( element + " " ) ;
}
✝ ✆

Κάτι πιο εύκολο από τους iterators, είναι το for:each loop. Ο παραπάνω κώδικας γράφεται
ως εξής:
✞ ☎
System . out . print ( " Original contents of al : " ) ;
for ( String element : a )
System . out . print ( element + " " ) ;
✝ ✆

Ε: Πώς μπορώ να διατάξω ή να συγκρίνω αντικείμενα μιας κλάσης με βάση κάποιο πεδίο;
Α: Η java υποστηρίζει διάταξη αντικειμένων σε κάποιες βασικές κλάσεις όπως Integer,
String. Αν θέλουμε να διατάξουμε κάποια αντικείμενα μια κλάσης που φτιάξαμε μό-
νοι μας πρέπει να δηλώσουμε ότι η κλάση υλοποιεί τη διαπροσωπεία Comparable και
να ορίσουμε μια μέθοδο compareTo(), η οποία θα καθορίζει με βάση ποιο πεδίο θέ-
λουμε να γίνεται η διάταξη των αντικειμένων μας. Η μέθοδος αυτή συγκρίνει δύο
αντικείμενα της κλάσης: το τρέχον αντικείμενο και ένα άλλο το οποίο περνιέται σαν
όρισμα στη μέθοδο. Η τιμή που επιστρέφει η μέθοδος ορίζει τη φυσική σειρά ταξι-
νόμησης για τα αντικείμενα αυτής της κλάσης: Εάν το τρέχον αντικείμενο πρέπει
να ταξινομηθεί πριν (πιο πάνω) από το άλλο αντικείμενο, επιστρέφει -1. Εάν το τρέ-
χον αντικείμενο πρέπει να ταξινομηθεί μετά (πιο κάτω) από το άλλο αντικείμενο,
επιστρέφει 1. Εάν τα δύο αντικείμενα είναι ίσα, επιστρέφει 0. Ο κώδικας του συγκε-
κριμένου παραδείγματος βρίσκεται στην Εικόνα 4.

10
✞ ☎
import java . util .*;

public class Shop {


public static class Item implements Comparable {
int id ;
String name ;
float price ;

public Item ( int idIn , String nameIn , float priceIn ) {


id = idIn ;
name = nameIn ;
price = priceIn ;
}

public int compareTo ( Object obj ) {


Item temp = ( Item ) obj ;
if ( this . price < temp . price )
return -1;
else
if ( this . price > temp . price )
return 1;
else return 0;
}
}

public static void main ( String [] args ) {


Item item1 = new Item (0 , " pen " , 2) ;
Item item2 = new Item (1 , " pencil " , 1) ;
Item item3 = new Item (1 , " post - it " , 3) ;

LinkedList catalog = new LinkedList () ;


catalog . add ( item1 ) ;
catalog . add ( item2 ) ;
catalog . add ( item3 ) ;
Collections . sort ( catalog ) ;
Item min_item = ( Item ) Collections . min ( catalog ) ;
System . out . println ( " The cheapest item is " + min_item . name ) ;
}
}
✝ ✆

Σχήμα 4: Μια κλάση οριζόμενη από το χρήστη η οποία επιτρέπει σύγκριση μεταξύ αντι-
κειμένων κλάσης Item.

11
3 Λίγη θεωρία...
Χρήσιμες Δομές Δεδομένων
Vector Είναι δομή δεδομένων παρόμοια με έναν παραδοσιακό πίνακα, εκτός από το γε-
γονός ότι μπορεί να μεγαλώνει, όταν απαιτείται, για να δέχεται νέα στοιχεία, ενώ
μπορεί επίσης και να μικραίνει. Όπως και στον πίνακα, τα στοιχεία ενός αντικει-
μένου Vector μπορούν να προσπελάζονται μέσω μιας τιμής δείκτη. Το πλεονέκτημα
που παρέχει είναι ότι δε χρειάζεται να προβληματιστούμε σχετικά με το μέγεθος του
αντικειμένου κατά τη στιγμή της δημιουργίας του καθώς μεγαλώνει και μικραίνει
αυτόματα, όταν και όπως απαιτείται.
✞ ☎
// Create Vector
Vector v = new Vector () ;
// Create vector with initial capacity
Vector v = new Vector (25) ;
// Create Vector with initial capacity and step
Vector v = new Vector (25 ,5) ;
// Add and remove elements
v . add ( " orange " ) ;
v . add ( " apple " ) ;
v . add (2 , " lemon " ) ;
v . remove (2) ;
// Get the last element added
String s = ( String ) v . lastElement () ;
// Get elements using a pointer
String s1 = ( String ) v . get (0) ;
✝ ✆

Hashtable To Hashtable είναι μια δομή που αντιστοιχίζει κλειδιά (keys) σε τιμές (values).
Αυτό είναι χρήσιμο όταν θέλουμε να προσπελάσουμε τα δεδομένα μέσω ενός συγκε-
κριμένου κλειδιού και όχι ενός ακέραιου δείκτη. Τα κλειδιά και οι τιμές μπορούν να
είναι οποιουδήποτε τύπου αντικείμενα. Αντικείμενα εισάγονται ως τιμές με κάποιο
αναγνωριστικό κλειδί, με το οποίο μετά μπορούν να καλεστούν ή να αφαιρεθούν από
το Hashtable.
✞ ☎
// Create a hashtable
Hashtable numbers = new Hashtable () ;

// Add elements
numbers . put ( " one " , new Integer (1) ) ;
numbers . put ( " two " , new Integer (2) ) ;
numbers . put ( " three " , new Integer (3) ) ;

// Find an element
Integer n = ( Integer ) numbers . get ( " two " ) ;
if ( n != null ) {
System . out . println ( " two = " + n ) ;
}
✝ ✆

12
Modifiers
Οι modifiers είναι δεσμευμένες λέξεις που προσθέτονται μπροστά σε ορισμούς για να
αλλάξει η σημασία τους.

Έλεγχος Πρόσβασης
Σύμφωνα με το προκαθορισμένο επίπεδο πρόσβασης, μία μέθοδος ή μεταβλητή είναι
ορατή σε οποιαδήποτε άλλη κλάση περιέχεται στο ίδιο πακέτο. Για να αλλάξουμε το επί-
πεδο πρόσβασης χρησιμοποιούμε τους παρακάτω modifiers.

private Η μέθοδος ή η μεταβλητή είναι ορατή μόνο μέσα στην κλάση στην οποία ορίστηκε.
Μια μεταβλητή private μπορεί να χρησιμοποιείται από μεθόδους της ίδιας κλάσης,
αλλά όχι από αντικείμενα οποιωνδήποτε άλλων κλάσεων. Μια μέθοδος private μπο-
ρεί να καλείται από άλλες μεθόδους της κλάσης, αλλά όχι από άλλες κλάσεις.

public Η μέθοδος ή η μεταβλητή είναι ορατή σε όλες τις κλάσεις. Η μέθοδος main() μιας
εφαρμογής πρέπει να είναι public.

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

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

final Υποδεικνύει ότι η κλάση, μέθοδος ή μεταβλητή δε θα αλλάξει. Συγκεκριμένα: μια


κλάση final δεν μπορεί να έχει υποκλάσεις, μια μέθοδος final δεν μπορεί να υπερ-
καλύπτεται απ’ οποιεσδήποτε υποκλάσεις, και μια μεταβλητή final δεν μπορεί να
αλλάξει τιμή.

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

4 Eclipse: Ένα πλήρες IDE για τη Java


Εγκατάσταση
Για Windows, Linux, Mac OS X η έκδοση Εclipse Classic είναι διαθέσιμη για download
από τη σελίδα:
http://www.eclipse.org/downloads/

Δεν απαιτεί installation, απλό τρέξιμο του εκτελέσιμου eclipse. Προυποθέτει όμως ήδη
εγκατεστημένη Java και χρόνο ή καλή σύνδεση για το download γιατί είναι 151 ΜΒ.

13
Ξεκινώντας
• Στην εκκίνηση του Eclipse, επιλέγουμε Workspace, δηλαδή τον φάκελο όπου θα απο-
θηκεύονται τα Project που θα γράφουμε και πατάμε OK.
• Κλείνουμε την οθόνη υποδοχής και μπροστά μας βλέπουμε το interface που θα χρη-
σιμοποιούμε.

Λίγα λόγια για το interface


• Αποτελείται απο πολλά παράθυρα. Με διπλό κλικ στον τίτλο ενός παραθύρου ή
Ctrl+M, μεταβαίνει σε full screen mode. Με τον ίδιο τρόπο επανέρχεται πάλι στο
αρχικό του μέγεθος.
• Για να επανέλθει στην αρχική του διάταξη παραθύρων το περιβάλλον επιλέγουμε
Window → Reset Perspective.
• Αριστερά βρίσκεται ο Package Exlorer όπου εμφανίζονται με τα περιεχόμενα τους
όλα τα Project που θα δημιουργούμε και στο κέντρο βρίσκεται ο Editor όπου γρά-
φουμε τον κώδικα.
• Κάτω βρίσκονται τα παράθυρα Problems, στο οποία εμφανίζονται τα λάθη του προ-
γράμματος μας, και Console που αποτελεί την κονσόλα όπου διαβάζεται η είσοδος
και εκτυπώνεται η έξοδος.

Το πρώτο project
Ένα Project στο Eclipse είναι μια ομαδοποίηση των αρχείων που αποτελούν ένα πρό-
γραμμα. Μπορεί να περιέχει .java αρχεία, μεταγλωττισμένα .class αρχεία, .jar αρχεία
και διάφορα άλλα μέρη.
• Επιλέγουμε File → New → Java Project.
• Δίνουμε όνομα στο project (πεδίο Project Name) και επιλέγουμε Finish.
• Mε διπλό κλικ στο project που δημιουργήθηκε, στον Package explorer, βλέπουμε ότι
περιέχει ένα φάκελο src. Με δεξί κλικ στο φάκελο αυτό, επιλέγουμε New → Class για
να δημιουργήσουμε την πρώτη μας κλάση. Με την ίδια μέθοδο μπορούμε αργότερα
να δημιουργήσουμε και άλλες κλάσεις μέσα στο ίδιο Project.
• Στο πεδίο Name δίνουμε όνομα στην κλάση μας (δε χρειάζεται η επέκταση .java) και
προαιρετικά τσεκάρουμε το κουτί με όνομα

public static void main(String[] args)

για να δημιουργηθεί αυτόματα ένας σκελετός για τη main στην κλάση μας. Επιλέ-
γουμε Finish.
• Στον editor έχει δημιουργηθεί η κλάση και περιέχει (αν το έχουμε επιλέξει) μια άδεια
main μέθοδο. Συμπληρώνουμε τον κώδικα μας. Για παράδειγμα για ένα απλό Hello
World :
✞ ☎
public class HelloWorld {
/* *
* @param args
*/
public static void main ( String [] args ) {
// TODO Auto - generated method stub

System . out . println ( " Hello , World " ) ;

}
}
✝ ✆

14
• Με File → Save. (ή Ctrl+S) το πρόγραμμα μεταγλωττίζεται. Είναι ισοδύναμο με την
εντολή javac HelloWorld.java στη γραμμή εντολών. Αν υπάρχει κάποιο error στη με-
ταγλώττιση θα εμφανιστεί στο παράθυρο Problems στο κάτω μέρος της οθόνης, αμέ-
σως μετά το Save.
• Aν δεν υπάρχουν errors τότε,στο HelloWorld.java στον Package Explorer με δεξί κλικ
επιλέγουμε Run as → Java Application.
• Η έξοδος του προγράμματος τυπώνεται στο παράθυρο Console στο κάτω μέρος της
οθόνης.
Για να τρέξουμε το πρόγραμμα που εκτελέσαμε την τελευταία φορά μπορούμε απλά να
πατήσουμε Ctrl+F11.

Χρήσιμα Εργαλεία
Auto-complete Kάθε φορά που γράφουμε κάτι που πιστεύουμε ότι το Eclipse μπορεί να
το “μαντέψει”, αξίζει να δοκιμάζουμε το Ctrl+Space να δούμε αν θα το κάνει. Το Eclipse
βρίσκει πράγματα όπως ονόματα μεθόδων, μεταβλητές σε εμβέλεια, ονόματα κλάσεων ή
ακόμη και να κάνει προτάσεις για μια μεταβλητή που δηλώνουμε.
Για παράδειγμα, αν ορίσουμε ένα String hello και στη συνέχεια πάμε να το εκτυπώ-
σουμε, πληκτρολογώντας απλά το πρώτο γράμμα του String, δηλαδή το h, και Ctrl+Space
θα έχουμε το αποτέλεσμα που φαίνεται στην παρακάτω εικόνα :

Σχήμα 5: Auto-complete example.

Code Templates Αν πληκτρολογήσουμε κάποιες λέξεις κλειδιά, το Eclipse μπορεί να


γεννήσει μόνο του ένα πρότυπο για κάποια κομμάτια κώδικα που χρησιμοποιούμε συχνά.
Για παράδειγμα με τη λέξη main και Ctrl+Space, επιλέγοντας το main-main method παράγε-
ται ένας σκελετός της main ( 6). Ακόμα με sysout και Ctrl+Space παράγεται μια κλήση
System.out.println() και με foreach και Ctrl+Space ένα for loop για τη διάσχιση ενός πί-
νακα ή άλλης δομής. Για να δείτε τα υπάρχοντα templates ή για να δημιουργήσετε νέα
Window → Preferences → Java → Editor → Templates.

Formatter Aν ο κώδικας σας δεν είναι σωστά στοιχισμένος, είναι πολύ απλό να γίνει
αυτόματα, πατώντας απλά Ctrl+Shift+F σε οποιοδήποτε σημείο του κώδικα σας για να
στοιχίσετε όλο το αρχείο κώδικα, ή Ctrl+I για να στοιχίσετε μια συγκεκριμένη γραμμή.

15
Σχήμα 6: Main template example.

Quick Fix Όταν στον κώδικα μας υπάρχει κάποιο compile error, τότε στο εικονίδιο
του αρχείου που έχει το error, αλλά και σε αυτό του project εμφανίζεται ένα κόκκινο Χ.
Μπορούμε να εντοπίσουμε το λάθος στον κώδικα μας με διπλό κλικ στο συγκεκριμένο
πρόβλημα στο παράθυρο Problems (η επιλέγοντας το κόκκινο σημάδι στην στήλη στα δε-
ξιά του editor). Αριστερά από τη γραμμή όπου υπάρχει το error, υπάρχει είτε απλά ένα Χ,
οπότε πρέπει να βρούμε μόνοι τη λύση στο πρόβλημα μας, είτε ένα Χ με μια μικρή λάμπα,
που σημαίνει ότι το πολύ χρήσιμο Quick Fix του Eclipse έχει κάποια ιδέα για το πώς να
μας βοηθήσει να το λύσουμε. Για να δούμε την πρόταση του, πατάμε Ctrl+1 ή κάνουμε
κλίκ στο είκονίδιο αυτό. Τότε, όπως φαίνεται και στην παρακάτω εικόνα, εμφανίζεται
ένα μενού με προτάσεις για τη διόρθωση του λάθους απ’ όπου επιλέγουμε κάποια που μας
ικανοποιεί, εάν υπάρχει.

Σχήμα 7: Quick Fix menu.

Προσοχή : Στο Eclipse η μεταγλώττιση γίνεται όταν κάνουμε Save, επομένως για να
αντιληφθεί ότι διορθώσαμε κάποιο πρόβλημα, μετά τις αλλαγές μας επιλέγουμε πάντα
Ctrl+S ή File → Save.

Setters-Getters Στη Java για να προσπελαύνουμε και να αλλάζουμε τις τιμές private
πεδίων κάποιας κλάσης, χρησιμοποιούμε setters και getters. Το eclipse μπορεί να δη-
μιουργήσει αυτόματα τον κώδικα που χρειάζεται για αυτές τις μεθόδους. Πηγαίνουμε
στο Source και επιλέγουμε Generate Getters and Setters. Στο παράθυρο που εμφανίζεται
υπάρχει η λίστα των πεδίων της κλάσης στην οποία βρισκόμαστε. Μπορούμε να επιλέ-
ξουμε ποια πεδία θέλουμε να δημιουργηθεί αυτόματα κώδικας για μέθοδο Set ή/και Get.
Επίσης μπορούμε να επιλέξουμε πού θα μπει ο κώδικας και το πεδίο πρόσβασης που επι-
θυμούμε να έχει (public,protected,default,private).

16

You might also like