Variante 2: Operazioni estendibili
Note preliminari sul documento
Questo documento intende essere punto di riferimento in fase di sviluppo e germe della documentazione che accompagnera' il progetto.
Questo documento e' presente in duplice copia: fra i sorgenti in modo da essere facilmente editabile e leggibile in fase di sviluppo, e sul wiki e fra i sorgenti per aumentarne la facilita' di utilizzo. Dal momento che i commenti sul wiki possono essere lasciati anche da chi non segue direttamente lo sviluppo, l'utilizzo del file presente nei sorgenti e' da ritenersi una copia della pagina wiki; pertanto chi effettua modifiche al primo si dovra` preoccupare di sincronizzarlo tempestivamente con il secondo.
In caso di divergenze di opinioni su una sezione e' preferibile il commento pittosto della brutale modifica, in modo da tenere traccia delle considerazioni fatte. Convenzioni utili per i commenti:
- posporli alla sezione a cui fanno riferimento;
- usare il carattere corsivo per indivduarli facilmente;
- premettere il nome dell'autore accompagnato dalla data.
Specifiche
Si assuma che il numero e la struttura dei costrutti del linguaggio MiniScheme sia dato e non soggetto a cambiamenti:
- Si fornisca l'implementazione di tutti i costrutti originali
Si assuma che il numero delle operazioni polimorfe sia destinato ad evolvere rapidamente
- Si fornisca un interprete e una prettyPrint modulari
Il modello deve supportare la definizione modulare di operazioni.
- Si vuole supportare la definizione di operazioni modulari sia sotto forma di visitors che di iteratori
- Il modello deve fornire API di manipolazione specifiche e generiche
- L'aggiunta di una operazione non deve richiedere nessun
cambiamento al modello
20060819-1310 [SoujaK] Le operazioni non devono pertanto risiedere all'interno del modello, ma all'esterno, negli specifici visitor/iterator. Il modello esporra' nella sua interfaccia la classicaaccept(AbstractVisitor)
. Raggruppare l'interfaccia dei visitor permette al modello di essere totalmente indipendente dall'aggiunta di nuove operazioni.
20060819-1617 [SoujaK]
E per quanto riguarda gli iteratori?
Le scelte di design devono essere imposte al cliente.
- Si vuole imporre al cliente l'uso dei pattern stabiliti (in particolare quelli creazionali)
- Il codice e i pattern non conformi a questa specifica non
devono fare parte del sistema fornito
20060818-1501 [SoujaK] Quali pattern creazionali? E' necessario il redesign di questa sezione, oppure la vecchia Factory adempie gia' a questo compito? Volendo si potrebbe pensare a soluzioni alternative, ma reputo la cosa a bassa priorita'.
Diaro di bordo: dubbi, problemi e conseguenti scelte
Il modello
200606??-????
Branch
: diventeranno figlie delle Expression
?
20060820-1238 [SoujaK]
Natura del modello: la struttura solitamente pensata come un albero di
espressioni e' rappresentata grazie a campi interni degli oggetti che compongono
l'aggregato. Si tratta di List
o ArrayList
nel caso di nodi non terminali,
altrimenti tipi specifici (bool, String, int) a seconda del caso. Il problema
che sorge e' come riuscire a far attraversare un aggregato cosi' eterogeneo da
un Iterator.
Forse potrebbe avere senso fare come e' stato fatto per Variante 1, cioe'
far diventare i campi interni degli SchemeValue
. Si tenga pero' presente che lo
SchemeValue? e', di per se', gia' un prodotto della valutazione.
20060924-1633 [SoujaK]
Un pattern ancora non considerato nello svilupo di questa variante e'
singleton, che si rivela applicabile non solo alla vecchia Factory
(si
richiede l'imposizione al cliente dei pattern creazionali), ma anche nei
nostri visitor: Interpreter
e PrettyPrinter
.
20060926-1355
L'eterogeneita' dell'albero rispetto al modello (inteso come espressioni) pone
problemi rilevanti, apparentemente non risolvibili se non a spese dell'eleganza
progettuale finora perseguita.
La soluzione scelta e' la creazione di un supertipo di ogni entita' del modello,
il che si traduce nella creazione dell'interfaccia SchemeEntity
. Si e' inoltre
pensato di aggiungere anche una classe astratta SchemeEntityAbstract
che
permette un'implementazione di default.
API di manipolazione generica
20060725-1758 [gnappo]
Le API di manipolazione generiche sono dei getter e setter che agiscono
sull'intero modello. Per approfondire seguite il
link.
Se non bastasse vi rimando all'
esempio
del prof. Solmi: prestate attenzione alle classi LanguageEntity e alle varie
Abstract* (sono particolarmente chiarificatrici).
20060818-1918 [SoujaK]
Le API di manipolazione generica sul modello possono essere implementate in un
genitore comune che fornira' l'operazione di default (verosimilmente il
sollevamento di un eccezione).
20060923-1120 [Roma]
Grazie alle API di manipolazione generica possiamo gestire in tutto per
tutto quelle che Solmi chiama [LanguageEntity]. Dando un'occhiata alla
suddetta classe si può notare come questa associ un indice
sia all'entità padre sia ai suoi figli.
La funzione getType()
inoltre restituisce il tipo di entità; ancora
però non riesco a capire bene a cosa ci possa servire il sapere di che
tipo è un'entità.
Gli indici servono per navigare il sottoalbero di espressioni quindi
in ogni istante posso sempre sapere a che livello dell'albero
mi trovo (dare un'occhiata alla classe [EntityIterator]).
20060925-1842
Come saranno implementate queste API generiche? In particolare, restituiranno
i singoli figli dell'espressione (selezionati grazie ad una enum), oppure
una lista che li contiene?
20060927-1813
Le API di manipolazione del modello (getters e setters) soffrono della
stessa incoerenza di Java riguardo ai tipi: i valori di tipo
Scheme[Expression | Value][Int | Bool]
mantengono campi interni sotto forma di
tipi primitivi mentre il resto del modello sfrutta oggetti. Tale eterogeneita'
si manifesta in una strategia di passaggio/ritorno dei parametri che e',
rispettivamente, per valore e per riferimento.
API di manipolazione specifica
20060820-1332 [SoujaK]
Le API di manipolazione specifica (getter
e/o setter
) diventano piu' che mai
necessari in questa variante, perche' le operazioni sul modello devono essere
effettuate all'esterno di esso. Affinche' i Visitor
riescano nel loro intento
hanno ovviamente bisogno di conoscere l'oggetto che intendono visitare.
Visitor
20060820-1817 [SoujaK]
Durante l'implementazione dei Visitor si pone un problema di natura progettuale:
chi e` responsabile dell'attraversamento della struttura a oggetti?
La possibilita' di utilizzare un Iterator esterno e` stata discussa da tutta la
squadra e non verra' realizzata sebbene si lasci tale facolta' al cliente.
Rimane pertanto da chiedersi se chi si fa carico dell'iterazione e' la stessa
struttura ad oggetti (come accade sovente a detta della GoF) oppure i Visitor
(si veda "GoF - Design Pattern - pagg 340-1).
Il dott. Solmi, nel suo esempio sui Visitor
snatura il significato di
Visitor
declassandoli a miseri proxy
usati per evitare di esporre
pubblicamente le operazioni come evaluate
o prettyPrint
. La richiesta di
chiarimenti, inoltrata al professore esattamente un mese fa (20060720-1854), non
ha ancora avuto risposte.
A mio avviso demandare l'attraversamento della struttura a oggetti Visitor
e'
la soluzione migliore in termini di eleganza e chiarezza dell'algoritmo di
visita, dal momento che la sua implementazione risiede, in tutte le sue
componenti, proprio fra i metodi di visita degli oggetti.
20060821-1316 [SoujaK]
Nell'implementazione dell'interfaccia del Visitor
ho preferito usare nomi
diversi per i vari metodi (visitA()
, visitB()
...), piuttosto che un unico
metodo visit()
sovraccaricato. La chiarezza del codice aumenta.
20060827-1326 [SoujaK]
Cosi' facendo ci si gioca la possibilita' di trattare genericamente con delle
Expression. Nell'implementazione delle operazioni polimorfe questa diventa quasi
una necessita' poiche' costringerebbe a controllare la classe di appartenenza
dell'oggetto che vogliamo visitare: si parla di if-else in cascata. Per venire a
capo della cosa il dott. Solmi aggiunge ai suoi costrutti un metodo ad-hoc
che permette di identificare il costrutto, mettendolo in corrispondenza con una
enumerazione.
20060826-2018 [SoujaK]
Sto iniziando l'implementazione del VisitorInterpreter
: non devono forse
riuscire ad attraversare qualsiasi componente del modello, compresi gli stessi
Value e le Definition, dal momento che si riferisce alle operazioni sul modello
come polimorfe?
20060827-1744 [SoujaK]
Come si gestisce il passaggio delle informazioni indispensabili al processo di
valutazione, in relazione al pattern Visitor
?
Il modo piu' immediato e' inserire, dove necessario, un Environment fra i
parametri di ingresso e un Value fra quelli di uscita nei metodi accept() e
visit(). Cosi' si diminuisce l'omogeneita' delle varie Expression e si sporca
l'interfaccia classica di questo pattern.
20060827-1905 [SoujaK]
La soluzione per cui ho optato e' quella di utilizare campi interni al visitor.
Aggiungere ai Visitor
uno stato interno e' una pratica piuttosto diffusa che
non intacca l'eleganza del pattern, pur aumentandone le potenzialita':
- nel caso della prettyPrint potrebbe essere lo stato potrebbe essere sfruttato come contatore dei livelli di indentazione;
- l'
Interpreter
invece potrebbe mantenere internamente unValue
e unEnvironment
.
20060828-1328 [SoujaK]
Ecco come ho pensato debbano andare le cose per la visita dell'Interpreter
:
// prima: value = expression.evaluate(environment); // ora: interpreter.setEnv(environment); expression.accept(interpreter); value = interpreter.getResult;
Non ho ancora approfondito come le successive invocazioni di accept()
si
comportino durante la ricorsione, e se i due campi interni rimangano coerenti;
tantomeno ho avuto modo di testare praticamente il codice prodotto.
La mancanza di eleganza che i lettori piu' sofisticati hanno sicuramente notato
potra' in futuro venire nascosta da un metodo proxy di questo genere:
SchemeValue interpret(SchemeExpression expr, SchemeEnvironment env)
20060829-20:14 [SoujaK]
E' sorto qualche problema attorno alle chiusure, dal momento che il loro metodo
applyTo()
e', per certi versi parte del meccanismo di valutazione. A seguito
di questa considerazione, si e' pensato di far eseguire l'applicazione delle
chiusure direttamente all'Interpreter
, nel suo metodo di visita delle
ExpressionApply
.
Rimane insoluta una questione: come innescare la valutazione, ora che il metodo
applyTo()
e' da considerarsi deprecato? Qualora si abbia il tempo di rendere
utilizzabile l'intero sistema sara' necessario elaborare una soluzione.
20060927
Invece di mantenere traccia dei vari ambienti locali (in caso di gerarchie di
ambienti locali) sfruttando l'annidamento delle chiamate di visit()
, si
potrebbe guadagnare in eleganza utilizzando una cara e vecchia ... pila.
20060928-1736
La profondita' della gerarchia degli ambienti e' troppo bassa da giustificare un
aumento della complessita' del codice.
20060928-1910
L'interpreter
portebbe essere arricchito con un metodo che, preso in input un
programma (inteso come lista di definizioni), faccia partire la valutazione.
Il che significa iterare su tale lista arricchendo l'ambiente globale per poi
innescare la valutazione di ogni applicazione di funzione ExpressionApply
.
20060929-0830 [SoujaK]
Riguardo a declare()
e define()
, esse sono, in realta' parte del meccanismo
di valutazione, dal momento che, di fatto, si preoccupano della chiamata
accept()
sull'espressione parte della definizione. Considerando inoltre che
l'ambiente e' parte non marginale del processo di valutazione, reputo utile lo
spostamento dei due metodi all'interno del visitor. [NB: e' necessario
chiarire se l'accorpamento dei due metodi non precluda l'applicazione di qualche
strategia (si pensi ai doppi declare... )]
La proposta e'da inserire nella lista degli enhancment a bassa priorita'.
Iterator
200606??
Gli iterator sono da intendersi come esterni.
20060925-1606
Come realizzare l'iteratore sul modello? Le possibilita' analizzate sono:
- stack di "puntatori", in cui mantenere informazioni necessarie alla risalita dell'albero;
- iteratori sulle singole espressioni che ne scorrrono i figli (di primo grado),
la composizione dei quali permette la realizzazione da parte del cliente di
veri e propri iteratori che scorrono l'intero modello. L'interfaccia
iterable
dovra' pertanto essere estesa dall'interfaccia dell'intera gerarchia di espressioni.
20060927-2126
In merito alla questione iteratori infine si e' deciso di optare per la seconda
scelta. Operando in tale senso si assicura indipendenza tra iteratori sul
modello e struttura delle entita', in quanto il compito di attraversamento di
queste ultime e' demandato agli iteratori opportuni. Inoltre un cambiamento
dei cosiddetti EntityIterator non pregiudica il funzionamento del ModelIterator,
al massimo ne influenza la visita.
Il cliente smaliziato potra' quindi definire nuovi ModelIterator semplicemente
utilizzando gli iteratori sulle entita' pur ignorandone la struttura.
Operazioni aggiunte: contrib
Questo e' il posto in cui rendere note le proprie scelte.