HNHacker News
TopNewBestAskShowJobs

bccdee

3,448 karma · joined May 23, 2020

Canadian software engineer.
submissionscomments
bccdee··on I was right about ATProto key management
ATProto may be the closest we'll get to that. PDSes are granular enough to serve individual users, and you can (theoretically) pull from a relay and index only posts from users you're interested in for your appview, if you're hardware-limited. Relays are fungible and pretty lightweight themselves, so you're not depending too much on any central server.

But people don't want to run an always-online server to send their stuff to peers, so they host it on the main bsky servers. The problem with p2p is UX; people don't want to DIY their server.

bccdee··on Beowulf's opening "What" is no interjection (2013)
Yeah—I'm reminded of the opening sentence of Slaughterhouse Five, which goes, "Listen: Billy Pilgrim has come unstuck in time." Kurt Vonnegut isn't worried that you're not listening; he's trying to create a sense of intimacy and emphasis, as one who is taking you aside to tell you something crucially important which he fears you may dismiss out of hand.

It seems intuitive to me that the "hwaet" functioned more as a literary device than as a simple call for attention. It is, after all, a poem.

bccdee··on A Social Filesystem
If you run a separate appview, bluesky can't censor your interactions in any way, and you can seamlessly view posts from both bluesky users and users banned on bluesky.

Of course, running an appview that indexes the whole world is expensive, and running an appview that doesn't index the whole world is less convenient than just using bluesky's appview, so the only people running an independent appview right now are blacksky.community. It isn't stable yet, but it works, and there are active users who are banned on bluesky: https://staging.blacksky.community/profile/did:plc:63hvnyjvq....

I'm optimistic that running independent appviews (especially appviews that index content lazily, on demand) will get easier in future.

bccdee··on A Social Filesystem
I never managed to get into mastodon because mastodon isn't a single space. It's a bunch of separate, linked spaces. Which server you're on matters a lot, and I never found one that really had the vibe I was looking for.

Bluesky, though, is one big pool. There's no seam between servers. The experience is much nicer.

Also, bluesky caught on among my peers in a way that mastodon never did, which was always going to be the deciding factor. I imagine not having to "choose a server" was a big part of that.

bccdee··on Agent Psychosis: Are We Going Insane?
The --help page lists 70+ commands, most of which are intended for use by agents.
bccdee··on Agent Psychosis: Are We Going Insane?
> Beads, which is basically some sort of issue tracker for agents, is 240,000 lines of code that … manages markdown files in GitHub repositories. And the code quality is abysmal.

I'd heard of beads as a lightweight issue tracker for agents, so this gave me a real shock. What could all that code POSSIBLY be doing? Going to the repo and poking around, I truly cannot tell. There's an enormous `docs/` folder with no hierarchy, containing files like `MULTI_REPO_HYDRATION.md`, which "describes the implementation of Task 3 from the multi-repo support feature (bd-307): the hydration layer that loads issues from multiple JSONL files into a unified SQLite database," and `ANTIVIRUS.md`, a 7KB text file about how `bd.exe` sometimes gets flagged as untrustworthy by antivirus software.

I opened a random go file, `detect_pollution.go`. This is a CLI command for detecting and cleaning up test tickets from a production database by (1) scanning ticket titles for testing-related prefixes like "debug," "test," or "benchmark," (2) scanning for short descriptions, (3) scanning for suspicious phrases like "sample ticket," and (4) scanning for batches of tickets that were created all at once. It uses these signals to compute a confidence score for each ticket that determines whether it should be deleted. This command was deprecated and replaced by `doctor_pollution.go`, which reimplements large parts of `detect_pollution.go` and is not, at a glance, substantially different. Two seconds of thought will tell you that this feature is unnecessary, since you can create tickets with a "#test" tag and then delete them by tag.

I don't want to come across as mean, but Steve should be embarrassed by this. It's grotesquely baroque and completely unmaintainable—proof positive that whatever he's doing isn't working.

bccdee··on SparkFun Officially Dropping AdaFruit due to CoC Violation
Suing is way more expensive and way more hostile than simply airing your grievances in public. And why not? That's what public forums are for. "Do nothing" and "sue them" are not your only options; we are part of a society, not merely party to a legal system.
bccdee··on SparkFun Officially Dropping AdaFruit due to CoC Violation
> You don't tell or demand another company do something with their own employees.

Depends what they did. Let's check the forum post…

> they had created and promoted hate sites, photoshopped images, and harassment targeting limor, me, and others at adafruit. this was done on company time, shared, promoted.

