The term "orthogonality" is a layman's term for a precise mathematical notion: the language terms form an initial algebra (look it up in Wikipedia).
It basically means "there is a 1-1 correspondence between syntax and meaning". This means: there is exactly one way to express things, and, if you can put some expression in a particular place, then you can put any other expression there too.
Another way to think about "orthogonal" is that the syntax obeys the substitution principle. For example if you have a statement with a slot for an expression, then any expression can be put there and the result is still a syntactically valid program. Furthermore, if you replace
I want to emphasise that "meaning" does not imply computational result. Clearly, 1 + 2 and 2 + 1 both equal 3. However the terms are distinct, and imply a different calculation even if it has the same result. The meaning is different, just as two sort algorithms are different.
You may have heard of "abstract syntax tree" (AST). The word "abstract" here means precisely "orthogonal". Technically most AST's are not in fact abstract!
Perhaps you have heard of the "C" programming language? C type notation is not abstract. Consider:
int f(int);
So here is a function declaration returning type int
. The type of a pointer to this function is given by:
int (*)(int)
Note, you cannot write the type of the function! C type notation sucks bigtime! It isn't abstract. It isn't orthogonal. Now, suppose we want to make a function which accepts the above type instead of int:
int (*) ( int (*)(int) )
All ok .. but .. what if we want to return it instead:
int (*)(int) (*) (int)
Woops! Invalid. Lets add parens:
(int (*)(int)) (*) (int)
Woops! That doesn't work either. We have to do this (it's the only way!):
typedef int (intintfunc*) (int);
intintfunc (*)(int)
Now its OK, but having to use a typedef here is bad. C sucks. It isn't abstract. It isn't orthogonal. Here's how you do this in ML, which is:
int -> (int -> int)
We condemn C at the syntax level.
Ok, now lets flog C++. We can fix the stupidity above with templates and get an ML like notation (more or less):
fun<int, int>
fun< fun<int,int>, int>
but the actual type system is fundamentally flawed by references: if T
is a type, then is T&
a type? The answer is waffly: at the syntax level, if you have a type U = T&, then U& is allowed but it just means T&: a reference to a reference is the original reference. This sucks! It breaks the uniqueness requirement semantically. Worse: T& & is not allowed syntactically: this breaks the substitution principle. So C++ references break orthogonality in two different ways, depending on the binding time (parsing or type analysis). If you want to understand how to do this right .. there's no problem with pointers!
Almost no real languages are orthogonal. Even Scheme, which pretends great clarity of expression, isn't. However many good languages can be judged to have a "reasonably close to orthogonal feature basis" and that is a good recommendation for a language, applied both to the syntax and to the underlying semantics.