Economy Of Expression

A very important criterion for evaluating the suitability of a programming language is how economically the notation reflects the intent of the programmer.

I don't know who coined the term, but I learnt it from one of the great Software Experts Ipersonally Respect, Rob Pike. (Anyone know the exact quote in which he gives his opinion of how pointer arithmetic in Cee Language helps its Economy Of Expression?)

Of course not all expressions are created equal, and various languages choose different trade-offs with regards to Economy Of Expression. Some expressions are more economical in one language while more tenuous in another. One of the most obvious examples are arrays. In the Algol Family of languages, you'll see this notation in any algorithm involving arrays:

x=a[i]; a[i]=y;

In Scheme Language one sees it as:

(set! x (vector-ref a i)) (vector-set! a i y)

Of course, the Lisp family is optimized for the economy of Ess Expressions rather than any expression. :) But, joking aside, the problem which is also present in other functional languages reflects the fact that Lisp/Scheme programmers and functional programmers in general are less likely to use arrays, and more likely to use Linked Lists. These are fine as far as Economy Of Expression is concerned, but are not as fine as far as Economy Of Execution: they generate more garbage (to collect) and object creation overhead.

It is hard to tell from that example alone whether the total app would be more compact. Being compact in one area may not necessarily translate to another area. The meta abilities of Ess Expressions may win the war even though it loses that particular battle. I stamp this "insufficient info". One would probably have to see the whole app or several apps. Note that a language that uses dot syntax for maps (and maybe uses maps and objects interchangeably) may have:

x = a.i a.i = y

If we allow nested maps, one can do:

x = a.i.j.k a.i.j.k = x

(There's the distinction between subscript value and variables as subscripts that is not addressed here. [digging up related link....])


It is my observation that Economy Of Expression is best achieved by creating a Domain Specific Language or domain-specific API/interfaces/utilities (Api Is Language). I've never seen a realistic code example of one paradigm clearly beating the pants of off another in terms of code volume, at least not related to my domain. Most of the expressive economy I've seen comes from fitting the domain well, and some participants in Challenge Six Versus Fp Discussion seem to at least partly agree. Of course Economy Of Expression is not the only factor in town.

For example, a custom query language may be more compact for a given project than SQL. However, one must weigh that against tossing a decent standard (SQL). A future different maintenance programmer would probably prefer that SQL be used instead of a custom query language because he/she doesn't have to learn a new query language. (SQL could use some adjustments, but that's another story.)

Flexibility to future change also tends to counter EOE. Abstractions wound too tight are often not as flexible as looser ones, requiring complete overhauls if requirements no longer fit the tight abstraction.

--top


While Domain Specific Language has some promise, bear in mind that its most common application is often to express domain models. In a Domain Specific Language a 'domain model' is a collection of domain data (who, what, when, where, why) minus the axioms and assumptions of this model (which tend to be 'assumed' from the context: the language in which you are stating the domain model). The basic idea is that one may then systematically explore it and make predictions (aka 'running' the model). In practice, this usually requires constructing a processor for the Domain Specific Language, an implementation, a task that is usually error-prone and difficult, especially if one wishes to achieve modularity or efficiency. Economy Of Expression only happens on the assumption that some other dude has already suffered through making that processor.

Uh... and before I start a Laynes Law war, just to be clear, I would not call "Domain Specific Language" a sublanguage whose domain is some aspect of constructing a computation, even if said language is not, by itself, a complete General Purpose Programming Language. I.e. Regular Expressions, Type Descriptors, Dataflow Programming specifications, and Data Manipulation languages (like SQL or LINQ or XPATH), are not what I am discussing above. I'm talking about 'Domain Specific Language' in the stronger sense of 'this is a set of terminology that might be used to describe a situation by a domain expert... when the domain expert is not an expert of programming describing the aspects of a program' - e.g. a language to describe a blueprint of a house, a set of business contracts and business rules, parts of a Scene Graph, a Document Object Model, tasks of a mission, a choreographed dance, music, a script for actors on a stage, obstacles on a map, describing diagnostic symptoms, etc.