Ok yeah if a company is sharing bigoted photoshops of my likeness, yes, I'm gonna demand that they discipline the responsible employees. Obviously.

I don't have any reference point for what "hate sites, photoshopped images, and harassment [meriting HR training]" constitute, but if it's anywhere near as bad as it sounds, sure, escalate the situation and air your dirty laundry in public. This is unacceptable behaviour, and apparently it went on for years. When you're being publicly harassed, you have no duty to indefinitely restrain your response to private, polite emails.

bccdee··on ASCII-Driven Development
Trouble with this is, it's pretty much LLM-only. I don't want to type out a request for Clause to draw a box for me and describe where, and I don't want to be pasting box-drawing characters. I want to click & drag. This is just boxes, arrows, and labels, which are all WAY faster to make by hand.
bccdee··on An Honest Review of Go (2025)
Yeah, your error handling should not always be backwards/forwards compatible. If the new version of a library is throwing a new kind of error, that library's clients need to be updated. Being able to break your clients when necessary is a feature & not a bug. Of course, if you don't need to break your clients, simply don't change your ErrorKind enum.

Backwards/forwards compatibility matters a lot across service boundaries. I'm not saying Protobuf needs to be exhaustively matchable. But native types from a static library? You want to be able to match those exhaustively.

bccdee··on Eat Real Food
I don't think you do eat starches in a higher volume. The associated fibre and resistant starches lead to many starchy foods being quite satiating—potatoes, oatmeal, and whole grains in particular. Moreover, the relevance of insulin response is unclear; low GI diets have been found to do nothing for obesity.

To reiterate, starches have been a dietary staple in pretty much every society forever, whereas meat has been relatively expensive and rare. The obesity epidemic, which began in the 1970s, did not correspond with the invention of flour or rice.

> I would use this metric: could you conceivably produce this in an average kitchen with the raw materials?

This is completely arbitrary. Why is "milling grains into a fine dust" proscribed, when blending soup in an average kitchen's food processor is fine? We've had mills for millennia and food processors less than a century. Plenty of raw foods smell unpalatable; that doesn't mean the cooked version is secretly smelly or whatever. Besides, what is and isn't a "raw ingredient" is itself arbitrary. I can't make any type of vegetable oil at home, deodorized or otherwise—I don't have an oil press. I can make lard, but that doesn't mean it's better for me.

You're talking about the vibes given by various foods here, rather than their actual health effects.

bccdee··on An Honest Review of Go (2025)
> There is good reason why even the languages that support defined error sets have trended back to using open-ended constructs.

Rust does it that way & has never trended in any other direction. The generally-accepted wisdom is "thiserror¹ for libraries, anyhow² for applications"—i.e., any error that's liable to be handled by a machine should be an enum of exhaustively-matchable error kinds, whereas anything that's just being propagated to the user should be stringified & decorated with context as it bubbles up.

Certainly, unified error types which wrap and erase more specific errors are sometimes desirable, but equally they often are not. Languages which support exhaustive matching support both, permitting us to choose based on context.

[1]: https://docs.rs/thiserror/latest/thiserror/

[2]: https://docs.rs/anyhow/latest/anyhow/

bccdee··on An Honest Review of Go (2025)
You're right, but I still think they have a point. What I miss from `error.Is/As` is exhaustive matching. I'd love a way to statically guarantee I haven't missed an important error type. It really comes back to the absence of sum types.
bccdee··on Eat Real Food
Assuming that "chemical modification" is when you modify something by adding a chemical reagent to it, milk is chemically modified to create cheese curds, sugars are chemically modified to create vinegar and alcohol, and breads & cakes are chemically modified when they rise.

However, this definition of chemical modification doesn't really include hydrogenated vegetable oil. Industrial hydrogenation is done by raising oil to very high temperatures in the presence of a nickel catalyst & then adding hydrogen. We modify it on a chemical level, but primarily by heating it, not by adding reactive substances. And if that counts as chemical modification, then so does cooking!

Anyway, no—people generally used "processed" to describe a particular vibe they get from certain foodstuffs whose production seems too industrialized. There's no rigorous basis for determining what is and isn't "processed" because people use it to describe their feelings about food, not any underlying property of food.

If you search a simple question like "is bread processed," you get a bunch of articles saying "well, since there's no agreed-upon definition for processing and the definitions we do have aren't particularly clear, there's really no answer to the question. But don't worry, because (given the overwhelming vagueness of the category), it's also impossible to say whether processed foods as a category have any health implications, so you shouldn't worry about it."

