Hi Gavin, I've seen your blog before, including some posts about Yao.
After feedback on Lobste.rs, I plan on adding a section on conditions and restarts, hopefully sometime later today if time permits. :)
I'd be happy to add some information about Yao as well alongside Common Lisp. It would be helpful for me to have some more details about Yao before writing about it, so I have some follow-up questions below. Please feel free to link me to existing writing; I may be misremembering details, but I don't think the answers to these have been covered somewhere.
I looked at the docs on the master branch as of mid Jan 2025 (could you confirm if these are up-to-date), particularly design.md, and I noticed these points:
> That means that Yao will have unsafe code, like Rust's unsafe. However, unlike Rust, Yao's way of doing unsafe code will be harder to use,
So Yao has a delineation between safe and unsafe code, correct? Does "safe" in Yao have the same (or stronger) set of guarantees as Rust (i.e. no memory safety if all necessary invariants are upheld by unsafe code, and boundary of safe-unsafe code)?
> Yao's memory management will be like C++'s RAII [..]
Does Yao guarantee memory safety in the absence of unsafe blocks, and does the condition+restart system in Yao fall under the safe subset of the language? If so, I'm curious how lifetimes/ownership/regions are represented at runtime (if they are), and how they interact with restarts. Specifically:
1. Are the types of restart functions passed down to functions that are called?
2. If the type information is not passed, and conditions+restarts are part of the safe subset of Yao, then how is resumption logic checked for type safety and lifetime safety? Can resumptions cause run-time type errors and/or memory unsafety, e.g. by escaping a value beyond its intended lifetime?
Thank you for the offer, but I don't think Yao should be featured yet.
Edit: I guess I'll answer your questions anyway.
> So Yao has a delineation between safe and unsafe code, correct?
Correct. However, Yao's "unsafe" will actually be separate files, written directly in an LLVM-like assembly (Yvm in the repo). That's how it will be harder to use.
> Does "safe" in Yao have the same (or stronger) set of guarantees as Rust (i.e. no memory safety if all necessary invariants are upheld by unsafe code, and boundary of safe-unsafe code)?
Stronger.
First, Yvm (Yao's assembly/unsafe) is made specifically for structured languages, and it will still do bounds checks by default. Yes, there will be ways of not doing bounds checks, of course, but even in "unsafe," bounds checks will exist.
Second, Yao and Yvm are both explicitly designed for better formal verification. [1] This includes user-defined formal properties.
> Does Yao guarantee memory safety in the absence of unsafe blocks?
Yes.
> does the condition+restart system in Yao fall under the safe subset of the language?
It's still being implemented (hence why Yao should not be featured), but it will be a part of the safe subset. That is a guarantee; I will completely redesign Yao if I cannot fit conditions and restarts in the safe subset. But I am confident that they will work as-is because I implemented a prototype in C that is safer than C itself.
> 1. Are the types of restart functions passed down to functions that are called?
Not directly. My C code uses, and Yao will use, what I call "context stacks," an idea that comes from Jonathan Blow's Jai.
These are more useful than just restart functions, but there is explicitly one context stack for restart functions, per thread. Registering a restart function means pushing it onto the context stack.
Then, when an error happens, the context stack is walked backwards until a restart function handles the error. If no function handles it, the context stack for the parent thread is walked (starting at the point where the child thread was created), and so on until some function handles it.
I push a default restart function at the root, so errors will always be handled.
> 2. If the type information is not passed, and conditions+restarts are part of the safe subset of Yao, then how is resumption logic checked for type safety and lifetime safety? Can resumptions cause run-time type errors and/or memory unsafety, e.g. by escaping a value beyond its intended lifetime?
This is one of the craziest parts of Yao: it will have the capability to be generic over types at runtime. In addition, Yao has something like Go interfaces or Rust traits. What it has is more powerful, though.
The end result is that errors will actually be interfaces, and everything will be type checked at comptime, but be memory safe.
Hi, blog post author here (unrelated to paper authors).
> So in a laps of 15 years (2010-1025),
The paper was published in 2014, so the period is 2010-2014, not 2010-2025.
> they hand picked 20 bugs from 5 open source filesystem projects (198 total)
The bugs were randomly chosen; "hand picked" would imply that the authors investigated the contents of the bug reports deeply before deciding whether to include them (which would certainly fall under "bad science"). The paper states the following:
> We studied 198 randomly sampled, real world fail-
ures reported on five popular distributed data-analytic
and storage systems, including HDFS, a distributed file
system [27]; Hadoop MapReduce, a distributed data-
analytic framework [28]; HBase and Cassandra, two
NoSQL distributed databases [2, 3]; and Redis, an in-
memory key-value store supporting master/slave replica-
tion [54]
So only 1 out of 5 projects is a file system.
> and extrapolated this result. That is not science.
The authors also provide a measure of statistical confidence in the 'Limitations section'.
> (3) Size of our sample set. Modern statistics suggests that
a random sample set of size 30 or more is large enough
to represent the entire population [57]. More rigorously,
under standard assumptions, the Central Limit Theorem
predicts a 6.9% margin of error at the 95% confidence
level for our 198 random samples. Obviously, one can
study more samples to further reduce the margin of error
Do you believe that this is insufficient or that the reasoning in this section is wrong?
> The paper was published in 2014, so the period is 2010-2014, not 2010-2025.
Oh indeed, my bad.
> The bugs were randomly chosen; "hand picked" would imply that the authors investigated the contents of the bug reports deeply before deciding whether to include them
No, no, they were not randomly chosen, they even have a whole paragraph explaining how they randomly picked _from a pool of manually selected bugs_. Their criterion of selection varied from "being serious", to "having a lot of comments" and "they can understand the patch", or "the patch is not from the reporter".
> The authors also provide a measure of statistical confidence in the 'Limitations section'.
This is a measure of confidence of their random sampling being representative of the hand picked bugs...
> under standard assumptions, the Central Limit Theorem predicts a 6.9% margin of error at the 95% confidence level
I would love to see them prove the normality assumption of bug root cause distribution.
Also, the whole categorization they do seem purely qualitative.
I have been guilty of such kind of comments and I have come to realize they do nothing to further my point. I would suggest to read the guidelines because they are very well written about this: https://news.ycombinator.com/newsguidelines.html
> We studied 198 randomly sampled, real world fail- ures reported on five popular distributed data-analytic and storage systems, including HDFS, a distributed file system [27]; Hadoop MapReduce, a distributed data- analytic framework [28]; HBase and Cassandra, two NoSQL distributed databases [2, 3]; and Redis, an in- memory key-value store supporting master/slave replica- tion [54]
So, analyzing databases, we picked Java, Java, Java, Java, and one in C. This does not seem very random. I suppose this may provide insight into failure modes in Java codebases in particular, but I'm not sure I'd be in a hurry to generalize.
Hi, author here. I'm a big fan of Armstrong's work, I've watched several of his talks multiple times and always get something new out of them even if I don't agree entirely. :)
I do mention Erlang near the start of the post, around the 360 word mark:
> Joe Armstrong’s talk The Do’s and Don’ts of Error Handling: Armstrong covers the key requirements for handling and recovering from errors in distributed systems, based on his PhD thesis from 2003 (PDF) [sidenote 3].
> [sidenote 3] By this point in time, Armstrong was about 52 years old, and had 10+ years of experience working on Erlang at Ericsson.
> Out of the above, Armstrong’s thesis is probably the most holistic, but it’s grounding in Erlang means that it also does not take into account one of the most widespread forms of static analysis we have today – type systems
When I was reading your article my first thought was most people would only read the intro but many don’t even do that before commenting.
Given how prevalent just reading the headline -> immediately posting a preconceived take is, most information is probably communicated through the top comments these days.
It's a great article but calling Erlang's philosophy of 'Let it crash' a pithy, catchy statement that brushes over the complexities of the real world was an unwarranted attack. There's much more to it and with Elixir on top it goes a long way in lightening the cognitive load of error handling and building fault tolerant software.
They aren't perfect tools but they are also not merely misguided academic exercised combined with vapid catch phrases, as the analysis seems to imply.
I have removed the mention of "Let it crash" from that section, and added a clarification for my original intent. I did not mean it as criticism of Erlang or Joe Armstrong, although I 100% understand how it could've been interpreted as such.
> Hi, author here, the title also does say “for systems programming languages” :)
> For continuations to work in a systems programming language, you can probably only allow one-shot delimited continuations. It’s unclear to me as to how one-shot continuations can be integrated into a systems language where you want to ensure careful control over lifetimes. Perhaps you (or someone else here) knows of some research integrating ownership/borrowing with continuations/algebraic effects that I’m unfamiliar with?
> The closest exception to this that I know of is Haskell, which has support for both linear types and a primitive for continuations. However, I haven’t seen anyone integrate the two, and I’ve definitely seen some soundness-related issues in various effect systems libraries in Haskell (which doesn’t inspire confidence), but it’s also possible I missed some developments there as I haven’t written much Haskell in a while.
Thanks for writing the summary notes and sharing those here. After reading the Usenix article, I was thinking that we could apply some of the ideas at $WORK, but the exact "How" was still not super clear. Your notes offer a compact and accessible starting point without having to ask colleagues to dive in to a 100+ page PDF. :D
> If you have any language where it is "semantially correct" to execute it with a simple interpetter, than all optimizations in that language are not semantically important by definition, right?
Technically, yes. :)
But I think this should perhaps be treated as a bug in how we define/design languages, rather than as an immutable truth.
- We already have time-based versioning for languages.
- We also have "tiers" of support for different platforms in language implementations (e.g. rarer architectures might have Tier 2 or Tier 3 support where the debugging tooling might not quite work)
One idea would be to introduce "tiers" into a language's definition. A smaller implementation could implement the language at Tier 1 (perhaps this would even be within reach for a university course project). An industrial-strength implementation could implement the language at Tier 3.
(Yes, this would also introduce more complications, such as making sure that the dynamic semantics at different tiers are equivalent. At that point, it becomes a matter of tradeoffs -- does introducing tiers help reduce complexity overall?)
> In my view, if a compiler optimization is so critical that users rely on it reliably “hitting” then what you really want is for that optimization to be something guaranteed by the language using syntax or types. The way tail calls work in functional languages comes to mind. Also, the way value types work in C#, Rust, C++, etc - you’re guaranteed that passing them around won’t call into the allocator. Basically, relying on the compiler to deliver an optimization whose speedup from hitting is enormous (like order of magnitude, as in the escape analysis to remove GC allocations case) and whose probability of hitting is not 100% is sort of a language design bug.
>
> This is sort of what the article is saying, I guess.
I agree with this to some extent but not fully. I think there are shades of grey to this -- adding language features is a fairly complex and time-consuming process, especially for mainstream languages. Even for properties which many people would like to have, such as "no GC", there are complex tradeoffs (e.g. https://em-tg.github.io/csborrow/)
My position is that language users need to empowered in different ways depending on the requirements. If you look at the Haskell example involving inspection testing/fusion, there are certain guarantees around some type conversions (A -> B, B -> A) being eliminated -- these are somewhat specific to the library at hand. Trying to formalize each and every performance-sensitive library's needs using language features is likely not practical.
Rather, I think it makes sense instead focus on a more bottoms-up approach, where you give somewhat general tools to the language users (doesn't need to expose a full IR), and see what common patterns emerge before deciding whether to "bless" some of them as first-class language features.
My point is that if in the course of discovering common patterns you find that the optimizer must do a heroic optimization with a 10x upside when it hits and weird flakiness about when it hits, then that’s a good indication that maybe a language feature that lets you skip the optimization and let the programmer sort of dictate the outcome is a good idea.
By the way, avoiding GC is not the same thing as having value types. Avoiding GC altogether is super hard. But value types aren’t that hard and aren’t about entirely avoiding GC - just avoiding it in specific cases.
I'm guessing you've tried these flags mentioned in the blog post but haven't had luck with them?
> LLVM supports an interesting feature called Optimization Remarks – these remarks track whether an optimization was performed or missed. Clang support recording remarks using -fsave-optimization-record and Rustc supports -Zremark-dir=<blah>. There are also some tools (opt-viewer.py, optview2) to help view and understand the output.
I've added a clarification in the post to make my position explicit:
> This is not to imply that we should get rid of SQL or get rid of query planning entirely. Rather, more explicit planning would be an additional tool in database user’s toolbelt.
I'm not sure if there was some specific part of the blog post that made you think I'm against automatic query planning altogether; if there was, please share that so that I can tweak the wording to remove that implication.
> I'm not sure if there was some specific part of the blog post that made you think I'm against automatic query planning altogether; if there was, please share that so that I can tweak the wording to remove that implication.
The quote from another article (which I didn't read) starting with "I dislike query planners".
"Against ... altogether" is mildly stronger than I took away from this, more like "generally of the opinion that the tradeoff nearly everyone is making with sql isn't worth it".
Judging by the lack of upvotes other people didn't react as strongly to this quote as I did, so take it as you will.
The preceding paragraph had "and occasionally language features" so I thought it would be understood that I didn't mean it as an optimizer-specific thing, but on re-reading the post, I totally see how the other wording "The knobs to steer the optimizer are limited. Usually, these [...]" implies the wrong thing.
I've changed the wording to be clearer and put the D example into a different bucket.
> In some cases, languages have features which enforce performance-related properties at the semantic checking layer, hence, granting more control that integrates with semantic checks instead of relying on the optimizer:
>
> - D has first-class support for marking functions as “no GC”.
After feedback on Lobste.rs, I plan on adding a section on conditions and restarts, hopefully sometime later today if time permits. :)
I'd be happy to add some information about Yao as well alongside Common Lisp. It would be helpful for me to have some more details about Yao before writing about it, so I have some follow-up questions below. Please feel free to link me to existing writing; I may be misremembering details, but I don't think the answers to these have been covered somewhere.
I looked at the docs on the master branch as of mid Jan 2025 (could you confirm if these are up-to-date), particularly design.md, and I noticed these points:
> That means that Yao will have unsafe code, like Rust's unsafe. However, unlike Rust, Yao's way of doing unsafe code will be harder to use,
So Yao has a delineation between safe and unsafe code, correct? Does "safe" in Yao have the same (or stronger) set of guarantees as Rust (i.e. no memory safety if all necessary invariants are upheld by unsafe code, and boundary of safe-unsafe code)?
> Yao's memory management will be like C++'s RAII [..]
Does Yao guarantee memory safety in the absence of unsafe blocks, and does the condition+restart system in Yao fall under the safe subset of the language? If so, I'm curious how lifetimes/ownership/regions are represented at runtime (if they are), and how they interact with restarts. Specifically:
1. Are the types of restart functions passed down to functions that are called? 2. If the type information is not passed, and conditions+restarts are part of the safe subset of Yao, then how is resumption logic checked for type safety and lifetime safety? Can resumptions cause run-time type errors and/or memory unsafety, e.g. by escaping a value beyond its intended lifetime?
---
For reading the docs, I used this archive.org link (I saw you're aware of the Gitea instance being down in another comment): https://web.archive.org/web/20250114231213/https://git.yzena...