Unlocking the Power of AI Driven Development with SudoLang

The Limitations of Natural Language Prompts for Complex AI Interactions

By Eric Elliott
June 25, 2024
Learning Learning (source: Pixabay)

As AI continues to advance at a rapid pace, developers are increasingly turning to AI-driven development (AIDD) to build more intelligent and adaptive applications. However, using natural language prompts to describe complex behaviors to AI can be a real challenge. While natural language is expressive, it lacks the structure and efficiency needed to clearly communicate intricate instructions and maintain complex state.

One of the biggest issues with natural language prompts is the lack of clear encapsulation and delineation of related instructions. Unlike source code, which uses elements like braces and indentation to group and organize code blocks, natural language prompts can quickly turn into a wall of text that’s a nightmare to read and maintain. This lack of structure makes it harder for AI to understand and follow the intended instructions accurately.

Learn faster. Dig deeper. See farther.

Join the O'Reilly online learning platform. Get a free trial today and find answers on the fly, or master something new and useful.

Learn more

Natural language is not always the most efficient way to represent complex instructions. Language models can suffer from “sparse attention,” where the model’s attention capacity is stressed as the input context grows. This can lead to the AI forgetting or misinterpreting important information within the prompt, particularly in the middle of the input, a phenomenon known as the “Lost in the Middle” problem (Liu et al., 2023). As a result, the AI may struggle to adhere to the intended instructions, leading to incorrect outputs.

To overcome these limitations and unlock the full potential of AIDD, we need a more structured approach to AI communication. This is where pseudocode prompting and the SudoLang programming language come into play.

Pseudocode Prompting and SudoLang: A Solution for AI-Driven Development

Pseudocode prompting is a technique that combines the expressiveness of natural language with the structure and precision of programming concepts. By using pseudocode-like syntax, developers can provide contextual clues to the AI, guiding it to understand and follow instructions more accurately.

Language models behave like role-players, and everything in your prompt is used as context and connotation for the AI to ground its response in. Large language models (LLMs) are trained on code, and they understand that it is a language used to instruct computers in a detailed and precise manner. Because of this, seeing things that look like code carry the connotation that it must think carefully about the content. It triggers enhanced thinking, state tracking, and reasoning in the language model. The paper “Prompting with Pseudocode Instructions” (Mishra et al., 2023) demonstrated that pseudocode prompts generated 12%–38% response score improvements.

One of the key benefits of pseudocode prompting is its ability to efficiently represent complex instructions. By using programming constructs such as constraints, interfaces, and functions, pseudocode can express intricate logic and algorithms in a way that is concise, readable, and expressive. This not only reduces the token count of the prompt, which saves money and time, but also makes it easier for the AI to comprehend and execute the desired behavior.

SudoLang, a programming language specifically designed, discovered, and curated with the help of GPT-4 to communicate complex ideas and programs with AI, takes pseudocode prompting to the next level. I say designed, discovered, and curated because, while some intelligent design went into SudoLang, the real effort went into discovering and testing the pseudocode understanding inherent in language models, by curating common features and concepts from existing languages—both programming and natural language. In fact, all sufficiently advanced large language models already know SudoLang—it was always there in the latent space. We just uncovered it, curated features, and documented it.

SudoLang is a multiparadigm language that features natural language constraint-based programming inspired by Ivan Sutherland’s Sketchpad. Sketchpad was a graphical programming language that allowed users to select multiple elements on the canvas using direct screen interaction with a light pen. For example, you could select two lines, constrain them to be parallel, and then changing one line would cause the constraint solvers to kick in and update the other line to maintain the parallel constraint. Constraints are a powerful concept in SudoLang, allowing developers to specify desired behaviors and relationships between entities in a declarative manner.

Constraints in SudoLang are written in natural language and can consist of either positive guidance or elements to avoid. They can be formal mathematical axioms or whimsical instructions for a playful puppy chatbot. SudoLang is a declarative language, meaning that you should focus constraints on what you want rather than write out detailed instructions on how to do it. The AI is usually smart enough to figure out the “how” for you, and that ability will save you a lot of work, time, tokens, and money.

With SudoLang, developers can define constraints that the AI will continuously solve for, ensuring that related states remain synchronized and consistent. For example, you can specify that a calculated value should always be updated whenever a key variable changes. Constraints also provide a way to guide the AI’s behavior, such as giving it a particular personality or tailoring its output for a specific target audience.

SudoLang’s interface-oriented design is another key feature that sets it apart. Developers can clearly and succinctly define data structures and operations using typed interfaces. While most types can be inferred, SudoLang allows for explicit type declarations when needed, and all common programming language types are supported. This interface-driven approach promotes modularity and reusability, making it easier to build and maintain complex AI-driven applications.

Composition is another strength of SudoLang. The language supports functions, function composition, and interface composition, enabling developers to create complex programs by combining simpler, independent components. Function composition allows you to pipe a series of functions together one after the other to form a new, more complex function. The algebraic definition of function composition is: (f ∘ g)(x) = f(g(x)) (often pronounced “f after g“).

In SudoLang, you can write this as g |> f—which means: apply the function g to the supplied value, then apply the function f to the output of g. Composing interfaces is easy too:

