Il termine "ortogonalità" è il termine di un laico per una nozione matematica precisa: i termini del linguaggio formano una algebra iniziale (cercalo su Wikipedia).
Significa sostanzialmente "esiste una corrispondenza 1-1 tra sintassi e significato". Ciò significa: esiste esattamente un modo per esprimere le cose e, se riesci a mettere qualche espressione in un determinato luogo, allora puoi inserire anche qualsiasi altra espressione lì.
Un altro modo di pensare a "ortogonale" è che la sintassi obbedisce al principio di sostituzione. Ad esempio, se si dispone di un'istruzione con uno slot per un'espressione, allora è possibile inserire qualsiasi espressione e il risultato è ancora un programma sintatticamente valido. Inoltre, se si sostituisce
Voglio sottolineare che il "significato" non implica un risultato computazionale. Chiaramente, 1 + 2 e 2 + 1 sono entrambi uguali 3. Tuttavia i termini sono distinti e implicano un calcolo diverso anche se ha lo stesso risultato. Il significato è diverso, così come due algoritmi di ordinamento sono diversi.
Potresti aver sentito parlare di "albero di sintassi astratto" (AST). La parola "astratto" qui significa precisamente "ortogonale". Tecnicamente la maggior parte degli AST non sono in realtà astratti!
Forse hai sentito parlare del linguaggio di programmazione "C"? La notazione di tipo C non è astratta. Prendere in considerazione:
int f(int);
Quindi ecco una dichiarazione di funzione che restituisce il tipo int
. Il tipo di puntatore a questa funzione è dato da:
int (*)(int)
Nota, non puoi scrivere il tipo di funzione! La notazione di tipo C fa schifo! Non è astratto. Non è ortogonale. Supponiamo ora di voler creare una funzione che accetti il tipo sopra invece di int:
int (*) ( int (*)(int) )
Tutto ok .. ma .. cosa succede se vogliamo invece restituirlo:
int (*)(int) (*) (int)
Woops! Non valido. Consente di aggiungere parentesi:
(int (*)(int)) (*) (int)
Woops! Neanche questo funziona. Dobbiamo farlo (è l'unico modo!):
typedef int (intintfunc*) (int);
intintfunc (*)(int)
Ora va bene, ma dover usare un typedef qui è male. C fa schifo. Non è astratto. Non è ortogonale. Ecco come farlo in ML, che è:
int -> (int -> int)
Condanniamo C a livello di sintassi.
Ok, ora lascia flog C ++. Possiamo correggere la stupidità sopra con i modelli e ottenere una ML come notazione (più o meno):
fun<int, int>
fun< fun<int,int>, int>
ma l'attuale sistema di tipi è fondamentalmente imperfetto dai riferimenti: se T
è un tipo, allora è T&
un tipo? La risposta è waffly: a livello di sintassi, se hai un tipo U = T &, allora U & è permesso ma significa solo T &: un riferimento a un riferimento è il riferimento originale. Questo fa schifo! Rompe semanticamente il requisito di unicità. Peggio ancora: T & & non è permesso sintatticamente: questo rompe il principio di sostituzione. Quindi i riferimenti C ++ interrompono l'ortogonalità in due modi diversi, a seconda del tempo di associazione (analisi o analisi del tipo). Se vuoi capire come farlo bene ... non c'è problema con i puntatori!
Quasi nessuna lingua reale è ortogonale. Perfino Scheme, che pretende una grande chiarezza espressiva, non lo è. Tuttavia, molte buone lingue possono essere giudicate avere una "ragionevolmente vicina alla base delle caratteristiche ortogonali" e questa è una buona raccomandazione per una lingua, applicata sia alla sintassi che alla semantica sottostante.