1. Introduction
High-level programming languages are back. More and more developers and companies are using high-level programming languages, either interpreted languages such as Python or functional languages such as Scala or Erlang (or both, such as Clojure). Whatsapp is well-known for using Erlang which it sees as a major reason for maintaining such a large system with few engineers (cf. the Wired article : “Whatsapp serves 900 million users with 50 engineers”). Twitter is using Scala, a functional object-oriented programming language, for similar reasons and with similar success. Many of these object-oriented functional programming languages are 20 or 30 years old, but they have experienced a regain in popularity. This renewed success is both a testimony to Moore’s law (with faster machines, one can afford to lose a little bit of performance to gain expressiveness and speed of development) and to the digital world’s need for agility. High-level programming languages yield shorter code which is faster to modify. This is a key idea which I have developed in my last book (English edition is coming soon).
This
is a topic dear to my heart. Thirty years ago, I worked as a research scientist
and developed various
object-oriented functional programming languages. The last
one, CLAIRE,
has been the programming toolbox for my own software
development projects for the last two decades. This blog post
talks about my decision to refresh CLAIRE with a new release, based on a new
target environment (i.e., leveraging the superb Go
programming language platform produced by Google). This short post is organized as follows.
Section 2 will briefly recall the benefits of interpreted functional
object-oriented programming languages. Section 3 describes why and how I
decided to create a new release of CLAIRE. CLAIRE was born in the 90s as an
open-source project, with a small user community for a couple of years and was
used successfully in a number of enterprise decision-aid software. It then lost
its appeal and has been dormant for 20 years. 18 months ago, I decided to
switch from C++ to Go as a foundation language for CLAIRE. This blog post is a
very personal account of the “resurrection” of the CLAIRE programming language.
This post is much more personal than what you may find regularly in either of
my blogs. You should probably stop reading if you do not have a keen interest
with programming languages. To give you “a one-page summary about CLAIRE”, I
reproduce here a slide from a 1998 presentation about CLAIRE. If this does not make sense, now is a right time to stop :)
2. The Benefits of High-Level Programming Languages
Functional
programming has many meanings. Here I refer to the ability to manage nameless
functions as first-class citizens of the language and pass them as arguments.
The archetype of functional programming language is LISP (direct inspiration
for Clojure, but also for CLAIRE, since CLAIRE’s ancestor was as LISP overlay).
Functional
programming matters, because it supports high-level of abstraction,
yielding code which is both more concise and easier to maintain because the
intent is more visible. Function composition, or applying a function to a set/list
with the famous “map”
function, is made much simpler when functions (the famous “lambdas” of LISP) are
first-class citizens. As any programmer knows, there is a learning curve (with
LISP, Scala, Erlang, …) because programming at a higher abstraction level is
not always simpler. However, as soon as the code needs to change, this investment
pays handsomely. The structure of the program (how functions are composed) is
much more visible, and the number of lines that need to be changed is much smaller.
Functional programming languages are ideal scripting languages over a library
of lower functions precisely because of the capacity to manage functional
expressions. There is no better example than Python which has become the standard
for combining functions from a machine learning algorithm library.
Interpreted languages are an alternative to compiled languages which inherit the “Print(eval(read()))” loop from LISP. An interpreted language is capable running dynamically any code fragment. The performance of modern computers and modern compilers have made interpreted languages almost irrelevant during the last two decades because one could modify code “on the fly” on their favorite IDE and re-run almost instantaneously. However, when prototyping complex algorithms, the interactive capability of the interpreter loop is unmatched. It allows for a complex inspection of the working environment that surpasses the best IDE. The return to popularity of interpreted language – here also, Python is the foremost example – is the consequence of computer performance (Python programs are slow, but usually most of the heavy work is done in the machine learning library that has been compiled earlier) and the need for agility. As soon as the software development process is a learning process, where many iterations are requited to grow the program structure, interpreted languages offers competitive value. To be more explicit, there are two kinds of required agility, which correspond to two time-horizons. If you follow a specification to produce your code, the added value of interpreted languages is very small (any modern IDE will give you enough flexibility). If, on the other hand, the code needs to be fine-tune through multiple iterations where one needs to explore the program output to understand how to improve the current algorithm, then an interpreter’s loop is a great tool.
There are many others programming patterns
that contribute to the “high-level” of abstraction. The title of this blog post
talks about “high-level programming language”, which is a shortcut for languages
with a high level of abstraction. Other famous programming patterns include “object-oriented
programming” (with a lot of debates about its virtues or what it means), “rule-based
programming” (think of PROLOG
and the GOFAI – good old-fashion AI – tools of the 80s) or
“set-based programming”, to
name a few. Object-oriented programming is controversial precisely because it
has many flavors. The “simple kind” of the 80s (SMALLTALK) is actually a great
way to increase the abstraction level, while the “compiled kind” that you find
in C++ or Go is much more debatable. What raises the “level of abstraction” is
the combination of conciseness and a clear semantics (you can express your
ideas with fewer lines, and it is easier to understand what they mean). As
expressed in the introduction, high-level languages have become more popular
because of the request for agility. High-level abstraction means a code that is
easier to change and to maintain. Here we mean the capability to change on a
longer time-horizon, which is why the arguments apply to compiled languages
(such as Swift or Java) as well as interpreted ones. High-level of abstraction also means a code
that is easier to share and to reuse.
3. How I got sidetracked when picking a new programming language
Five years ago, I decided to write an iOS application and worked for two years with the Swift language. Swift is not interpreted, but it is an elegant language with high level of abstraction, hence I figured it as time to drop CLAIRE for my future projects and to pick a modern programming language. After completing Knomee, I spent the 2020 summer looking at Go, Java, Python, Rust, Node, Scala as possible alternatives. Somewhere along the way, I decided to stick the CLAIRE and to replace the old C++ compiler with a CLAIRE-to-Go compiler.
The
first learning from my experiments is that I did not want to lose the flexibility
and speed that comes from combining an interpreted language with set-base
programming capabilities. Here is
another slide that is extracted from this CLAIRE presentation,
it would require a much longer development to give you a true account of the benefits
of set-based programming.
Set-based programming under the interpreter makes developing complex algorithms much easier (here again, no surprise if Python is so popular). Moving to Go or Java would have been a logical choice (CLAIRE was based on C++ with its own memory management and garbage collection, and it was not doing a first-class job … nor was Swift actually). However, most of my projects are related to GTES (Game-Theoretical Evolutionary Simulation) and require a lot of tuning and iterations.
The second obvious choice would have been to pick Python to replace CLAIRE. However, the same need for iterative algorithm growing means that I was not ready to accept the performance penalty of Python (or Scala, for that matter). This choice is very subjective, depending on what you are doing, you may find that the x2 performance penalty that you pay for using (compiled) CLAIRE over Java / Go is not acceptable (but I would argue that this is limited to specific low-level system programming) or you may think – on the opposite side – that the x50 penalty that you pay with Python is perfectly fine (precisely, if you use Python to write the glue while relying on lower-level compiled libraries). To make this more concrete, the following table is the result of my experiments, using simple functions to evaluate the various programming languages. Performance is “normalized”: it is the execution time divided by the best time (which varies for each test, since each language has its strengths and weaknesses). I have regrouped the tests into four categories (e.g., “function” is a combination of recursive function and problem solving, “object” means creating / reading / updating 10 million of objects, etc.). The global score is weighted which is highly subjective since it reflects my own experience. But you will get a sense of the overall ranking by looking at the full (preliminary) table.
4. Conclusion
If you are intrigued with CLAIRE, feel free to visit the web site and the GitHib repository. CLAIRE is a “cool language” for many reasons other than was mentioned earlier:
- Knowledge representation in CLAIRE is made easier, with many “high-level” features that support entity-relationship modelling.
- CLAIRE inherits “old-fashion AI” capabilities: hypothetical reasoning and production rules. This comes from CLAIRE background as a “language for combinatorial optimization algorithms”.
- CLAIRE is self-described, self-compiled and easily extensible.
To give a last example, CLAIRE was designed
so that elaborate algorithms, such as the Hungarian
Matching Algorithm, could be written in an elegant and concise manner. The following
illustration shows the core of the CLAIRE implementation (complete algorithm
takes less than 50 lines).
CLAIRE4 is the new version of this 28ys-old
language. It is an “alpha” release because it will require a few full-sized projects
to be developed with CLAIRE 4 to reach “beta” stability level. I will apply
CLAIRE 4 to “Global
Warming Dynamic Games”, a GTES project which I left aside more than 10
years ago but which seem highly relevant in 2022.