The Death of Computer Languages, The Birth of Intentional Programming

ion mechanisms: The act of abstraction is the obtainment of the general in favor of the specific. Our human civilization rests on the power of abstraction insofar as the volume of specific cases handled by an abstraction is typically much greater than the overhead of the abstraction mechanism itself. This is especially true in software so the ability to abstract is key to any encoding. Directness (simplicity) of expression: At some level, all parts of a program are specific: either they deal with specific facts or they describe a specific abstraction. Directness of expression just means that the units that the programmer thinks of as specific correspond closely to the units of encoding. Note that this is not the same as saying that the encoding be simple because there is no guarantee at all that the programmer’s thoughts (or the programmer’s problems) are simple. All that is required here is that the mapping between the two be straightforward. Accessibility of desired implementation (implementation efficiency): The programmer’s goals might well include a degree of performance which depends on a particular implementation method or the exploitation of operating system or hardware features. While this requirement is far from universal, in a competitive environment performance becomes a goal for most pieces of software. This rule basically says that there should be no incentive to go outside of the normal language framework (for example by descending to machine code) even if a specific implementation is desired In the best of possible worlds, there would be no conflict between abstraction and efficiency, indeed the best way to express a specific implementation would be by abstracting it into its computational intent. The notion of directness is also related to abstraction, in that if something is not done directly one should be able to abstract it to become direct. IP’s ―intentions‖ are such universal abstractions Next the doubtful ones. If you disagree with any of the points, please re-read the definition of the technical term ―doubtful‖ above. IP in each case will focus on finding ways to delegate the choice to the user, not to try to propose new super-duper choices. IP will not solve the problems, but enable the user to make the easier, specific choice in the user’s domain. The precise methods for delegation will be described in the sequel. Simplicity of language: The word ―simple‖ is sometimes used in software engineering as if it were a synonym for ―good‖ when it is really value-free akin to, say, ―small‖. While the simplicity of expression of the solution is unassailable, the simplicity of the language itself is doubtful. The existing tension is between the language designer’s desire on one hand to keep the language simple in order to reduce learning and system implementation costs, and on the other, to try to optimize ease of programming in some domains and often to also consider the speed of execution. At one extreme of the simplicity scale is, of course, the fearsome ―Turing tarpit‖, where the speed of programming and object program execution both are brought to a virtual standstill due to simplicity of the language primitives. Syntax: Syntax has long been considered somewhat unimportant as the term ―syntactic sugar‖ indicates. This term is used when there already exists some basic syntax which needs to be made more palatable for the user by the admission of the sweetener syntax, the syntactic sugar. But if, at the extreme, we consider the underlying syntax to be an abstract syntax tree, all other syntaxes become just sugar. IP takes this view. Users will directly manipulate the program tree while they view the program using an arbitrary and non-permanent syntax. Present day syntax had been predicated on a character stream that could have been input from punch cards, or teletypes. IP’s ―syntax‖ will allow arbitrary typographical or even graphical notations. Decades ago, Algol 60 has already expressed the longing toward clarity of notation when it defined a typographically rich publication language separate from the so-called reference and implementation languages. Types and type checking: The type system of a language is really a compile-time sublanguage, so the usual language issues, except for execution efficiency, will apply to types as well. The benefit of types comes mainly from type checking. Type sublanguages are not ―universal‖ in the Turing sense, so there is no guarantee that some given type-like semantics of a class of quantities in the user’s program can be expressed at all. This is acceptable, because type checking is just a convenience, albeit an important one. There are two common forms of escape when the ―true‖ type is not expressible: either an available superclass is used instead, or an explicit ―coercion‖ is used to paper over the difficulty. An example for the former is when an integer type is used for a logical name, such as a window number. An example for the latter might come up if the window number can have its own type but this prevents it from participating in superclass integer operations, for example I/O. However there are numerous other cases where a typical modern type system is simply unable to express directly what the user wishes to create. IP will delegate the problem of exact type creation to the user by providing for universal computation in the type machinery at compile time. Other standards enforcement:The remarks on types are also applicable to instances where a language enforces or encourages some general software engineering discipline, such as goto-less programming, object oriented programming, use of interfaces, or modularization. In each of these cases a sublanguage exists and there is a potential tension between the user’s requirements, the expressiveness of the sub-language, and the escape mechanism. The latter effectively becomes the extension machinery in cases when the requirements and the sub-language do not match. Finally we have the list of ―terrible things‖ which are taken for granted: Languages are not compatible with each other: For example, my system is in C, I can not combine it with Ada. Or, I can’t use a nice feature of Ada in C, as I would use a foreign bon mot in normal English text. It is true that modules compiled from different languages can be often linked together, but this just shows that the difficulties are not necessarily caused by semantic incompatibility. For a permanent and maintainable combination, the declarations would have to be sharable and the code mixable. Languages are not compatible with themselves: For example, I share two independently developed modules in my C program. Unfortunately, both modules include a definition for a macro called X which cause a ―name collision‖. This is as if in real life if a John Smith worked for a company as an accountant, a second John Smith could not be hired as another accountant or even as a lawyer without inordinate repercussions, such as legally changing the name of one of the Smith’s or replacing the first Smith. Languages are not compatible with a fixed point: For example, if the string desired by the operating system, proprietary software package, or efficient algorithm is not represented the same as in the language, a potentially costly and unreliable conversion has to be employed. In effect, the languages act as additional fixed points when they should be the accommodators. Computational intent and implementation details are intermingled. Consider, for example the intention of selecting a member m from a container c. There are a large number of implementation possibilities, as implied by the following access codes: c.m c->m c[m] m[c] c(m) m(c) (*c)[m] (c>>m)&1 etc. etc. Classes, inlining, and other abstraction machinery can hide implementation detail, but still leave the uniform reference at the declarations unresolved. What is the form of a class declaration for a container with a single boolean member (i.e. class c{ bool m;};) which could be implemented in any of the above ways? This is not a frivolous question because of the fixpoint problem, namely that there may be strong reasons elsewhere in the program under construction which support the fixing the implementation in a certain way. It may not be critical that any container should be smoothly re-implementable, for example as separate arrays for each member, indexed by container instance number (the m[c] example), but the issue is rather that given some such existing arrays, can we still consider them as containers? Saying that the already fixed implementation should be changed, or that the objects in question should not be considered containers under the circumstances ignores realistic boundary conditions on one hand and violates sound software engineering on the other. Progress in programming languages is limited, among other factors, by the small number of language designers. Many pieces of program text are not abstractable, or abstractable only in pre-processors. Examples in C are iterators, or repeated pieces of code in procedures which could be easily expressed as local procedures if such existed. Reliance on pre-processors creates a whole different set of problems in terms of naming, and the limited computing capabilities which are available. Meta-work (i.e. consistent rewriting of a program) is not expressible in languages even though it comprises a substantial part of programmer’s workload. Lisp is an exception in this regard, but only at the cost of failing most other tests for usability. Domain-specific knowledge can not be injected into the compilation process. Programmers are often given complete suzerainty over run-time, but their special knowledge of the abstractions, or of the rules of combining the abstractions, constant folding, initializations, and so on, would not be used at all by the compiler. Programmers are actually encouraged to make part of their contributions in the form of nonmachine processable text