DEV Community

Cover image for Pudu Programming Language
Chris M. Pérez
Chris M. Pérez

Posted on

Pudu Programming Language

Pudu is a statically typed, expression-oriented programming language that I’m designing for building services, developer tools, and native applications. The project is still young and is currently moving toward its first 0.1.0 pre-release, but it has grown well beyond the stage of being a syntax experiment. Today, the repository contains the language implementation, standard library, testing infrastructure, development tools, editor integration, documentation, examples, and the foundations of the ecosystem I want to build around it.

I think this is the right point to start talking more openly about why Pudu exists, what I’m trying to accomplish with it, and where I want to take the language.

The source code is available on GitHub:

https://github.com/chrismichaelps/pudu-lang

Why Pudu?

Creating another programming language in 2026 naturally raises a question: why?

We already have an extraordinary number of good languages. Rust has pushed systems programming toward stronger safety guarantees. Haskell has demonstrated how far expressive type systems and functional programming can go. OCaml has spent decades showing how practical and elegant the ML family can be. Go demonstrated the value of simplicity and excellent tooling. TypeScript transformed the experience of working with large JavaScript codebases.

Pudu does not exist because I believe those languages have failed.

It exists because programming languages are collections of trade-offs, and I wanted to explore a particular combination of them.

One of the ideas behind Pudu is that important uncertainty in a program should be visible. If an operation can fail, that possibility should be represented by its type. If a value can be absent, that absence should be explicit. If a program branches over every possible form of a type, the compiler should be able to tell us when one of those possibilities has been forgotten.

At the same time, I don't want those guarantees to require writing code that feels unnecessarily academic or complicated.

That balance—expressiveness, explicitness, safety, and practical usability—is one of the central design problems I’m exploring with Pudu.

What Pudu looks like

A small Pudu program currently looks like this:

module Shapes

import Std.Io as Io

type Shape
  = Circle(Float)
  | Rectangle(Float, Float)

fn area(shape: Shape) -> Float {
  match shape {
    case Circle(radius) => 3.14159 * radius * radius
    case Rectangle(width, height) => width * height
  }
}

export fn main() -> Result[(), Str] {
  for shape in [Circle(1.0), Rectangle(3.0, 4.0)] {
    Io.writeLine(show(area(shape))) ?
  }

  Ok(())
}
Enter fullscreen mode Exit fullscreen mode

There are several parts of Pudu's philosophy hidden inside this relatively small example.

Shape is not represented through an arbitrary integer, string, or inheritance hierarchy. It is a type with a finite set of possible forms. A Shape is either a Circle containing a radius or a Rectangle containing a width and height.

When the value is inspected with match, those possibilities become explicit.

More importantly, matching is exhaustive. If the definition of Shape evolves and another case is introduced, code matching against that type should not quietly continue while forgetting that the new state exists.

I like this property because it changes the relationship between the programmer and the compiler. The compiler is not simply translating syntax into something executable. It is participating in the process of maintaining the assumptions encoded in the program.

That idea appears repeatedly throughout Pudu.

Making failure visible

Error handling is one of the areas where programming languages make very different philosophical choices.

In Pudu, ordinary recoverable failure is intended to be represented as a value.

A function that can fail can expose that directly through its signature:

fn loadConfig(path: Str) -> Result[Config, Error]
Enter fullscreen mode Exit fullscreen mode

You do not need to inspect the implementation or documentation to discover that loadConfig may fail. The possibility is part of the function's contract.

Pudu also provides ? for propagating failure without turning every operation into deeply nested control flow.

let config = loadConfig("pudu.toml") ?
Enter fullscreen mode Exit fullscreen mode

This does not mean that Pudu somehow eliminates runtime failures. That would be an unrealistic promise for a general-purpose programming language. Instead, the objective is narrower and more useful: failures that we reasonably expect software to handle should be difficult to accidentally ignore.

The same philosophy applies to absence. Rather than allowing a null value to appear almost anywhere, absence can be represented intentionally through types such as Option[T].

A value is either present or absent, and code interacting with it has enough information to reason about both possibilities.

These concepts are not inventions unique to Pudu. Languages from the ML family, Haskell, Rust, Swift, and others have demonstrated variations of them for years. What interests me is how these ideas can be combined into a language that remains approachable while still providing strong foundations for larger programs.

