This document provides some thoughts on the limitations of traditional languages, and describes how cxlang can be used to surpass these limitations.
Examples of inflexibility: the C++ approach
My own background is in C++, so I will provide relevant examples from this field:
- The C preprocessor is a macro language which can provide simple conditional compilation (generally considered beneficial), symbol substitution (useful but potentially problematic) or even be abused to act as a nearly-complete compile-time language (but don’t try this at home.)
- The C template engine is a compile-time generics mechanism and offers many of the same features as the preprocessor but more closely aligned to the underlying language. Template code is somewhat sensitive to the compile-time class of the methods and data on which it operates.
Both of the above can, through metaprogramming techniques, be used to substantially expand the expressiveness of the underlying language. The boost.signals library, for example, provided C++ with a powerful delegate system which far exceeds the capabilities of the language’s native function pointer mechanism. This is much more than syntactic sugar, in the same way that a car is much more than a good set of shoes. Much of the power of any language comes from the ability to define a concept once, then reuse the functionality throughout your program.
There are a number of downsides common to both of the above techniques. Firstly, while the end result may be a thing of beauty, the code used to implement the technique is ghastly. The language was not really built with these functions in mind; they are instead a combination of successive generations of very clever hacks and matching backend support from the language maintainers. The ‘language’ is incredibly arcane and verbose – programming is more a matter of understanding the available tricks that can be used to achieve a goal rather than the usual task of expressing yourself clearly. It’s certainly not impossible, as various people have proven, but it’s a discipline all in itself and one that is probably beyond the majority of C++ programmers. This is a real problem when you consider that in order to debug their own programs, they need to have a comprehensive understanding of the flow through this template or macro code.
Secondly, the syntax that such techniques may expose to the programmer is very limited. On the whole, the syntax for using such techniques tends to be relatively alien to the syntax of the host language. Even where there specific examples have evolved to the extent that the syntax is nearly native, odd quirks mean that programmers are quickly dropped back into the implementation details.
Finally, these techniques are unable to change the runtime environment in which they execute. Boost.signals cannot provide closures. The C++ smart pointers do not provide garbage collection, nor can the compiler reliably optimise away unnecessary refcount operations as in ARC. Despite each of these concepts being relatively simple for a programmer to grasp, it is not feasible to introduce these within a C++03 program. Similarly, it is not feasible to cleanly introduce reflection into the language, despite the presence of compile-time concepts such as offsetof() and runtime concepts such as RTTI.
C++11 is a solid step forward in addressing the various concerns with the C++ language. Among many other things, it adds rvalue references, lambda functions, user-defined literals, and assorted other functionality that C++ has been sorely lacking. This is a very good thing for C++ programmers, but it does not address the underlying problem that programmers are incapable of cleanly adding such features by their own volition.
In my mind, a next-generation language is one which actively minimises the barriers to coding. A discussion of any language which ends with “it’s good, but I wish it had this one or two other features..” is describing a current-generation language. And let’s remember the cars/shoes comparison- just because a language can technically achieve something if you get the syntax right does not mean that the language is well-suited to that task – follow that to its extreme and we would never have evolved past machine code.
What we need are languages which provide a sensible common style-guide that people can agree on (similar to how many C++ programmers agree to use STL, even though you can in fact replace it with something custom) but which do not stand in the way when a team decides that their own standard needs some slight differences to the common language. Teams already do this with “coding standards” which define acceptable subset grammar, layout, whitespace rules, import libraries and so on. Extending this to superset grammar is not a big reach.
The obvious critique of this approach is that it is possible for the language to degenerate into a series of small groups, each with incompatible syntax. While this is obviously possible, I would argue that this is not inherently a bad thing: After all, we already have many languages- if we’re going to waste time making more, then let’s at least minimise the effort involved in such. Furthermore, it is already possible for different teams using a language to have fundamentally different approaches to problem solving. Adapting the language grammar may be a symptom of such differences, but it is not a cause. Finally, a bad programmer is a bad programmer in any language. If you take each concept to its logical extreme without exercising caution or common sense, then it is already quite possible to write code which is completely unreadable to most other programmers. Even top-notch programmers fall victim to this (try a casual read-through the STL or boost implementations sometime.)
Alternative Runtime Environments
Beyond the grammar, the runtime environment is a big factor in language choice. Nobody uses C++ for web programming or scripting. Why is this? Because of the lack of a suitable runtime. NaCl, ActiveX, and other such technologies exist which are capable of deploying native code via a web browser, but they are not well supported, often have serious stability, performance, and security concerns. They may also lack suitable API support from the host environment.
While there are a host of differentiating factors in the syntax, one of the immediately obvious differences between C# and C++ is the memory access model. C++ is considered “close to the metal”, a systems-programming language like it’s namesake. While a good C++ programmer will most likely understand the details of how C++ maps to the underlying memory layout, this isn’t something that a programmer takes advantage of on a daily basis. It’s likely that many C++ programs could run unmodified with a C#-style garbage-collected and pointer-safe runtime (aka. Java) under the hood. So why do we get into debates about which model is superior? Surely this is something that can be decided at compile time? In fact, C++11 specifically allows that the underlying runtime may be garbage collected- but since such a runtime would need to be implemented separately by each tools vendor, it’s not currently available to programmers in practice.
Both of these problems should ideally be solved by separating the concept of the language front-end (or “grammar”) and back-end (or “runtime”) such that a given language grammar can be retargeted to any desired runtime environment. Some toolsets (such as LLVM) seek to solve this to some degree, but at the current time suffer from usability issues such as the inability to integrate a customised toolchain with dominant IDEs and the substantial domain-specific knowledge required to create new LLVM front-end and back-end modules.