Download PDF version of this article PDF

Unleashing the Power
of End-User Programmable AI

Creating an AI-first program Synthesis framework

Erik Meijer

As a demonstration of what can be accomplished with contemporary LLMs (large language models), this paper outlines the high-level design of an AI-first, program-synthesis framework built around a new programming language, Universalis, designed for knowledge workers to read, optimized for our neural computer (Automind; queue.acm.org) to execute, and ready to be analyzed and manipulated by an accompanying set of tools. We call the language Universalis in honor of Gottfried Wilhelm Leibniz, a 17th century German polymath who had an amazing vision long before our time. Leibniz's centuries-old program of a universal science (scientia universalis; wikipedia.org) for coordinating all human knowledge into a systematic whole comprises two parts: (1) a universal notation (characteristica universalis; oxfordreference.com) by use of which any item of information whatsoever can be recorded naturally and systematically, and (2) a means of manipulating the knowledge thus recorded in a computational fashion, to reveal its logical interrelations and consequences (calculus ratiocinator; wikipedia.org). Exactly what current day LLMs provide!

This may sound a bit abstract and academic, but it is extremely concrete and practical. Here is an example of a question that can be asked in natural language (the what):

 

Question

Alice bought a kilo of apples for $[@B]. She sold them for $[@S]. How much percent %[@P] profit or loss did Alice make?

This is the generated Universalis implementation (the how):

Answer

The apples cost $[@B], and the selling price was $[@S], so Alice made a profit of $[@D is (@S-@B)].The profit percentage is therefore [@P is (@D/@B)*100)]%.

 

While this may seem like a whimsical example, it is not intrinsically easier or harder for an AI model compared to solving a real-world problem from a human perspective. The model processes both simple and complex problems using the same underlying mechanism. To lessen the cognitive load for the human reader, however, we will stick to simple targeted examples in this article.

To actually run a Universalis program, users need to be able to enter initial values for variables and view the values of computed variables. Depending on the environment in which Universalis code runs, the user interface can vary. Assuming a spreadsheet-style "live programming" environment where you can toggle between showing formulas and showing values, setting @B=10 and @S=17, @P=70 and the code renders as follows:

 

Question

Alice bought a kilo of apples for $[10]. She sold them for $[17]. How much percent %[70] profit or loss did Alice make?

Answer

The apples cost $[10], and the selling price was $[17], so Alice made a profit of $[7 is (17-10)].The profit percentage is therefore [70 is (7/10)*100)]%.

 

Unlike traditional programming languages, which prioritize syntax and structure optimized for writing by professional developers, Universalis is designed with the philosophy that code should be read by domain experts and written by machines.

This design tradeoff means that Universalis scripts are structured in a way that closely resembles natural language, making them intuitive and accessible even to those without formal programming training. A simple language reduces the complexity that an AI model needs to handle, making it easier for the model to generate accurate and error-free code. This shift in focus from writing to reading represents a significant departure from conventional programming paradigms, paving the way for a more inclusive and user-friendly approach to harnessing the power of AI.

Think of Universalis clauses as some kind of literate Excel spreadsheet formulas such as [@D is (@S-@B)] over named tables, or relations, enclosed in hedges surrounded by natural language explanation, where cells in the table correspond to variables such as @B, @S, and @D (figure 1).

Unleashing the Power of End-User Programmable AI | FIGURE 1: Universalis clause, like a literate Excel spreadsheet formula

The main goal in designing Universalis was that any user who can write basic Excel formulas should be able to understand Universalis scripts. Technically, Universalis is based on Prolog, and code fragments inside [...] hedges are logic predicates.

You can also optionally specify, or let Automind generate, pre-conditions that are checked before the code is run by Automind:

and post-conditions that are checked after Automind has run the code:

Based on these pre- and post-condition contracts, you can reason about whether your program does what you think it does and raise an error if a condition is violated. Note that pre- and post-conditions are also vanilla Universalis programs, thus limiting the concept count for the user.

By embedding pre- and post-conditions directly into the language semantics, Universals provides a pragmatic and extensible method for implementing AI safety. This approach allows developers to monitor and enforce the logical correctness and ethical compliance of all Automind computations.

The current approach to AI safety is to align foundation models to human values using various techniques such as reinforcement learning from human feedback (RLHF). We believe this is simultaneously not scalable as well as not compositional. Safety is context- dependent and should be layered on top of unconstrained base models and enforced by formal methods. This results in a more robust and flexible approach to AI safety.

The concept of pre- and post-conditions should be familiar to Excel users as they correspond directly to Excel's data-validation rules. For example, the pre-condition [@B>0] corresponds to the rule in figure 2.

Unleashing the Power of End-User Programmable AI | FIGURE 2: The pre-condition [@B>0]

The rest of the program text of the pre-condition corresponds to the input message that explains to the user why the constraint is put in place (figure 3).