Expression-oriented by design

Pudu is also expression-oriented.

I have always liked languages where constructs compose naturally rather than being divided into many artificial categories of statements and expressions. If something logically produces a value, the language should generally make it possible to use that value directly.

This has implications beyond syntax.

Expression-oriented programming encourages data flow that can often be easier to reason about. Instead of creating temporary mutable state merely to communicate the result of a branch or computation, the result can become part of the expression itself.

Pudu is influenced here by languages such as Haskell, OCaml, Rust, and other members of the functional and ML traditions, but I am deliberately avoiding the goal of cloning any particular language.

Influence is useful. I don't think imitation is enough reason to create a programming language.

Pudu needs to develop its own identity and its own answers to these problems.

A language is more than its syntax

One lesson that became increasingly clear while building Pudu is that designing syntax is probably the easiest part of creating a programming language.

The difficult work begins when that syntax needs semantics.

The parser needs to recover intelligently from incomplete programs. The type checker needs to understand what the programmer intended and explain when those expectations cannot be satisfied. Modules need predictable resolution rules. Diagnostics need stable source locations. The standard library needs coherent conventions. Editor tooling needs to understand programs while they are being written, not only after they successfully compile.

Then everything has to work together.

Because of that, I don't consider Pudu to be only the language grammar. I think of Pudu as the complete development environment surrounding the language.

The project currently uses a single pudu executable as its primary interface. That executable is responsible for operations such as checking programs, running them, testing projects, formatting source code, linting, generating documentation, bundling programs, and serving editor functionality through the Language Server Protocol.

The intended experience is deliberately straightforward:

pudu init hello
cd hello
pudu run src/Main.pudu
pudu test
Enter fullscreen mode Exit fullscreen mode

Editor support is exposed through:

pudu lsp
Enter fullscreen mode Exit fullscreen mode

A VS Code extension is also being developed alongside the language.

I want the tooling to feel like part of Pudu rather than an ecosystem of unrelated programs that users have to discover and assemble themselves.

Installing a programming language should get you much closer to actually programming in that language.

Building the implementation in Haskell

The Pudu implementation is currently being developed in Haskell.

For a compiler project, Haskell provides a number of useful properties. Algebraic data types map naturally to syntax trees and intermediate representations. Pattern matching works well for compiler transformations. Strong typing makes many invalid internal states harder to construct, and functional composition fits naturally with the different phases of a compiler.

But using Haskell does not mean Pudu is intended to become Haskell with different syntax.

Pudu has its own design goals.

The implementation language is a tool used to build those ideas, not a restriction on what the final language should become.

Working on the compiler has also changed how I think about programming languages in general. Features that appear almost trivial from the outside quickly turn into deeper design questions.

What happens after the parser encounters malformed syntax? How much should it recover before continuing? When two interpretations of an expression are possible, which rule wins? What information should survive from parsing into type checking? How should generic types be represented? What makes an error message genuinely useful instead of merely technically correct?

Once you build a compiler, things that previously looked like language syntax start looking like contracts between multiple systems.

That has probably been the most interesting part of the project for me.

Compiler diagnostics matter

One area I care particularly about is diagnostics.

A compiler error is a user interface.

When something is wrong, telling the programmer that the program is invalid is technically correct but practically insufficient. A useful compiler should identify where the problem originated, explain what it expected, preserve enough context to make the message understandable, and avoid producing a cascade of unrelated errors caused by the first mistake.

This becomes particularly important for editor integration.

Developers spend much more time working with incomplete programs than perfectly valid ones. Every few keystrokes, a source file may temporarily contain missing delimiters, unfinished expressions, incomplete generic arguments, or unresolved names.

A parser and language server that only behave well when the program is already valid do not provide a good development experience.

For that reason, parser recovery, diagnostics, and LSP behavior are not things I want to bolt onto Pudu after the language is "finished." They are part of the language engineering itself.

Why the name Pudu?

The name comes from the pudu, a small deer native to South America.

Programming languages often end up with highly technical names, acronyms, or names intended to communicate something about their implementation. I wanted something simpler: short, recognizable, easy to say, and capable of developing an identity of its own.