I posit that Domain Specific Language, in the sense described above, should not be the mechanism by which 'programmers' achieve Economy Of Expression. Writing code in a Domain Specific Language is the job of end-users, data-entry personnel, chemists and physicists and scientists and doctors and artists and musicians and people doing taxes. Entry of domain models via Domain Specific Language might be supported by forms and feedback or might be a poorly documented configuration data in a Plain Text file... but is ultimately input into a 'processor' whose implementation is the task of a 'programmer' - in essence, someone whose expertise is implementing a Domain Specific Language (one might consider 'interaction' an implementation of an interactive language). The closest a Domain Specific Language should come to expressing computation concerns is describing that certain domain events happen in parallel while others happen sequentially. Designing a Domain Specific Language is a task that will probably require both a Language Designer and an expert in the domain... likely both in the same brain.

For the programmer, the 'domain' is computation. That's a big domain, and is filled with dualities (data and process, Object Vs Model, mutable variables and immutable values, calculation and communication, trigger and event, state and function, hard and soft, policy and mechanism, configuration and definition, type and representation), concerns (functionality, safety, security, performance, distribution, delay and disruption tolerance, hardware failure, graceful degradation, changing requirements, runtime upgrade), and engineering issues (code management (Once And Only Once, Economy Of Expression), configuration management (for each user of a project), project breakdown and composition, gain and loss of personnel, scheduling and funding, maintenance, transitioning systems, training people in them, and demonstrating improvement). The goal of a Language Designer for a General Purpose Programming Language is to support concerns without causing harm to engineering issues.

As a Language Designer and Programming Language Theory enthusiast, I have observed that the dualities alone will ultimately doom any 'Everything Isa Big Idea language'. Supporting only half of a duality will make that half really easy at the cost of making the other half completely counter-intuitive. 'Everything Isa object' will be brought to its knees when it comes expressing data that reflects the world outside. 'Everything Isa rule' will have difficulty expressing Configurable Modularity that is achieved by use of exchangeable objects with predefined interfaces. Further, I have observed that blending a duality also has problems: functional programming languages that are 'impure' lose many of the potential performance, safety, security, etc. benefits one might have achieved by separating them (reordering computations, versioning and transactions, migration of functions in a distributed system, and many more detailed in a rant on Ideal Programming Language). Blending trigger and event prevents reuse of either. Blending hard and soft results in the elegant simplicity of Cee Plus Plus combined with the blazing speed of Small Talk =).

So I'm armed with two observations (1) both sides of any given duality need support lest you solve only half the programming problem, and (2) blending them often results in a Turing Tarpit - the loss of discernible language features other than being Turing Complete. One might call these Symmetry Of Language and Yin Yang Principle, respectively. Patterns that follow these combined principles include such gems as Alternate Hard And Soft Layers, Separate Policy From Mechanism, Separate Calculation And Io, Observer Pattern (which separates trigger from action), and so on. Doing so as a Design Pattern keeps pieces of the design simple and reusable... and Design Patterns indicate a Missing Feature Smell.

Based on these observations, I believe that the means to achieve Economy Of Expression (and every other Software Engineering benefit) is to identify as many dualities as possible, then, with an eye towards implementability, performance, and other concerns, one should select and divide these dualities at the language level... being careful that the language supplies both sides of each duality, and providing a mechanism for them to interweave. Examples:

If one supports Object Oriented with its 'local' data that 'projects' into the world around it, one must also support a reflective Data Model that keeps information about the world outside it. Interweaving these two could be done by allowing objects to automatically exist as logical views (queries) on data, and by allowing objects to, using this model, maintain local views of the world as they see it (perhaps in a manner that effectively 'overrides' the global data by essentially including local and logical inserts/updates/deletes to a global data set).

One might aim to support 'pure' values and functions with separate procedure-descriptions for processes/objects and IO. Interweaving these could be performed by only allowing IO to be done with pure values, and using functions to abstract procedure-descriptions. Doing so allows one to pass procedure-descriptions as values, and provides relatively simple semantics for distribution of computation and inter-process communication, and further allows one to introduce First Class processes and potentially support transactions.

