Ereditarietà , so what? - era Re: [?

On 6/1/06, Riko [email protected] wrote:

Io non direi proprio che se fa “quack” è una papera. Io direi che se fa
“quack” lo puoi trattare come una papera.

Dire che “è” una papera significherebbe lasciare intendere che un is_a?
Duck darebbe true.

Interessante il disorso sull’ “essere” e sul “trattare come se”.

Più uso ruby, più tendo a delegittimare il valore dell’ essere/is_a in
favore del “trattare come”/respond_to. Anche l’ereditarietà sembra in
questo contesto perdere un pò di senso, e sto osservando il mio stile che
di
conseguenza tende a cambiare.

Voi cosa ne pensate? Qual’è il ruolo dell’ereditarietà in un linguaggio
come ruby?

chiaro scuro wrote:

Più uso ruby, più tendo a delegittimare il valore dell’ essere/is_a in
favore del “trattare come”/respond_to. Anche l’ereditarietà sembra in
questo contesto perdere un pò di senso, e sto osservando il mio stile
che di conseguenza tende a cambiare.

Io purtroppo non ho mai programmato in Smalltalk. Il contatto più
diretto con Smalltalk lo ho avuto tramite ObjectiveC (sono macchista de
fero) e appunto Ruby.

A questo proposito vorrei citare alcune righe (o forse una parafrasi,
tiro fuori il libro, vedo quanto è lungo) da DP della GOF, libro appunto
scritto con esempi in C++ e in Smalltalk.

In tutto il libro viene mantenuta la distinzione introdotta nel primo
capitolo fra e classe.

"Il tipo è il nome usato per denotare una particolare interfaccia.
Diciamo che un oggetto è di tipo Window se accetta tutte le richieste
per le operazioni definite nell’interfaccia Window. Un oggetto può avere
molti tipi e oggetti estremamente differenti condividere un tipo.
Parte dell’interfaccia di un oggetto può essere caratterizzata da un
tipo, e parte da un altro. Due oggetti dello stesso tipo hanno bisogno
solamente di condividere parte delle loro interfacce.

Interfacce possono contenere altre interfacce come sottoinsiemi. Un
tipo è un sottotipo di un altro oggetto se la sua interfaccia contiene
l’interfaccia del suo sopratipo."
(…)
“L’implemenmtazione di un oggetto è definita dalla sua classe”.
(…)
È importante capire la differenza fra la classe di un oggetto e il suo
tipo. La classe definisce come è stato implementato. Definisce il suo
stato interno e l’implementazione delle sue operazioni. D’altra parte il
tipo di un oggetto si riferisce solo alla sua interfaccia, all’insieme
di richieste a cui può rispondere. Un oggetto può avere molti tipi e
oggetti di classi differenti possono avere lo stesso tipo

Naturalmente c’è una una stretta parentela fra classe e tipo. Poichè una
classe definisce le operazioni che un oggetto è in grado di eseguire,
definisce anche il suo tipo. Quando diciamo che un oggetto è un’istanza
di una classe, diciamo implicitamente che l’oggetto supporta
l’interfaccia definita dalla classe.

Linguaggi come C++ e Eifell (e Java ndt) usano le classi per specificare
sia il tipo di un oggetto che la sua implementazione.
".

http://www.akropolix.net/rik0/blogs/2006/06/01/sul-duck-typing-ruby-python-etc/

Voi cosa ne pensate? Qual’è il ruolo dell’ereditarietà in un linguaggio
come ruby?

Per me l’ereditarietà è in primo luogo una questione di DRY,


blog: http://www.akropolix.net/rik0/blogs |
site: http://www.akropolix.net/rik0/ |
forum: http://www.akropolix.net/forum/ |

chiaro scuro wrote:

Interessante il disorso sull’ “essere” e sul “trattare come se”.

Più uso ruby, più tendo a delegittimare il valore dell’ essere/is_a in
favore del “trattare come”/respond_to. Anche l’ereditarietà sembra in
questo contesto perdere un pò di senso, e sto osservando il mio stile
che di conseguenza tende a cambiare.

Voi cosa ne pensate? Qual’è il ruolo dell’ereditarietà in un linguaggio
come ruby?

Dicevo… .per me è una questione di DRY al 90% l’ereditarietà (oltre che
nel caso di modellare la realtà esistente).

Mah… qua è partito un flash di riflessioni un po’ confuse ma corpose:

In tempi di Duck Typing ridiventa certamente di moda la distinzione
fra classi e tipi che si trova per esempio nel capitolo introduttivo di
Design Patterns di Gamma, Helm, Johnson e Vlissides.

La maggior parte delle persone sono oggi abituate a linguaggi come
Java o C++ dove classi e tipi in buona sostanza coincidono (con la
possibile eccezione della programmazione generica).

Un tipo è il nome usato per denotare una particolare interfaccia.
Questo non ha direttamente a che vedere con le Interfaces del Java, per
inciso. L’interfaccia è l’insieme di metodi (o di messaggi, per dirla
con Ruby, Smalltalk o ObjectiveC) cui un oggetto risponde.

Tuttavia un oggetto può implementare diversi tipi, e tipi diversi
possono essere condivisi da oggetti molto diversi fra loro. In questo i
tipi si comportano come le interfacce Java. Qualunque programmatore Java
sa che ci sono Interfacce (uso il maiuscolo per indicare che intendo la
parola nel senso “Javesco”) supportate da diversi oggetti (leggi
Cloneable, per esempio) e che un qualunque oggetto può implementare un
numero grande a piacere di Interfacce.

La differenza è che le Interfacce di Java sono statiche e “legate”
alle classi. Una data classe dice di implementare un certo numero di
interfacce. Le istanze di quella classe avranno quindi per tipo le varie
interfacce.
In Ruby tuttavia questo non accade. Un oggetto ha un dato tipo perché
risponde a determinati messaggi, ma non specifichiamo da nessuna parte
quale sia il tipo. Per il solo fatto di rispondere a certi messaggi un
oggetto è di un dato tipo (a prescindere dalla sua classe). I tipi sono
“informali” (usando la stessa differenza fra protocolli formali e
informali di ObjectiveC – i protocolli formali si comportano
sostanzialmente in modo simile alle Interfacce di Java, quelli informali
sono appunti “non staticamente tipizzati”).

La classe è invece legata direttamente all’implementazione. Citando DP
del GOF
"È importante capire la differenza fra la classe di un oggetto e il
suo tipo. La classe definisce come è stato implementato. Definisce il
suo stato interno e l’implementazione delle sue operazioni. D’altra
parte il tipo di un oggetto si riferisce solo alla sua interfaccia,
all’insieme di richieste a cui può rispondere. Un oggetto può avere
molti tipi e oggetti di classi differenti possono avere lo stesso tipo

Naturalmente c’è una una stretta parentela fra classe e tipo. Poiché
una classe definisce le operazioni che un oggetto è in grado di
eseguire, definisce anche il suo tipo. Quando diciamo che un oggetto è
un’istanza di una classe, diciamo implicitamente che l’oggetto supporta
l’interfaccia definita dalla classe.

Linguaggi come C++ e Eifell (e Java ndt) usano le classi per
specificare
sia il tipo di un oggetto che la sua implementazione"

D’altra parte linguaggi dinamici come Ruby o Python (o Smalltalk) non
dichiarano i tipi delle variabili. Mandano messaggi (o chiamano metodi,
per dirla con Python) agli oggetti denotati dalle variabili e se tali
oggetti supportano il messaggio, tutto
funzionerà.
La differenza fra ereditarietà di classe e ereditarietà di tipo è
importante. L’ereditarietà di classe riguarda definire l’implementazione
di un oggetto attraverso l’implementazione di un altro oggetto. È senza
dubbio un concetto “DRY”. Se ho già definito delle cose (e gli oggetti
sono sufficientemente vicini) posso mantenerle uguali. In Ruby a questo
si associano i mixin che permettono di condividere codice senza andare
in ereditarietà (ma questa è un’altra storia), in Python si può usare
l’ereditarietà multipla per emulare i mixin.

L’ereditarietà di tipo è invece relativa all’utilizzare un oggetto al
posto di un altro. In questo senso siamo più vicini al Principio di
Sostituzione di Barbara Liskov. Un ottimo articolo relativo al LSP si
trova qui (Vacation Rental Hacks). In
buona sostanza possiamo enunciare il Principio di Sostituzione di Liskov
come:

T1 è un sottotipo di T2 se per ogni oggetto O1 di tipo T1 esiste un
oggetto O2 di tipo T2 tale che per ogni programma definito in termini di
T1 il comportamento del programma è invariato sostituendo O2 al posto di
O1.

Confondere ereditarietà di tipo e di classe è facile. Molti linguaggi
non hanno alcuna distinzione fra le due. Anzi, vengono usate le classi
per definire i tipi. Molti programmatori per esempio vedono il LSP solo
in relazione alle sottoclassi (in effetti in C++ è importante fare in
modo tale che gli oggetti si comportino “bene” in relazione al LSP, in
quanto è sempre possibile fare puntare un puntatore o una reference di
un supertipo ad un oggetto del suo sottotipo).

In realtà il principio di Liskov è una cosa molto severa. Due
oggetti sono sostituibili secondo Liskov se davvero (limitatamente al
tipo comune) si comportano allo stesso modo, e viene naturale pensarli
come classe - sottoclasse (anche se effettivamente questo non è
necessario).
Il DuckTyping è meno severo: non chiede che il programma abbia lo
stesso comportamento. Due oggetti sono appunto sostituibili se
rispondono alle stesse cose, anche se rispondono in modo diverso. Non ha
nulla a che fare con il Design By Contract: è molto più vicino a
quello che in C++ si fa con i templates (che alcuni in effetti vedono
come il corrispettivo statico del Duck Typing).

Tornando a noi, se un oggetto si comporta secondo un certo tipo
(ovvero risponde ai metodi propri di quel tipo), allora trattiamolo come
tale. Da cui: se si comporta come una papera, trattiamolo come una
papera (Duck Typing, appunto).

Un linguaggio come Ruby rende molto problematico considerare come
stretta la relazione fra tipi e classi. In ogni punto del programma
(anche se non è sempre buona pratica farlo) possiamo cambiare il tipo di
tutti gli oggetti di una data classe (aggiungendo o togliendo metodi
alla stessa), o singolarmente ad un singolo oggetto. Sintomatico è in
questo senso avere deprecato il metodo Object#type in favore di
Object#class.


blog: http://www.akropolix.net/rik0/blogs | Enrico F.
site: http://www.akropolix.net/rik0/ | RiK0
forum: http://www.akropolix.net/forum/ | Usenet: [email protected]

Mi hai steso :slight_smile: Stavo per dirti di postarla su un blog, ma vedo che
hai
già fatto.

Ma dimmi, allora 'sta ereditarietà… la usiamo solo come meccanismo per
non
ripetere implementazione e per non sbatterci a fare delegation? Perchè non
usare solo mixin e delegation e delle pure interfaces (dichiarazioni di
metodi/respond_to) da poter dichiarare come contratto su alcuni
argomenti
dei metodi?

L’ereditarietà ci rimane allora solo come una ‘sporca’? un modo
conveniente
di poter dire queste cose (riutillizzo implementazione, delegation,
dichiarazione di supporto di certi metodi) in modo rapido? Esistono
altri
modi convenienti di dire cose che sono ortogonali
all’ereditarietà?
PS: è una provocazione, non datemi addosso e proviamo a vedere dove ci
conduce questo pensiero

On 6/1/06, Riko [email protected] wrote:

chiaro scuro wrote:

Dicevo… .per me è una questione di DRY al 90% l’ereditarietà (oltre che
nel caso di modellare la realtà esistente).

Mah… qua è partito un flash di riflessioni un po’ confuse ma corpose:


Chiaroscuro

Liquid Development: http://liquiddevelopment.blogspot.com/

chiaro scuro wrote:

Mi hai steso :slight_smile: Stavo per dirti di postarla su un blog, ma vedo che hai
già fatto.

Si beh… in realtà prima mi sarebbe anche piaciuto se in lista se ne
fosse parlato un poco. Nel senso che era una bozzettona, l’ho messa su
tanto per me, ma imho lavorandoci su un poco sarebbe venuta fuori una
bella cosa sgrezzata.

Ma dimmi, allora 'sta ereditarietà… la usiamo solo come meccanismo per non
ripetere implementazione e per non sbatterci a fare delegation? Perchè non
usare solo mixin e delegation e delle pure interfaces (dichiarazioni di
metodi/respond_to) da poter dichiarare come contratto su alcuni argomenti
dei metodi?

Credo per ragioni di praticità. Quando in effetti il modello “reale” ha
ereditarietà non vedo perchè non usarla (a parte i casi particolari,
tipo il quadrato e il rettangolo).

Credo che tutte le “regole” rigide falliscano in un numero non
trascurabile di casi. Meglio essere flessibili: capire che
l’ereditarietà non va usata sempre (e davvero, è difficile pensarci in
certi linguaggi… parecchi guru hanno dovuto dire la loro per esempio
per spiegare che certe catene di ereditarietà erano troppo lunghe).

L’ereditarietà ci rimane allora solo come una ‘sporca’? un modo conveniente
di poter dire queste cose (riutillizzo implementazione, delegation,
dichiarazione di supporto di certi metodi) in modo rapido? Esistono altri
modi convenienti di dire cose che sono ortogonali all’ereditarietà?

IMHO bisogna soprattutto sfatare il mito che la programmazione ad
oggetti “is about inheritance”. E poi comunque resta il fatto che
l’ereditarietà di sottotipo è comunque molto importante.

Quindi l’obiezione sarebbe soprattutto sull’ereditarietà di classe. Ma
anche li, dopo tutto… a patto di non esagerare.
Altro esempio classico è fare ereditare un aereo da un motore. In fondo
con un po’ di buon senso, tenendo presente se effettivamente le
relazioni reali sono is_a has_a o altro ancora, ce la si può cavare.


blog: http://www.akropolix.net/rik0/blogs | Enrico F.
site: http://www.akropolix.net/rik0/ | RiK0
forum: http://www.akropolix.net/forum/ | Usenet: [email protected]