Wednesday, May 16, 2007 

Metaprogramming, OOP, AOP (Part 1)

In a previous post, I introduced the concept of [Layered] Virtual Machines a a useful metaphor for aspect oriented design. I've further elaborated the concept in a later post, but I understand it's slightly elusive :-), so a realistic example could be beneficial.

In this post, and in the following 2, I'll take a look at the same problem under the metaprogramming, OOP, and AOP perspective. While doing so, my aim won't be to evaluate the respective merits of each approach. Right now, I'm more concerned about how any given approach will influence the reasoning process. Also, for AOP, I'd like to show how the Virtual Machine metaphor could be useful to think at the appropriate abstraction level.

I've chosen a problem that I hope will be familiar to many programmers: localization. To keep the discussion focused, I've further restricted the problem to GUI localization, and within that, only to text localization (leaving aside, for instance, bitmaps that may need to be localized as well).


As Gregor Kiczales said, The key property that all metaprograms share is that they explicitly manipulate other programs or program representations. Examples of metaprogramming include a code-generating IDE, so let's take a look at how Visual Studio 2005 is dealing with localization in .NET (Windows Form).

When you add controls to a Windows Form application, Visual Studio generates code (that's, of course, metaprogramming). If you add (for instance), a button, a listbox with a few preloaded values, and a listview with a few column headers, you'll get code like:

this.button1 = new System.Windows.Forms.Button();
this.listBox1 = new System.Windows.Forms.ListBox();
this.listView1 = new System.Windows.Forms.ListView();
this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
this.columnHeader3 = new System.Windows.Forms.ColumnHeader();
// button1
this.button1.Text = "button1";
// listBox1
this.listBox1.Items.AddRange( new object[] {
"value3"} );
// column headers
this.columnHeader1.Text = "Column1";
this.columnHeader2.Text = "Column2";
this.columnHeader3.Text = "Column3";

String literals are placed directly into the code. That makes the code itself fast and lean, but won't help much with localization. In a while, we'll take a look at how the code changes if you set the Localizable property to true in the container (usually the form). The code changes, because in Visual Studio / Windows Forms, localization is being dealt with at the metaprogramming level. Is that a sound approach? As I said, that's out of scope, but to keep a long story short, metaprogramming at the IDE level has some nice properties: most notably, very high integration with the development tool itself. Assuming, of course, that you consider localization as an integral part of software development (I do not necessarily agree with that).

Leaving the soundness question aside, if you're a designer thiking about providing metaprogramming-based support for localization, you'll have to look into a few common metaprogramming issues:

- the programmer may inadvertendly tamper with the generated code. Microsoft answer was to introduce partial classes in .NET, which are obviously just a partial solution, but not that bad anyway.

- the programmer may have problems debugging some (possibly) obscure, machine-generated code. This was certainly an issue for ugly macro-based, forward-only code generators like the old style ATL COM wizards. For the relatively simple job of static GUI creation, the issue is rather moot.

- extensibility: what if the programmer rolls his/her own GUI controls? How will they participate in code generation, or ultimately, in string localization?

The latest issue is probably the single most important problem affecting many metaprogramming tools. When we design a metaprogramming environment, we're basically building a code generator. The input to the code generator is a language in itself, possibily a visual language. In this case, the input is the (visual) selection of a control from a palette of tools, plus a set of properties.

In the most naive approach, the code generator needs to "know" the GUI, that is, each control and its localizable properties. It needs to know that a button has a Text property that must be localized. It needs to know that a listbox may have design-time values that must be localized. It needs to know that a listview may have headers, and that they in turn have a text, which must be localized.

Under the naive approach, the designer of the metaprogramming environment is thinking about a closed world: the set of supported controls is fixed, because knowledge about the localization of each control is built inside the IDE itself. Needless to say, that's quite easy to do, but very limiting.

Once you open up to user-defined controls, you can basically follow one of the following approaches:

- The programmatic approach. Here the component takes part in code generation. Each component with localizable properties will have to implement some "code generation" interface, defined in what can be considered a metaprogramming framework. The IDE will call the appropriate functions, but the component will ultimately be responsible for code generation. This approach is very flexible, but uncomfortable. There is also the nontrivial issue of having code generated in a specific, high-level language (like C#; but what if I'm using the component in a VB.NET form?) or directly into a neutral, platform defined, low-level language (like MSIL).

- the declarative approach. Here the component will "tell" the metaprogram (through some mechanism yet to be defined) which properties need to be localized. The metaprogram will then work uniformously over all those properties, perhaps guided by type (e.g. a string Vs. a collection of strings). If you're familiar with .NET, attributes may come to mind as a way to "tell" the metaprogram about localizable properties.

Let's take a look at how Visual Studio 2005 deals with localized strings. Here is the former example, with localization turned on:

this.button1 = new System.Windows.Forms.Button();
this.listBox1 = new System.Windows.Forms.ListBox();
this.listView1 = new System.Windows.Forms.ListView();
this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
this.columnHeader3 = new System.Windows.Forms.ColumnHeader();
// button1
resources.ApplyResources( this.button1, "button1" );
// listBox1
this.listBox1.Items.AddRange( new object[] {
resources.GetString("listBox1.Items2")} );
resources.ApplyResources( this.listBox1, "listBox1" );
// listView1
resources.ApplyResources( this.listView1, "listView1" );
// column headers
resources.ApplyResources( this.columnHeader1, "columnHeader1" );
resources.ApplyResources( this.columnHeader2, "columnHeader2" );
resources.ApplyResources( this.columnHeader3, "columnHeader3" );

The underlying idea is a mixture of reflection and metaprogramming. Metaprogramming is applied as, for each control, a call to ApplyResources is generated. Also, through metaprogramming, strings are not stored inside code anymore. Without going into much details, strings will be placed in resource-only DLLs (known as satellite assemblies), one for each language. Note that the choice of storing the strings into DLLs has nothing to do with the metaprogramming approach - they could have chosen to store strings into an XML file (I would have appreciated that).
Reflection is then applied inside a unique (uniform) ApplyResources method of ComponentResourceManager . This method looks for properties tagged with the Localizable attribute, then gets the localized text from the satellite assembly. If you want to find out more, you can read Improvements to Localization in Visual Studio 2005 and Localization in Whidbey. The latter describes also the original localization model for Visual Studio, which was based on design-time reflection, not on run-time reflection.
That's not the full story, however: note how the Items collection is being directly localized into the generated code, not through reflection. This seems more like a quirk, so let's skip that for now. Also, notice how ApplyResources is needlessy called on the listview as well, although there is nothing to be localized - only the headers really need to be localized. Again, this seems more like a small quirk, something that could be easily fixed.

At the end of the day, the declarative approach to metaprogramming extensibility is quite handy: you only have to tag your localizable properties with the right attribute, and the metaprogram will do the rest (supported by the run-time framework, especially under the run-time reflection model).
As usual, you can only get that far with a declarative model. Here, the need to localize a finite set of types (like string) is a huge simplification. Again, a good designer will look for the right balance of extensibility and rigidity, possibly providing alternative ways to deal with the most complex situations.

Just a final comment: there is a common problem with the adoption of a metaprogramming (or AOP with static weavers) approach to -ilities. Since the code dealing with the -ility (like "localizability") is either generated or woven as part of the mainstream code, the application will always be "bloated" by the ility, even if you don't need it.
Consider an application that will be used 90% of the cases in English-speaking countries, 10% in others. You want to provide localization, but ideally, you shouldn't pay for it when not needed (90% of the cases). In fact, a popular paper from David Parnas was "Designing software for ease of extension and contraction". Somehow, most people got the "extension" part, but just a few got the "contraction" part. Moore's law did the rest :-).

Next time, I'll take a look at how we could approach the same problem under a strictly object-oriented approach - no metaprogramming allowed. Then we'll move to AOP and the Virtual Machine metaphor.

Labels: ,

La sintassi del C#, il generatore di codice per la UI, istruzioni tipo this.button1.Text = "button1";... sembrano gli anni '90! :-)
Cari amati vecchi tempi...

(Forse solo perché avevamo ancora trent'anni :-))
Gentile Dott.Pescio,
stimolato dalla sua interessantissima
discussione, provo a "buttar giu'" qualche riflessione.
Come gia' nel mio commento di qualche settimana fa, premetto che non sono un professionista della programmazione e che la mia "forma mentis" risente molto della lettura di Meyer.

Credo vi possa essere una "terza via", che richiede pero' un approccio alternativo a livello di linguaggio. Possiamo infatti rendere parte integrante della definizione del linguaggio che useremo come target un insieme di classi "kernel": non quindi "classi di libreria", ma classi che contibuiscono alla semantica "di base" del linguaggio.
A questo punto, vincoliamo ogni framework che si ponga a livello di metaprogrammazione o code-generation a basarsi unicamente sulle "classi kernel".
In tal caso i widget "di libreria" cosi' come i widget "alternativi" sviluppati da noi devono derivare da una classe widget kernel.
Indubbiamente, affinche' il tutto sia fattibile in pratica si rende pressocche' indispensabile l'eredita' multipla (e nella sua forma piu' completa alla Eiffel, non quella evirata delle interfaccie).

L'approccio mi sembra ragionevole da un punto di vista teorico. Da un punto di vista pratico, occorre sottolineare che sono indispensabili:

1) un ottimo supporto a livello di potenza espressiva del linguaggio target

2) quel che piu' conta (e tocco un argomento largamente generalizzabile su cui mi piacerebbe avere la sua opinione): una profondissima fore-knowledge da parte di chi definisce il linguaggio e le classi kernel. L'estensibilita' infatti sarebbe strettamente limitata alla definizione delle classi-kernel: una definizione incompleta o mediocre costituirebbe il chiodo nella bara di tutto questo discorso.

Se ha avuto la pazienza di scorrere le righe fin qua senza annoiarsi le faccio i miei piu' vivi complimenti! :)
Grazie mille per l'attenzione e per i tanti spunti di riflessione che ci offre sempre!

Guido Marongiu
citrullo: purtroppo la mia abilita' nella lettura della mente e' piuttosto limitata :-), e non so se ho capito cosa intendi... cosi' divago un po' (nel senso che con il post originale quanto segue non ha molto a che vedere)