One might support Alternate Hard And Soft Layers at the language level by providing staged compilation, such as Compile Time Resolution and Partial Evaluation as well as Extensible Programming Language and the ability to grab the parser for an extended language in order to 'evaluate' using it at runtime. Doing so reduces the degree to which external Code Generation is necessary, and simplifies code management relative to using multiple external languages to solve the problem.

Following Yin Yang Principle and Symmetry Of Language also helps clarify a Language Smell associated with simple implementations of modules: in a common implementation, 'service' modules provide definitions that inject values into 'client' modules. Symmetry would require that client modules also be able to inject values back into 'service' modules. Doing exactly this is the whole basis for Aspect Oriented Programming and Hyper Spaces, and is achieved to lesser degrees by Multi Methods, open functions, and open data types.

Applying Yin Yang Principle and Symmetry Of Language, one might predict that composition of programs (configuration) and the definition of units of composition (objects or actors) are opposite ends of the same task and should be separated at the language level. Evidence of this is available in Object Oriented design, in particular noting that the task of putting all the objects together before one starts using them is currently obtuse requiring complex implementations of Observer Pattern, extra 'subscription management' information in each object, horribly complex initialization and finalization schemes due to cycles in object graphs, inefficient management of memory when one could presumably grab a slab of memory large enough for whole object configurations then later garbage collect it all at once, and so on. A potential separation is to redefine objects or classes to support abstract composition (regarding the initial 'outputs' from and necessary 'inputs' to each actor), then define another sublanguage (different from 'class') to combine objects into object configurations. If done right, configurations will be composable, and one will be able to interleave objects containing configurations containing objects.

There is no Domain Specific Language for this purpose... there is no way to model the who, what, when, where, why, and how of a 'program' without a General Purpose Programming Language. But these 'separations' ultimately result in a bunch of sublanguages - possibly simply trios of keywords that express dualities, none of which are a model of the program but each of which, in an aspect-oriented manner, interweaves to support the construction of a complete program.

By these principles we escape the Turing Tarpit. By these principles we achieve Economy Of Expression.

Can I request that you provide some examples, or work with existing examples from this wiki to illustrate this?

There is a problem with 'principles': they aren't algorithms. They might help Language Designers diagnose Language Smells and even suggest approach for repair, but they do not allow one to automate language design and they do not tell Language Designers how to best fix the smells... i.e. precisely how to achieve the duality, and how the dual components should be interleaved, are still difficult tasks that belong to the Language Designer. To the degree the above principles help, I provided a few examples above (a group of four bullet points). They aide with Economy Of Expression mostly because it is usually roundabout, obtuse, indirect, and inefficient to 'express' (via hand-implementation) any given unsupported duality in a language, such as expressing Dependency Injection and open functions in a 'modular' language that only allows modules to 'export'.


Being that principles often conflict with each other, do you agree that they should be road-tested, or at least an approximation of road-testing? All tools should be tested, if possible.

Principles are tested first by their application to known data to look for conflicts and evidence in the scope of their desired application, then again by their actual application to see whether they succeeded in making useful predictions or suggesting solutions. I've already provided the former testing. The latter you might call 'road testing' and in practice can only happen after a principle is being promoted. Anyhow, I'm curious which principles you think conflict with those listed above.

It's not quite specific enough. It almost sounds to me like "marketing speak" because I cannot find enough concrete statements to latch onto without heavy risk of categorizing things different than what you intended. I'm not sure where to start.

You asked a very broad question (should 'principles' be road-tested?). Why does it now sound like you expect a more specific answer?

Principles are tested by the same means as all other hypotheses: filter first by looking into the past for observed evidence and conflicts, and perhaps by looking at existing accepted models for evidence and conflicts. You can also run a few small-scale tests, e.g. by following a principle through scenarios and use-cases and seeing if they result in the expected observations, but ultimately that is a point test highly subject to confirmation bias. To 'road test' a principle that has made it through the initial filters, you really must promote it, teach it, encourage people to apply it... then observe, likely a decade or two on, looking for conflict and success and continued adoption. The idea behind this is simple.

If you believe that tools, such as principles, can be 'road-tested' before they are 'road-promoted' in the marketing and economic sense, then you're far more an idealist than I am. The short answer is: Yes, I believe principles should be road-tested... but it must be done by their application, and it must be done in the real world.

See original on c2.com