I came across the pudu and liked the name.

Sometimes that is enough.

As the project has grown, the name has also made it possible to develop a visual identity around the language without tying that identity to a particular implementation detail that might change later.

Where Pudu actually stands today

I think transparency is particularly important when talking about a new programming language.

Pudu is currently pre-release software.

The first milestone is 0.1.0, not 1.0, and that distinction matters.

Programs are currently interpreted. Dependencies are local directories. Platform support remains limited. Some language semantics will change. APIs will evolve. Parts of the standard library will be redesigned as real programs expose weaknesses in the current design.

I don't want to hide those limitations behind ambitious language.

Building a mature programming language and ecosystem takes years. Rust, Go, Swift, Kotlin, and other successful languages did not become what they are today with their first release.

The purpose of Pudu 0.1 is therefore not to claim that the language is finished.

It is to establish a credible foundation.

I want the language, compiler, standard library, documentation, tooling, editor integration, tests, examples, and development workflow to reach a point where other developers can meaningfully experiment with Pudu and provide useful feedback.

That is a much more valuable milestone than prematurely declaring stability.

Where I want to take it

The longer-term direction is to make Pudu capable of building serious software while keeping the language understandable.

I want a language where the type system helps communicate intent without dominating every program. I want error handling to remain explicit without becoming repetitive. I want pattern matching and algebraic data types to feel natural rather than advanced. I want compiler diagnostics that developers can actually learn from.

I also want tooling to remain a first-class concern as the project grows.

It is easy for a language to accumulate independent tools over time until basic development requires configuring a compiler, formatter, linter, test runner, package manager, language server, documentation generator, and build system independently.

Pudu has an opportunity that mature languages often don't have: these decisions can be considered together from the beginning.

Performance is another important part of the long-term work. The current interpreter is appropriate for developing and validating the language, but it is not the end of the story. Moving toward serious native applications will eventually require increasingly sophisticated compilation and optimization work.

I would rather approach that incrementally and measure the results than make performance claims before the implementation can support them.

What building Pudu has taught me

Building Pudu has made me appreciate how much invisible engineering exists underneath a mature programming language.

As developers, we write if, match, call a generic function, import a module, or ask an editor for autocomplete without thinking much about what happens underneath.

When you build the language yourself, none of those behaviors simply exist.

Someone has to define what a module actually means. Someone has to determine when two types are compatible. Someone has to decide whether a parser should stop or recover after an error. Someone has to define precedence, name resolution, visibility, generic substitution, diagnostics, package boundaries, source locations, formatting rules, and hundreds of other details.

And once those decisions are made, they have to remain coherent with one another.

That has become one of my favorite parts of this project.

Pudu is not only an attempt to create another programming language. It has become a way for me to study programming languages at a much deeper level by actually building one.

The road to 0.1

Right now, my focus is getting the foundations right.

I would rather spend more time fixing inconsistencies in the language, improving diagnostics, strengthening the standard library, testing edge cases, and polishing the development experience than rush toward a version number.

There will be plenty of opportunities to add features later.

The difficult part is deciding which foundations should remain when those features arrive.

Pudu 0.1 will still be experimental. That is expected. What I want it to demonstrate is that the ideas behind the language can work together as a coherent system.

From there, real programs and real developers can begin challenging those assumptions.

That feedback is ultimately how the language will improve.

This is only the beginning

Pudu is still a small project compared with established programming languages, and there is a significant amount of engineering ahead.

That's also what makes this stage exciting.

The architecture is still evolving. Some decisions can still be reconsidered. The standard library is still taking shape. The compiler can still change substantially. There are still opportunities to experiment before compatibility requirements make those decisions much more expensive.

I'm building Pudu in the open, and the project is available on GitHub.

If you're interested in programming languages, compiler engineering, type systems, language tooling, or open-source development, you're welcome to explore the source code, try the language, open an issue, contribute, or simply follow the project as it develops.

Pudu doesn't need to pretend to be the future of programming.

Right now, it needs to become a good programming language.

That's the challenge I'm interested in solving.

Pudu is currently heading toward its 0.1.0 pre-release.

https://github.com/chrismichaelps/pudu-lang

Top comments (0)