Se usavo nomi migliori, facendo si' che il codice diventasse ad es.
okButton.Text = "Ok" ;
era sempre anni 90?

Sempre a latere, io non uso il "this." nel mio codice, ma per un generatore, ci sono almeno due buoni motivi (correlati) per farlo.

Sul fatto invece che per descrivere la GUI il visual studio generi codice C#, piuttosto che codice in un altro linguaggio (il vecchio .RC di Windows, lo XAML del WPF, ecc), sarebbe una discussione lunga e totalmente off-topic... certo e' che quando disegni un bottone, da qualche parte quella conoscenza va codificata :-).
Guido, possiamo darci del tu? Inizio io :-).

Occorre distinguere il problema circoscritto che sto usando come esempio (la localizzazione delle stringhe) da un problema piu' complesso ed ambizioso (una libreria GUI). Il tuo commento mi pare che si spinga piu' nella seconda direzione.

Ora, una volta che assumiamo di avere alcune classi (su libreria o "kernel" ci torno piu' avanti) da cui derivare, possiamo pensare ad un supporto per la localizzazione in ottica OO. Questo puo' essere banalmente basato su ereditarieta' di implementazione, o di interfaccia, o esternalizzato tramite reflection, o basato su una combinazione di tecniche. Di questo parlero' nel mio prossimo post.
Per quanto riguarda la problematica specifica della localizzazione di stringhe, pero', non credo che spingere le classi GUI "dentro" il linguaggio permetta di essere particolarmente piu' flessibili o potenti. Al solito, servirebbe un esempio concreto di linguaggio GUI-aware su cui ragionare.

Rimanendo invece sul tema generale del linguaggio "custom", in linea di principio io sono profondamente contrario. Un buon linguaggio di programmazione e' quello che definisco sempre (conscio di non aver scelto una terminologia molto espressiva) un linguaggio a due livelli.
Ribaltando la cosa in un'ottica costruttiva: se qualcuno mi mostrasse come ottenere un risultato di particolare pregio, rendendo alcune classi GUI parti integranti del linguaggio, la vera domanda che mi porrei sarebbe: come dovrei cambiare [se devo cambiarlo] un linguaggio general-purpose in modo tale da poter ottenere un analogo risultato rimanendo general purpose e scrivendo una opportuna libreria?

Storicamente, la carta di un linguaggio di alto livello business-aware e' stata tentata molte volte. I risultati, presi in isolamento, non sono stati neppure malvagi, se li valutiamo nel contesto in cui sono nati. Ma sono sempre linguaggi estremamente limitativi, dove da un lato abbiamo figure "divine" che creano il linguaggio e la libreria di base, dall'altro i tontoloni che lo dovranno usare (noi). E' un approccio che personalmente disapprovo. Piu' avanti (finita la trilogia) mi piacerebbe far vedere come il C#, con LINQ, stia andando nella direzione di un linguaggio a due livelli (dove molti vedono solo uno shift verso il paradigma funzionale). E' una delle ragioni per cui, superando alcune idiosincrasie di fondo, come linguaggio non mi dispiace.

Non dimentichiamo che il successo dei linguaggi general-purpose e' anche dovuto al fatto che se ho bisogno di scrivere un programma con parti real-time, parti computazionali, parti di user interface, imparare tre linguaggi ed integrare tre sottosistemi scritti in modo diverso e' sempre piuttosto triste... come dicevo altrove, il mio linguaggio ideale e' cosi' piccolo e flessibile che anche il ciclo for sta in libreria :-).

