Sul Duck Typing (Ruby, Python, etc…)
June 1st, 2006
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 . 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.
3 Responses to “Sul Duck Typing (Ruby, Python, etc…)”
1gabriele
June 2nd, 2006 @ 09:11
ti buttò la altre due o tre idee, vorrei avere tempo per parlarne a lungo ma non posso..
Esiste una specie di Duck Typing statico, ed è il cosidetto sistema “strutturale”, in contrapposizione al “nominale”. In java devo dirti che un oggetto implementa un’interfaccia, in altri linguaggi statici non è necessario, il che è un vantaggio notevole a ben pensarci, perché non fa altro che permettere la proliferazione di micro-tipi composti da un paio di metodi, ed è un principio di design assodato che un’interfaccia deve essere piccola
A proposito di distinguere classe e tipo, Sather è un linguaggio che divide completamente le due cose,e se non ricordo male anche in Eiffel adesso dovrebbe esserci un meccanismo per la cosidetta non-conforming inheritance.
2Enrico Franchi
June 9th, 2006 @ 09:47
Sono senza dubbio d’accordo sul “problema” delle interfacce forzate.
Per Sather ci darò un occhio, con il tempo. Eiffel non so nemmeno più a che punto sia. Aveva tante buone idee, ma non si è affermato.
Di recente alcuni sostenitori lo avevano indicato come possibile sostituto del C nello sviluppo di GTK e GNOME, facendo notare alcune delle sue cose più belle.
Sembra interessante, in effetti. Ma non so quanto *adesso* con oggetti come Ruby e Python perfettamente funzionanti.
3ADSL in Via Nazionale (Padernello di Paese - TV) » duck typing
June 9th, 2007 @ 05:12
[...] un interessante digressione sulla distinzione tra tipo e classe e le differenze di significato di “interfaccia” fra java e altri linguaggi. >> [...]
Leave a Reply