Unleashing the Power of End-User Programmable AI | FIGURE 3: Explaining to the user why a constraint is put in place

When the user enters a value that violates the pre-condition, an error is raised
(figure 4).

Unleashing the Power of End-User Programmable AI | FIGURE 4: Data Validation rule error message

In addition, post-conditions are data-validation rules that are checked when the value of a cell is recalculated and serve as a test to catch potential errors in the cell's formula (figure 5).

Unleashing the Power of End-User Programmable AI | FIGURE 5: Post-condition

In Excel, however, validation conditions are not automatically run after a cell's value is calculated, but validation can be enforced on recalculated cells using VBA to implement post-condition checks.

To allow for more complex calculations, besides chaining or sequential composition, of clauses, Universalis also supports conditionals, implicit lifting over sequences, and query comprehensions.

 

Conditionals

In problem-solving and decision-making processes, people need a way to handle different scenarios based on varying conditions or inputs. Conditionals make it possible to define specific actions or outcomes that depend on whether certain conditions are met.

Traditional programming often makes conditionals complex and difficult to read for non-developers. By structuring conditionals as checklists and explaining each branch in natural language, Universalis ensures that the logic is clear and intuitive for the human reader, while the Universalis interpreter Automind can still recognize them as control-flow decision points as it executes the program.

 

Question

Erik has [@btc] BTC. If that is enough to buy [@msft] MSFT, he'll do it; otherwise, he'll keep the BTC. Help Erik decide, and compute how much BTC he has left as [@btc_left].

Answer

First, we need to get today's date [TODAY(@today)].

Next, let's find the current price of MSFT stocks [STOCK("MSFT", @today, {..."close":@msft_price...})]. The total cost of [@msft] MSFT stocks is [@msft_total is @msft_price*@msft].

Then, let's find the current price of BTC [SEARCH("current price of BTC in USD", {..."price":@btc_price...})]. The total value of [@btc] BTC is [@btc_total is @btc_price*@btc].

Now, let's compare the two values:

This example assumes that the condition [@btc_total >= @msft_total] is false, so that branch is not taken. With different initial values for @btc and @msft, however, the result of the test might be true, leading to the other branch being taken. In such cases, the Universalis user interface crosses out the other branch as unused, clearly indicating the chosen path, while still displaying all possible continuations.

 

Bulk Processing/Loopless Programming

When solving tasks, you often want to apply an operation to a collection of values rather than a single value. This need arises frequently, and traditional programming languages address it with for- and while-loops, or higher-order functions such as map and forEach. These constructs can be an unnecessary distraction for users who lack a computer science background.

In Universalis, this is handled by implicitly broadcasting operations on single elements to collections, similar to how NumPy or Pandas operate in Python or how dynamic array formulas and spilled array behavior in Excel allow for loopless programming.

Consider a primitive [toPdf(@src: File, @dst: File)] that converts a single file to PDF, and another primitive [listFiles(@directory: Directory, @files: List<File>)] that lists all files in a given directory.

 

Question

Convert all files in [@dir] to pdf.

Answer

List all files in the [@src] directory [listFiles(@directory, @files)]. Then, convert each file in [@files] to pdf [toPdf(@files, @dst)].

 

Even though toPdf is defined to convert a single file to PDF, the Universalis implementation implicitly converts the call [toPdf(@files, @dst)] on a list of files to something equivalent to [map(toPDF, @files, @dst)]. This means the user does not need to worry about how to stretch an operation on single files to collections of files similar to the conventions used in Excel and (researchgate.net), the precursor of LINQ (learn.microsoft.com).

In Excel, dynamic array formulas allow users to perform operations across multiple cells without needing to write separate formulas for each cell as well. For example, to apply a unary operation, such as calculating the square of each number column A in a named table Data, you can simply enter the formula =Data[A]^2 in the first cell of column B. Excel will automatically spill the results into the appropriate range in column B, applying the formula to each element in column A.

 

Query Comprehensions

A common scenario is where users want to process data by counting, filtering, grouping, aggregating, etc. By focusing on a structured natural-language approach for comprehending queries, Universalis ensures that even those with minimal experience in programming can perform advanced data manipulations.

Here is a very simple example that requires filtering and counting:

 

Question

How many [@customers] live in Palo Alto in [@total]?

Answer

Consider each customer [@c = { ..."city": @city, ...}] from [@customers]:

 

The corresponding query in SQL would be SELECT COUNT(*) FROM customers WHERE city = 'Palo Alto'. While the SQL version looks more concise, consider that both the SQL as well as the Universalis query are expressing how to solve the user's original question, and it is not what the users themselves write but what they should read. The Universalis query expresses the same solution in a way that is easier to understand for users who might be a little intimidated by SQL. If a model were asked to explain the SQL query, it would answer with something close to the Universalis formulation.