Domanda d'obbligo: hai in mente una sintassi/semantica che aiuterebbe a gestire la localizzazione (o l'intera creazione della GUI) attraverso un linguaggio GUI-aware? In caso affermativo, mi fai vedere un esempio, cosi' ci ragiono su?
Il mio commento era puramente estetico... Nel codice che hai scritto ritrovo quella sensazione meravigliata nel constatare che l'IDE puo' generare codice al posto del programmatore. E che il linguaggio capisce cose magiche tipo l'assegnamento di una costante stringa a una variabile. Era una sensazione che tutti i programmatori hanno provato negli anni 90, grazie al C++.
La discussione off-topic non sarebbe male :-), ma capitera' sicuramente un'altra occasione...
Mentre sul tema del post originale, sono molto curioso di leggere i prossimi due capitoli.
Dalla risposta di Carlo a Guido:

mi piacerebbe far vedere come il C#, con LINQ, stia andando nella direzione di un linguaggio a due livelli
come dicevo altrove, il mio linguaggio ideale e' cosi' piccolo e flessibile che anche il ciclo for sta in libreria

Queste devi proprio spiegarle... Il C# sta cercando di dare risposte a problemi di tutti i tipi definendo nuove keyword. Ma perche' ingrassare il linguaggio, sebbene costruito su due livelli, invece che sviluppare librerie decenti?
Perche' il LINQ e' una estensione del linguaggio e non un pezzetto, pure piccolo, di codice?