bccdee··on Eat Real Food
Yeah, (1) there is no "war on protein," (2) you do not need to eat very much protein unless you are trying to build muscle and you already work out a lot.

The normal recommended daily intake for protein is 0.8 g/kg. 1.2-1.6 is silly; that's a recommendation for athletes.¹

Starches have been a dietary staple in pretty much every society forever. Sugars have not. It's silly that they treat grains as a "sometimes" food.

There's also the weird boogeyman of "processed food." Almost all food is processed to some degree & always has been. We've been cooking, baking, juicing, fermenting, chopping, grinding, mashing, etc. long enough that it influenced the shape of our teeth. Certainly we haven't been making Pizza Pockets that long, but the issue there isn't processing, it's ingredients. And the reason people buy Pizza Pockets isn't that they think they're healthy—it's that Pizza Pockets only need to be microwaved, and cooking a real meal takes time that a lot of people just don't have.

[1]: https://www.strongerbyscience.com/athlete-protein-intake/

bccdee··on There were BGP anomalies during the Venezuela blackout
There's a fair argument to make that, by that standard, a civilized civilization has never existed. Atrocity has ever been our giddy companion.
bccdee··on Stop Forwarding Errors, Start Designing Them
That worst case shouldn't happen. When you do something like this:

  let data = serialize(&doc)
      .or_raise(|| StorageError::permanent("serialization failed"))?;
you're burying the error thrown by `serialize(&doc)` in such a way that you have to dig for it dynamically to recover it.

The closure in `or_raise` should take the error from `serialize(&doc)` as an argument and save the actionable details. It makes sense to have an "outermost error" when you're talking about a context trace that provides information to humans, but an error which a machine responds to should be flat & statically matchable.

Something like this:

  let data = serialize(&doc)
      .or_raise(|e| Exn::with_message(
          "serialization failed",
          StorageError::fromInner(e)))?;
where `StorageError::fromInner` decides whether the error should be permanent or not based on the contents of `e`, and saves any details that would be relevant for automatically recovering from the error.
bccdee··on All AI Videos Are Harmful (2025)
> AI will become good enough to write songs better than humans; it's a matter of time.

I'm unconvinced. The process of songwriting is so dependent on being able to listen to what you've made and decide whether you enjoy it or not. We can train a model to imitate popular music, but we can't train a model to enjoy music, because we can't quantify enjoyment and turn it into a data set. You can train an LLM on soup recipes, but you can't train one to taste the soup and tell you whether it's good or not.

That's part of what offends me so much about the notion of AI-assisted "creativity." Creating music should be a way of engaging more deeply with music, but you've discovered a way to pay even less attention to music than before. None of the details in an AI-generated song really matter; they were chosen arbitrarily, because they seemed normal. Indeed, they are so normal that your ear will slide right off of them.

You can't fix a soup that someone else prepared—not as an amateur. You don't know what went into it, so you can't pick out the individual flavours and decide whether they're right or not. They were never "right" for you, because you didn't pick them. It's like ordering a Big Mac, taking it apart, and trying to workshop it into Duck à l'Orange. All you get is a Big Mac with some orange slices on it. Maybe you like Big Macs; maybe you're happy. It's a pretty poor substitute for creativity, though.

bccdee··on Decorative Cryptography
Okay, nothing is secure against every threat model. The only way to secure against rubber hose cryptanalysis is by hiring a team of bodyguards, and even that won't protect you from LEOs or nation-state actors. Your threat model should be broad enough to provide some safety, but it also needs to be narrow enough that you can do something about it. At a software level, there's only so much you can do to deal with hardware integrity problems. The rest, you delegate to the security team at your data centre.

> "This system is unhackable, if the user doesn't do the thing that hacks it" is not very useful.

It's the best you're gonna get, bud. Nothing's "unhackable"—you just gotta make "the thing that hacks it" hard to do.

