Le terme «orthogonalité» est un terme profane pour une notion mathématique précise: les termes du langage forment une algèbre initiale (recherchez-la dans Wikipedia).
Cela signifie essentiellement "il y a une correspondance 1-1 entre la syntaxe et le sens". Cela signifie: il y a exactement une façon d'exprimer les choses, et, si vous pouvez mettre une expression à un endroit particulier, vous pouvez également y mettre n'importe quelle autre expression.
Une autre façon de penser «orthogonale» est que la syntaxe obéit au principe de substitution. Par exemple, si vous avez une instruction avec un emplacement pour une expression, n'importe quelle expression peut y être placée et le résultat est toujours un programme syntaxiquement valide. De plus, si vous remplacez
Je tiens à souligner que le «sens» n'implique pas un résultat de calcul. Clairement, 1 + 2 et 2 + 1 sont tous les deux égaux à 3. Cependant, les termes sont distincts et impliquent un calcul différent même s'il a le même résultat. La signification est différente, tout comme deux algorithmes de tri sont différents.
Vous avez peut-être entendu parler de «arbre de syntaxe abstraite» (AST). Le mot «abstrait» signifie ici précisément «orthogonal». Techniquement, la plupart des AST ne sont pas en fait abstraits!
Peut-être avez-vous entendu parler du langage de programmation "C"? La notation de type C n'est pas abstraite. Considérer:
int f(int);
Voici donc une déclaration de fonction retournant un type int
. Le type de pointeur vers cette fonction est donné par:
int (*)(int)
Remarque, vous ne pouvez pas écrire le type de la fonction! La notation de type C suce bigtime! Ce n'est pas abstrait. Ce n'est pas orthogonal. Supposons maintenant que nous voulons créer une fonction qui accepte le type ci-dessus au lieu de int:
int (*) ( int (*)(int) )
Tout va bien .. mais .. si nous voulons le retourner à la place:
int (*)(int) (*) (int)
Woops! Invalide. Ajoutons des parens:
(int (*)(int)) (*) (int)
Woops! Ça ne marche pas non plus. Nous devons le faire (c'est le seul moyen!):
typedef int (intintfunc*) (int);
intintfunc (*)(int)
Maintenant c'est OK, mais devoir utiliser un typedef ici est mauvais. C est nul. Ce n'est pas abstrait. Ce n'est pas orthogonal. Voici comment procéder en ML, qui est:
int -> (int -> int)
Nous condamnons C au niveau de la syntaxe.
Ok, laisse maintenant floguer C ++. Nous pouvons corriger la stupidité ci-dessus avec des modèles et obtenir une notation de type ML (plus ou moins):
fun<int, int>
fun< fun<int,int>, int>
mais le système de type réel est fondamentalement vicié par les références: si T
c'est un type, alors est-ce T&
un type? La réponse est waffly: au niveau de la syntaxe, si vous avez un type U = T &, alors U & est autorisé mais cela signifie simplement T &: une référence à une référence est la référence d'origine. Ça craint! Il casse sémantiquement l'exigence d'unicité. Pire: T & & n'est pas autorisé syntaxiquement: cela rompt le principe de substitution. Les références C ++ cassent donc l'orthogonalité de deux manières différentes, en fonction du temps de liaison (analyse syntaxique ou analyse de type). Si vous voulez comprendre comment faire cela correctement ... il n'y a aucun problème avec les pointeurs!
Presque aucune langue réelle n'est orthogonale. Même Scheme, qui prétend une grande clarté d'expression, ne l'est pas. Cependant, de nombreux bons langages peuvent être considérés comme ayant une «base de caractéristiques orthogonales raisonnablement proche» et c'est une bonne recommandation pour un langage, appliqué à la fois à la syntaxe et à la sémantique sous-jacente.