I enjoyed Hillel Wayne’s recent newsletter about microfeatures they’d like to see in programming languages. A “microfeature” is essentially a small convenience that makes programming in that language a bit easier without fundamentally changing it. I love this idea. I’m partial to a bit of syntactic sugar, even if it can cause cancer of the semicolon. I have a few features that I’d love to see more languages adopt, so I thought I’d join in the fun. Unlike Hillel’s, however, most of mine are much larger changes. I’ve long thought it was time for a bit more experimentalism to return to programming language design, and not just for type systems! Maybe these ideas will inspire you to think up some whacky new programming languages. Let me know in the comments.
E’s quasi-literal syntax
The E language is a veritable cornucopia of interesting programming language ideas. But to pick one that I really like it’s the quasi-literal syntax for safely constructing values in other languages: SQL, HTML, etc. There were several iterations of this idea in E, and the documented versions are older I believe. But the basic idea is that you have a generic syntax for constructing multi-line strings with embedded expressions. On the surface this is a familiar idea from many languages, but E has a nice twist on it. For example, given a code snippet like the following
def widgetName := “flibble”
def widgetCount := 42
def sql := ```
INSERT INTO widgets(name, count)
VALUES($widgetName, ${widgetCount + 1})
```
db.exec(sql)
what gets constructed here is not a simple string, but rather some kind of Template object/record. That template object has two fields:
- A list of fragments of the literal string before and after each variable reference: “INSERT INTO … VALUES(”, “, ”, and “)” in this case.
- A list of the (evaluated) expression values. In this case, that is the value of the widgetName variable and the result of adding 1 to widgetCount.
The database exec() method then doesn’t take a String, but rather one of these Template objects and it applies appropriate processing based on the contents. In this case, by constructing a prepared statement, something like the following:
def exec(template) {
def sql = template.fragments.join(“?”)
def stmt = db.prepare(sql)
stmt.bind(template.values)
stmt.execute()
}
Here the (completely made up) code replaces the originally expressions with SQL placeholders in a prepared statement: “INSERT INTO … VALUES(?, ?)”. It then binds those placeholders to the actual values of the expressions passed in the template. (The exact type of the “values” field is questionable: E was dynamically-typed. For now, let’s assume that all values get converted to strings, so “values” is a list of strings: in this example “flibble” and “43”).
This has the effect of allowing the easy use of prepared statements, while having a syntax that is as easy to use as string concatenation. Safety and convenience. The same syntax can be used to provide safe templating when outputting HTML, XML, JSON, whatever.
(E’s actual approach was somewhat different to how I’ve presented it, but I think this version is simpler to understand. E also allowed the same syntax to be used for parsing too, but I think that is less compelling and complicates the feature).
Edit: /u/kn4rf and /u/holo3146 on Reddit point out that this already exists in JavaScript in the form of tagged template literals, and has been proposed for Java too in JEP-430. Very cool!
Datalog/Prolog as a sub-language
I’ve always loved Prolog and its cleaner cousin Datalog, but like many people, I find them hard languages to write a full application in. Logic programming is great for writing, well… logic. But it’s less good at the messy stuff. Robert Kowalski famously described the design of Prolog using the equation “Algorithm = Logic + Control”: the idea that the logical form of an algorithm can be separated from how it is executed (the control flow). In my opinion, we can go further and separate all the messy stuff around the edges. I propose the following equation:
Program = Logic + Control + Interaction
“Interaction” here covers a multitude of sins: user interfaces, networking, file I/O, operating system calls, etc. (Aside: parallelism is a control strategy, concurrency is interaction – hope that’s cleared that up!)
I would like to program the logic of my program in a pure logic programming language. (With a bit of sugar for functions, which are after all just a certain type of relation). And then I’d like to separately be able to specify how that logic should be executed: top-down vs bottom-up, sequential or parallel, etc. And I’d like to be able to call into this pure declarative logic from an imperative shell that handles all the messy stuff. Is that too much to ask?
Teleo-Reactive Programs
While we’re talking about the messy stuff of interaction, how about we do something a bit better than simple procedures or Java-style methods? When I was doing a PhD in AI (before deep learning took over the world), I was very taken with Nilsson’s Teleo-Reactive Programs (TRP). This was an approach to programming robots and other systems that is goal-driven but also reacts to unexpected changes in the environment. It doesn’t just keep blindly following a fixed procedure.
The basic idea is that you define a method to achieve some goal as an ordered list of conditions and corresponding actions, like the following contrived example:
to make_tea:
when perfect(tea) -> done
when brewed(tea) -> remove_teabag; add_milk
when hot(water) -> pour_into(cup); add_teabag
when cold(water) and full(kettle) -> boil_kettle
when empty(kettle) -> fill(kettle)
…
Although this looks like a simple set of if-then statements, the execution is quite different. When you invoke the make_tea method it starts a persistent process that continues until the tea is made (or an unrecoverable error occurs). At each cycle it scans through the list of conditions and executes the first one it finds that isn’t satisfied. Actions further down the list attempt to satisfy the preconditions of actions higher up the list. In this way, it is always trying to move closer to its goal condition (perfect tea).
However, the approach has some advantages. If some earlier task was completed but is somehow undone by another action — say, someone comes along and makes coffee with our freshly boiled water—the TRP will notice this the next time it scans through the list and so will automatically boil some fresh water. On the other hand, if a change in the environment serendipitously causes a higher-up goal to be satisfied already (someone puts the teabag in for us), the system will also notice that and automatically avoid duplicate work.
Although many of the examples for TRPs involve robotics and real-world tasks, this kind of intelligent goal-directed execution is incredibly useful in everyday programming too. If you’ve ever had to code retry and recovery logic with back-offs and abort conditions, you may see the value of something like TRP. Kubernetes has some similar ideas in its approach to declarative config: you describe your desired goal state for the system, and it performs a continuous diff between the current and desired states and corrects any differences it sees.
Design by Contract…
This is a simple one. I’d like to be able to easily annotate methods with pre- and post-conditions and have them automatically checked and documented:
to make_tea:
achieves full_of(cup, tea)
requires not empty(tea_box)
…
… with a STRIPS planner?
Once I’ve annotated all my methods with pre- and post-conditions, maybe I shouldn’t even have to manually bother calling them? What if I could just write:
achieve full_of(cup, tea)
and the language runtime went off and found some sequence of method calls that would achieve that post-condition.
Combine that with pre- and post-conditions specified in Datalog and methods implemented in TRPs and this starts to sounds kinda interesting! Of course, it might be terrible and I’m sure debugging would be a nightmare, but wouldn’t it be fun to at least try programming in a language like this?
“A “microfeature” is essentially a small convenience that makes programming in that language a bit easier without fundamentally changing it.”
This is called a macro. The syntactic parts can be covered by a programmable program reader. Common Lisp provides both of these things, properly and trivially.
Raku subsumes this feature as a part of a general framework:
Raku has no fixed syntax.
It presumes bootstrapping from a metacompiler (cf META II: https://en.wikipedia.org/wiki/META_II).
The metacompiler is written in itself.
The metacompiler targets a runtime that’s written in itself (plus platform specific backends).
There is in fact no single language at the syntax level, not even the metacompiler “language”, but instead an arbitrary collection of mutually embedding languages.
All of this happens ARTACT — at run-time at compile time. (Variously known as compile-time code execution, multi stage programming, etc.) This way all code is parsed and checked and codegen’d at compile time despite being modifiable by user code written in itself.
Thus nothing that should be code need be in the form of a string subject to injection attacks. Instead it’s compiled code checked at compile-time.
And this is unified with the module system. So users can create “slangs” (short for “sub-languages”) that can be shared as modules. Thus, for example, https://raku.land/zef:tony-o/Slang::SQL
It’s not yet nicely polished. But there’s good reason to think it will be.
See also https://gist.github.com/raiph/849a4a9d8875542fb86df2b2eda89296
Same deal as above.
That sounds like an informal formulation of (the theory and practice of) the Actor model: https://en.wikipedia.org/wiki/Actor_model
(The Wikipedia page focuses on the dry theory, but the starting point was Carl Hewitt’s team at MIT considering the evolution of unbounded numbers of purpose driven autonomous agents concurrently communicating via a network of unbounded time and space dimensions in the 1960s. “Teleo-Reactive Programs”, and the description you wrote, seem completely consistent with the Actor model.)
.oO ( Eiffel is still going strong. )
This is a simple one.
I don’t think it’s coincidental that Carl Hewitt et al started out with PLANNER. And then, as he and his MIT students pondered what that focused them on, namely the issue of arbitrary levels and configurations of intelligence that mixed human and machine decisions, and then the unavoidable long term issues of metastability of physical systems due to the fundamental limits on logic established by quantum mechanics — you cannot avoid the problems of time and space and uncertainty just because you think you’re dealing with logic — they arrived at the need to first refocus on getting right what became the Actor model.)