Secondo me per due motivi:
1) per legare a se' gli sviluppatori
2) perche' i linguaggi con sintassi cool tipo il Visual Basic fanno presa emozionale sugli sviluppatori. Sempre piu' anni '90! :-)
Come dicevo e' un argomento un po' articolato e prima o poi gli dedico un post intero con qualche esempio concreto.

Rispondo comunque rapidamente alla tua osservazione su come stiano aggiungendo keyword e piu' in generale concetti / costrutti.
E' vero, ed e' a causa di un peccato originale :-), ovvero il linguaggio e' nato ad 1 livello e ora gradualmente sta diventando a 2, con il risultato che ci teniamo tutto il baraccone 1 + tutto il 2.

Esempio concreto: i metodi anonimi e le chiusure si potevano introdurre sin dall'inizio, spostando poi in libreria alcune strutture di controllo.
Un altro: event non doveva essere una parola chiave, doveva essere definibile in libreria, sul concetto primitivo di delegate (e gia' che c'era, poteva evitarmi il test su null, come fa in C++/CLI). Ecc ecc.

Il succo di un linguaggio a 2 livelli e' proprio quello di riuscire a dare una "sintassi cool" scrivendo librerie e non estendendo il linguaggio. Tuttavia, se il substrato non si presta, ci sara' necessariamente una fase di ingrassamento :-) del linguaggio (se fossero furbi, sarebbe seguita da uno snellimento, di cui pero' dubito).

Sempre se sono furbi, i concetti introdotti nell'ingrassamento non saranno un mapping diretto degli obiettivi finali, ma invece tali da permettere l'introduzione degli obiettivi finali come librerie con sintassi "cool" (che poi dovremmo forse chiamare semplicemente "sintassi naturale").
LINQ si muove parzialmente in quella direzione, e questa e' la parte interessante; poi introduce anche cose ributtanti come "var", ma qui mi arrendo all'idea che qualcuno proprio non capisce che in un linguaggio come C# la keyword new non ha alcun senso (se non per aiutare il parser :-).

Una nota sulla sintassi naturale: piu' che di una questione emozionale, a me pare sia una evidente questione di carico cognitivo (che poi magari viene percepita come un sollievo :-> ed apprezzata anche a livello emotivo). Qui sarebbe interessante sapere che ne pensa Guido...
Caro Carlo, negli anni hai avuto un ruolo nella mia formazione intellettuale e poterti dare del tu per me e' un bel traguardo!

Ho riletto i tuoi post sulle "VM come metafora" e mi sembra che il fulcro della questione, ridotta al nocciolo, sia: OOP, AOP, metaprogramming sono essenzialmente tecniche, ispirate a buoni principii di design, il cui valore essenziale e' dare al programmatore gli strumenti per ragionare al livello di astrazione piu' prossimo al suo problema.
Data l'abbondanza dei problemi, ad esempio le -ilities di cui vorremmo
volta per volta fare uso, la maggiore o minore ricchezza dei vari approcci e' la capacita' linguisticamente a DUE livelli: offrire un kernel di concetti sui quali implentare le astrazioni che diverranno i "mattoni di base" dello specifico problema.

Negli ultimi giorni ho messo un poco a fuoco le idee. Proprio perche' la mia formazione professionale e' la neurologia, l'idea mi e' familiare. Porto un esempio pratico. Il bambino nasce con un hardware neuronale capace di manipolare alcuni concetti di base nonche' di espletare alcune "facilities": per esempio manipolare i numeri naturali e contare.
L'anello di Moebius fra i due livelli linguistici si ha per esempio in Peano o Russell: la capacita' di RIDEFINIRE le "facilities native" in termine di "concetti kernel" di livello ancora piu' basso.

Nel commento precedente sono stato involuto. Non volevo suggerire l'adozione di un linguaggio special-purpose. Al contrario, volevo rimarcare un'ovvieta': quando implementiamo una VM, la scelta delle sue "operazioni primitive" e' di per se TOTALEMNTE ARBITRARIA. Il punto e' che se parliamo di una VM che deve fare da fondamento a molti altri layer sovrapposti, la caratteristica piu' desiderabile diviene la GENERALITA'. Per usare le tue parole: "poter definire perfino il ciclo for come operazione di libreria fondato su astrazioni ancora piu' generali".

Giungo al punto. Per rispondere alla tua domanda finale, prendiamo per esempio il linguaggio Self: non potrai negare che obbedisce al quel requisito di minimalita' che hai giustamente definito. Corrediamo (se non e' gia' stato fatto, non so) il linguaggio Self di un meccanismo di invocazione di procedure esterne tipo la native-API di Java. Aggiungiamo una serie di classi-kernel che chiamano metodi nativi per esprimere delle primitive grafiche e di gestione eventi di base.
Ora, come dicevo nel commento precedente, abbiamo cio' che volevamo: totale programmabilita' e capacita' di definire astrazioni a qualuneue livello, dal controllo di flusso al widget! I "mattoni" di livello piu' basso divengono i concetti nativi di Self oggetto/metodo piu' le operazioni grafiche native che abbiamo ARBITRARIAMENTE scelto come parte integrante della nostra VM.

Guido Marongiu
Continuo a non capire il bisogno di definire estensioni (magari ridefinibili) del linguaggio appoggiandosi al linguaggio stesso. Cos'hanno le librerie che non va? Di tutti i casi esposti (GUI, LINQ, e gli altri leggibili tra le righe), nessuno richiede obbligatoriamente estensioni del linguaggio, ovvero tutti possono essere risolti egregiamente tramite librerie.

La keyword new citata da Carlo... e se fosse un metodo? Non sarebbe tanto, tanto, tanto bello?
ho un po' il dubbio che la tua vera (e provocatoria :-) domanda sia "non sarebbe bello che il linguaggio non richiedesse estensioni e tutto si potesse fare in libreria?".

Tutto sta ovviamente nella famosa "sintassi naturale". Se il linguaggio non permette di esprimere qualcosa in libreria attraverso una sintassi naturale, la libreria sara' scomoda da usare. Ora, e' ovvio che si puo' mettere [quasi] tutto in libreria se non ci preoccupiamo della scomodita' d'uso. Pero' poi, nella pratica, le persone non usano volentieri le librerie scomode. Ad es. pochissimi usano for_each in C++ perche' e' scomodo estrapolare in un funtore quello che in alternativa e' banalmente il corpo di un loop, locale alla funzione che si sta scrivendo, con tanto di accesso alle variabili locali. Nonostante vari tentativi a suon di metaprogrammazione template, se non si aggiunge un concetto in stile metodo anonimo con chiusura al linguaggio, for_each restera' per sempre scomoda da usare.
Altro esempio elementare: se voglio poter fare una classe Quaternion in Java e usare + e - come operatori, o cambio il linguaggio o mi adatto ad usare .Add .Sub et similia, scrivendo espressioni al limite del ridicolo e poco comprensibili.

Poi ci sono cose che (se le desideriamo) richiedono comunque una modifica al linguaggio. Vedi il duck typing messo in LINQ, che a me non piace, ma che non si puo' mappare su concetti di libreria. Allo stesso modo, rimuovere la keyword new in C# va molto oltre il fatto che sia o meno un metodo, e' una modifica di fondo al parser del linguaggio.

Come sopra (forse per quelle vecchie discussioni su objective c :-) mi torna il dubbio che la tua domanda fosse "ma se il linguaggio fosse tutto diverso ecc ecc...", nel qual caso, pero', stiamo dicendo la stessa cosa :-).
No, questa volta non stavo provocando... Non è il mio unico divertimento! :-)
Dici che è tutto un problema di sintassi. Allora, uno potrebbe chiedere: perché non si lavora su una sintassi migliore? Siamo sicuri che non si possa fare niente, che l'unica via sia estendere il linguaggio?
È una di quelle discussioni infinite... lascerei stare. :-)

