Repr Design (LLO Archive)
Created 2025-11-30, last modified 2025-11-30
Part of my archive of Layover Linux Official posts on Tumblr.
2025-09-20
It really only took an embarrassing amount of time because I hardly get a chance to work on Prone lately, but as of tonight, the machinery to produce a "repr" (representation) of values is a lot more robust, a lot more tested, and a lot better designed. That last part is actually somewhat interesting to dig into.
So let's say you're in the interactive interface, and you try to print out a built-in native C function like op::plus. What should that print? There's a pretty wide space of possible approaches, but it gets narrower because of a personal goal: that a repr is the source code you'd type to get that value. If I were to go too Pythonic on this:
>>> sum
<built-in function sum>
Well, something like <native op::plus> wouldn't be valid Prone syntax even though it looks pretty. And some Python objects print with their memory address, which would be a real disaster to take inspiration from. Giving users the ability to construct a native function at an arbitrary memory location and say "trust me bro there's executable code there" is the kind of feature that, if it exists, should be a bit more hidden so that novices don't use it by accident.
What I eventually ended up doing for native functions, in order to be hint-y at the type (but valid source code), is to wrap it in a guard function, which I'll implement eventually.
prn> op::plus conv::fn(op::plus)
The behavior of conv::fn is pretty simple. Given a function, it just... returns that function, like nothing ever happened. But provide any other value, and you'll get a dispatch error. I haven't yet decided if I want a visual distinction between native C functions and in-language functions, but that's all in the realm of stuff that's easy to fiddle with and see what works in practice.
Likewise, one of the key features of Prone is Constructs. These are really just lists with a special flag set (and some conventions about their contents), but that's exactly why metaprogramming is going to be so easy in Prone, for very similar reasons to LISP. Want to make a function from just data? Well, make a list and pass it into the construct() function. Exactly like this:
my::function = construct([
#FN, // What type of construct is this?
[#a], // Arguments
construct([#STMT_RETURN,
construct([#FN_CALL, op::plus, [#a, #a]]) ])
])
The standard for the AST here is still subject to change, but the gist is that we're taking ordinary lists and marking them as executable code equivalent to returning a+a. And you could take a function defined with regular old keywords, and unpack it by converting to a list. What's really cool is that the construct() call with contents in it is the repr! This function is a value, and that repr string is what you'd need to recreate it.
As for making the code more robust, that mainly amounted to making separate individual API functions for the different things that can be repr'd, a function that dispatches a single dynamic value (dv_repr), and a function that wraps dv_repr so it can be used in-language in full dynamism. This means we're taking advantage of native C types known statically at compile time for a lot more of the code, which means fewer type checks, less work to write tests, and more testing (because it's not obnoxious anymore).