bccdee··on Stop Forwarding Errors, Start Designing Them
I'm not sure I like how they're trying to dynamically cast to an error type.

  Err(report) => {
      // For machines: find and handle the structured error
      if let Some(err) = find_error::<StorageError>(&report) {
          if err.status == ErrorStatus::Temporary {
               return queue_for_retry(report);
          }
          return Err(map_to_http_status(err.kind));
      }
They get it right elsewhere when they describe errors for machines as being "flat and actionable." `StorageError` is that, but the outer `Err(report)` is not. You shouldn't be guessing which types of error you might run into; you should be exhaustively enumerating them.

I'd rather have something like this:

  struct Exn<T> {
      trace: Trace,
      err: T,
  }
  
  impl<T> Exn<T> {
      #[track_caller]
      fn wrap<U: From<T>>(self, msg: String) -> Exn<U> {
          Exn {
              trace: self.trace.add_context(Location::caller(), msg),
              err: self.err.into(),
          }
      }
  }
That way your `err` field is always a structured error, but you still get a context trace. With a bit more tweaking, you can make the trace tree-shaped rather than linear, too, if you want.

I think actionable error types need to be exhaustively matchable, at least for any Rust error that you expect a machine to be handling. Details a human is interested in can be preserved at each layer by the trace, while details the machine cares about will be pruned and reinterpreted at every layer, so the machine-readable info is kept flat, relevant, and matchable.

bccdee··on The suck is why we're here
Amazon self-publishing is full of spam, obviously, but nobody buys those. They're the book equivalent of a broken 3-star GitHub repo with 2 commits. If that was all that coding agents were used for, I'd call those a failed use case too.
bccdee··on The suck is why we're here
Nah, that misunderstands writing pretty fundamentally. Language is how we express ideas. An LLM that could write as well as a person would need to be able to think as well as a person, and they just don't. That's why nobody's publishing LLM books, and why the only LLM articles are SEO slop and advertorials.

We've had LLMs for years. Image models and coding agents have gotten remarkably good, and their output is all over the place. So where is the AI writing? Outside of automated summaries, formulaic essays, and overly verbose LinkedIn posts, nowhere.

bccdee··on Fighting Fire with Fire: Scalable Oral Exams
Obviously the session should be recorded & transcribed. If you take issue with your mark, you can escalate it to the professor, same as you would for a written exam.

If you're looking for suggestions, I'd love for you to start with a problem that isn't trivially fixable.

bccdee··on Fighting Fire with Fire: Scalable Oral Exams
Oral exams scale fine. A TA makes $25 per hour, and an oral exam is going to take an hour at most. I absolutely would not accept a $25 tuition rebate in exchange for having my exam administered by an LLM.
bccdee··on Fighting Fire with Fire: Scalable Oral Exams
Oh my god, this sounds awful. After the first few paragraphs, I was ready to be impressed, but then they started dropping all these insane details:

---

> Only 13% preferred the AI oral format. 57% wanted traditional written exams. 83% found it more stressful.

> Here is an email from a student: "Just got done with my oral exam. [...] I honestly didn't feel comfortable with it at all. The voice you picked was so condescending that it actually dropped my confidence. [...] I don't know why but the agent was shouting at me."

> Student: "Can you repeat the question?" Agent: paraphrases the question in a subtly different way.

> Students would pause to think, and the agent would jump in with follow-up probes or worse: interpret the silence as confusion and move on.

---

Based on these highlights, you'd think the experiment was a wash. The author disagrees!

> But here's the thing: 70% agreed it tested their actual understanding: the highest-rated item.

Man, you could shoot me with a gun, then make me write an essay, & I'd be forced to agree that you had tested my "actual understanding." That doesn't mean my performance wouldn't suffer. Also, 70% is not very high. That's barely two thirds.

Even the grading was done by LLMs (rather than having a TA grade a transcript, and the results were lower. The author defends this by saying, "Students will be evaluated outside the university, and the world is not known for grade inflation," but the world isn't "known for grade inflation" because it doesn't grade you at all. That's not even an excuse, it's just nonsense. It'll toughen you up, or whatever. Was this post written by an LLM too?

> Take-home exams are dead. Reverting to pen-and-paper exams in the classroom feels like a regression.

"Regression"? I mostly wrote pen & paper exams, and I only graduated a few years ago. If students want more flexibility, team up with other courses to supervise multiple exam sessions. Leaked questions aren't going to be any more of a problem than it was for take-home exams, especially since they can't take the booklets with them when they go.

It sounds like these students had a terrible time, and for what? Written exams work fine. These guys just wanted to play with LLMs.

bccdee··on Was It a Billion Dollar Mistake?
> Rust by default doesn't really encourage the group-element mindset and people have to work against the language in order to do it.

It's true that you don't see stuff like arena allocators very often, but Rust's lifetime/ownership semantics mean it's often best for related pieces of data to be owned by a single parent, which unifies their lifetimes such that they all start and stop being valid at the same time. I suppose that's only part of the equation, since an arena has you allocate everything at once in a continuous block, which has performance advantages.

Still, I can't help but see an underlying conceptual symmetry there. I feel like the scoped arena school of memory management would be nicely complemented by compiler lifetime analysis. Arenas definitely simplify lifetime issues, but that feels like an opportunity to experiment with additional static compiler checks, not a reason to eschew them. I'd love to see a language which required explicit deallocation but enforced it with linear types.

bccdee··on Was It a Billion Dollar Mistake?
> If you want to make pointers not have a nil state by default, this requires one of two possibilities: requiring the programmer to test every pointer on use, or assume pointers cannot be nil. The former is really annoying, and the latter requires [...] explicit initialization of every value everywhere.

I used to be an "initialize everything" partisan, but meaningful zero-values have grown on me. I still don't think everything should be zero-initialized, though (that is, valid when initialized to zero). I'd prefer it if there were a two-colour system where a type is only zero-initialized if it don't contain a pointer, either directly or transiently.

The trick would be that zero-initialized sum types have a default variant, and only that variant needs to be zero-initialized. So a type that requires explicit initialization can be made zero-initialized by wrapping it with Optional<T>, whose default value is the zero-initialized None value. So even though you end up with coloured data types, they're easily contained & they do not spread virally.

I think this offers the best of both worlds. It gives you explicit nullability while still permitting something like `make([]SomeStruct, n)` to return a big block of zeroes.

> Languages like Rust were designed from day zero around explicit individual-element based initialization

> Ownership is a constant concern and mental overhead when thinking in an individual-element mindset. Whilst ownership is obvious (and usually trivial) in the vast majority (99+%) of cases when you are in the grouped-element mindset.

I think there's a sort of convergence here, because one of the tips that's often recommended for dealing with lifetimes issues in Rust (e.g. cyclic data structures) is grouping elements together in a parent structure and referring to them with handles instead of references. Often this is done with integer handles in a simple Vec<T>, but libraries like slotmap exist which make that pattern safer & more ergonomic, if desired. The language features of Rust naturally encourage grouped-element thinking by making individual-element strategies high friction. In fact, I wouldn't be surprised if a reliance on individual-element strategies were part of the reason so many people struggle with Rust when they're first learning the language.

Ownership and lifetimes are a source of essential complexity in programming. I don't see any contradiction between managing them with a grouped-element mindset and managing them with language features; in fact, I think the two go hand-in-hand quite nicely.

bccdee··on The compiler is your best friend
It's much better to have a `panic!("this should never happen")` statement than to let your program get into an inconsistent state and then keep going. Ideally, you can use your type system to make inconsistent states impossible, but type systems can only express so much. Even Haskell can't enforce typeclass laws at the compiler level.

A program that never asserts its invariants is much more likely to be a program that breaks those invariants than a program that probably doesn't.

bccdee··on The Second Great Error Model Convergence
> There really was a strong consensus about exceptions, and then an agreement that checked exceptions are a failure, and now, suddenly, we are back to “checked exceptions” with a twist, in the form of “errors are values” philosophy.

I think the key difference between error values and checked exceptions is that functions in an error-value language tend to fully own their error types. With checked exceptions, if foo() calls bar() and then bar's implementation changes to add a new exception, suddenly foo is throwing stuff it didn't know about. With error values, foo is never simply throwing whatever bar throws, because foo has to explicitly transform bar's error value into its own error type if it wants to bubble it up.

It's a subtle change—perhaps a purely cultural change, even—but the difference is obvious if you picture a Java method assuming responsibility for its errors the way Rust functions do. Just imagine someone writing `try { bar(); } catch (BarError f) { raise FooError::fromBarError(f); }`. It's weird. It isn't done. But `bar().map_err(|e| FooError::fromBarError(e))?` is completely idiomatic in Rust.

It helps that defining a new type is low-ceremony in Rust, whereas it's high-ceremony in Java. I think that has a lot of benefits. When we model everything with types, it's important that types be cheap.

bccdee··on Professional software developers don't vibe, they control
So far what I've been doing is, I look for the parts that seem like they'd be rewarding to code and I do them myself with no input from the machine whatsoever. It's hard to really understand a codebase without spending time with the code, and when you're using a model, I think there's a risk of things changing more quickly than you can internalize them. Also, I worry I'll get too comfortable bossing chatbots around & I'll become reluctant to get my hands dirty and produce code directly. People talk about ruining their attention spans by spending all their time on TikTok until they can no longer read novels; I think it'd be a real mistake to let that happen to my professional skill set.
← PreviousPage 5 of 34Next →