Però faccio una osservazione proprio sull'esempio della classe Quaternion. Vale per qualunque concetto matematico o geometrico.
Dici che la scomodità pratica di avere metodi come Add e Sub fa venire voglia di operatori + e -. Certo, col C++ abbiamo imparato quanto sono elegantemente utili (e anche cool). Ma io ho anche imparato, con la pratica, che troppe volte gli operatori sono causa di errori e fraintendimenti. Tanto che alla fine io preferisco le esplicite chiamate ai metodi: non ci si sbaglia.

Esempio pratico, che mi è capitato davvero: classe Point, classe Vector. Point definisce operator+, ma per qualche strano motivo dimentica di definire operator-. Vengono definite le conversioni automatiche da Point a Vector e viceversa.
Questo codice:

Point a, b, c, d;
a = c + d;
b = c - d;

compila senza problemi. L'utilizzatore è portato a pensare che le due operazioni siano chiamate ai metodi Add e Sub, mentre la sottrazione in realtà passa per la classe Vector. Naturalmente il codice eseguito è diverso da quello che ci si aspetta, e pure il risultato è diverso (roba relativa alla gestione della tolleranza), oltre al maggior tempo di esecuzione. E naturalmente nessuno lo scoprirà mai, almeno finché qualcuno che non ama gli operator ci va a guardare. :-)
Qui il problema è che gli operator sono definiti incompletamente, è chiaro. Da un singolo caso non traggo la conclusione che gli operator fanno male. Però di casi ne ho trovati diversi...

