Letting Ideas Cook: Rethinking File Handling in Luma
When you’re building something new, progress rarely moves in straight lines. More often, it moves in circles: you sketch an idea, implement it, live with it for a while, and then realize that although it works, it doesn’t quite belong.
That’s where we found ourselves with file handling in Luma.
Our first design wasn’t wrong. It was logical, explicit, and familiar. It used clear verbs and namespaces, and it looked a lot like what many other languages do. At the time, that felt comforting. Familiar patterns reduce risk, and when you’re still shaping a language, safety matters.
But over time, something kept bothering us.
The API felt foreign. Not broken — just… not Luma.
When familiarity gets in the way
The original file handling design treated files as a set of operations: open, read, write, close. That model is deeply ingrained in programming culture, so it’s easy to reach for. The problem is that it brings along a lot of baggage: modes, flags, and a mental model that forces the programmer to think about how things are done instead of what they want.
Luma was never meant to be that kind of language.
From the beginning, Luma has been about expressing intent with as little ceremony as possible. Types aren’t just annotations — they carry meaning. They describe what something is, not how it should behave internally. When we looked at the file API through that lens, it became clear that we were fighting our own philosophy.
We were asking users to explain too much.
A shift in perspective
The breakthrough came when we stopped thinking about files as an API and started thinking about them as values.
A file isn’t an action.
It isn’t a namespace.
It’s a thing you work with.
Once we treated a file as a first-class value, everything started to align.
Instead of asking the user to specify whether they want text, lines, or bytes through method names or flags, we let the surrounding context speak:
content: str = file("config.txt").read()
lines: [str] = file("config.txt").read()
data: [byte] = file("config.txt").read()The method stays the same. The intent changes. And the intent is already visible — because the type tells the story.
This felt like a small change, but it fundamentally reshaped the design. It removed decisions from the call site and placed them where they belong: in the type system.
Designing for how people actually work
Most of the time, file operations are simple. People read text files. They write text files. They append lines to logs. These are not edge cases — they’re the everyday reality.
So the common case should feel effortless:
log: file = file("app.log")
log.add("Application started")And when you do need something more precise — raw bytes, no newline, streaming access — it should be available, but not forced on everyone else.
This is a recurring theme in Luma: make the common path obvious and lightweight, and keep the rare path explicit and deliberate.
Progress means revisiting decisions
It’s tempting, especially early in a project, to lock ideas in quickly. Changing course can feel like lost time. But in practice, revisiting decisions is often how a project finds its identity.
This redesign is not an admission of failure. It’s a sign of progress.
Good ideas don’t always appear fully formed. Sometimes they need to be implemented, questioned, and reshaped before they become what they were meant to be. Letting an idea “cook” a little longer often reveals what really matters.
For us, this redesign brought the file API back in line with what Luma is trying to be: a language where intent is visible, where types carry meaning, and where the code reads closer to how humans think about problems.
Moving forward
Luma is still evolving, and it will continue to evolve. Changes like this are part of that journey. We’d rather take the time to make things feel right than rush to freeze designs that don’t quite fit.
This new file handling approach isn’t just about files. It’s about staying honest to the principles that shaped Luma in the first place — and being willing to change when something drifts away from them.
Sometimes progress means adding features.
And sometimes, it means stopping, taking a breath, and saying:
“Let’s rethink this.”