Of course, if users ever wish to see the equivalent SQL for a Universalis query, they can ask Automind to provide it, allowing them to understand the underlying structure from another perspective.

The next question generates a complex answer that involves grouping and aggregation:

 

Question

Given [@Players], I need to select the best team for the next World Cup soccer.

Answer

Consider each player [@P = {"position": @Position, "stats": @Stats, "games": @Games, "age": @Age}] from [@Players].

Group each player [@P] by their position [@Position]. This organizes players into groups based on their playing positions (e.g., Forward, Defender).

For each group of players in the same position:

Keep only groups where [@AverageStats > 100] and [@MinimumGames > 3]. This ensures that only groups with high overall performance and sufficient experience are considered.

 

Writing this query in SQL is quite difficult since SQL does not support nested tables, but assuming you have a COLLECT aggregate, the equivalent SQL for this query would be something like:

SELECT
  P.Position,
  AVG(P.Stats) AS averageStats,
  MIN(P.Games) AS minGames,
  COLLECT(P) AS players
FROM
  Players P
WHERE
  P.Age > 18
GROUP BY
  P.Position
HAVING
AVG(P.Stats) > 100 AND MIN(P.Games) > 3

To make this query work in SQL, you would have to collect the rows into a JSON list, but the syntax and details for this differ among the various SQL dialects. Despite the tenacity of SQL, its lack of compositionality makes it a suboptimal query language for average users.

To implement, or approximate, this query in Excel requires pivot tables and the use of XLOOKUP and VLOOKUP functions. Users who can manage that themselves are so sophisticated that they do not need Automind and Universalis.

The actual implementation of Universalis uses Kotlin DataFrames, and Automind translates the query into the following code:

players.groupBy(Player::position).aggregate {
  it.mean(Player::stats) into AggregatedPlayerGroup::averageStats
  it.min(Player::games) into AggregatedPlayerGroup::minGames
  it into AggregatedPlayerGroup::players
}.filter {
  it[AggregatedPlayerGroup::averageStats] > 100 &&
  it[AggregatedPlayerGroup::minGames] > 3
}

While this implementation does support nesting, it is even less accessible for the nondeveloper user than the corresponding SQL.

 

Pattern Matching in Universalis

A lot of people seem to be focused on guiding LLMs to generate correct JSON. The real issue is not generating JSON, however, but making sense of the messy and complex JSON data that tools often produce. Consider an example where a tool generates JSON data for a stock query like [STOCK("IBM", @json)]. The resulting JSON might look like this:

{"data": [{"symbol":"IBM", "name":"International Business Machines Corp", "exchange":"NYSE", "mic_code":"XNYS", "currency":"USD", "datetime":"2024-04-19", "timestamp":1713533400, "open":"182.42999", "high":"182.80000", "low":"180.57001", "close":"181.58000", "volume":"3037600", "previous_close":"181.47000", "change":"0.11000", "percent_change":"0.06062", "average_volume":"0", "is_market_open":false, "fifty_two_week":{ "low":"180.17000", "high":"183.46001", "low_change":"1.41000", "high_change":"-1.88000", "low_change_percent":"0.78260", "high_change_percent":"-1.02475", "range":"180.169998 - 183.460007"} }], "status":"ok"}

From this blob, a user might want to extract specific information such as "volume" and "closing" price. Writing code to parse and extract this information can be daunting, even for those with a strong programming background, let alone for end users.

This is where Universalis excels with its pattern-matching capability. Users can simply specify the patterns they want to match within the JSON structure:

{ ... "volume": @V ... "closing": @P ... "currency": @X }

This pattern matching creates the bindings @V=3037600, @P=181.58000, and @X="USD.".

Pattern matching in Universalis streamlines the process of working with complex structured data, ensuring that even those with minimal programming experience can effectively interact with and use JSON data.

As a language designer, it's always tempting to add more features to a language (think C++) rather than limit them (think the Nicklaus Wirth's family of languages like Pascal, Modula2, Oberon). For Universalis, we intentionally keep the language minimal, focusing on sequential composition, implicit looping by lifting singleton operations over collections, and fully nested dataframe queries. This simplicity ensures Universalis is easily readable by non- specialists, can be generated by LLMs, and is compatible with traditional tools and theorem provers.

Most workflow tools, like UIPath, Workato, and Azure Logic Apps, primarily use sequential composition with limited control flow and repetition validating our design choices. One complication in workflow tools is the need for a "language" of functions to manipulate data as it flows between actions. We replace this with pattern-matching to simplify and enhance the expressiveness of the base language without the need for an extra layer of ad-hoc functions.

Under The Hood

A typical compiler stack is implemented by first lexing (tokenizing) and parsing the program text into an AST (abstract syntax tree), doing semantic analysis on the AST to check for type errors, undefined variables, etc. Next, the AST is lowered into an IR (intermediate representation) to prepare for code generation and optimization. Finally, the optimized IR is mapped to target (machine) code.