Altro esempio: e se gli operator portassero ambiguità? Se esistessero almeno due operazioni di somma, leggermente diverse, il + a quale corrisponderebbe? Il programmatore deve andare a sincerarsi del codice (o sbagliare allegramente).
Questo caso sembra accreditare l'idea che è meglio un linguaggio estensibile a piacere, in modo da aggiungere nuovi operatori e azzerare l'ambiguità. Oppure suggerisce che è meglio lasciare stare e chiamare un metodo, senza cattive sorprese. :-)

Torno alla pratica che citavi. Un linguaggio con decine di parole chiave lo trovo davvero poco pratico, tutto qui. Poi va bene che alcuni concetti richiedono per forza modifiche al linguaggio (sempre che uno li voglia, come dici tu).

PS: Vedi il duck typing messo in LINQ, che a me non piace, ma che non si puo' mappare su concetti di libreria. Su questo stai tranquillo che provocherò di brutto! ;-)
Esempio pratico, che mi è capitato davvero: classe Point, classe Vector. Point definisce operator+, ma per qualche strano motivo dimentica di definire operator-. Vengono definite le conversioni automatiche da Point a Vector e viceversa.
Qui il problema è che gli operator sono definiti incompletamente, è chiaro

A mio avviso il problema principale e' un altro, o meglio sono 2:

