David Brandt · trains of thought

Programming languages · The lowest common denominator, part 2 of 4

Levels of denominator

Why one lowest common denominator was never going to be enough

Seeded 2001 Written November 2026

I was not under the delusion that the same LCD would work with everything.

PL/SQL makes the point by itself. It was on the list for one reason: the product I was working on used it. It is Oracle's own proprietary language, and outside Oracle's databases nobody in their right mind writes it. It is SQL with loops, records, collections and exceptions bolted on, so you can write procedural code in it if you must, but its idea of data is rows. Nothing in it is shared the way objects are shared in Java: you assign a record and you get a copy. Put PL/SQL in the same bucket as Java and C++, and the denominator shrinks to whatever PL/SQL can do. Leave it out, and you have left out the database, which is where a lot of the rules had to run.

What I thought instead was that there could be levels of common denominator. I was mostly thinking about imperative languages, and about whether the idea could spread further than that.

The ladder

Here are the levels. Each one is the common denominator of a smaller family of languages. Each level contains the one below it.

Level 0: data. Booleans, integers, decimals, text, dates, enumerations, records, lists and maps. No null. This is what my RPC code generators at OZ already did: describe a shape once, and get it in every language. Almost everything can carry level 0, because it is just declarations.

Level 1: calculations and decisions. Functions of values, with no state and no loops: arithmetic, conditions, choices, failures, and the pure parts of the standard library. This reaches further than you'd think. Every imperative language can do it. So can PL/SQL. So can an SQL query, as an expression. So can COBOL, with COMPUTE and EVALUATE. So can Lisp, and so can Prolog, as a predicate that never backtracks.

Level 2: structured imperative. Local variables, assignment, loops, procedures, and records and collections passed around by value. This is the procedural core: C, C++, Java, Pascal, Ada, PL/SQL's procedural half, the structured dialects of BASIC, and COBOL within its limits. SQL drops out here, because SQL doesn't do steps.

Level 3: managed references. Values that are shared between owners and change in place, with their lifetime managed by a garbage collector or by reference counting. Java, C++ with reference-counted handles, Delphi, Perl and Lisp can do this. PL/SQL drops out here.

That last level is the language I wanted: imperative, with managed memory. The levels below it are what you get when you ask how much of it survives in environments that can't manage memory that way.

A module would say which level it needs, and the transpiler would refuse anything above that. A target would say which levels it can carry. A rule written at level 1 could then be used from level-3 Java code and from an SQL query in the database alike. The discipline for whoever wrote the rule would be simple: use the lowest level that can say it, because that is the level that reaches the most places. Most business rules are decisions and calculations. Most of them would have lived low.

What a level 1 rule looks like

LCD never got a syntax, so the examples in this series borrow Y's.

module pricing
level calculations


-- A price after a discount, to the cent.
--   why: The order screen, the invoice run and the month-end report must show the same price.
function discounted(amount is decimal(12, 2), rate is decimal(5, 4)) returns decimal(12, 2):
    return round_half_away_from_zero(amount * (1 - rate), places: 2)

In the database, a level 1 function doesn't need to be a function at all. It can be an expression in a query, and run over every row at once, which is where the database earns its keep:

SELECT order_id, ROUND(amount * (1 - rate), 2) AS discounted
FROM   order_lines;

In COBOL it becomes a small subprogram, with overflow reported through a status field:

       PROCEDURE DIVISION USING AMOUNT RATE RESULT STATUS-CODE.
           MOVE 0 TO STATUS-CODE
           COMPUTE RESULT ROUNDED = AMOUNT * (1 - RATE)
               ON SIZE ERROR MOVE 1 TO STATUS-CODE
           END-COMPUTE
           EXIT PROGRAM.

The rounding mode in the LCD version is not an accident. Rounding half away from zero is what Oracle's ROUND and COBOL's ROUNDED both do natively, so neither of them needs any help. Pick a different mode and you'd need a library in each of them to do it for you. Part 3 is largely about choices like that one.

Who carries what

This is roughly how the languages I knew in 2001 would have sorted themselves. A full mark means the language carries the level natively. A half mark means it can, with a runtime library or within limits. A cross means it can't.

Language 0 Data 1 Calculations 2 Structured 3 References
C ✓ ◐ ✓ ◐
C++, in each framework ✓ ✓ ✓ ✓
Java ✓ ✓ ✓ ✓
Pascal (Delphi) ✓ ✓ ✓ ◐
Ada ✓ ✓ ✓ ◐
PL/SQL ✓ ✓ ✓ ✗
SQL ✓ ✓ ✗ ✗
COBOL ✓ ✓ ◐ ✗
BASIC ◐ ✓ ◐ ✗
Lisp ✓ ✓ ✓ ✓
Prolog ✓ ✓ ◐ ✗

C gets a half mark at level 1 because it has no decimal type, so money needs a library. COBOL gets a half mark at level 2 because its tables have fixed sizes and, in the COBOL of that time, nothing could call itself. Prolog gets one because loops have to become recursion and assignment has to become new names.

Why transpiling forces the levels

This is where the obsession with transpiling comes back in.

If LCD had been compiled, levels would hardly matter. A compiler can put any program on any machine, because in the end every target is machine code. One denominator, the whole language, would have done.

A transpiler can only produce what the target language can say, and say readably. The common denominator that matters is one of expression, not one of execution. And expression is exactly where language families differ: SQL can say a calculation beautifully and a loop not at all, and Java can say almost anything but says a decimal calculation in a way nobody would call beautiful.

So the levels are not a compromise forced on the design. They fall straight out of the decision to transpile.

The same decision shapes everything else. Transpiled code is code people own. It has to read as if a careful programmer of that language wrote it, with its own types, its own loops and its own way of failing. Names have to survive unchanged, so that a search for a rule finds every copy of it. Each generated function has to say where it came from, and nobody edits it by hand. Whatever a target lacks, such as decimals in C, has to come from a small library written in that target's own language and shipped as source beside the generated code. And the whole thing has to fit into each environment's build as it already is: the C++ goes through the C++ build, the PL/SQL is installed like any other package.

Spreading further

The other half of the thought was whether LCD could spread beyond the imperative family. Each direction would have needed something different.

Sets. Level 1 already reaches into SQL, as an expression in a query. A level of set operations, such as filtering, joining, grouping and summing lists of records, would sit beside level 2 rather than above it. It would transpile into SQL and Lisp naturally, and into loops everywhere else.

Logic. Level 1 maps onto Prolog predicates. What Prolog adds, searching and backtracking, has no counterpart anywhere else, so it would stay out. The interesting direction is the other one: LCD's rules written out as Prolog facts and clauses could be asked why a decision came out the way it did.

Lisp. Lisp could carry every level, and its macros could be LCD's transpiler for Lisp. It is the one target where LCD would have been a library rather than a code generator.

Assembly. Assembly can run anything and explain nothing. LCD's whole promise was readable code in each environment, so assembly would have been a compiler's job, never LCD's.

Next, in part 3: what happens inside a level, when the languages that are supposed to agree simply don't.

David Brandt is a computer scientist, systems architect and independent scholar based in Reykjavík.