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 SoftwareExpertsIpersonallyRespect
. (Anyone know the exact quote in which he gives his opinion of how pointer arithmetic in CeeLanguage
helps its EconomyOfExpression
Of course not all expressions are created equal, and various languages choose different trade-offs with regards to EconomyOfExpression
. Some expressions are more economical in one language while more tenuous in another. One of the most obvious examples are arrays. In the AlgolFamily
of languages, you'll see this notation in any algorithm involving arrays:
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 EssExpressions
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 LinkedList
s. These are fine as far as EconomyOfExpression
is concerned, but are not as fine as far as EconomyOfExecution
: 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 EssExpressions 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 EconomyOfExpression
is best achieved by creating a DomainSpecificLanguage
or domain-specific API/interfaces/utilities (ApiIsLanguage
). 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 ChallengeSixVersusFpDiscussion
seem to at least partly agree. Of course EconomyOfExpression
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.
has some promise, bear in mind that its most common application is often to express domain models. In a DomainSpecificLanguage
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 DomainSpecificLanguage
, an implementation, a task that is usually error-prone and difficult, especially if one wishes to achieve modularity or efficiency. EconomyOfExpression
only happens on the assumption that some other dude has already suffered through making that processor.
Uh... and before I start a LaynesLaw
war, just to be clear, I would not call "DomainSpecificLanguage
" a sublanguage whose domain is some aspect of constructing a computation
, even if said language is not, by itself, a complete GeneralPurposeProgrammingLanguage
. I.e. Regular Expressions, Type Descriptors, DataflowProgramming
specifications, and DataManipulation
languages (like SQL or LINQ or XPATH), are not what I am discussing above. I'm talking about 'DomainSpecificLanguage
' 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 SceneGraph
, a DocumentObjectModel
, 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 DomainSpecificLanguage
, in the sense described above, should not be the mechanism by which 'programmers' achieve EconomyOfExpression
. Writing code in a DomainSpecificLanguage
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 DomainSpecificLanguage
might be supported by forms and feedback or might be a poorly documented configuration data in a PlainText
file... but is ultimately input into a 'processor' whose implementation is the task of a 'programmer' - in essence, someone whose expertise is implementing a DomainSpecificLanguage
(one might consider 'interaction' an implementation of an interactive language). The closest a DomainSpecificLanguage
should come to expressing computation concerns is describing that certain domain events happen in parallel while others happen sequentially. Designing a DomainSpecificLanguage
is a task that will probably require both a LanguageDesigner
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, ObjectVsModel
, 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 (OnceAndOnlyOnce
), 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 LanguageDesigner
for a GeneralPurposeProgrammingLanguage
is to support concerns
without causing harm to engineering issues
As a LanguageDesigner
enthusiast, I have observed that the dualities
alone will ultimately doom any 'EverythingIsa BigIdea
language'. Supporting only half of a duality will make that half really easy at the cost of making the other half completely counter-intuitive. 'EverythingIsa
object' will be brought to its knees when it comes expressing data that reflects the world outside. 'EverythingIsa
rule' will have difficulty expressing ConfigurableModularity
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 IdealProgrammingLanguage
). Blending trigger and event prevents reuse of either. Blending hard and soft results in the elegant simplicity of CeePlusPlus
combined with the blazing speed of SmallTalk
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 TuringTarpit
- the loss of discernible language features other
than being TuringComplete
. One might call these SymmetryOfLanguage
, respectively. Patterns that follow these combined principles include such gems as AlternateHardAndSoftLayers
(which separates trigger from action), and so on. Doing so as a DesignPattern
keeps pieces of the design simple and reusable... and DesignPatterns
indicate a MissingFeatureSmell
Based on these observations, I believe that the means to achieve EconomyOfExpression
(and every other SoftwareEngineering
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
- If one supports ObjectOriented with its 'local' data that 'projects' into the world around it, one must also support a reflective DataModel 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 FirstClass processes and potentially support transactions.
- One might support AlternateHardAndSoftLayers at the language level by providing staged compilation, such as CompileTimeResolution and PartialEvaluation as well as ExtensibleProgrammingLanguage 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 CodeGeneration is necessary, and simplifies code management relative to using multiple external languages to solve the problem.
- Following YinYangPrinciple and SymmetryOfLanguage also helps clarify a LanguageSmell 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 AspectOrientedProgramming and HyperSpaces, and is achieved to lesser degrees by MultiMethods, open functions, and open data types.
- Applying YinYangPrinciple and SymmetryOfLanguage, 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 ObjectOriented 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 ObserverPattern, 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 DomainSpecificLanguage
for this purpose... there is no way to model the who, what, when, where, why, and how of a 'program' without a GeneralPurposeProgrammingLanguage
. 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 TuringTarpit
. By these principles we achieve EconomyOfExpression
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 LanguageDesigner
s diagnose LanguageSmell
s and even suggest approach for repair, but they do not allow one to automate language design and they do not tell LanguageDesigner
s 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 LanguageDesigner
. To the degree the above principles help, I provided a few examples above (a group of four bullet points). They aide with EconomyOfExpression
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 DependencyInjection
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.