Notes
2026/08/23

Explicit semantics, implicit syntax, explicit arenas

I've recently been thinking about adding explicit uniqueness annotations to the VM I'm working on, so that only values that are explicitly annotated as being unique are directly mutated, in contrast to the default case of values that can be freely borrowed. I'm similarly considering adding explicit comptime annotations as a feature, as opposed to either not having comptime evaluation at all or making it implicit through macros or something like full-blown partial evaluation.

I think that there's value in making these concepts explicit. Not just because they simplify the implementation, but also because explicit annotations help when reasoning about code. Distinguishing mutable from immutable variables through something like a mut keyword, for example, has value beyond just helping the compiler.

It's obvious that being explicit about a distinction isn't always valuable in language design. As always when building software, it depends. Annotating every variable with its type gets really annoying really fast. Some people would even argue that annotating any variable with its type gets annoying really fast. Others disagree and prefer sophisticated type systems that require heavy annotations. So in some ways the question of being explicit vs. implicit in language design is definitely a question of preference and taste.

I've previously written about the idea of adding explicit annotations for boxing and ownership to the functional language that I'm working on, which is something that seems to be rare for functional programming languages, as opposed to more imperative “systems languages”. On some level, I do understand why explicit boxing/ownership is mostly found in lower level systems languages. It would certainly feel weird to encounter explicit boxing in a language that is otherwise extremely dynamic and doesn't care about memory layout and efficiency at all. But is there really just one dimension, roughly from abstract / high level to concrete / low level, which determines whether exposing an explicit distinction makes sense?

Explicit semantics

The main reason why I'm interested in explicit comptime calls is that I want macros, which end up being really slow if they are resolved at runtime. I could try to make the VM a lot faster, but that adds a lot of complexity, or I could try to figure out implicitly which calls can be evaluated at comptime, which adds just as much complexity because I basically need full partial evaluation or something like a JIT compiler. The secret of many dynamic high level languages seems to be that they pay for it with sophisticated VMs/JITs, which then make it hard to guess what is going on behind the scenes.

This is why I find exposing “low level” concepts such as uniqueness, boxing, and comptime calls interesting in the context of a functional language. Not because I value these distinctions for their own sake, but because just ignoring them isn't a real alternative, unless you're happy with an implementation that is slow enough to severely restrict the possible applications.

The real alternative is to make these distinctions implicit and push them into the compiler and VM, which adds more complexity there, a complexity which programmers often have to pay for, as increased mental load.

In addition to an explicit comptime stage, explicit uniqueness/ownership, and explicit boxing, I'm also interested in having explicit function arity, in other words no implicit currying if a function is called with too few arguments. Currying is theoretically elegant (multiple arguments are just currying + repeated function application) but leads to confusing errors without a static type system, because a function will happily accept too few arguments and then raise an error far away from its original call site due to being a partial function instead of a value.

Implicit syntax (within reason)

On the more syntactic side, my take on the explicit vs. implicit debate is slightly different. I'm not opposed to implicit syntax if it can be trivially statically checked (which for me basically means purely in the parser / through desugaring and without a type system). An example of a trivial check like this is the requirement that all variables can be statically and lexically resolved, which makes compilation trivial. More interesting is that this also extends to the macro system, which guarantees that the scope of all variables can be syntactically and statically resolved without any macro evaluation.

I think that, in general, implicit syntax is more defensible than implicit semantic features, because implicit syntactic features can be statically checked and errors can be reported during parsing or compilation. This principle should be applied within reason, though, or we end up with syntax that is unambiguous but hard to reason about. This is why I'm not a fan of arbitrary infix precedence (where the parser needs to be aware of user-defined infix functions) or even multiple levels of fixed infix precedence (because it's pretty easy to misremember precedence levels as soon as there's more than just infix arithmetic). This is also why I like explicit infix syntax that always requires parentheses for grouping whenever different infix operators are involved.

Explicit arenas

Another feature that I haven't seen made explicit in most functional languages is arena allocation as a data type. Not just arena allocation as a pattern of allocating in a pre-allocated array by growing the array for new elements and freeing the entire arena. Arenas as a data type that enforces arena-like behavior in a functional language so that mutation is never visible.

After all, what is an arena? It's a list-like data type (usually implemented as an array, but let's ignore the implementation for now) that can be used to store newly created elements, returning a reference to each newly stored element. The benefit of using an arena is that it manages each element's lifetime by guaranteeing that all elements are freed together when the arena is freed.

Mutating a data structure is normally a problem in functional languages because the mutation would be observable, but that doesn't have to be the case for an arena. All an arena guarantees is that references to elements stored in the arena stay alive. It is thus a data type with a single operation, push, which immediately returns a reference to the newly stored element. An arena might be sequential, but that's an implementation detail: As a data type in a functional language, an arena's length and the order of elements must not be observable.

An interesting question is what should happen when an arena runs out of capacity. Returning an error if the arena is of a fixed size and doesn't have capacity is not an option, because then the length of an arena (and whether other users of the arena have pushed to it) would become observable through the back door. Growing the arena automatically (and thus making its size opaque) solves the problem.

Is there value in exposing such an allocation concept as an explicit data type in a functional language? Given that arena allocation seems to be becoming more popular as a simplified form of an ownership system / more structured form of manual memory management, perhaps there's value in pairing it with a stack-based VM with no boxing with 2-pass mark-and-compact. Values would remain stack-allocated as part of call frames by default and thus share a function's lifetime (or be moved on function return), but could be pushed into an arena that sits lower on the stack and gets deallocated all at once when it goes out of scope.

Whenever such an arena reaches its max capacity and needs to grow (e.g. by doubling it, thus amortizing the growth), the entire stack above the arena has to be shifted. The nice thing is that this is a mirror of the existing 2-pass mark-and-compact algorithm discussed. Instead of fixing refs and then compacting everything, it's fixing refs and then growing everything.

Would such a referentially transparent arena be a useful notion to expose explicitly in a functional language?