- Sono stati definiti degli operatori di conversione implicita, senza troppa cautela. Se non li aveste definiti, avreste avuto un errore di compilazione, a ricordarvi del mancato operatore. Va detto che gli operatori di conversione sono anche una delle cause piu' frequenti di template silenziosamente istanziati su qualcosa di diverso da quanto volevamo, ecc ecc. Sono *molto* piu' pericolosi dell'operator overloading, perche' sono una forma di "corto circuito" del type system. Notare che una conversione esplicita (es. un costruttore con explicit) non darebbe problemi.

- Sono stati definiti degli operatori "discutibili". Siamo tutti d'accordo che i vettori si sommano e si sottraggono. Siamo anche d'accordo che esiste un isomorfismo tra punti e vettori (nello stesso spazio). Pero', non so voi che ne pensate, ma a me l'idea di "sommare punti" disturba parecchio: per me si sommano i vettori isomorfi ai punti, ed eventualmente si prende poi il punto isomorfo al vettore somma. Oppure, se li volete realmente trattare nello stesso modo, tanto vale eliminare uno dei due concetti e fine del gioco.

Per qualche considerazione in piu' sul perche' l'overloading e' utile, vedi il mio vecchio post che ricorda come una feature vada sempre considerata come parte di un linguaggio e non da sola.

PS: Vedi il duck typing messo in LINQ, che a me non piace, ma che non si puo' mappare su concetti di libreria. Su questo stai tranquillo che provocherò di brutto! ;-)

Prima magari vedi la mia controproposta di eliminare il new :-)
Post a Comment

<< Home