Language comparisons

This section describes some of the differences and similarities between QDL and other
languages.
C/C++
C++ could be called the "parent" language to QDL, as QDL inherits the largest
minority of concepts and syntax elements from C++. QDL is designed to overcome many
of the problems C++ has; most of the problems mentioned in C++?? : A Critique of C++ (3rd Edition) are solved
by QDL. These problems include
Cryptic Syntax
Not only is C++ hard for humans to understand, even compilers and other code scanners
often have to be very complex to decipher C++ code. QDL is designed to be more
human-readable, but also easier for a compiler or other code scanner to parse. In
particular, QDL is designed to be easier for programmer's editors that perform real-time
code analysis for such things as class browsers, and automatic statement completion.
QDL is not nearly so scared of having too many keywords as C++ is, although it doesn't
create a new reserved word for every purpose as Basic does.
Pointers
Arguably the worst thing C++ inherited from C is pointers. The critique mentioned
above covers the problems of pointers well. QDL uses references instead, and
although the main differences between the two are syntactical, references turn out to be
much safer, easier to understand, and easier to use.
RTTI and type casts
Type casts are much safer in QDL. Although unsafe and unchecked casts are possible
in QDL by dropping to the pointer level, they cannot be used accidentally like in C++.
QDL has far more run-time type information (RTTI) than C++.
Case sensitivity
The critique argues well why case sensitivity is a bad thing, and I agree
wholeheartedly. Case-insensitivity is an important little feature of QDL.
Brackets
I'm surprised at how few people seem annoyed at the sheer amount of brackets you have to
type in C/C++. Shift-9, Shift-0, Shift-9, Shift-9, Shift-0, Shift-9, Shift-0,
Shift-0. We have to do it constantly! You will notice immediately the amount
of bracket reduction in QDL. Here, check out how a C++ if statement might translate
to QDL:
if (!(Func1() && Func2(x))) ...
Unless Func1 && Func2(x) Do ...
As well as being faster to type, bracket reduction also makes code more readable: your
brain has at least one less set of brackets to match up in a complicated expression.
I've been programming C/C++ for six years now, and still I have to burn several extra
brain cycles to match up brackets.
Precedence changes
A curious bit of C++ design, possibly intended to show off operator overloading, has
programmers writing things like:
cout << "Hello I am " << 12 << " years old.";
This happens to work well only because the shift operators have low precedence; it
fails when you start writing things like
cout << "Setting a variable in an expression is C tradition..." << c=12 << " eh?";
Anyway, this would break down further if it were attempted in QDL, because of the
increased precedence of the shift operators. This was not mentioned in the critique,
but I believe C++'s precedence rules are flawed. While they got most of it
right--the basics, such as arithmetic--the bitwise operators seem placed
inappropriately. I mean, how often do you write
unsigned int x;
x = 2 << 16 - 1;
and actually intend this?
X = 2 << (16 - 1);
It has been my experience that
- Shifts, like multiplications, are usually intended before addition and
subtraction. Therefore, the shift operators have been placed between add/subtract
and multiply/divide.
This of course eliminates the possibility of using shift operators as they are used with cout,
unless the users are willing to bracket the arguments. cout, however, is
advantageous over printf: it provides something similar to a variable-argument list
while remaining type-safe. Luckily, QDL does provide a type-safe variable-argument list
facility. It is limited in that all the arguments must have the same type, but
inheritance, polymorphism, and RTTI pretty much make this a non-issue.
- Bitwise mask operators usually need to be applied before a relational test; for
example:
If FileFlags & (FileExists | IsDirectory) == FileExists Then ...
In C you have to remember to parenthesize:
if ((FileFlags & (FileExists | IsDirectory)) == FileExists) ...
Of course, if you forget, then your program starts acting bizarre. In QDL,
bitwise operators are placed above relational operators. This is fine as long as you
aren't one of those naughty boys who uses a bitwise operator where a logical operator is
appropriate:
if (x > 0 & y < 0 | y == 100)
I figure the people who do this need to learn the difference sooner or later anyway.
Turbo C++ had a warning feature that would tell you whenever you combined operators
with little-known precedence relationships, without brackets. I figure I'll probably have
to put such a warning in my compiler, this time to warn veteran programmers that the
precedence rules they took so long to get straight have now changed.
The Preprocessor
The C preprocessor allows you to make a parameterized macro that is used like a
function. The arguments against these macros include:
- Macros can behave inappropriately, because of either failure to bracket every use of a
given argument, or because an argument is evaluated multiple times. A classic
example that suffers from both problems would be #define max(a,b) (a > b ? a : b)
- Type checking is non-existent and hence uses may be unsafe. The use of a macro to
simulate a function that can deal with any argument type is unnecessary in C++, as template
functions accomplish the same thing.
- They are not compatible with the object-oriented view of the world.
An additional problem that would occur if C-style macros were allowed in QDL regards
function calling. When a function is called in QDL and its return value discarded,
the brackets around the arguments are omitted. This syntax is not really compatible
with function-style macros.
Despite the arguments against macros, there are at least a couple of legitimate uses
that cannot be achieved any other way:
- assert(). This macro checks that an expression is true. If not, the
expression, source file name and line number is printed at run-time, and the program is
usually aborted.
- offsetof(). This macro figures out the offset of a variable from the
beginning of a class or struct.
- TRACE(). This macro prints debug information (to
"trace" the code path as it is running).
Recognizing this, QDL abolishes function-style macros, but adds something more powerful
in exchange: Automatic and Abstract function arguments.
In short, a kind of macro expansion mechanism is built into function calling. As well as
answering the criticisms above, this feature allows for some unique new function-calling
possibilities.
Bad Features Kept
QDL resembles C++ in that it can be used for much more low-level work. Global
variables and functions are retained, except that they are typically not
"totally" global, as they are located in a "namespace".
A few things in the critique are not solved by QDL, such as:
- Functions that don't return a value are still called functions, even though it might
make more sense to call them "procedures". Why did I do it? Well, I
just don't agree that the distinction is necessary. I considered calling them
"routines" instead, since that term has no implication of returning or not, but
QDL is intended to feel comfortable to C/C++ programmers, so I kept the term
"function". In fact, in this documentation I sometimes refer to everything
that is made of code a "function", including overloaded operators, for example;
however, the language itself has special keywords for these things.
- Pointers are still present in QDL, though their use is restricted. You can still
do the "unsafe" things in QDL that you could do in C, although it is much more
difficult to do them accidentally. I've done this because I don't like a language
that can't do low-level stuff.
I've seen documentation on multiple OO languages suggesting that if you want to do various
kinds of low-level things you should write that low-level stuff in C and use some obscure
platform-dependent facility for getting the two languages to interface. This is odd:
after all their criticisms of C, they concede that you have no choice but to use it for
certain tasks? By including limited pointers, and the Compile Option
statement (which maps loosely to C's #pragma), QDL could potentially be used for any task
currently entrenched in the C/C++ domain.
- = and == are unchanged. It was suggested that = is an inappropriate symbol
to represent assignment; it should be := instead. While I sorta-kinda see the
logic there, the strongest reason to me to keep = for assignment is that it's faster to
type. And of course, it's more familiar to the C/C++ programmers.
Other annoying things removed
C++ had some other annoying "features" that are not present in QDL:
- Variables can't be declared in C++ switch statements. When you want to make
a variable in a case, you have to enclose the whole case in an anonymous
block. This design has a certain logic to it, but is annoying (especially to Windows
plain-API programmers who write half the code of a program into a switch statement.)
Oh yeah, and what's with having to break every case? QDL has no
fall-through.
- Visual C++ broke the rule that a variable declared in the init expression for a for
loop has the scope of the loop. This is Microsoft's fault, not C++'s, but it's a big
pain for people using VC++ or people porting code to or from VC++.
- Having to put #ifdef SOMETHING_H/#define SOMETHING/#endif around
every header file. Uses makes this practice obsolete.
- Having to put template<class T> in front the definition for every function
in a template class. Actually I could think of all sorts of other annoying extra
typing you must do in C++. Another one that comes to mind is having to put ClassName::
in front of every function's name. Plus, if the arguments are of an inner-enum
or inner-class type you have to do it some more.
Scope resolution operator
From the beginning, QDL used the scope resolution operator, ::, in
many places, the same way it is used in C++. It's been argued to death that it's
"confusing" having separate dot (.) and scope resolution
operators (::), but it was never confusing to me: When you want to access
members of a class, enumeration, or namespace, use scope
resolution, and when you want to access members of an object, use dot. Look,
people, it's really not that hard!
However, over time I've have been warmed to this weird idea, and after over one year
with :: in the spec, I've finally decided to remove it from the language.
This has introduced certain ambiguities into the language:
- Does Y.Z refer to static member Z of class Y,
or, say, member Z of object Y
- If C derives from A and B, but both A
and B have an F, and you have an instance of C
called CInst, it's no longer possible to resolve the conflict thusly: CInst.A::F,
CInst.B::F. I suppose the translation options of the Inherits
clause will have to suffice.
Oh well.
Visual Basic
Visual Basic is very different from QDL in most ways, but you can see some resemblence
in there:
- Measures taken for bracket reduction use constructs like the If...Then in
VB.
- The capitalize-the-first-letter recommended typographic style is taken from VB.
- The idea for function delimiters is derived from the strange syntax constructs in VB,
such as that for the Line and Rename statements.
- The idea for Automatic Vars is based upon the primitive VB method of doing a
similar thing (DefInt, etc.)
Pascal
QDL again has little in common with Pascal that it doesn't have with every other
language listed here, but you can see some similarities here too:
- The variable-defining syntax (variable-colon-type) is borrowed from Pascal.
- Pascal and QDL both use a keyword to define a procedure/function rather than trying to
make the compiler/reader figure out that "that thing is a function
declaration?!".
You might think ending constructs with"End" is borrowed from
Pascal or VB, but you'd be mistaken. The End pseudo-statement is used to
help code scanners see a program's structure.
Java
Java posed some very interesting ideas to me in the design of QDL.
- Interfaces. QDL did not end up using interfaces per se, but the Java concept
certainly got me thinking. I had put a construct called an Interface in QDL
for about two weeks before I decided multiple inheritance would be a (slightly) superior,
and less complicated, solution after all. What's that I hear you saying?
Interfaces are a simpler solution? Well, no; the "interfaces" I'd
put in QDL had the potential to bloat into something significantly more complicated than
MI. Java-style interfaces, meanwhile, were not powerful enough for my taste.
- RTTI. QDL's RTTI was originally a small part of QDL, but the usefulness of things
like serialization and JavaBeans provided by Java is undeniable, and the level of
automation Java provides is only possible through advanced RTTI support. Garbage
collection is assisted by special forms of RTTI.
- Extra measures for prevention of name collisions through a namespace tree
structure. Java has some very strict rules about source code filenames, directory
structure, and the relationship between classes and files. QDL is not nearly so
strict, and does not bind classes closely to files, but some of the Java ideas are
retained.
- Garbage collection. It seems every decent language has it nowadays (except C++*), but
Java was the first language I heard about that had GC.
* The purely conservative garbage collector doesn't count.
Although Java is also based on C++, it ends up looking significantly different from
QDL.
- I've heard Java's designers decided operator overloading was a "bad" feature:
it's too hard to use, and how it works is confusing. Furthermore, you often have to
overload a ton of operators to get the class working in a way that makes complete
sense. For example, to make a class that supports comparing to other instances of
the class, you have to overload ==, !=, >=, <=, > and <. Admittedly I
was quite confused at first by C++'s syntax and rules for operator overloading, but I
still think it is useful under many circumstances. QDL, at least, solves the second
problem by providing automatic symmetry during operator overloading. For example, to
accomplish the above relational capability, you need only overload >, <
and ==.
- Java has a singly-rooted object hierarchy. In general I think this is presumptuous
since it assumes that all objects should have particular properties (such as the ability
to convert to a string.) Besides, a singly-rooted hierarchy seems inappropriate for
a language that uses multiple inheritance.
- Java makes a distinction between classes and primitives; QDL does not. In Java, all
objects are heap based and can only be accessed through references. In QDL, objects can be
anywhere; even references are themselves objects.
Eiffel
I looked at Eiffel only briefly, but what studying I did helped me solidify a couple of
the ideas floating around in my head.
- Function and variable renaming/hiding, to overcome name collisions. I simplified
Eiffel's model slightly, then allowed the same constructs to be applied to Uses
statements as well as Class definitions. It is worth mentioning that, in QDL,
the renaming facility can also be used to adapt a class to work with a parameterized type.
C#
C Sharp, or C Hash as I like to call it, or C Number Sign as people who have never
heard of C# tend to call it, is a language that popped out of nowhere as I was making
(what I thought were) final revisions to the QDL language definition. Apparently Microsoft
also recognized that C++ is becoming more and more obsolete, and decided to make a
language of their own. I'm naturally suspicious of anything that slithers out of One
Microsoft Way, and C# is no exception. After reading the preliminary documentation for an
hour I was:
- First, greatly annoyed, because C# kept so many of the problems of C. The most striking
problem was keeping C's syntactical baggage. As the reference says, C# is firmy planted in
the C and C++ family tree of languages. Look, I don't mean to bash C. It was a great
language in its time, and even today there are good reasons to choose it over all other
languages. However, these reasons are getting fewer and fewer. Its glory days are over;
it's time to move on!
- Second, relieved, because QDL was still superior in enough ways to allow it to compete.
That is, it could compete if it were developed quickly enough and had some serious
marketing dollars behind it. Now, I fear, C# will prevent QDL from ever becoming a serious
contender. Still, I'm not just going to quit. I've put a lot of work in and I have to try.
Perhaps one saving grace for QDL is that C# appears firmly rooted in Microsoft Land.
The reference declares that all objects are COM objects, and encourages C# programs to be
built upon a foundation of Microsoft, Microsoft, and more Microsoft. I get the impression
that C# can only work on platforms Microsoft puts it on: namely, Windows, and maybe, if
you're really lucky, Macintosh. The way it looks to me is that things can go one of two
ways for Microsoft:
- Microsoft attracts legions of developers to C# and the many other charms of Microsoft
tools, and infiltrates all the Universities, dooming our impressionable youth to a life of
Microsoft development. Linux and BeOS lose their niche markets, Apple stocks crash, and
Microsoft completes its goal of world domination. Windows is installed in every computer
(maintaining the price point of $200+ per copy, of course), and the term "operating
system" falls into obscurity as people shorten it to "Windows".
- Microsoft's proprietary-everything policy backfires. Because Microsoft is obsessed with
owning everything in software development from the compilers to the APIs to the file
system to the underlying operating system, software written under the all-Microsoft
ideology is very difficult to port to a non-Microsoft-saturated platform. Nevertheless,
the reverse is not true; a mindful developer can develop software that will work on many
platforms including Windows. Recognising this, and being tired of being smothered on all
sides by Microsoft, developers slowly move away from tools that tie them tightly to
Microsoft. More portable software is written, and alternative platforms and tools become
increasingly predominant. Eventually, Microsoft loses its monopoly, and crumbles, being
unable to compete without it.
Unfortunately, I don't think scenario #2 can happen. People naturally take the path of
least resistance, and Microsoft is that path. So, I'm hoping for the current DoJ antitrust
action to be successful (sure, DoJ won the case, but luckily for Microsoft, we have all
these higher courts to drag out the case for years, and the new Bush administration, which
might exercise bad influence over the courts; anyway this sentence has run on far too
long, so adios).