#pronelang — Public Fediverse posts
Live and recent posts from across the Fediverse tagged #pronelang, aggregated by home.social.
-
Since Elon is spitting bad takes about AI again as part of blatant pump-n-dumping (a thing he has practice with after many exes), I'm going to take a second to talk about how the design of #pronelang went the exact opposite direction before it was even controversial.
Let's start with the actual "engineering compassionately" part of things first. In software, there's a lot of stuff you can get away with on a streak of good days. What matters for your long-term fate is how you handle *bad* days.
The most clear example of this is backups. You can have a very successful business, even holding other people's money, with no backups! ... for awhile. No sane person would recommend this approach, because we all understand the weight of that "for awhile."
1/n
-
Since Elon is spitting bad takes about AI again as part of blatant pump-n-dumping (a thing he has practice with after many exes), I'm going to take a second to talk about how the design of #pronelang went the exact opposite direction before it was even controversial.
Let's start with the actual "engineering compassionately" part of things first. In software, there's a lot of stuff you can get away with on a streak of good days. What matters for your long-term fate is how you handle *bad* days.
The most clear example of this is backups. You can have a very successful business, even holding other people's money, with no backups! ... for awhile. No sane person would recommend this approach, because we all understand the weight of that "for awhile."
1/n
-
@nate The best crash course of the concepts and motivation is on my website!
It's really just called Prone, but I use the #pronelang tag for clarity/avoiding tag collisions on Fedi, similar to how Go is often marked as "golang" to make it sanely searchable online.
Anyways, the executive summary is that it lets you write code in a highly dynamic interpreted language explicitly designed for being able to do metaprogramming without shooting yourself in the foot, but it transpiles to C, which you can compile with the latest GCC/clang have to offer, or an old crusty embedded toolchain, or whatever. A goal is for the produced C code to be focused on readability rather than looking like it went through a minifier, so you can see all the way through the toolchain if you need to, and tightly FFI with anything (so, great for writing libraries).
-
@nate The best crash course of the concepts and motivation is on my website!
It's really just called Prone, but I use the #pronelang tag for clarity/avoiding tag collisions on Fedi, similar to how Go is often marked as "golang" to make it sanely searchable online.
Anyways, the executive summary is that it lets you write code in a highly dynamic interpreted language explicitly designed for being able to do metaprogramming without shooting yourself in the foot, but it transpiles to C, which you can compile with the latest GCC/clang have to offer, or an old crusty embedded toolchain, or whatever. A goal is for the produced C code to be focused on readability rather than looking like it went through a minifier, so you can see all the way through the toolchain if you need to, and tightly FFI with anything (so, great for writing libraries).
-
Okay, if I can get my abdomen to stop hurting for a goddamn half hour, I think my goal for today is implementing `if` in the #pronelang stack VM. Funny thing, this actually wasn't implemented in the existing interpreter yet - in fact, I'm going to need to make a bool type! - so we're really venturing into territory now where the two worlds each have unique features, and will until they finally unite again later.
I still don't consider the new VM to be the guaranteed future of the implementation, so I'm looking to get to that point of confidence/validation as early as possible, trying to go for hard problems first. Block definitions and conditional logic certainly are a good way to play chicken with engineering contradictions right now.
-
Okay, if I can get my abdomen to stop hurting for a goddamn half hour, I think my goal for today is implementing `if` in the #pronelang stack VM. Funny thing, this actually wasn't implemented in the existing interpreter yet - in fact, I'm going to need to make a bool type! - so we're really venturing into territory now where the two worlds each have unique features, and will until they finally unite again later.
I still don't consider the new VM to be the guaranteed future of the implementation, so I'm looking to get to that point of confidence/validation as early as possible, trying to go for hard problems first. Block definitions and conditional logic certainly are a good way to play chicken with engineering contradictions right now.
-
I was thinking that I'd need to use an integer flag rather than boolean to handle multiple nested blocks, but I actually suspect I don't. Say we have this code:
;; ~{ foo ~{ bar } baz }
This produces a single block on the stack containing the commands `foo ~{ bar } baz`. When that block evaluates, only then will it create a block with `bar` on the stack, at that time. Which is kind of exactly what you want for deferred execution.
I'd love to hear the opinion of @ireneista on this, since this part of the #pronelang project has ventured so dramatically into their neck of the conceptual woods, and I'm not exactly an old hand at Forth, steeped in the practical lessons of stack machine experience.
-
I was thinking that I'd need to use an integer flag rather than boolean to handle multiple nested blocks, but I actually suspect I don't. Say we have this code:
;; ~{ foo ~{ bar } baz }
This produces a single block on the stack containing the commands `foo ~{ bar } baz`. When that block evaluates, only then will it create a block with `bar` on the stack, at that time. Which is kind of exactly what you want for deferred execution.
I'd love to hear the opinion of @ireneista on this, since this part of the #pronelang project has ventured so dramatically into their neck of the conceptual woods, and I'm not exactly an old hand at Forth, steeped in the practical lessons of stack machine experience.
-
Haven't done a lot of #pronelang work today because it hasn't been a great health day. But it hasn't been zero either. The current thought experiment I've been working through is: "can `repr` be implemented in the standard library instead of a C builtin?"
Regardless of whether that ends up being the way things are done long-term, it's actually an excellent stress test of the stack machine's usefulness, and holds the strict line that anything that doesn't require C doesn't use it.
I'm currently working on an even thinner thought experiment than that: can we implement the `repr` of `u8` values specifically? And the answer so far seems to be an expansion of the headline, "sure. But it'll be a lot of work and you'll probably be a bit pissed off by the end of it."
-
Haven't done a lot of #pronelang work today because it hasn't been a great health day. But it hasn't been zero either. The current thought experiment I've been working through is: "can `repr` be implemented in the standard library instead of a C builtin?"
Regardless of whether that ends up being the way things are done long-term, it's actually an excellent stress test of the stack machine's usefulness, and holds the strict line that anything that doesn't require C doesn't use it.
I'm currently working on an even thinner thought experiment than that: can we implement the `repr` of `u8` values specifically? And the answer so far seems to be an expansion of the headline, "sure. But it'll be a lot of work and you'll probably be a bit pissed off by the end of it."
-
As a final note before I go to bed, the invariant checks are often less expensive than they sound, for the same reason that most asserts are. Real world execution is overwhelmingly biased towards the happy path, which is kindergarten difficulty for a branch predictor. And by reusing some internal helper functions, we can reduce code size and make the predictor's job even easier (the cache heirarchy's job too!). Multiple VM instructions are sharing and amortizing the costs they bring to CPU acceleration infrastructure, even for warmup time. It's a performance potluck!
Almost unrelated, but long term it's likely that some of the instruction logic is going to be generated code from templates using bash scripts instead of the C preprocessor. While either can do a valid job, human-readable simple generated code can be a lot easier to debug and reason about, especially when dealing with compile errors. We'll see!
-
As a final note before I go to bed, the invariant checks are often less expensive than they sound, for the same reason that most asserts are. Real world execution is overwhelmingly biased towards the happy path, which is kindergarten difficulty for a branch predictor. And by reusing some internal helper functions, we can reduce code size and make the predictor's job even easier (the cache heirarchy's job too!). Multiple VM instructions are sharing and amortizing the costs they bring to CPU acceleration infrastructure, even for warmup time. It's a performance potluck!
Almost unrelated, but long term it's likely that some of the instruction logic is going to be generated code from templates using bash scripts instead of the C preprocessor. While either can do a valid job, human-readable simple generated code can be a lot easier to debug and reason about, especially when dealing with compile errors. We'll see!
-
The deeper I get into the new stack VM experiment, the more intuition I have about the engineering tradeoffs. I can actually make this approach sound kinda bad if I want to! More errors are moved to run time instead of compile time. Everything is fallible. Everything has some performance taxes, because we're doing manipulation of a virtual stack, and checking invariants more frequently. Consider the example where you create a list and then put stuff in it - each append needs to type check that the thing you're appending to is a list, which is a very amnesiac way to operate.
And yet, this *feels* so much better, because we're getting things for those costs. We have a coherent, consistent, effort-amortized error model that always operates on DVs, killing off two axes of function coloring. And we can expose this API in-language with ;; syntax basically unchanged because of runtime invariant checks. And the interpreter doesn't need to be crazy fast - transpilation is coming.
-
The deeper I get into the new stack VM experiment, the more intuition I have about the engineering tradeoffs. I can actually make this approach sound kinda bad if I want to! More errors are moved to run time instead of compile time. Everything is fallible. Everything has some performance taxes, because we're doing manipulation of a virtual stack, and checking invariants more frequently. Consider the example where you create a list and then put stuff in it - each append needs to type check that the thing you're appending to is a list, which is a very amnesiac way to operate.
And yet, this *feels* so much better, because we're getting things for those costs. We have a coherent, consistent, effort-amortized error model that always operates on DVs, killing off two axes of function coloring. And we can expose this API in-language with ;; syntax basically unchanged because of runtime invariant checks. And the interpreter doesn't need to be crazy fast - transpilation is coming.
-
Come to #pronelang, we have pop stars! I'm kidding, obviously, this is me sketching out the (reeaally) basic string manipulation tools in the new VM.
My thinking is, `repr` would be a really useful thing to add, but it requires the ability to get a string back out of the VM. Which then leads to: okay, how do we deal with the lifetime issue of the returned string? We have a good type for exactly that, it just felt a little odd when the construction functions I had were the `pshstrc` and `pshstrn` ones. So, we end up with this API, where you can feed string data in with multiple means (whatever's convenient), but the VM normalizes this to exactly one format you can get back out.
This is way easier than the tough philosophical problems I was facing with the `mk.h`, despite needing a bit of consideration, and I'm liking the outcomes much better so far.
-
Come to #pronelang, we have pop stars! I'm kidding, obviously, this is me sketching out the (reeaally) basic string manipulation tools in the new VM.
My thinking is, `repr` would be a really useful thing to add, but it requires the ability to get a string back out of the VM. Which then leads to: okay, how do we deal with the lifetime issue of the returned string? We have a good type for exactly that, it just felt a little odd when the construction functions I had were the `pshstrc` and `pshstrn` ones. So, we end up with this API, where you can feed string data in with multiple means (whatever's convenient), but the VM normalizes this to exactly one format you can get back out.
This is way easier than the tough philosophical problems I was facing with the `mk.h`, despite needing a bit of consideration, and I'm liking the outcomes much better so far.
-
Baby steps, but she works. As this grows, I'll probably want to add some similar macro stuff to the test suite, as each VM instruction is fallible in multiple (but regular) ways that lend themselves well to simple automated coverage. For example, no matter the type, every `addw` (add wrapping) instruction is going to:
* Fail if there are fewer than two items on the stack
* Fail if either item is not the type for that instruction
* Need some demonstration of wrapping behavior when overflowing
* Need a basic demonstration of non-overflow values workingI'm also a bit stylistically proud of a couple things that Don't Gotta Have Functions anymore. Initializing a blank VM is just assigning the value `{}` (oops! all zeros!) to it, which is also used to reset a VM after failure. Likewise, using unions and bit-fields in the Dynamic Value type eliminates a lot of "wrap or unwrap this value" functions.
https://git.sr.ht/~maddiem4/prone/commit/35a500bd098f46af58450cd160ccdbcdb06107d6
-
Baby steps, but she works. As this grows, I'll probably want to add some similar macro stuff to the test suite, as each VM instruction is fallible in multiple (but regular) ways that lend themselves well to simple automated coverage. For example, no matter the type, every `addw` (add wrapping) instruction is going to:
* Fail if there are fewer than two items on the stack
* Fail if either item is not the type for that instruction
* Need some demonstration of wrapping behavior when overflowing
* Need a basic demonstration of non-overflow values workingI'm also a bit stylistically proud of a couple things that Don't Gotta Have Functions anymore. Initializing a blank VM is just assigning the value `{}` (oops! all zeros!) to it, which is also used to reset a VM after failure. Likewise, using unions and bit-fields in the Dynamic Value type eliminates a lot of "wrap or unwrap this value" functions.
https://git.sr.ht/~maddiem4/prone/commit/35a500bd098f46af58450cd160ccdbcdb06107d6
-
Okay, here's a question where I'd love to have the perspective of more experienced #cprogramming folk. I'll make a poll, but I also really would love replies about reasoning.
I have no problem with just about any wild and crazy cast you can do in C, but *implicit* conversions can be a lurking hazard, so my goal is to make conversions all intentional and implicit. The question is how far to go on that quest.
This is an experimental part of the #pronelang codebase, where a uint8_t value is being pushed onto the interpreter VM's stack. The question is whether `prn_vm_pshu8` should accept a uint8_t or a wrapper struct - struct wrapping is just about the only thing I've seen that makes C say "that implicit conversion is above my pay grade, I'm not compiling that." Struct wrapping is still something you can do inline without needing to store in an lvalue (I considered passing a pointer to uint8_t but it likely kills register passing). But idk about the ergonomic cost.
-
Okay, here's a question where I'd love to have the perspective of more experienced #cprogramming folk. I'll make a poll, but I also really would love replies about reasoning.
I have no problem with just about any wild and crazy cast you can do in C, but *implicit* conversions can be a lurking hazard, so my goal is to make conversions all intentional and implicit. The question is how far to go on that quest.
This is an experimental part of the #pronelang codebase, where a uint8_t value is being pushed onto the interpreter VM's stack. The question is whether `prn_vm_pshu8` should accept a uint8_t or a wrapper struct - struct wrapping is just about the only thing I've seen that makes C say "that implicit conversion is above my pay grade, I'm not compiling that." Struct wrapping is still something you can do inline without needing to store in an lvalue (I considered passing a pointer to uint8_t but it likely kills register passing). But idk about the ergonomic cost.
-
Did some early #pronelang VM experimentation despite thick brain fog. Basically made a tiny implementation inside the test file itself. While performance is a concern I'm mostly trying to stuff down right now in favor of making the amount of C smaller, early results are promising, but a little hard to judge because thinking is an uphill battle, and the apples don't compare well to the oranges.
So like, I have one test, but it does a few things in sequence. VM objects are non-RC'd and stack-allocated, and within that bit of C stack memory, you have an internal structure with its failure code, stack size, and stack values. The test exercises this by confirming you can push and pop a value and round-trip that successfully, and also stack underflow and overflow scenarios. All of that fits in 200ns. There's a test for `mke_u8` passing in 41ns, which is a tiny fraction, but it's doing fewer things, most or all of which are actually being optimized out by the compiler. So what DOES that tell us?
1/n
-
Did some early #pronelang VM experimentation despite thick brain fog. Basically made a tiny implementation inside the test file itself. While performance is a concern I'm mostly trying to stuff down right now in favor of making the amount of C smaller, early results are promising, but a little hard to judge because thinking is an uphill battle, and the apples don't compare well to the oranges.
So like, I have one test, but it does a few things in sequence. VM objects are non-RC'd and stack-allocated, and within that bit of C stack memory, you have an internal structure with its failure code, stack size, and stack values. The test exercises this by confirming you can push and pop a value and round-trip that successfully, and also stack underflow and overflow scenarios. All of that fits in 200ns. There's a test for `mke_u8` passing in 41ns, which is a tiny fraction, but it's doing fewer things, most or all of which are actually being optimized out by the compiler. So what DOES that tell us?
1/n
-
RE: https://raphus.social/@MaddieM4/117052598460059839
Welp. Operating on two hours of sleep and a lot more sober, I wouldn't say any of this stuff feels like a clearly solved problem in my head, but it's certainly a good set of ingredients.
One thing I've realized is that my "assembly-like" syntax can be used to pick a prelude at the top of a piece of code, which should allow the parser to pick a set of rules for interpreting non-RPN sections, kind of switching a state machine inside the parser. But then at another layer, when applying semantic meaning, this can also act as a trigger for including a specific prelude. So a script in #pronelang might start with:
```
#!/usr/bin/env prone
;; 2026 edition// code goes here....
```The use of the word "edition" is extremely strategic here! This is literally just "push the number literal 2026 onto the stack, then call the `edition` builtin", but it happens to read nicely in an English grammar way, which is a smooth move for something every #pronelang source file will start with.
-
RE: https://raphus.social/@MaddieM4/117052598460059839
Welp. Operating on two hours of sleep and a lot more sober, I wouldn't say any of this stuff feels like a clearly solved problem in my head, but it's certainly a good set of ingredients.
One thing I've realized is that my "assembly-like" syntax can be used to pick a prelude at the top of a piece of code, which should allow the parser to pick a set of rules for interpreting non-RPN sections, kind of switching a state machine inside the parser. But then at another layer, when applying semantic meaning, this can also act as a trigger for including a specific prelude. So a script in #pronelang might start with:
```
#!/usr/bin/env prone
;; 2026 edition// code goes here....
```The use of the word "edition" is extremely strategic here! This is literally just "push the number literal 2026 onto the stack, then call the `edition` builtin", but it happens to read nicely in an English grammar way, which is a smooth move for something every #pronelang source file will start with.
-
Why do I work on these things while high and trying to go to sleep? I think special characters basically have to be treated like reserved keywords by default or we're all fucked when #pronelang adds new syntax (sugar) ughhhhh
-
Why do I work on these things while high and trying to go to sleep? I think special characters basically have to be treated like reserved keywords by default or we're all fucked when #pronelang adds new syntax (sugar) ughhhhh
-
Welp, two-hour call with the 'cule later, my brain is mush, but I do broadly like the direction I'm sketching out, which is now built around these little bits of computation between checkpoints. It kinda reminds me of SQL transactions. But there are two challenges standing out that I'll need to think through when I can think again.
1. I really need to figure out the error model, which is a can I'd been kicking for awhile, but now I have a bit more context that'll help resolve the design ambiguities and make the correct path clear.
2. I need to assess if this is going to be a problem for the reference counting model #pronelang is built on, and if so, how to modify the design to avoid it. This isn't a logical risk, it's safe either way - the risk is locking myself into a design that requires object cloning more often. I really don't mind a temporary drop in performance right now, but permanent design-based limitations are no bueno.
-
Welp, two-hour call with the 'cule later, my brain is mush, but I do broadly like the direction I'm sketching out, which is now built around these little bits of computation between checkpoints. It kinda reminds me of SQL transactions. But there are two challenges standing out that I'll need to think through when I can think again.
1. I really need to figure out the error model, which is a can I'd been kicking for awhile, but now I have a bit more context that'll help resolve the design ambiguities and make the correct path clear.
2. I need to assess if this is going to be a problem for the reference counting model #pronelang is built on, and if so, how to modify the design to avoid it. This isn't a logical risk, it's safe either way - the risk is locking myself into a design that requires object cloning more often. I really don't mind a temporary drop in performance right now, but permanent design-based limitations are no bueno.
-
"Just when I think I'm out, they pull me back in." - me when I realize that the new C API for #pronelang I'm sketching out in a blank file is drifting back in the direction of being a Lua-like stack of values.
-
"Just when I think I'm out, they pull me back in." - me when I realize that the new C API for #pronelang I'm sketching out in a blank file is drifting back in the direction of being a Lua-like stack of values.
-
Hard to tell sometimes whether I'm wintering or my working memory is permanently cooked, and I won't know for awhile. But I'm holding out hope for the former. Even if I get doomerish sometimes, I'm good at finding the bottom of the pool and pushing off of it to find the surface. Maybe moreso than ever - I've had to put myself together multiple times a week for months and months, and that's nothing if not practice.
When I get back to #pronelang, I have my work cut out for me. I'm partway through the `mk.h` work that turned out to be challenging enough to break me, and trying to conceptualize why it was such a vertical wall, and what to do about it. But maybe even more significantly, because of the maintenance model, I'm having to significantly rethink the surface area of the C API and how to make it smaller, changing the emphasis from performance to small size - the smallest that could possibly enable a prelude and stdlib.
-
Hard to tell sometimes whether I'm wintering or my working memory is permanently cooked, and I won't know for awhile. But I'm holding out hope for the former. Even if I get doomerish sometimes, I'm good at finding the bottom of the pool and pushing off of it to find the surface. Maybe moreso than ever - I've had to put myself together multiple times a week for months and months, and that's nothing if not practice.
When I get back to #pronelang, I have my work cut out for me. I'm partway through the `mk.h` work that turned out to be challenging enough to break me, and trying to conceptualize why it was such a vertical wall, and what to do about it. But maybe even more significantly, because of the maintenance model, I'm having to significantly rethink the surface area of the C API and how to make it smaller, changing the emphasis from performance to small size - the smallest that could possibly enable a prelude and stdlib.
-
For awhile now I've been pondering what the path syntax should be for #pronelang. What do I mean by "paths"? well, in this context, I'm talking about lists of atoms, like `[#hello, #world, #3]`, which can then be used for things like chaining the dot operator. This is used for things like imports (for the module names), under the hood for complex lvalues in assignments (`foo.bar = 5u8`), and more. As far as lists go, these are something of an "egregiously common special case" worthy of some sort of syntax, and for a long time, that was `@foo.bar` in my head.
A better answer was kinda obvious, but snuck under my nose for a long time! Preceding dot! So `.foo.bar` is desugared to `[#foo, #bar]`. This will almost certainly be used directly in imports, which I think I can pull of without any special syntax, which would allow imports to be less of a special case and more of a teacher of language concepts (`exports->import(.foo.bar)`).
-
For awhile now I've been pondering what the path syntax should be for #pronelang. What do I mean by "paths"? well, in this context, I'm talking about lists of atoms, like `[#hello, #world, #3]`, which can then be used for things like chaining the dot operator. This is used for things like imports (for the module names), under the hood for complex lvalues in assignments (`foo.bar = 5u8`), and more. As far as lists go, these are something of an "egregiously common special case" worthy of some sort of syntax, and for a long time, that was `@foo.bar` in my head.
A better answer was kinda obvious, but snuck under my nose for a long time! Preceding dot! So `.foo.bar` is desugared to `[#foo, #bar]`. This will almost certainly be used directly in imports, which I think I can pull of without any special syntax, which would allow imports to be less of a special case and more of a teacher of language concepts (`exports->import(.foo.bar)`).
-
At least at this point I feel pretty strongly committed to reducing the pure C surface area of #pronelang and moving as much stuff as possible into prelude and stdlib. This is mainly about maintenance - as I've mentioned before, the stdlib will be versioned on a yearly basis to allow backwards-incompatible changes and optimizations. But I've lately been thinking about how big a deal it is to have transpilation be part of that flexible layer instead of achieved at the "backwards compatible for ten thousand years" layer, especially if there's multiple transpilation target languages - C is the most popular and important, but I always wanted to also be able to transpile to JS, and that becomes dramatically easier if it's in-language.
-
At least at this point I feel pretty strongly committed to reducing the pure C surface area of #pronelang and moving as much stuff as possible into prelude and stdlib. This is mainly about maintenance - as I've mentioned before, the stdlib will be versioned on a yearly basis to allow backwards-incompatible changes and optimizations. But I've lately been thinking about how big a deal it is to have transpilation be part of that flexible layer instead of achieved at the "backwards compatible for ten thousand years" layer, especially if there's multiple transpilation target languages - C is the most popular and important, but I always wanted to also be able to transpile to JS, and that becomes dramatically easier if it's in-language.
-
I might get back to work on Prone a bit today, we'll see. I had to take a bit of a break because of life stuff. I have this slightly sly idea that, at the C level, it makes sense to just have one type for "bytestrings", and then layer on the concept of encodings (ASCII vs UTF-8 primarily) via a newtype system. At this point, I feel like the more stuff I can move to the standard library, the better, because the stdlib is going to have a yearly versioning system. Things in the underlying interpreter need to be rare because they're stable and supported forever, so that 10 years from now, you can still do `exports->include(&std.2026)` or `exports->include(&std.2036)` and both will still work unchanged.
-
I might get back to work on Prone a bit today, we'll see. I had to take a bit of a break because of life stuff. I have this slightly sly idea that, at the C level, it makes sense to just have one type for "bytestrings", and then layer on the concept of encodings (ASCII vs UTF-8 primarily) via a newtype system. At this point, I feel like the more stuff I can move to the standard library, the better, because the stdlib is going to have a yearly versioning system. Things in the underlying interpreter need to be rare because they're stable and supported forever, so that 10 years from now, you can still do `exports->include(&std.2026)` or `exports->include(&std.2036)` and both will still work unchanged.
-
@elebertus Yep. I mention here and there that I started the #pronelang project to have a better implementation language for the Layover package manager, but I don't talk a ton about Layover lately. One of the things that I originally threw into the design as a reasonable feature, but now feel more eager about, is user-level installations. It's so nice to be able to have different update paces for your base system, vs this-or-that package. I think having my whole system operate on a rolling basis is a bit much, but fine for now.
Things would probably be a bit easier if I was less grossed out by Flatpak, Snap et al. But then I wouldn't be writing my own language to write my own package manager.
-
@elebertus Yep. I mention here and there that I started the #pronelang project to have a better implementation language for the Layover package manager, but I don't talk a ton about Layover lately. One of the things that I originally threw into the design as a reasonable feature, but now feel more eager about, is user-level installations. It's so nice to be able to have different update paces for your base system, vs this-or-that package. I think having my whole system operate on a rolling basis is a bit much, but fine for now.
Things would probably be a bit easier if I was less grossed out by Flatpak, Snap et al. But then I wouldn't be writing my own language to write my own package manager.
-
@hyc this used to give me so much confusion, trying to understand the output of 'perf report' while working on #Pronelang. I really appreciate you spending time to explain these implementation details that often feel "beneath the floorboards" even while working in C.
-
@hyc this used to give me so much confusion, trying to understand the output of 'perf report' while working on #Pronelang. I really appreciate you spending time to explain these implementation details that often feel "beneath the floorboards" even while working in C.
-
This is a nice panel discussion, though it won't feel too novel to anyone who watches the C++ circuit. I do want to point out around 17:34, when Herb is emphasizing that standards compliance in modern C++ requires having two implementations - a compiler and an interpreter - because that's just how constexpr has to work.
#Pronelang is actually far more closely related to C++ in its approach to (and motivation for) build time metaprogramming than it is to academic precedent, but approaching from an opposite angle - building an interpreted language first, and then adding compilation over time. We also have a sustainable answer to stdlib creep! But profiles are a useful idea that I don't have a great answer to yet, just a few intuitions.
-
A lot of what I'm focusing on with #pronelang lately is consistency at all costs, but obviously that'll teach you very quickly whether a thing you're trying to do consistently is Bad Actually. So the skill the universe is trying to teach me today is cutting losses quickly.
Specific example: type-specific allocation functions like `prn_alloc_t_vvec`. Obviously, that should allocate the `vvec_t` structure, but should it also allocate the underlying buffer? You can't tell from the name! Even from an authorial "it's whatever I decide is right" perspective, the framing doesn't lend itself to any particular thing being right. Meanwhile I'm splitting the allocator logic between places in a nasty way, I'd like to have all the allocator debug chars in a single place.
Meanwhile, just using `prn_alloc_malloc` solved the problem instantly.
-
A lot of what I'm focusing on with #pronelang lately is consistency at all costs, but obviously that'll teach you very quickly whether a thing you're trying to do consistently is Bad Actually. So the skill the universe is trying to teach me today is cutting losses quickly.
Specific example: type-specific allocation functions like `prn_alloc_t_vvec`. Obviously, that should allocate the `vvec_t` structure, but should it also allocate the underlying buffer? You can't tell from the name! Even from an authorial "it's whatever I decide is right" perspective, the framing doesn't lend itself to any particular thing being right. Meanwhile I'm splitting the allocator logic between places in a nasty way, I'd like to have all the allocator debug chars in a single place.
Meanwhile, just using `prn_alloc_malloc` solved the problem instantly.
-
@phoenix @loon I dunno how much I can say for NDA reasons, but I think I can say a little, especially if I don't clarify which job.
We didn't actually use NixOS itself in prod, but we did use Nix and nixpkgs. It was for building artifacts like Docker images, which then ran in prod. It was also used a bit for local developer stuff on Mac and Linux desktops.
It wasn't my first encounter with Nix, I've used it a couple times before. Gods willing, I will not use it again. I find it so badly designed from a UX/engineering standpoint that it actually started me on the road of writing my own reproducible build/atomic installation package manager, which then derailed into writing a programming language for implementing the Layover package manager, which is why my account is full of posts tagged #pronelang today. You know how most package managers use efficient binary indexes of guaranteed-prebuilt binary packages, instead of a giant RAM-hungry interpreted FP language? There's a reason.
-
In another episode of "Adding the `mk.h` header to #pronelang is forcing me to answer a bunch of difficult questions", I find myself refining a doctrine I've talked about before: frontstage vs backstage types.
To recap: a frontstage type is a type that is visibly exposed when using Prone as a language. A backstage type is an implementation type that's visibly exposed when using Prone as a C API. Often, a single backstage type is used to implement multiple frontstage types, like how [lists], [% thunks %] and ~{ blocks } are all VVecs under the hood.
At the time I came up with this terminology, I had the attitude that frontstage types should not exist in the C API, but actual practice is changing my mind about that.
-
In another episode of "Adding the `mk.h` header to #pronelang is forcing me to answer a bunch of difficult questions", I find myself refining a doctrine I've talked about before: frontstage vs backstage types.
To recap: a frontstage type is a type that is visibly exposed when using Prone as a language. A backstage type is an implementation type that's visibly exposed when using Prone as a C API. Often, a single backstage type is used to implement multiple frontstage types, like how [lists], [% thunks %] and ~{ blocks } are all VVecs under the hood.
At the time I came up with this terminology, I had the attitude that frontstage types should not exist in the C API, but actual practice is changing my mind about that.
-
I'm really feeling the struggle to pick this part of development back up. Quite a bit of the #pronelang codebase got its smallness from being more weakly typed, in a certain sense: sure we had `list_t` and `block_t` and `thunk_t`, but they were all aliases to `vvec_t`, and interchangeable throughout the code. It's kind of to-the-spec that these are all the same representation under the hood.
Well, in the move towards more explicit types and conversions for error checking reasons, it turns out a lot of code breaks all at once when these types are no longer implicitly equivalent.
-
I have other stuff I need to finish, but I'm second-guessing the "thunk/thunkify/dethunkify" naming choices in Prone, as it's slightly off from the proper definition of thunk. It's closer to quoting from the LISP world, but I hate that term because it collides with the use of quotes as string delimiters.
Almost like naming things is one of the hard problems of software development or something, gosshhhh
-
@ireneista this is very much on the same sort of track as a conversation I had with @loon semi-recently, about how dynamic strongly typed languages (Python, Lua, JS) tend towards using pure values and print debugging for the same goal that static strongly typed languages (Rust, OCaml, Haskell) use enforced type signatures to achieve: giving the programmer a mental entrypoint to the code, where it's known what's going on at that point.
Makes sense that for stack languages, you'd want stack comments to be those checkpoints. I found myself doing a bit of that just for the stack machine in the #pronelang parser!
-
@ireneista this is very much on the same sort of track as a conversation I had with @loon semi-recently, about how dynamic strongly typed languages (Python, Lua, JS) tend towards using pure values and print debugging for the same goal that static strongly typed languages (Rust, OCaml, Haskell) use enforced type signatures to achieve: giving the programmer a mental entrypoint to the code, where it's known what's going on at that point.
Makes sense that for stack languages, you'd want stack comments to be those checkpoints. I found myself doing a bit of that just for the stack machine in the #pronelang parser!
-
Welp, my mission today is to buy a couple energy drinks from the corner store. It's not like I'm hard up for caffeine - I just finished off my supply of cold coffee - it's the secondary stuff that I'm probably having some deficiencies of, like B vitamins and creatine. Inshallah the day ends with a bit more visible progress on #pronelang - the `mk.h` header won't make itself.
-
I want #pronelang to have a large and centrally organized stdlib, because as Master Node-a teaches us: small standard libraries lead to microlibs, microlibs lead to transitive dependencies, transitive dependencies lead to supply chain attacks, and supply chain attacks... lead to suffering.
That's also too big a project to do myself even if I could handle most of the core language, so obviously coordinating teams of people is a non-optional skill, and no less important than the technical stuff. So I'm thinking about how to do that kind of leadership.
Something I think would be "cool if workable", but probably not workable, is having a "you must contribute before you can make bug or feature request tickets", but having an abundance of avenues for non-technical people to contribute in atypical ways, for example, contributing doodles to a doodle wall. I think there's something cool about the psychological effect of having your name on something.
-
I want #pronelang to have a large and centrally organized stdlib, because as Master Node-a teaches us: small standard libraries lead to microlibs, microlibs lead to transitive dependencies, transitive dependencies lead to supply chain attacks, and supply chain attacks... lead to suffering.
That's also too big a project to do myself even if I could handle most of the core language, so obviously coordinating teams of people is a non-optional skill, and no less important than the technical stuff. So I'm thinking about how to do that kind of leadership.
Something I think would be "cool if workable", but probably not workable, is having a "you must contribute before you can make bug or feature request tickets", but having an abundance of avenues for non-technical people to contribute in atypical ways, for example, contributing doodles to a doodle wall. I think there's something cool about the psychological effect of having your name on something.
-
@nyanpasu64 @catsalad You are on track to unintentionally bully me into finishing my LOTR-themed optimization blog post about improving the reference counting performance in #pronelang last year, where the ability to use "one lend function to rule them alllll!" was directly facilitated by using void pointers.
-
@renardboy Yup! Even in C++, which still has void pointers and is plenty low-level by most folks' standards, modern code rarely uses void pointers, because most of the things you'd use a void pointer _for_ have more type-checked alternatives, like subclasses or std::variant.
They're still used a lot in raw C, of course, and I write a decent amount of C these days since I'm trying to implement a programming language (see https://maddiem4.cc/prone or my #pronelang tag). So I'm using void pointers on the regular. They don't really require too much extra care, either, because C's type system is already full of escape hatches, so you're already having to pay attention in ways that catch void pointer abuse.
-
@catsalad This has genuinely given me a tattoo idea that I'll probably actually get someday, despite the fact that it's probably a particularly painful site to get inked. I really would love to get the word "void" one one of my pointer fingers, probably the right, so it'll be a void pointer.
Edit based on responses: a null pointer means "a pointer to nowhere", conventionally the memory address 0. If you try to use it, your computer will catch that as an error and present it as a "segmentation fault", or segfault for short - it says hey, that's not a memory segment you're allowed to use!
A void pointer is void in terms of *type*: we know where it is, but not what kind of thing it is. For example, in #pronelang, many types have reference counting information at the start. The reference counting functions accept a void pointer as input, because the same function can handle many types - as long as it starts with a valid RC struct, we good.
-
@oxy Such a good talk. VCL kind of blew my mind when I was first reading docs about it recently - it's such a good concept, and actually feels very similar to the principles I was aiming for when I started working on #pronelang, except they've already been doing it in production for years (and have the narrow domain of caching reverse proxies). And of course, they're doing the multi-stage compilation on the live server rather than as a build step prior to deployment.
-
Was talking to @loon about my schemes to someday (among many other eternally pushed-out projects) write my own Fediverse service in #pronelang, which should be light on resources, not have a ton of vulnerabilities to constantly patch, and be a Sourcehut-like rendered-HTML model which works great on light browsers like Dillo and Links.
We talked a little about numeric limits, like options in a poll, chars per post, etc. Because I know people customize this stuff all the time. I found an article talking about how this was supported in a proper, easy way with the `MAX_TOOT_CHARS` environment variable.
Yeah, turns out that article was an LLM hallucination and the internet is fucking dead, actuallyyyyy? It seems to have gotten confused by PRs where people have tried to get that behavior merged upstream. And I had to find the ground truth by CLONING A 370MB REPO TO DISK because Github no longer allows anon users to do code search. Thank fuck we're living in the future, amirite? GAWD DAMN.