// Some interfaces to compose:
Character {
  name: ""
  health: 1..10 // ".." is the range operator in SudoLang
  strength: 1..10
  speed: 1..10

Objective {
  status: ready | started | finished

Quest {
  objectives: []

// The following composes all of the above interfaces together:
Player {
  // The `...` here is the spread operator. It will mix
  // all the properties of "character" into the top level
  // of the player object, so instead of player.character.health
  // we get a direct reference to player.health.
  ...character // include the properties of "character"
  quests: []

Let’s see how a language model might interpret and execute a text adventure scenario using SudoLang. This example is based on an interaction with Anthropic’s Claude 3.5 Sonnet:

Village Elder: "Greetings, brave adventurer. Our village is in dire need of ancient wisdom. In the grand library to the east, there lies a scroll of immense power. I task you with retrieving this scroll and bringing it back to me. Will you accept this quest?"

Player: I accept the quest, Elder.

Village Elder: "Excellent! The scroll is said to be hidden in the deepest chamber of the library. Be cautious, for the path may be treacherous. Return to me once you have the scroll in your possession."

Your quest log updates:

  - name: Retrieve the Ancient Scroll
    description: Fetch an old scroll from the ancient library and return it to the village elder.
      - name: Fetch the scroll
        description: Find and retrieve the ancient scroll from the library.
        status: started
      - name: Return to the elder
        description: Bring the scroll back to the village elder.
        status: ready

[Time passes as the player journeys to the ancient library and navigates its chambers…]

You enter a dimly lit chamber deep within the library. Dust motes dance in the air as your torch illuminates rows upon rows of ancient texts. In the center of the room, atop a weathered stone pedestal, you spot a scroll emanating a faint, ethereal glow.

Player: I carefully approach the pedestal and take the glowing scroll.

As your fingers touch the parchment, you feel a surge of energy course through your body. The scroll's glow intensifies for a moment before settling into a steady, warm light. You've found the ancient scroll the elder spoke of!

You have a new item: Ancient Scroll

Your quest log updates:
name: Adventurer
- Ancient Scroll
- name: Retrieve the Ancient Scroll
description: Fetch an old scroll from the ancient library and return it to the village elder.
- name: Fetch the scroll
description: Find and retrieve the ancient scroll from the library.
status: finished
- name: Return to the elder
description: Bring the scroll back to the village elder.
status: started

This example demonstrates how SudoLang can be used to create dynamic, state-aware interactions in a text adventure game. The AI interprets the player’s actions, updates the game state accordingly, and provides appropriate responses and quest log updates, obeying its given constraints and respecting the interfaces we have defined.

In SudoLang, the more you can lean on the AI’s ability to infer your intent, the better, so we usually don’t need to reach for features like looping and branching, but it’s worth noting that SudoLang does include loop and branch constructs, though without all the syntax clutter and precise references and function definitions that most programming languages require. For example, loops can be expressed as:

for each user, send update email

This construct allows the AI to handle the implementation details. If you guide it with the right constraints, it could even customize each email based on the user’s preferences or recent activity, demonstrating how SudoLang can lead to more sophisticated and personalized outcomes compared to traditional programming approaches.

Likewise, there are branching constructs. AI responds just fine to “if” and “else,” but you can also use the language model’s understanding of semantics and natural language to do semantic pattern matching, e.g.:

if the player dies, extract the penalty for dying and respawn in the nearest safe location

But if you’re token golfing (trying to reduce the token count to optimize for cost and performance), this might also work:

(player died) => extract penalty, respawn

Leaning just a little harder on inference and introducing a tiny bit of syntax in this case reduced the token count on GPT-4 from 17 to 8.

Pattern matching is a powerful feature inspired by languages like Haskell, Elixir, Rust, Scala, F#, and so on.

The basic gist in traditional programming languages might look something like:

// Non-standard, hypothetical pattern matching syntax in JavaScript
function area(shape) {
  return match (shape) => {
    ({ type: "circle", radius }) => Math.PI * Math.pow(radius, 2);
    ({ type: "rectangle", width, height }) => width * height;
    ({ type: "triangle", base, height }) => 0.5 * base * height;

console.log(area({ type: "rectangle", width: 4, height: 5 })); // 20

It’s worth noting that in SudoLang, you don’t need to define the shapes or their properties, as the AI can infer them from the context. In SudoLang, that function won’t need explicit pattern matching and would probably look more like:

function area(shape) => Number

One of the most powerful aspects of SudoLang is its ability to leverage the omnireferential inference properties of LLMs. LLMs are trained on vast amounts of human knowledge available on the internet, enabling them to understand a wide range of domains. SudoLang takes advantage of this by allowing developers to define functions without always providing implementation details.

In many cases, the AI can infer what a function should do based on the program context and the function name alone. This arguably makes SudoLang the programming language with the largest standard library, as it can tap into the AI’s extensive natural language understanding to infer functionality across a broad spectrum of domains.

I frequently use welcome() as my initializer to instruct the AI how to start, without defining what welcome() should mean. Very often, if you supply a good preamble and main interface with commands, welcome() will just do the right thing without any additional instruction required.

Other commands or functions that just work include standard library functions from common programming languages, many of the functions from JavaScript’s Lodash or RxJS work, for example. sort(list) |> take(3) will sort a list based on some inferred criteria (e.g., alphabetically), and return the top three results. Of course, you can specify the criteria and the sort order when you call sort.

As AI continues to transform the software development landscape, tools like pseudocode prompting and SudoLang will play a crucial role in enabling developers to harness the full potential of AIDD. By providing a structured and efficient way to communicate with AI, SudoLang empowers developers to create intelligent, adaptive, and robust applications that push the boundaries of what’s possible with artificial intelligence.

As you can see, SudoLang and pseudocode prompting can unlock the true potential of AI-Driven Development. The future of software development is here. Let’s make some magic!

Post topics: AI & ML, Artificial Intelligence
Post tags: Research

Get the O’Reilly Radar Trends to Watch newsletter