(| Definizione dei campi separati da "."| Sequenza di espressioni separate da "." )All'esecuzione di tale oggetto, prima si crea l'oggetto con i suoi campi, poi viene valutata la sequenza di espressioni. Il risultato dell'esecuzione è il valore restituito dall'ultima espressione. Quando la sequenza è vuota, si restituisce l'oggetto stesso. Per esempio, la valutazione di
(| x = 2. y = 3 | x + y)restituisce l'oggetto che rappresenta l'intero 5. Invece, la valutazione di
(| x = 2. y = 3 |)restituisce un oggetto con due campi, x che contiene 2, e y che contiene 3.
L'oggetto vuoto si rappresenta come (||) o (). La valutazione di
(1+2)crea l'oggetto vuoto e restituisce 3.
Inizializzando un campo con x = valore, si crea un campo x in sola lettura il cui valore è accessibile con il metodo x.
Inizializzando un campo con x <- valore, si crea un campo modificabile, il cui valore è accessibile con il metodo x e che può essere modificato dal metodo x:. Per esempio, la valutazione di
(| x <- 2 | x: (x + 2). x)restituisce 4.
Un campo non inizializzato corrisponde ad un campo modificabile inizializzato con l'oggetto nil. Dunque
(| x. y |)è equivalente a
(| x <- nil. y <- nil |)In Self, ci sono solamente oggetti, dunque nil, true, false, 0, 1, ... sono tutti oggetti.
Dentro un campo si può mettere qualsiasi oggetto. Per esempio, la valutazione di
(| point = (| x <- 1. y <- 2 |) | point x: 2. point x)restituisce 2: si crea un oggetto con un campo point che contiene un oggetto con due campi inizializzati x e y, la prima espressione aggiorna il campo x del campo point con 2, la seconda espressione restituisce il campo x del campo point.
(| x <- 1. y <- 2. sum = (x + y) | x: 3. sum)restituisce 5. Si crea un oggetto con un campo x che contiene 1, un campo y che contiene 2 e un metodo sum senza parametri che restituisce la somma del campo x e del campo y. Il codice associato si valuta nel modo seguente: la prima espressione aggiorna il campo x, la seconda espressione chiama il metodo sum.
Un metodo con un argumento è rappresentato da un campo il cui nome ha il suffisso :. L'oggetto contenuto in questo campo ha esattamente un campo il cui nome inizia con :. Per esempio, la valutazione di
(| x <- 1. incr: = (| :n | x: (x + n)) | incr 2. incr 4. x)restituisce 7. Si crea un oggetto con un campo x inizializzato con 1 e un metodo incr: che prende un argomento n e incrementa il contenuto del campo x con n.
Le operazioni sui numeri non seguono questa convenzione di denominazione dei metodi. Invece di scrivere 1 add: 2, i.e. il metodo add applicato a 1 con argomento 2, si scrive più semplicemente 1 + 2.
La convenzione di usare il suffisso : per il nome di un metodo con un solo argomento spiega quello che accade quando si crea un campo modificabile. La valutazione di (| x <- 1 |) crea infatti un oggetto con un campo x e un metodo x: che prende un solo argomento e permette di aggiornare il campo.
Un metodo può anche avere altri argomenti non prefissati con il simbolo : per simulare variabili locali. Per esempio, la valutazione di
(| x <- 1. y <- 2. sum = (| res <- 0 | res: (res + x). res: (res + y). res) | sum)restituisce 3. Si crea un oggetto con due campi e un metodo senza argomenti che utilizza una variabile locale res per accumulare i valori contenuti nei campi x e y.
Per definire metodi con più di un argomento si utilizzano nomi di metodi con più parole che permettono di separare i diversi argomenti. Ogni parola ha come suffisso il simbolo :, la prima inizia con una minuscola, le altre con una maiuscola. Per esempio, la valutazione di
(| add:With:And: = (|:x :y :z| x + y + z) | add: 1 With: 2 And: 3)restituisce 6. Crea un oggetto con un metodo che prende 3 argomenti. Nel codice, si utilizza questo metodo per calcolare 1+2+3.
(| x <- 1. y <- 2|)è equivalente a
(| x. y | x: 1. y: 2. self)L'applicazione di un metodo su self è implicita. Dunque
(| x <- 1, y <- 2, sum = (x + y) | sum)è equivalente a
(| x <- 1, y <- 2, sum = (x + y) | self sum)Il valore restituito dall'aggiornamento di un campo restituisce self. Dunque, la valutazione di
(| point = (| x <- 1. y <- 2 |) | (point x: 2) y: 3. self)restituisce un oggetto con un campo point che contiene un oggetto che ha due campi: x che contiene il valore 2, y che contiene il valore 3. Il codice si esegue come (((self point) x: 2) y: 3): il risultato dell'aggiornamento point x: 2, i.e. self, è l'oggetto sul quale è chiamato il secondo aggiornamento.
(| x1 = (| x <- 1 |) . x2 = (| y <- 2 |) . x3 = (| parent1* . parent2* . sum = (x + y) |) | x3 parent1: x1. x3 parent2: x2. x3 sum)restituisce l'intero 3. Si crea un oggetto con tre campi:
Nota che il codice seguente non è valido
(| x1 = (| x <- 1 |) . x2 = (| y <- 2 |) . x3 = (| parent1* <- x1 . parent2* <- x2 . sum = (x + y) |)Non si può usare un campo di un oggetto fino a quando la definizione dell'oggetto non è completa.
La regola per selezionare campi e metodi è la seguente. Si guarda nell'oggetto se tale campo o metodo esiste, altrimenti si cerca ricorsivamente negli oggetti contenuti nei campi d'ereditarietà (quelli con il suffisso *). Se esiste un'unica soluzione, questa viene selezionata. Se non esiste soluzione o esiste più di una soluzione, l'esecuzione è abortita con un messaggio d'errore. Per esempio, l'esecuzione di
(| x1 = (| x <- 1 |) . x2 = (| x <- 2 |) . x = (| parents1* . parents2* |) | x3 parents1: x1. x3 parents2: x2. x3 x)genera un messaggio che indica che chiamare x su x3 è ambiguo. Nota che quando si eredita un campo o un metodo, non si cambia il valore di self. Dunque, la valutazione di
(| x1 = (| sum = (x + y) |) . x2 = (| parents*. x <- 1. y <- 2 |) | x2 parents: x1. x2 sum )restituisce 3. La chiamata del metodo sum sul contenuto di x2 trova il suo codice dentro l'oggetto contenuto dentro x1 ma poiché, per l'esecuzione del corpo del metodo sum, self è sempre l'oggetto contenuto dentro x2, i campi x e y sono visibili.
(| x1 = (| x <- 1 |) . x2 = (| parents* . x = (resend.x + 2) |) | x2 parents: x1. x2 x )restituisce 3. La sovrascrittura del campo x incrementa il campo ereditato di 2.
Si può anche dare il campo d'ereditarietà esplicitamente. Per esempio, la valutazione di
(| x1 = (| x <- 1 |) . x2 = (| x <- 2 |) . x3 = (| parents1* . parents2*. x = (parents1.x + parents2.x |) | x3 parents1: x1. x3 parents2: x2. x3 x )restituisce 3. La sovrascrittura del campo x fa la somma dei due campi ereditati.
(| x <- 1. action: = (| :a | x: (a value: x)) | action: [| :n | n + 2]. action: [| :n | n + 4]. x )restituisce 7. Il metodo action è chiamato con due chiusure diverse, la prima incrementa di 2, la seconda di 4.
Una proprietà fondamentale della chiusura è che la sua valutazione si fa nello stesso contesto (il self) che quello della sua definizione. Per esempio, la valutazione del codice
(| x1 = (| x <- 3. action: = (| :n | n + n). eval: = (| :a | a value: x) |). x <- 2. action: = (| :n | n * n). | x1 eval: [| :m | action: m] )restituisce 9. Il metodo action che è chiamato è quello della creazione della chiusura, non quello dell'oggetto contenuto dentro x1 che attiva la chiusura.
La chiusura permette di implementare le strutture di controllo. Per esempio, il condizionale è implementato da un metodo ifTrue: False: sui booleani che prende due chiusure. La prima indica che sarà eseguita se il booleano è vero, la seconda se il booleano è falso. Ciò significa che l'implementazione di questo metodo per l'oggetto true è
ifTrue: False: = (| :codeT . :codeF | codeT value)l'implementazione di questo metodo per l'oggetto false è invece
ifTrue: False: = (| :codeT . :codeF | codeF value)Usando il condizionale, si può definire l'iterazione. Per esempio, la valutazione di
(| res <- 0. from:To:Do: = (| :start :end :action | (start > end) ifTrue: [] False: [action value: start. from: (start + 1) To: end Do: action]) | from: 1 To: 10 Do [| :i | res: (res + i)]. res )restituisce 55, i.e la somma dei numeri da 1 a 10.
Su tutti gli oggetti si possono applicare due metodi:
(| x1 <- (| x <- 1 |) . x2 | x2: (x1 clone). (x1 == x2) ifTrue: [] False: [x2 x: 2]. (x1 x) + (x2 x) )restituisce 3.
Sui booleani, il metodo == è l'uguaglianza di valore e il metodo not restituisce il complemento.
Sui numeri, il metodo == è l'uguaglianza di valore e i metodi +,-,*,<=,< permettono le operazioni usuali sui numeri.
Si può aggiungere nuovi elementi al lobby. Usando questa possibilità, si può riscrivere il codice seguente
(| x1 = (| sum = (x + y) |) . x2 = (| parents*. x <- 1. y <- 2 |) | x2 parents1: x1. x2 sum )in modo più naturale. Una prima valutazione
x1 := (| sum = (x + y) |)dove la valutazione di (| sum = (x + y) |) è associata con il nome x1 al lobby. Adesso, la valutazione di
(| parents* = x1. x <- 1. y <- 2 | sum)restituisce 2.
La base del progetto consiste nell'avere una rappresentazione di un programma Self, i.e. una sequenza di valutazioni come un oggetto Java. Per esempio,
x1 := (| sum = (x + y) |); (| parents* = x1. x <- 1. y <- 2 | sum)potrebbe corrispondere all'oggetto
new Program( new Command[]{ new AddLobby("x1", new Object( new Slots[]{ new Method("sum", new Expr[]{}, new Call("+", new Call("x", new Self(), new Expr[]{}), new Expr[]{ new Call("y", new Self(), new Expr[]{}),})) }, new EmptyCode())), new Object( new Slots[]{ new Pointer("parents", new Call("x1", new Self(), new Expr[]{})), new Field("x", new Int(1)), new Field("y", new Int(2)) }, new Call("sum", new Self(), new Expr[]{})) })La base deve permettere di rappresentare tutti gli esempi presentati in questa pagina.
La base del progetto dovrà obbligatoriamente essere consegnata prima della prova scritta dell'esame.
A partire da questa base, si può completare il progetto realizzando per esempio:
Lo studente dovrebbe seguire le convenzioni di codifica date durante il corso. Per verificare la conformità del progetto, si può usare l'archivio checkstyle.jar. Eseguendo
java -jar checkstyle.jar File.javasi genera un file report che indica la conformità del codice.