winston/purnomo

Copyright © 2026 Winston Purnomo. All rights reserved.

All posts

July 26, 2025

The Programming Language I'd Build

As someone who spends significant time programming, I've developed strong opinions about what makes an ideal language. If I were to design a programming language today, here's how I'd approach several critical aspects of its design.

Go-style Imports

I love Go's import system and its clarity and predictability. Go requires a straightforward import declaration at the top of each file, clearly listing every external package being used. There is objectively a correct way to import a Go package, and it is the only way.

go

Contrast this clarity with Rust's ambiguity. Rust allows multiple ways to import names, creating unnecessary complexity and cognitive load.

rust
rust

Or even no explicit import:

rust

This flexibility might seem beneficial initially but introduces significant mental overhead when navigating unfamiliar codebases.

Swift takes an extreme opposite approach, implicitly importing entire namespaces, obscuring the origins of names:

swift

I think the reason why Apple built it this way is Apple products are built in many separate teams across many different repos. Codebases are relatively small, and if you stay on the same team, you'll be working on the same repo, so you mostly know where names are coming from.

In large-scale projects, particularly at monorepo scale such as Meta's, this quickly becomes untenable, forcing developers to excessively rely on IDE features for basic navigation. Therefore, a language I'd build would embrace the simplicity and explicitness of Go's import system.

Errors as Values (Result Types)

Traditional try-catch blocks often lead to deeply nested and complex error-handling code. These constructs are cumbersome, verbose, and obscure control flow.

A better alternative is result types, which explicitly represent success or failure, allowing errors to be handled clearly:

go

I would make one tiny change though -- While Golang’s explicit error returns are straightforward, tagged union enums offer a more ergonomic and type-safe approach. They provide clear control flow, simpler handling patterns, and reduce boilerplate.

Tagged Union Enums

Tagged union enums succinctly express mutually exclusive states or types. Languages like Rust and Swift leverage them effectively, clearly handling distinct cases:

swift

Tagged unions enhance robustness by explicitly requiring each state to be handled, reducing bugs and increasing maintainability. My ideal language would emphasize these enums to enhance clarity and safety.

Shapes and Structures (No Subclassing)

I prefer a clear separation between data representation and behavior encapsulation, leveraging Rust and Swift's one-implementation-per-type principle, without subclassing.

Shapes

Shapes represent pure data, otherwise known as Data Transfer Objects (DTOs). They are portable, serializable, and testable, resembling Swift structs or Rust structs. We can also derive common operations like Debug, Serialize and Deserialize, and Shapes are clone-able by default. They're not supposed to have anything inherently uncopy-able.

go

Shapes are ideal for configurations, DTOs, and persistent storage due to inherent encodability and decodability.

Structures

Structures encapsulate behavior and internal state, but without subclassing—achieving reuse and specialization via extensions (Swift) or impl blocks (Rust):

rust

This approach maintains a clean mental model: shapes represent data; structures encapsulate logic. It avoids complexities introduced by subclassing and inheritance.

Spread Operator

I also really love the spread operator in TypeScript, which allows us to quickly create a new object with a subset of properties from an existing object:

typescript

This is another annoyance in Go, where it is common but difficult to do so without using reflection, or manually copying the properties. A frequent source of these -- translating database-inferred types into DTOs or vice versa.

go

Utility Types

I also really love the utility types in TypeScript, which allow us to transform types into other types. For example, we can use Pick to create a new type with only the properties we want:

typescript

This is useful in database operations particularly -- e.g. the above UserWithoutID type would be a common pattern for creating a new user, if we want to use the database's default id generation logic.

Rich Standard Library

An ideal language should include a rich standard library, especially for common tasks like HTTP servers, encoding/decoding, JSON/XML parsing, and more. Having these capabilities built-in, as Go does, dramatically simplifies initial project setup and avoids fragmentation:

go

This approach allows developers to focus immediately on their application logic, rather than wasting valuable time selecting third-party libraries for basic functionalities.

Defer clause

I also really love defer in Go, which allows us to execute code after a funciton has returned, making cleanup really easy.

go

Go allows us to pass in a single function as a value to defer. Our first class support for blocks prevents this clumsy nonsense:

go

In our language:

mylang

No Async—Use Lightweight Tasks

I dislike explicit async/await syntax because it creates a function coloring problem that propagates little async/await keywords throughout the codebase. Instead, I'd embrace lightweight concurrency primitives like Go’s goroutines or Swift’s structured concurrency with tasks. This approach simplifies concurrent programming without the overhead and complexity of explicit async/await syntax:

go

Again, blocks make the inelegant go syntax more palatable.

mylang

We will provide primitives in the standard library to make managing concurrency easier.

mylang

And a ResultWaitGroup to simplify error handling. ResultWaitGroup.done() accepts a Result<T, E> type, and ResultWaitGroup.wait() returns a Result<T, Vec<E>> type:

mylang

We also have a concurrent keyword to simplify concurrent logic:

mylang

Conclusion

The programming language I want to build emphasizes clarity and simplicity. As a result, it borrows heavily from Go, while adopting niceties that have become popular in other languages since Go 1.0 was first released. Spread operators, tagged unions, and utility types make the language more ergonomic, particularly for the common workload of building APIs and data models.