In Lisp, the lexing and parsing steps are trivial since the concrete syntax is the same as the data structure that represents the AST. Language geeks like to say that Lisp is homoiconic. Similarly, Intentional Programming abstracts from concrete syntax by focusing on representing programs as ASTs based on the high-level intent of the developer. The same AST can then be rendered into different concrete syntaxes based on the context.

Since Universalis programs are trying to capture the high-level intent of the user as well, Automind does not generate the concrete syntax seen in the examples so far but instead creates an abstract, intentional representation of the Universalis code:

[{ "comment": "Let's first calculate the worth of the apples in dollars." }, { "expression": "@W is @A*@X" }, { "comment": "Next, we need to find the current price of gold (assumed to be in dollars per ounce)." }, { "expression": "WOLFRAM(\"gold price per ounce\", ...$@G/ounce...)" }, { "comment": "Convert the result to a number." }, { "expression": "TO_DOUBLE(@G, @GD)" }, { "comment": "Finally, convert the worth of apples from dollars to ounces of gold." }, { "expression": "@WorthInGold is @W/@GD" }, { "comment": "ounces of gold." }]

This representation can be rendered in different concrete syntaxes depending on the domain in which Automind is deployed.

 

Conclusion

Universalis enables users to instruct LLMs on performing various tasks through a natural and systematic approach. Complementing this, the neural computer, Automind, executes these programs using LLMs as its virtual machine.

Universalis is designed to be as intuitive as possible, allowing users to write, understand, and interact with AI-generated code with ease. By incorporating pre- and post-conditions directly into language, Universal ensures logical correctness and ethical compliance, offering a robust method for AI safety.

"I think of a programming language as a tool to convert a programmer's mental images into precise operations that a machine can perform. The main idea is to match the user's intuition as well as possible."
—Donald Knuth

Moreover, as we have seen above, Universalis supports advanced features such as conditionals, bulk processing, and query comprehensions, further extending its capabilities while maintaining a natural language feel. This versatility ensures that Universalis can handle a wide range of computational tasks, from simple arithmetic and pattern matching to complex data processing and workflow execution.

 

Erik Meijer brings a rare combination of technical expertise and people leadership to his latest quest to use AI to democratize end-user programming. As a renowned computer scientist, entrepreneur, and tech influencer, Meijer has made pioneering contributions to programming languages, compilers, cloud infrastructures, and AI throughout his tenures at Microsoft, Meta (Facebook), Utrecht University, and Delft University of Technology.

Copyright © 2025 held by owner/author. Publication rights licensed to ACM.

acmqueue

Originally published in Queue vol. 23, no. 3
Comment on this article in the ACM Digital Library





More related articles:

Michael Gschwind - AI: It's All About Inference Now
As the scaling of pretraining is reaching a plateau of diminishing returns, model inference is quickly becoming an important driver for model performance. Today, test-time compute scaling offers a new, exciting avenue to increase model performance beyond what can be achieved with training, and test-time compute techniques cover a fertile area for many more breakthroughs in AI. Innovations using ensemble methods, iterative refinement, repeated sampling, retrieval augmentation, chain-of-thought reasoning, search, and agentic ensembles are already yielding improvements in model quality performance and offer additional opportunities for future growth.


Vijay Janapa Reddi - Generative AI at the Edge: Challenges and Opportunities
Generative AI at the edge is the next phase in AI's deployment: from centralized supercomputers to ubiquitous assistants and creators operating alongside humans. The challenges are significant but so are the opportunities for personalization, privacy, and innovation. By tackling the technical hurdles and establishing new frameworks (conceptual and infrastructural), we can ensure this transition is successful and beneficial.


Erik Meijer - From Function Frustrations to Framework Flexibility
The principle of indirection can be applied to introduce a paradigm shift: replacing direct value manipulation with symbolic reasoning using named variables. This simple yet powerful trick directly resolves inconsistencies in tool usage and enables parameterization and abstraction of interactions. The transformation of function calls into reusable and interpretable frameworks elevates tool calling into a neuro-symbolic reasoning framework. This approach unlocks new possibilities for structured interaction and dynamic AI systems.


Chip Huyen - How to Evaluate AI that's Smarter than Us
Evaluating AI models that surpass human expertise in the task at hand presents unique challenges. These challenges only grow as AI becomes more intelligent. However, the three effective strategies presented in this article exist to address these hurdles. The strategies are: Functional correctness: evaluating AI by how well it accomplishes its intended tasks; AI-as-a-judge: using AI instead of human experts to evaluate AI outputs; and Comparative evaluation: evaluating AI systems in relationship with each other instead of independently.





© ACM, Inc. All Rights Reserved.