RSS Amplifier

Bit Maybe Wise · Apr 17, 2026

Tsonnet #36 - Call me maybe, but make it typed, part 2

0
Sign in to vote or save

Hercules Merscher · Bit Maybe Wise

Welcome to the Tsonnet series!

If you’re not following along, check out how it all started in the first post of the series.

In the previous post, we added basic function support — positional parameters, multiline bodies, and type inference that resolves at call time:

Now let’s make functions a little more forgiving. In this post, we’ll implement default function arguments.

// samples/functions/default_args.jsonnet
local my_function(x, y=10) = x + y;
my_function(2)

The call passes only one argument. y falls back to its default value of 10, so the result is 12.

The parser needs a small adjustment to accept an optional default expression per parameter. Previously, each parameter was just an ID. Now it can optionally include = <expr>:

And the AST type updates to carry the optional default alongside the parameter name:

Each parameter goes from a plain string to a string * expr optionNone for required, Some expr for optional with a default.

The arity check is a bit more involved now, since we have to distinguish between required and optional parameters:

The key change: instead of checking for an exact match, we now compute both the total number of defined parameters (num_def) and the number of required ones (num_required). The call is valid if the number of supplied arguments falls anywhere in the range [num_required, num_def]. For arguments not supplied by the caller, we fall back to the default expression.

Default arguments give the type checker a nice bonus: when a parameter has a default value, we can infer its type right at declaration time instead of waiting for the first call.

Parameters without defaults stay Tunresolved and get resolved at the call site, same as before. Parameters with defaults get their type resolved immediately. In our example, y=10 means y is typed as Number from the moment the function is declared.

The arity check at the call site follows the same logic as the interpreter — allow fewer arguments than the total, as long as the mandatory ones are all there:

When the caller omits an argument that has a default, we skip the type-checking step for that parameter and carry its already-resolved type forward. No extra work needed — the type was inferred when the function was declared.

my_function(2) with y=10 gives 12. Working as expected.

Default arguments are in. With a surprisingly contained change across parser, AST, interpreter, and type checker, my_function(x, y=10) now works as you’d expect — and the type checker even gets to resolve y‘s type at declaration time rather than waiting for the first call.

Here is the entire diff.

Next up, functions get a serious upgrade: closures.

Read the original on bitmaybewise.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.