Hi everyone!
I’d like to give an account of the research project we’re doing at JetBrains.
It is called Ludwig after Ludwig Wittgenstein and has an ambitious goal of re-engineering the foundations of the software development stack.
At the moment, we are still at a very early design and prototyping stage, but we believe that this project will let us create the next generation of development tools.
Here are the most important ideas we’re trying to materialize:
Liberation of code from the shackles of textual representation
Human-readable textual notations were a great innovation back in the 1950s. However, we have made great progress in the ways we store complex data structures since then, and the typical codebase sizes have also grown by a few orders of magnitude.
Code is essentially a graph with one tree-like perspective more important than others. An adequate way of storing such complex and evolving data as code would be to put it in a versioned graph / linked document database, supporting Git-like branching and merging. That would get rid of the need for continuous parsing, indexing, symbol resolution and typechecking that constitute the code editing workflow in modern IDEs. We would resolve symbols once, at the moment of typing and store their unique identifiers (not just names!) in an indexed by construction and preserving referential integrity database.
That would also make such intentions as Rename, Move, Find Usages or Go To Definition trivial to implement. Of course, structural representation of code will come hand-in-hand with structural (semantic) diff and merge.
This graph-like representation should allow for a fine-grained tracking of changes and dependencies and dramatically reduce feedback times in such scenarios as incremental compilation.
A minimalist approach towards programming language design
To prevent our language from becoming “fat and weak” as John Backus has put it in his famous lecture, we want to pass our language through a series of aggressive optimization rounds or distillation passes.
This should result in something comparable with Smalltalk’s “syntax on a postcard”. Actually, we believe that we could make it even more symmetric by eliminating the distinctions between methods, named and anonymous functions, operators and statements. (As you know, Smalltalk has different syntaxes and different behavior of return statements for methods and blocks and “surprising” execution order rules for different kinds of messages).
The goal is to come up with a language that would be easy to learn and read and straightforward to reason about for all kinds of agents-be it humans, static analysis tools or AI.
In terms of notation, it will look like an indentation-based syntax for a tiny subset of Lisp, but without its macros or the zoo of “special forms”. Being freed from the limitations of plain text, we’re going to use semantic coloring to make the notation even more expressive and compact.
Obviously, our programming language, as any other, should be able to express computation logic and code structure; what is less common is that we also want it to be able to express data, configurations and knowledge, thus eliminating the need for additional DSLs. A YAML-like data notation emerges from our tiny language as naturally as JSON emerged from JavaScript. The only difference is that JSON was discovered by chance, and our language is consciously designed to be able to declaratively describe complex data structures.
Unification of Object-Oriented and Functional programming
As a part of our minimalist program, we are aiming to heal the great schism that divided programming into the object-oriented and the functional worlds.
We believe that the class-free approach in the form proposed by Douglas Crockford will let us make OOP an integral part of functional programming, thus converging the two into what could be called unified programming. This form of OOP will keep the best parts—encapsulation and polymorphism and get rid of the "considered harmful" implementation inheritance. There will be no need for classes, constructors, prototypes, visibility modifiers, this and new - just immutable structures and anonymous functions.
An IDE designed for focus and context awareness
We want to build an immersive Smalltalk-like environment with structural navigation, a smaller editing scope and richer and more dynamic context compared to the traditional file- and text-based IDEs. This seems well-aligned with both the minimalist design of the language and the non-textual storage format. The latter should allow us to store some additional information alongside the code. That will include all kinds of metadata, normally invisible to the users, but also some unusual forms of embedded content. Think of a documentation comment containing a video explaining the algorithm or a discussion between multiple developers linked to a certain place in the code.
Smart typing
We also have some ideas on how we could implement some smart typing techniques, combining the convenience of automatic type inference with the solidness and discipline of explicit type annotations.
The key idea is that the flexibility of the non-textual representation will eliminate the need for the user to choose between the two worlds. Manual annotations can be hidden to reduce visual distraction, automatically inferred types can be displayed and persisted in the code database, etc.
Designed to be AI-fitting
Finally, we want the whole thing to be future-proof and provide better support for AI-aided development compared to the traditional languages. The simplicity of the language as well as its fine granularity and its property of always having all the symbols resolved should allow for high-quality “understanding” and retrieval-augmented generation of code compared to such languages as Python or Java.
As I said, at the moment we’re still at a very early stage. Many of our challenges are not technical, but about finding the way of how we could shape this set of ideas into a product vision. We are, of course, open to collaboration with like-minded people.
I will be happy to answer your questions and hear your feedback.
Very interesting. Thanks for sharing and great to see JetBrains represented here!
Your "liberation from textual representation" and the "context aware IDE" -paragraphs describe my thoughts exactly while building my textual projectional language workbench (some demo videos at: levlo.com). Although, I suppose one wouldn't build a projectional language at all if those paragraphs wouldn't resonate.. Is there a reason you don't seem to be referring to your language as a projectional language? To not compete with MPS 😅 ?
I certainly agree that having all the symbols resolved, and generally the AST/semantic tree in a valid state at all times, helps in guiding AI to produce semantically valid code and I'm heavily utilizing this for Levlo's AI Copilot. However, I feel that new programming languages might be at an disadvantage in the new AI era as there aren't large amount of samples for the LLMs to train on (and it would take a while for the common LLMs to train on new public samples). Coincidentally this doesn't seem to be an issue if the language is very natural language like.. I suppose having a similar syntax to existing languages could also help. Although, if the semantics are different it could also hurt. I'm not sure I understand what you mean when you say that the "fine granularity" of the language should make it more AI-fitting. Can you elaborate on that?
Your project indeed has a lot of similarities with our work. Many of the ideas I described had been proposed as early as in the 1960-70s. I know that Erik Meijer is also working on something very similar to what you are doing—programming in plain English that gets translated into something Prolog-like, able to call external functions.
I avoid the term "projectional editing" for two reasons. First, in my opinion, projectional editors have got a bad reputation for being pain to use. Problems typically start with arithmetic operators and handling of parenthesis. I am not saying that those problems are unsolvable, but the fact is that way too many attempts to implement projectional editing resulted in a terrible experience. We hope to avoid the usual pitfalls by having really minimalist and uniform syntax without precedence rules and by not trying to imitate text.
The other reason is that I think that a more specific term like "autocompletion-driven editing" would better reflect the idea of having most of the symbols resolved at the time they are typed. The term "projectional editor" is oversaturated, and it's hard to impress anyone by saying that you're building yet another one.
With regard to your question about LLMs, we hope that we will be able to transpile a reasonably big corpus of programs written in some mainstream language into our tiny little Lisp, preserving most of the structure, and then use that transpiled code to train the LLM. Another possibility is that the advancement of AI will soon allow us to simply teach LLMs to program in our simple language without feeding them with a large number of examples.
FWIW: brainstorming...
(1) Get LLMs to generate code in some large-corpus language, like JS, Python. Use text-to-text transpilation to convert the generated code, syntactically, into tiny little lisp (going from JS to Lisp-y, or Python to Lisp-y is straight-forward using PEGs)
(1a) Get LLMs to generate code in full-blown lisp, then ask nicely to get it to remove the hoary bits (special forms, macros)
(1b) No need to re-train LLMs with tiny little lisp if conversion can be done.
(2) Steve Philips used an LLM to generate OhmJS code to create a new language over top of an existing language (in his case, he built his new language using Golang as "assembly code"), he might have advice on how to ask an LLM to build syntax converters
Paul Tarvydas Thanks for your suggestions. There's indeed a number of techniques that can be used to LLM-generated transform code in some traditional language into our "Lisp". However, that would only increase the complexity of the development stack, and our goal is to radically simplify it. That's why we want the LLM to be able to generate code and "think" in terms of our language. That would be a faster, cheaper and more robust solution. Ideally, the LLM should be able to generate neat code without any knowledge about the bloated languages of the past 😉 This doesn't mean we couldn't use any dirty tricks and hacks for the initial training.
Interesting, didn't know about Erik Meijer's natural language programming stuff. Are there any material about it? I only found some possible references in his twitter.
And yeah, I'm sure it's possible to build an AI Copilot for a new language. And if your language is always authored using your IDE, there shouldn't be a need for users to go get help from Chat GPT for example.
@Jarno Montonen He gave a talk about it at KotlinConf and we had a long talk afterwards. The video of the talk should be published on YouTube. Unfortunately, the talk was interrupted by a false fire alarm.
It's hard to predict what AI will be capable of in let's say two years—a very optimistic estimate of how long it's going to take us to build our solution.
Sorry, I forgot to clarify what I meant by "fine granularity. I wanted to say that in our system the minimal unit of editing will be something like a function and that function will not be buried in a large file, so we will be able to provide a fully resolved and relatively small context for that single function only, That should make it easier for the AI to understand the meaning of a function and, at the same time, for the IDE to validate AI's output.
Oh and I sympathize with your thoughts about the term projectional language. The problem is that basically the same things are already called Projectional/Structured/Structural Language/Editor/Editing. I'm not sure one more term would help with the matter. 😅
I feel that both my self and the Hazel folks have basically solved the parenthesis and more generally "just boxes inside boxes" issue in projectional editing. Although, admittedly neither in a battle tested context.
I seriously considered to not implement support for operator precedence in Levlo, but concluded that if the target users are non-programmers, math just has to work the way it's taught in schools.. If the target audience was programmers, I'd probably go the same route you've decided to.. Precedence rules bring so much pain for no gain. Although, I wonder if you'll have some trouble trying to explain the missing operator precedence to LLMs..
I actually don't want the LLM to understand our indentation- and color-sensitive notation (that's a more correct term than syntax). That notation can be mechanically converted to/from S-expressions). A bigger challenge is that we want to use unique ids, invariant to renames (re-labeling) and moves, and yet, expect the LLM to generate human-readable labels and be able to get some insights from them.
IDE to validate AI's output
This is what I'm doing. Although I call the component that the LLM feeds into an "language engine". I have a very simple parser that reads through the LLM response and feeds it in appropriate pieces to the engine. The engine maintains the AST and is able to provide all the possible valid sequences at any given location. If LLM output doesn't match any of the valid options, I reprompt and ask it to reformulate the answer so that it continues with one of the valid sequences. Additionally I'm using the language engine to generate semantically valid samples for the LLM.
Yes, it's basically RAG. And by IDE I meant "the thing performing analysis of the generated code", not sure if we should distinguish between the IDE and the compiler here.
Moonbit seems to take a very similar approach moonbitlang.com/blog/moonbit-ai
📝 MoonBit: Exploring the design of an AI-Native Language Toolchain | MoonBit
Exploring the design of an AI-Native Language Toolchain
Yeah, I suppose it could also be called language server.. But not an important distinction.
So what kind of usages are you thinking for the color-sensitivity? And as for labels, I think it's beneficial for the LLMs to work with the human readable labels. Although I've faced some challenges getting LLM generate sensible human readable ones. But how else would it work if the LLM needs to define new variables for example? Or do I misunderstand what you meant by "use unique ids". Why couldn't you use human readable labels in the s-expressions when interacting with the LLM? In Levlo the LLM generated labels just go into the language engine and it resolves those into new identities or references to existing identities.
Well, it can, for example, have the form (let bf191635-10f4-4032-a90f-3a02e867b345 "the-answer" 42 ...)
And I like the idea of indentation- and color-based notation as both techniques allow you to express the structure and the semantics without cluttering the visual field. It works like magic - "show something without showing anything". Like what is called negative space in photography. And we know that at least indentation works very well in Python and YAML
Yes, I definitely agree about indentation, but I'm not sure I see how handy color-sensitive semantics would be. Or am I getting it wrong. Do you mean that changing the color of say the label in variable definition would change it's type?
I suppose readability vice it should work, I'm wondering more what the authoring experience would be. Although, I guess user could still type the type, but then the editor would remove it and set the label color instead. I do a bit similar thing with colors in one of my demos. The user can type the hex value (or use a picker) and then the editor just shows a colored circle.
The color will depend on the usage type. Look at the factorial example that I posted. The color red means it's a definition of a new symbol, so I can omit let
, blue means it's a parameter declaration, white - just passing value as a constant, teal - function application. Same symbol, like factorial
can be passed as a value to a high-order function or applied.
I see, but does the user choose in which color they're going to be typing next or is the color just applied by the IDE after the semantics have been resolved?
the idea is that you insert a symbol by typing a special symbol that defines the type of use - for example, d
for definition of a new symbol, v
- for passing as value, a
- for application of as function, etc. You would need to learn less than 10 shortcuts. And that doesn't mean typing more than in a traditional text editor — there you typically press SPACE before starting typing a new identifier. This is just a sketch of the idea—we're actually going to do some empirical and theoretical research to come up with a useful UX
This is a screenshot of my presentation where I explain the idea of semantic coloring. Not to be taken as the final design:
I see. That could work. It's been great chatting with you! However, getting late here so have to start heading to the bed.. :)