Threads for FeepingCreature

    1. 7

      Maybe I’m just slow, but like…is the main point here that the Anthropic figured out that they could stack a couple of autoencoders on an LLM and get a better mapping of what’s going on inside? Like, one could not unfairly summarize this finding as “LLMs too are susceptible to dimension reduction and remapping”?

      ~

      Some scattered thoughts:

      I feel like the geometric visualization of connectivity is…misleading? At least the way the author seems to have internalized it? It seems like more of an issue of clustering/topology of graphs than a geometric thing.

      I don’t particularly care for some of the sloppy anthropomorphization used; statements like “When it reaches digons (one neuron per two concepts) it stops for a while (to repackage everything this way?)” come with a lot of baggage around assumed reasoning or state, when it’s unclear what is happening is anything other than, say, annealing.

      The game of telephone here, especially going through an author not known for their CS/ML/mathematical chops–“Damnit Jim, I’m a psychiatrist, not a engineer”–is a big issue. Seeing things like “The Anthropic interpretability team describes this as simulating a more powerful AI” could mean pure marketing bullshit, it could mean something more akin to Turing machines, it could mean something about dimension reduction in models. To find out which, I imagine I’ll have to dust off the source paper and try to grok it.

      If this is a fruitful direction of research, it seems like it could add useful debugging tooling for LLMs. On a related note, I’m curious about what it would take to reverse the process and selectively stimulate features (ugh I hate speaking by analogy here but it is what it is).

      1. 1

        The interesting thing to me (if I understand it right) is that the network isn’t using these features to do computational work, it’s literally just reusing neurons that have a low chance to be firing in a given context in order to give them additional contextual meaning by combining them in a limited set of structures. If there’s a “schema” to this, that opens the door to structural analysis- instead of training another network on top, you should be able to unpack the network’s representation by just looking at connectivity, maybe even local connectivity.

    2. 2

      Previously, on Lobsters, nobody had anything to say about these concepts. I am disappointed that we are now engaging with a nearly-content-free summary provided by a racist neoreactionary. (Receipts are here, particularly these emails.) If we want to discuss the machine-learning papers, then we should prefer commentary from people who understand the underlying maths and who are not constantly trying to nudge the reader towards their hateful beliefs.

      1. 6

        we should prefer commentary from people who understand the underlying maths

        I strongly agree with this part. I also have a few problems with some of Scott’s other views, but I think that’s unimportant in this context. The main thing is that we would be reading stuff by people who know what they’re talking about.

      2. 8

        Yes, he follows the classic racist neo-reactionary hate-spreading strategy of writing a long and detailed argument against neo-reactionary beliefs a decade ago and not writing much of anything about race that I recall. Are you worried that people who read this article about machine learning are going to become neo-reactionaries by contamination?

        This post is being upvoted because it’s a well explained summary of a complicated topic. If you know of another introduction that’s as accessible from someone who understands the underlying math better, please share it!

        1. 4

          Yes, he follows the classic racist neo-reactionary hate-spreading strategy of writing a long and detailed argument against neo-reactionary beliefs a decade ago and not writing much of anything about race that I recall.

          Well, the leaked email in which he defends reactionaries and “human bio diversity” dates to several months after that. And I think that to this day he’s a fan of Charles Murray, which on its own I consider enough of a red flag to avoid anything he writes.

      3. 4

        Previously, on Lobsters, nobody had anything to say about these concepts. I am disappointed that we are now engaging with a nearly-content-free summary

        Shows the power of a good summary?

        1. 3

          I’d say it shows the power of a site that it popular on HN. The submission https://news.ycombinator.com/item?id=38438261 reached #2 on the frontpage on 2023-11-27.

          1. 5

            I’m just saying it’s not surprising that a well written pop article gets more engagement than a paper that presupposes expert knowledge of the state of LLM research, HN or here.

      4. 5

        The best you can come up with is ten-year-old email in which the author explains why he chose not to plug his ears when neoreactionaries were talking? I’m not saying he’s racist or not – it’s always hard to tell with these bright young dudes who believe they can think their way past human problems with pure reason – but these “receipts” are weak as hell.

        Between the presupposition that he’s a racist and the description of this summary as “nearly-content-free”, if there’s an agenda to discuss here, it might be yours.

        1. 6

          This is not just undeniably racist, he also attempts to “hide his power level” like many racists do:

          1. HBD is probably partially correct or at least very non-provably not-correct. https://occidentalascent.wordpress.com/2012/06/10/the-facts-that-need-to-be-explained/

          […]

          (I will appreciate if you NEVER TELL ANYONE I SAID THIS, not even in confidence. And by “appreciate”, I mean that if you ever do, I’ll probably either leave the Internet forever or seek some sort of horrible revenge.)

          (To this day, I believe Scott has not followed through on his threat.)

          1. 5

            This is probably because I’m not running in the circles that use the acronym much but I still haven’t figured out what HBD expands to (if anyone wants to tell me, I would be extremely appreciative of you spending your time educating me, but please do it by DMs because I’m about to argue this whole thread is off-topic).

            I have an extremely broad view of what’s technical enough to be on-topic for lobste.rs as my posting history hopefully suggests, but I think this criticism is off-topic. It doesn’t even try to tie the criticism of the author to the content they posted - I’d have a very different opinion here if there were a cogent argument of how the article is misleading or wrong, explained by the author’s underlying biases (which, for all I know, are pretty heinous, I just am not qualified to interpret these particular receipts). That would make those biases clearly relevant. As is, we’re being asked to discredit the post solely based on who wrote it, and the only evidence that the person is inherently untrustworthy is a non-technical argument that I think only makes sense to someone who already agrees with it.

            With regard to the posts - the new paper appears to be going one step further by demonstrating that you can disentangle the representations discussed in the old paper by using autoencoders, which is pretty neat and doesn’t seem inevitable, so it seems like a new development worth a second look. I agree that this particular writeup isn’t all that helpful - the whole business specifically hinges on one sentence “On the far left of the graph, the data is dense; you need to think about every feature at the same time.” and I genuinely cannot figure out what in technical terms is meant by this. I think they need to unpack those ideas in order to provide something helpful, but I’m not sure if they understand either. The writeup seems fairly fond of handwavey explanations, and I’ll want to get back to the original Anthropic paper to have any hope of untangling it.

      5. 3

        I’ve been reading Astral Codex for several years and have seen no evidence he’s a ”racist neoreactionary”. He’s got a very comprehensive essay (linked in another comment here) tearing down the alt-right ideology, and he describes himself as basically progressive. He’s not fond of the more leftist “woke”/“sjw” ideologies, but I’ve got problems with those too, and I think it’s fair to debate these things.

        If he wrote something reprehensible ten years ago in private email, and has since recanted and abandoned ties with that belief system, I’m willing to forgive. People do change their ideologies over time.

        I found the article informative. I’m not at all an expert on neural networks, and I’m sure if you are you would find it simplistic, but the original “Toy Models” paper you linked isn’t IMO approachable by non-experts. I read the first few pages and there was way too much jargon and assumed shared knowledge for me to get anything from it.

        I see a lot of articles posted to lobsters written by people who are not experts on the topic and who sort of oversimplify to some degree — stuff like “hey, I wanted to learn how malloc works, so here’s my toy implementation”. Sometimes these annoy me and I’ll point out mistakes, but this kind of intro can be really useful for people learning about the topic.

    3. 32

      In programming, there is a technique that is almost the opposite of this, called “dependency injection.” It’s a way of worrying, up front, about how to split the modules in your program from other code. You aim to give the “future you” a vague “future benefit” of being able to swap out a module dependency later.

      When writing code, I don’t use dependency injection to convey a “vague future benefit” to my “future self,” rather I’m almost always helping current me make my code testable as an isolated module so that current me doesn’t need to worry about my APIs being built on top of shifting sands.

      1. 14

        To me, dependency injection is largely about the initialization problem with a complex object graph. Can you program without it? Sure. But it is a good and useful solution to a not uncommon problem. The author’s misunderstanding of what DI is undermines whatever useful point they are trying to make, and doesn’t give me a lot of interest in digging for it.

        1. 9

          I am of two minds here, split on how the DI works. When this is “automatic” that is not written out in your codebase somewhere, it’s the single most detrimental practice in code readability that I see in products I’ve worked on. The issue is that very few systems have perfect names and what you are led to believe is the object graph is never what it actually is. The defense against this argument is usually something along the lines of “get gud”, which is not very pragmatic in collaborative environments.

          However, when the graph is explicitly built and all the units uses DI you instead get a very compact overview of the system as well as excellent test ability, at the cost of writing maybe a few hundred lines of factory methods. If this is hard, your design is probably bad and should just “get gud”.

          1. 1

            This matches my experience with DI as well.

        2. 6

          In case it’s helpful, my original quick description for DI in this post was “a way to formally split up modules in your code and let you initialize and swap them at runtime.” I revised it to the perhaps snarkier current version only because the post is not at all about dependency injection, and I was describing some of the worst forms of DI (e.g. DI frameworks and “Inversion of Control,” mentioned in the footnotes) to draw a stronger contrast for “dependency rejection,” which is what the article is actually about. The article is also not (entirely) about programming.

          p.s. I’ve used DI plenty, too. In fact, I once worked on a project for 3 years where the bulk of code was running inside the Spring Framework. (Yes, shudder … This was years ago and Java/Spring were the rage where I worked.) So that colors my experience. I appreciate your feedback though and now regret, a little, not toning down the snark here, as commenting on modern lightweight uses of DI was not the point of this post.

          (Update: I edited the snark out of the submitted post a little bit by getting rid of the scare quotes, without changing the basic gist of the opening. I also explicitly mentioned “dependency injection frameworks” in the next para, since that was my real target.)

          1. 7

            I revised it to the perhaps snarkier current version only because the post is not at all about dependency injection, and I was describing some of the worst forms of DI (e.g. DI frameworks and “Inversion of Control,” mentioned in the footnotes) to draw a stronger contrast for “dependency rejection,” which is what the article is actually about. The article is also not (entirely) about programming.

            So I agreed with the article’s spirit, but was also confused by the snarky take on DI. Not because I haven’t seen bloated over-abstract versions of it, but because the good version of DI, as I see it, is precisely in line with what you are arguing for. One of its main benefits is forcing you to reckon explicitly with every dependency you take on, so you can’t lie to yourself about them, and this creates beneficial pressure to use fewer dependencies.

            1. 2

              I’m with you. It was simply a mistake in my writing in the original published draft to be snarky on that topic. I was thinking of the “overwrought DI frameworks” while the writing only referenced “dependency injection.” Thus the cleanup. 😁

          2. 2

            So the problem here is mainly rhetorical, by which I mean, the art of rhetoric, of persuasive writing. A technical metaphor for a non-technical issue will often get you mired in a technical discussion (like this) that misses the point. A non-technical metaphor for a technical point usually doesn’t have this problem. If you want to make a subtle point, you might want to turn down the bombast/clickbait. People, especially lobste.rs readers, will still read and enjoy your piece, but if you start from a strong black-white position you invite the “well, actually” crowd to come and undermine it.

            1. 1

              I agree. Lesson learned. In a lot of ways, it was an honest mistake – the opening had no snark at all in my original (private) draft, and, for who knows what reason, I added it in during my last round of edits. Sometimes your writer brain just doesn’t get it right. I appreciate the comment – and I revised the post, accordingly! I only regret inflicting this writing error on the lobste.rs community, y’all deserve better!

    4. 2

      …has itself created a reactionary movement in the so-called “effective accelerationists” who, like some AI equivalent of rolling coal, refuse to contemplate any negative ramifications of technological development whatsoever.

      The author has the EA acronym wrong and apparently misunderstands its core ideology, and so I’m not sure this is a good source for deconstructing that ideology. In particular “effective altruists” and “longtermists” are precisely preoccupied with a particular endgame result of the negative consequences of technological development, such that they seek to prevent it without considering the short term harms.

      1. 6

        Effective accelerationism is its own thing, distinct from effective altruism.

        1. 1

          It’s a difference of degree, not kind.

          1. 5

            They are totally different things – effective accelerationism is a play on effective altruism, but otherwise has nothing to do with it.

            1. 2

              Potato, pohtahto - Molly White offers a counterpoint here: https://newsletter.mollywhite.net/p/effective-obfuscation

              Edit we’re dealing with the slipperiness of language here. Your use of “AI safety” in the linked submission aligns with mine - the unbridled use of automated systems to enable negative outcomes in the near future.

              For Effective Altruists (i.e. the subculture centered around figures like Elezier Yudkowsky and the LessWrong forum), “AI Safety” revolves around the idea of AI (or rather AGI (artificial general intelligence))[1] becoming an “existential risk” to humanity.[2] Effective accelerationism are ok with this, because the AGI will be pro-human (or rather, pro-anarchocapitalism) by default.

              For the OG effective altruists, it’s imperative to rebrand the kooky ultra-utilitarianists as something else. TESCREAL is the term adopted by their opponents.


              [1] the latest jargon involves ASI = artificial superintelligence.

              [2] Note that climate change or even global thermonuclear war are not existential risks, because there will always be a possibility of a nucleus of the human race surviving.

              1. 4

                Sure, but just to clarify: my piece didn’t mention effective altruism at all – and only mentioned effective acceleration to insult it…

                1. 1

                  I acknowledge that.

              2. 2

                For the OG effective altruists, it’s imperative to rebrand the kooky ultra-utilitarianists as something else.

                But… they are something else? Sure it’s useful for EA that e/acc is something else, but it just also happens to be objectively true that it’s something else. I think you’re going too far with the cui bono here; not everything that helps group X is a ploy invented by group X. Sometimes people are just right about things.

          2. 2

            No, they’re approximately opposites, with respect to AI

            1. 2

              This is almost exactly like discussing the fundamental differences between, say, the Roman Catholic church and Southern Baptism.[1] If you’re an adherent, or even just a scholar of religion, the differences are stark. If you’re a non-believer they’re both Christian denominations, and their fundamental common tenets (that there was a historical figure called Jesus Christ whose death somehow wiped clean the sins of humankind) is an irrelevancy.

              Both EA (the LessWrong variant) and e/acc believe the Robot God is inevitable. The remaining question is how to deal with it.


              [1] a better comparison would be between the early church and those it considered “heretics”, like the adherents of Arianism, because they were closer to the “source” of Christianity.

              1. 1

                Not every EA and not every e/acc actually believe that. I actually have no idea what the true fraction is.

                1. 1

                  Of course not, because the people who spend a lot of time on the forums and Twitter and produce the most ridiculous content are probably not numerically or even ideologically representative. That feeds into your response to my other comment in this thread - do you, if you truly believe in effective altruism, want these kooks to represent your “brand”?

                  Look, I don’t have a horse in this race. I find pointing and laughing at AI doomers funny, but at the same time it’s scary that a rich nutjob like El*n has so much potential to fuck stuff up. The more people are informed that a significant number of our current tech overlords believe that the Robot God must be born as a fast as possible, the better.

    5. 7

      What does it mean when we break the principle?

      Java has an immutable List<T> that extends from List<T>, but throws an exception when you try to call add (or any other method that would mutate the list). Does this mean that the immutable list isn’t a subtype of List, it just happens to inherit from List?

      1. 6

        Yes, exactly.

        Though arguably, Java’s method contract implicitly includes “Also, the method may fail for any reason at any time.”

    6. 5

      (Disclaimer: I will purposefully conflate subtyping and inheritance, because this is what most programmers are used to.)

      The Liskov’s substitution principle isn’t something you can choose to violate. It holds whether you want it or not, because it’s a tautology, and an obvious one at that:

      Let S be a subtype of T. If P(x) is a property that holds whenever x is a value of type T, then P(x) also holds whenever x is a value of type S.

      So what’s the problem? When the LSP seems not to hold, what’s actually happening is that your types don’t mean what you think they mean. For example, if a class X has an overridable method foo(), then whatever happens in the internal implementation of X.foo() is NOT the definitive semantics of X.foo(). Instead, the correct meaning is that anything could happen at all.

      You can make the meaning of X.foo() more precise by controlling who’s allowed to inherit from X. For example, if X belongs to a package P and your language allows you to prevent classes outside of P from inheriting from X, then, from the point of view of a user of P, the meaning of X.foo() is completely determined by the set of classes in P that do, in fact, inherit from X.

      But, since the set of classes in P that inherit from X is completely known to P‘s author, wouldn’t it be simpler and better to turn X into a sum type, and turn X’s subclasses into the constructors of this sum?

      1. 8

        Conflating subtyping and inheritance is a mistake here, because the point of Liskov’s paper was to define when inheritance is subtyping. If Y inherits from X and Y.foo() breaks any of the LSP criteria, then Y is not a subtype of X. LSP as a principle, then, is “make sure all your inherited classes are also subtypes.”

        1. 1

          Suppose you have a class Stack with non-final methods push() and pop(), whose implementation does what the names suggest. It might be tempting to prove that the following assertion never fails:

          Stack stack = new Stack();
          stack.push(2);
          assert( stack.pop() == 2 );
          

          However, if you program logic lets you do this, then it’s unsound. The problem is precisely that I can define another class FunnyStack that inherits from Stack and overrides push() and pop() to do something else. The correct thing to say is that the type Stack satisfies no nontrivial invariants concerning the behavior of push() and pop().

          1. 6

            I think you’re missing the point: subclassing and subtyping are not the same thing, even if you want to conflate them. Subclassing is one means by which to subtype. Standard ML’s functors are another. Defining different .c files to back the same .h file and linking them into different programs is another. Subtyping is about the mathematical intention of what we’re doing.

            1. 1

              What exactly do you mean by “mathematical intention”? The compiler doesn’t care about your intention.

              1. 3

                The compiler has nothing to do with this. When we write a program, we have an intention about what its correct behavior should be. If we produce a subclass that does satisfy Liskov’s principle, that may be a conscious choice on our part or a mistake. The compiler has no way of knowing. The difference is in what we were trying to do.

                1. 1

                  If we produce a subclass that does satisfy Liskov’s principle

                  “doesn’t”, perhaps?

          2. 1

            Or alternatively, FunnyStack is not a valid subtype of Stack? Not everything that your language lets you define is valid in any particular sense, after all!

            1. 1

              Well, my point of view is that the language’s semantics is the ultimate arbiter of the meaning of your code. So you should either (a) use the language correctly to express exactly what you mean or, if that’s impossible, then (b) switch to a language that lets you express what you mean.

              FunnyStack isn’t a valid subtype of Stack because I didn’t want it to be” is the programming equivalent of “I’m not guilty because I didn’t want to commit a crime”.

              1. 2

                To me it seems more like the equivalent of add(x, y) = x * y. Just because you can write it doesn’t make it correct!

                1. 1

                  A program is neither correct nor incorrect in isolation. That depends on the specification!

                  1. 2

                    Well, I agree. And the Liskov substitution principle is a (partial) specification, and a program can violate that partial specification or not.

      2. 5

        Instead, the correct meaning is that anything could happen at all.

        But since this is not useful at all to define things like this then we need some guiding principles about how to properly write a substitutions…

        But, since the set of classes in P that inherit from X is completely known to P‘s author

        It’s definitely not in the general case. Example: any kind of plug-in system where you inherit from a base class provided by the plug-in API - hell, it could be written in a way that allows the plugin to be written in a completely different PL than the host software.

      3. 4

        Instead, the correct meaning is that anything could happen at all.

        Not quite: the correct meaning is that anything could happen… that consumes no parameters and returns no values. Subclasses are required to conform to the parameter contract of the method, and that’s all in guarantees (interface contracts aside) that typesystems give you anyways.

        But, since the set of classes in P that inherit from X is completely known to P‘s author

        Also, libraries exist.

    7. 28

      My philosophy is to write these scripts in bash, because it’s such a horrible language that the team is forced to rewrite it soon after it starts bearing load and becoming important. It’s hard to tell what will actually end up being important, so if you’re judicious with your time, this is a decent way to optimize. The catch, though, is I’m starting to get too good at bash and I’m letting these scripts live too long now.

      1. 5

        I take the same approach.

        If you’re getting too good at bash it’s time to switch to a different first-line-of-defense tool.

        Pick a non-bourne shell like rc, ksh or csh and start using that for scripting.

        Use awk or expect or something similarly powerful and ubiquitous yet arcane.

        Maybe write simple automation in postscript and run it through ghostscript? Did you ever really learn perl? No? Great - use a bit of that!

        Are you a vim user? Use elisp! Emacs user? Use vim scripts!

        Ultimately, whatever you pick you’ll end up getting good at using it, but there are so many options and people keep adding new ones.

      2. 5

        set -euxo pipefail

        I will say, the good thing about bash is that it never ever breaks backward compatibility. Those scripts will still be in production in ten years? Yes, well, they’ll still work in ten years.

        1. 7

          … until some bonehead decides egrep is no longer a thing

          1. 2

            Or tries to run it on MacOS and --perl-regexp doesn’t exist.

        2. 5

          I will say, the good thing about bash is that it never ever breaks backward compatibility.

          I’m not sure if this is supposed to be sarcastic or not, but they absolutely break backward compatibility.

          As an example, the bash 5.2 upgrade was delayed for many months in Arch because it has changes to the shell option handling which broke a bunch of scripts.

          1. 1

            Huh! Okay, guess I didn’t run into any of those issues. That’s disappointing to learn.

      3. 2

        Aye we had this issue at Neo4j cloud.. bash with shellcheck and tests let you go, some would say, too far. You can still get that thousand-yard-stare out of the DBaaS devs if you ask them to recall “the bash script”..

        I think most of that’s rewritten in Go now though, but the bash-heavy approach let us get a lot of stuff off the ground and iterate a bit before bolting stuff down.

    8. 32

      after programming Go for a long time I write all my code in Rust now, and now that I have Result types and the ? operator, my code is consistently shorter and nicer to look at and harder to debug when things go wrong. I dunno. I get that people think that typing fewer characters matters. I just don’t think that. When I program Rust I miss fmt.Errorf and errors.Is quite a lot. Yeah, sure, you can use anyhow and thiserror but still, it’s not the same. You start out with some functionality in an app and so you use anyhow and anyhow::Context liberally and do some nice error message wrapping, but inspecting a value to see if it matches some condition is annoying. Now you keep going and your project matures and you put that code into a crate. Whoops, anyhow inside of a crate is rude, so you switch the error handling strategy up to thiserror to make it more easily consumed by other people. Now you’ve got to redo a bunch of error propagating things maybe you’ve got some boilerplate enum stuff to write, whatever; you can’t just move the functionality into a library and call it a day.

      Take a random error in Rust and ask “does any error in this sequence match some condition” and it’s a lot more work than doing the equivalent in Go. There are things that make me happy to be programming Rust now, but the degree to which people dislike Go’s error system has always seemed very odd to me, I’ve always really liked it. Invariably, people who complain about Go’s error system consistently sprinkle this everywhere:

      if err != nil {
          return nil, err
      }
      

      Which is … bad. This form:

      if err != nil {
          return nil, fmt.Errorf("some extra info: %w", err)
      }
      

      goes a long way, and errors.Is and errors.As compose with it so nicely that I don’t think I’ve seen an error system that I like as much as Go’s. Sure, I’ve seen lots of error systems that have fewer characters and take less ceremony, I just don’t think that’s particularly important. And I’m sure someone is going to come along and tell me I’m doing it wrong or I’m stupid or whatever, and I just don’t care any more. I quite like the ergonomics of error handling in Go and miss them when I’m using other languages.

      1. 10

        Isn’t wrapping the errors with contextual information at each function that returns them just equivalent to manually constructing a traceback?

        The problem of errors in rust and Haskell lacking information comes from syntax that makes it easy to return them without adding that contextual information.

        Seems to me all three chafe from not having (or preferring) exceptions.

        1. 17

          Isn’t wrapping the errors with contextual information at each function that returns them just equivalent to manually constructing a traceback?

          no, because a traceback is a map of how to navigate the code, and it requires having the code to be able to interpret it, whereas an error chain is constructed from the vantage point that the error chain alone should contain all of the information needed to understand the fault. An error chain can be understood by an operator who lacks access to the source code or lacks familiarity with the language the program is written in; you can’t say the same thing about a stack trace.

          The problem of errors in rust and Haskell lacking information comes from syntax that makes it easy to return them without adding that contextual information.

          not sure about Haskell but Rust doesn’t really have an error type, it has Result. Result is not an error type, it is an enum with an error variant, and the value in the error variant may be of any type. There’s the std::error::Error trait, but it’s not used directly; a Result<T, E> may be defined by some E that implements std::error::Error or not, but it’s inconsistent. It may or may not have some kind of ErrorKind situation like std::io does to help you classify different types of related errors, it might not.

          Go’s error type is only comparable to Rust’s Result type if you are confused about one language or the other. Go’s error type is less like Result and more like Vec<Box<dyn std::error::Error>>, because the error type in Go is an Interface and it defines a set of unwrap semantics that lets you unwrap an error to the error that caused it. (it’s actually more like a node in a tree of errors these days so even that is a simplification).

          Unlike a trait, a Go interface value is sized, it’s essentially a two-tuple with a pointer to the memory of the value and another pointer to a vtable. Every error in the Go standard library and third party ecosystem is always a thing with a fixed memory size and at least one layer of indirection. Go’s error system is more like if the entirety of Rust’s ecosystem used Result<T> to mean Result<T, Box<dyn std::error::Error>>. Since that’s not universal, even if you want to do it in your own projects, you run into friction when combining with projects that don’t follow that convention. Even that is an oversimplification, because downcasting a trait object in Rust is much more fiddly than type switching on an interface value in Go.

          There are a lot of subtle differences between the two systems that turn out to cause enormous differences in the quality of error handling across the ecosystems. Getting a whole team of developers to have clean error handling practices in Go is straightforward; you could explain the practices to an intermediate programmer in a single sitting and they’d basically never forget them and do it correctly forever. It’s significantly more challenging to get that level of consistency and quality with error propagation, reporting, and handling in Rust.

          1. 2

            a traceback is a map of how to navigate the code, and it requires having the code to be able to interpret it, whereas an error chain is constructed from the vantage point that the error chain alone should contain all of the information needed to understand the fault

            Extremely interesting. You’re both right: it is manually constructing (a projection of) a continuation, but the nifty point scraps makes is that the error chain is a domain-specific representation of the continuation while the traceback is implementation-specific. Super cool. This connects to a line of thinking rooted in [1] that I’ve been mulling over for ages and would love to follow up on.

            Here’s a “fantasy abstract” I wrote years ago on this topic:

            Felleisen et al[1] showed that an abstract algebraic treatment of evaluation contexts was possible, interesting and useful. However, algebras capturing implementation-level control structure are unsuitable for reflective use, precisely because they capture an implementation- rather than a specification-level description of the evaluation context. We introduce a language for arbitrary specification-level algebras describing high-level perspectives on program tasks, and a mechanism for ensuring that implementation-level control context refines specification-level control context. We then allow programs to not only capture and reinstate but to analyse and synthesise these structures at runtime. Tuning of the specification algebra allows precise control over the level of detail exposed in reflective access, and has practical benefits for orthogonal persistence, for code upgrade, for network mobility, and for program execution visualisation and debugging, as well as offering the promise of new approaches to program verification.

            [1] Felleisen, Matthias, et al. “Abstract Continuations: A Mathematical Semantics for Handling Full Functional Jumps.” ACM Conf. on LISP and Functional Programming, 1988, pp. 52–62.

        2. 1

          Or just give error types a protocol that propagation operators can call to append a call-stack level.

          1. 2

            That’s still only convenient syntax for manually constructing an error traceback, no?

            1. 1

              Sure, but it gives feature parity with exceptions when using the simple mechanism.

              1. 5

                creating a stack trace isn’t free. It’s fair to assume creating a stack trace will require a heap allocation. Performing a series of possibly unbounded heap allocations is a meaningful tax; whether that cost is affordable or not is highly context-dependent. This is one of the reasons why there is a whole category of people who use C++ that consciously avoid using C++ exceptions. One of the benefits of Rust’s system and the Result type is that the Result values themselves may be stack allocated. E.g., if you were inclined to do so, you could define a function that returns Result<(), u8>, and that’s just a single stack-allocated value, not an unbounded series of heap allocations. In Go, you could write a function that returns an error value, which is an interface, and that error value could be some type whose memory representation is a uint8. That would be either a single heap allocation or it would be stack-allocated; I’m not sure offhand, but regardless, it’s not an unbounded series of heap allocations. The performance implications are pretty far-reaching. Those costs are likely irrelevant in the case of something like an i/o bounded http server, but in something like a game engine or when doing embedded programming, they matter.

                The value proposition of stack traces is also different when you have many concurrently running stacks, as is typical in a Go program. Do you want just the current goroutine’s stack, or do you want the stacks of all running goroutines? goroutines are also not hierarchical in the scheduler, so “I want the stack of this goroutine and all of its parents” is not a viable option, because the scheduler doesn’t track which goroutines spawned which other goroutine (plus you could have goroutine A spawn goroutine B that spawns goroutine C and goroutine B is long gone and then C hits an error, which stacks do you want a trace of? B is gone, how are you gonna stack trace a stack that no longer exists?). If you want all the stacks, now you’re doing … what, pausing all goroutines every time you create an error value? That would be a very large tax.

                Overall, the original post is about the ergonomics of error handling; I think the ergonomics of Go’s system are indeed verbose, but my experience has been that the design of Go’s error handling system makes it reasonably straightforward to produce systems that are relatively easy to debug when things break, so generally speaking I think the tradeoff is worthwhile. Sure, you can just yeet errors up the chain, but Go doesn’t make that significantly more ergonomic than wrapping the error. I think where the original post misses the point by a wide margin is that it just yeets errors up the chain, which makes them hard to debug. Making the code shorter but the errors less-useful for debugging is not a worthwhile tradeoff, and is a misunderstanding of the design of Go’s error system.

                1. 1

                  creating a stack trace isn’t free

                  I think this is the point you may have missed further up asking about the analogy to exceptions and tracebacks. The usual argument I see in favor of something like Go’s error “handling” is that it avoids the allocations associated with exceptions and tracebacks. But then the standard advice for writing Go is that basically every level of the stack ought to be using error-wrapping tools that likely allocate anyway. So it’s a sort of false economy – in the end, anyone who’s programming Go the “right”/recommended way is paying a resource and performance price comparable to exceptions and tracebacks.

                2. 1

                  creating a stack trace isn’t free. It’s fair to assume creating a stack trace will require a heap allocation.

                  Not necessarily! This is pretty much the optimal case for per-thread freelists. Most errors are freed quickly and don’t overlap.

                  1. 2

                    Creating a stack trace means calling both runtime.Callers (to get the initial slice of program counter uintptrs) and runtime.CallersFrames (to transform those uintptrs to meaningful frame data). Those calls are not cheap, and (I’m not 100% confident but I’m pretty sure that) at least CallersFrames will allocate.

                    It’s definitely way way cheaper to fmt.Errorf.

                    1. 2

                      To create a stacktrace up from your current location, sure. But the unwind code you’re emitting knows what its actual location is, so as the error is handed off through each function, each site can just push its pc or even just a location string onto the backtrace list in the error value. And you can gather the other half of the stacktrace when you actually go to print it (if you even want it), which means until then it’s basically free: one compare, one load, two store or thereabouts. And hence, errors that aren’t logged don’t cost you anything.

              2. 2

                I see your point

      2. 3

        The thing that bit me recently was that Go uses the pattern of returning an error and a value so much that it is now encoded in tooling. One of the linters throws an error if you check that an error is not nil, but then return a valid result and no error. Unfortunately, there is nothing in the type system to say ‘I have handled this error’ and so this raised a false positive that can be addressed only with an annotation where you have code of the form ‘try this, if it raises an error then do this instead’. I don’t know how you’re supposed to do that in idiomatic Go.

        1. 5

          yeah but that sounds like a problem with the linter, not a problem with the language. That’s not reasonable linting logic at all.

      3. 1

        you can use anyhow and thiserror but still

        That whole part of the Rust error handling experience is such a mess. But then again nobody seems to think it’s an issue, really?

        1. 12

          basically every person I work with thinks it’s an issue, because we write production Rust code in a team setting and we’re on call for the stuff we write. A lot of the dialogue about Rust here and elsewhere on the web is driven by people that aren’t using Rust in a production/team setting, they’re using it primarily for side projects or solo projects or research or are looking at it from a PL theory standpoint; these problems don’t come up that much in those settings. And that’s not to dismiss those settings; I personally do my side projects in Rust instead of Go right now because I find writing Rust really fun and satisfying, and this problem doesn’t bother me much on my personal projects. Where it really comes up is when you’re on call and you get paged and you’re trying to understand the nature of an error in a system that you didn’t write, while it is on fire.

          1. 1

            Yeah okay but mean time people are writing dozens of blog posts about how async rust is broken (which really I don’t think it is) and this issue barely gets any attention.

      4. 1

        How exactly is thiserror and pattern matching on an error variant not strictly superior, if not, at worst, the exact same as fmt.Errorf and errors.Is? The whole point of thiserror is allowing you to write format strings for the error variants you return or are wrapping.

        1. 2

          errors.Is is not simply an equality check, it recursively unwraps the error, finding if any error in the chain (or tree, if an unwrap returns a slice) matches, so you can always safely add a layer of wrapping without affecting any errors.Is checks on that error. https://pkg.go.dev/errors#Is

          1. 1

            I wonder whether Rust’s not having this is due to Rust’s choice to have a more minimal standard library or simply to no one’s having proposed it yet. I think something like this would be very simple to implement in the Rust standard library (and only slightly more verbose to implement outside it).

          2. 1

            The tradeoff here is that it is breaking the abstraction of a function. For example changing underlying http library may change the error type. Your options are to break the error chain and the implementation-specific diagnostics or expose that to callers.

            Rust cares a lot about being explicit a out API breakage so it doesn’t surprise me that this isn’t a part of the standard library.

            It definitely is convenient for within your own code but across stable API boundaries it is a much worse tradeoff.

    9. 29

      This is the key thing that people talking about LLMs for coding miss altogether. The hard part is almost never typing the code out; it’s spending enough time thinking thru the system to know what code you need. It’s cool that you got a tool to help you type less but that’s not really going to solve anything but the most trivial of problems.

      1. 13

        I actually wonder if some of it is either a psychological illusion or just the “rubber duck” phenomenon

        If you’re forced to formulate a bunch of questions in words – that an LLM can plausibly answer – that actually gets you 75% of the way there

        Even if the answers are wrong, that can help you a bit. You break it down and structure it

        (Personally I’ve tried LLMs, but it hasn’t stuck. It’s not a net win for the things I’m doing. But I think this is part of what’s going on)

      2. 10

        One thing I have to remind myself of when I see people talking about using LLMs to write their code for them is that in an anonymous online discussion, there isn’t necessarily an easy way to tell whether you’re talking to a bootcamp dropout or a world-renowned expert. And the demographics of the industry are such that any random person in an online discussion is vastly more likely to be very junior than very senior.

        You look at these discussions and think they’re crazy: an LLM is next to useless for most of what you spend your time on. Me too. But if the person posting “ChatGPT writes all my code” is one of those juniors whose PRs are riddled with basic mistakes despite having been told exactly what to do, and whose code has to be rewritten by their more experienced coworkers half the time… well, maybe an LLM actually can do their job better than they can.

        1. 2

          I’ve found it very variable across domains. I tried using a couple of LLMs to generate some low-level systems code and it was riddled with security vulnerabilities (most obvious, a few subtle things that might sneak past code review), performance issues (quadratic algorithms where a slightly different data structure would make it linear), or just plain nonsense (move constructors that freed the thing that they took ownership of). I used it to try solving some problems with pgfplots and it sometimes gives a helpful answer but usually just sends me down a rabbit hole of exploring a feature that can’t actually solve the problem that I have.

          On the other hand, I’ve used them for some quick JavaScript for a demo and got code that was wrong but close enough that fixing it was much faster than writing it from scratch (for me. Probably slower than for someone who does browser JavaScript on a regular basis). I’ve used it to generate graphs in Python with plotly, and it’s been great.

          If the code you’re writing is in a domain where a lot of people have solved similar problems before, they work well. If your code requires some original problem solving or is lacking examples (try generating code for an LLVM back end, you’ll get plausible nonsense that cites the docs) then you will get something far less useful.

          A lot of commercial programming is solving variations of the same problem, over and over again. I’d be quite happy for this to all go away, they’re a waste of a human.

      3. 5

        Yeah, I agree – but that doesn’t mean our jobs are safe either. Part of the issue with LLMs for many fields is yeah, they can’t do this stuff, but it’s mighty good cover to fire experts, then hire them back as independent contractors at a lower rate for “touch up work” (see what happened to professional translation work, and what is happening to artists). Hopefully that’s stupid enough thing to destroy most companies that attempt it before it becomes a norm, but it’s something we need to watch out for.

        1. 3

          Who’s going to phrase the problem clearly/precisely enough to get an LLM to generate something that just needs “touching up”?

      4. 3

        I think the way people use LLMs effectively is as a sounding board for ideas. You say, What about A? The machine spits out some code for A. You say, but it still has bug X. The machine attempts to patch bug X and fails. You say, no, just do this. Now what about B? The machine spits out some code for B. Etc.

        1. 15

          But that’s even worse! Reviewing code written by someone with a tenuous grasp of how computers works is almost always harder than writing it yourself.

          1. 2

            It depends on your familiarity. I’ve found them most useful when I’m doing something in a language or ecosystem where I have little experience. I can get an LLM to generate something that looks plausible that I can then poke to see what changes do and get a feel for the environment. I’m not doing a detailed code review here: I don’t care if the code is good, I care that it shows speciifc things that I want to learn.

      5. 2

        I’ve been talking about this a bunch:

        https://simonwillison.net/2023/Sep/29/llms-podcast/#does-it-help-or-hurt-new-programmers

        I get two to five times productivity boost on the time that I spent typing code into a computer. That’s only 10% of what I do as a programmer, but that’s a really material improvement that I’m getting.

      6. 1

        LLMs reduce the trivial inconvenience of looking up APIs and writing boring code.

        But reducing trivial inconveniences is hugely valuable, way out of proportion with the actual time saved.

        Getting the LLM to test out an idea may only save you a few minutes, or it may also save you a mental context overflow.

    10. 4

      Swift lets you do something like:

      if let nullable1 = whatever, nullable1.shouldContinue == true, let nullable2 = nullable1.maybeReturn
      

      Which, lets you mix the checks with the if let.

      1. 1

        Yep, that comes down to the same thing, except having to name the variables. Which makes sense: it’s basically a compressed version of the if tree sample.

        I like not having to think of variable names though. :)

    11. 7

      Consider expanding what you can do with your condition (the thing that the if is predicated upon) instead of introducing arbitrary non-nested control flow. Otherwise, you might as well just name it GOTO instead of breakelse.

      Part of the problem with arbitrary non-nested control flow is that it is a PITA to deal with on the back end. As a fun exercise, try emitting WASM for your example.

      1. 5

        Part of the problem with arbitrary non-nested control flow is that it is a PITA to deal with on the back end. As a fun exercise, try emitting WASM for your example.

        That’s more of a reason to fix WASM than to change the construct. The reducible control flow requirement doesn’t actually buy you much over a requirement that everything must invoke a basic block from a list.

        1. 2

          I didn’t understand the design choice when they did it originally, but it seems to be a reasonable fit for a back end using SSA. It’s a bit of a pain converting traditional assembly to WASM, though.

          1. 3

            It’s quite painful for LLVM, which is SSA. Nothing in SSA requires reducible control flow, you can have arbitrary successors for a basic block. There is an algorithm to convert arbitrary control flow into the structure WASM requires but, as I recall, it’s O(n^2) in the number of basic blocks (code in languages designed for structured programming is unlikely to hit this case, but code generated by DSLs often can) and has a proportional increase in code size (which runs counter to the ‘load quickly’ goal of WASM). Worse, if you want to do any optimisation on the target, you do some fairly complex CSE to turn it back into the original form.

            1. 1

              I really don’t understand why WASM didn’t go with a SSA representation. To what extent is it just “well, the V8 backend doesn’t use SSA, sooooo :twiddles thumbs:”

              :edit: Hang on, TurboFan even is SSA. I could swear one of them was specifically not SSA. Then I’m confused again.

      2. 3

        WASM should work fine? It is a forward branch, but it should just translate to a nested if. Besides, I can always fall back to using a mutable bool variable for the condition, and only branching once. edit: Actually, shouldn’t block handle it?

        I have a SSA backend design anyways, so a lot of WASM isn’t gonna fit too well with what I’m emitting regardless. So I’d just throw a generic SSA-to-stack algo at it and not worry about the specifics of ?.

        Not sure what you mean by condition? if is a built-in primitive in the language.

        1. 3

          Not sure what you mean by condition? if is a built-in primitive in the language.

          At the AST level, I am referring to the likelihood that your grammar probably looks something like:

          IfStatement:
              if ( Condition ) Statement ElseStatement-opt
          
          ElseStatement:
              else Statement
          
          Condition:
              Expression
          

          For example, the solution that we use in Ecstasy is to allow a sequence of steps to occur within the condition (which we call a ConditionList), each of which produces a boolean, and only iff all are true does the “then” branch get taken. This allows us to declare a new variable at each step, assign it a value, and as long as doing so has the side effect of emitting a boolean, that can be used as part of the condition list:

          IfStatement
              "if" "(" ConditionList ")" StatementBlock ElseStatement-opt
          
          ElseStatement
              "else" IfStatement
              "else" StatementBlock
          
          ConditionList
              Condition
              ConditionList "," Condition
          
          Condition
              ConditionalAssignmentCondition
              "!" "(" ConditionalAssignmentCondition ")"
              Expression
          
          ConditionalAssignmentCondition
              OptionalDeclaration ConditionalAssignmentOp Expression
              "(" OptionalDeclarationList "," OptionalDeclaration ")" ConditionalAssignmentOp Expression
          
          ConditionalAssignmentOp
              ":="
              "?="
          

          Here’s an example from the crypto library:

          conditional Signer signerFor(Specifier specifier, CryptoKey key) {
              if (key.is(KeyPair),
                  Algorithm algorithm := findAlgorithm(specifier, Signing, key.privateKey, True)) {
                  return True, algorithm.allocate(key).as(Signer);
              }
          
              return False;
          }
          

          – edit –

          My suggestion to try emitting WASM had to do with the lack of an unconditional JMP rel-offset or JMP addr instruction.

          1. 2

            My suggestion to try emitting WASM had to do with the lack of an unconditional JMP rel-offset or JMP addr instruction.

            Yep, but any nontrivial forward jump has that issue, and that’s already all over the emitted code anyways. I’m not saving anything by avoiding it for this one feature.

            Re condition, you could do it like that and it kind of forms an intermediate form for the “if tree” pattern from the code, ie.

            if (auto var1 = op1; auto var2 = op2(var1); auto var3 = op3(var2)) {}, but then you’re still forced to form intermediate variables whose only purpose is to signal to if that it should do a truth-check. You cannot UFCS chain the operations on them, and those names serve little purpose. if (auto var3 = op1?.op2?.op3?) {} emphasizes the processing pipeline, not the steps in-between.

    12. 6

      Since in hindsight I chose a bit of an inexpressive title, tl;dr: breakelse is a new keyword that jumps directly to the else block of an if. With a bit of syntax sugar, this gives an alternative to conditional-access operators.

      1. 5

        You could have said so right away, not make me give up after a bunch of misdirections and tangents. I even scrolled down looking for what it actually is, saw the example, but wasn’t sure what exactly it did since I didn’t read all the text preceding it.

        I mean, I liked that you go in-depth with the reasoning, but it felt like the tangents on furries and step-by-step dives deeper into the problem before you’re fully explaining it are just there to keep me on the page longer.

        But it’s maybe just my preferences.

        Back on topic though.

        Anyway it’s neat that you do Neat. The breakelse keyword itself is also…neat, but I am not sure I like the semantics of it. Again it’s probably just my preference for simple things without a lot of magic and suspense, but going deeper into an if branch and then still possibly breaking off in a different direction… It just looks like overcomplicated algorithm to me. As @cpurdy says, I’d rather just use goto.

        And another thing which is probably not relevant, is compiler backend. If it can’t trust the if to discard a whole false branch, it’s gonna be tricky to optimise this code. It looks like it would be trivially easy to put a tricky bit like this on a hotpath.

        I do very little of lower-level programming these days and I’m probably out if the loop but this seems like an interesting thought experiment, but I don’t see it going much beyond that.

        1. 2

          Thanks for your feedback, I added a tl;dr section!

          Yeah, ? is a very “high-level” tool: as a language feature, it’s basically “get rid of anything from that sumtype on the left that isn’t the actual value”. It gets rid of error types by returning them, too. But at least it’s explicit about it - every time a ? appears in the code you can mentally draw flow arrows going to the closing } of the if block and the function. It’s the “clean this up for me, do whatever you think is right” operator.

          Performance-wise, note that conditional access has exactly the same problem but worse. With ?, you branch once - to the end of the if block. With ?. for conditional access, you have to keep branching every time you perform another chained operation on the value you’re accessing.

          I’m not sure what you mean with the “false branch” bit? The tag field of the sum type is an ordinary value and subject to constant propagation like anything else. The .case(:else: breakelse) branch will be removed if the compiler can prove that the sumtype will never be in :else.

    13. 9

      There’s a lot of text there and I didn’t get through all of it, but I’m stuck feeling that this is a violation of the maxim ‘never attribute to malice that which can be adequately explained by incompetence’.

      The FOA requests portray NIST talking to the NSA as some deep dark secret. The NSA is a dual-mission agency (which is a terrible idea, but that’s a separate discussion) and one of those missions is protecting US critical infrastructure and government / military communications. NIST would not be doing their jobs if they did not talk to NSA.

      The complexity analysis sounds wrong, but it’s not a case of confusing addition and multiplication it’s a case of confusing dependent and independent operations. If every compute step required 2^35 memory accesses, it would be correct. The fact that they’re independent operations means that this is not correct. That’s an easy mistake to make if you do the maths without properly looking at the context. Should it have been caught in review? Yes. Fortunately, it was caught in DJB’s review.

      It sounds more like someone at NIST had a promotion depending on getting something standardised for PQC and pushed things through without adequate review than a deep conspiracy. The message (Kyber is not as secure as NIST thinks’) would be a lot clearer without all of the conjecture.

      1. 9

        I don’t think it matters if it’s incompetence or malice. The point is that NIST has (not for the first time) acted in a way that has weakened public crypto for no clear reason. Their actions and procedures should be publicly investigated and reviewed, which it seems like DJB is trying to do with FOA requests and legal action, so I support that.

      2. 6

        Fortunately, it was caught in DJB’s review.

        And it would’ve been caught a lot sooner if they hadn’t constantly stonewalled him.

      3. 6

        Incompetence sufficiently advanced to be indistinguishable from malice is not particularly less damning for NIST, IMO.

      4. 5

        Yes, that is the interpretation favourable to NIST. But it does seem to be worth considering that for some reason other than the stated evaluation criteria of tbe competition someone really wanted Kyber to win. The most concerning possible rationalebeing that someone (probably the NSA) has discovered a backdoor or some other effective crack.

        I think you are right that it isn’t the likely reason. But if true it is incredibly important. A 1% chance of a backdoor seems worth investigating. (Not that FOI requests are likely to uncover that issue.)

      5. 5

        The conversations between NIST and NSA were kept secret, at least in part. It need not be “deep dark” in order to negatively affect the standardization process; merely being secret is an issue.

        1. 4

          A lot of what the NSA does is kept secret because that’s their default mode. They have, in the past, proposed tweaks to NIST standards to avoid possible attacks because they don’t want to disclose the attack (because some foreign power is using a different algorithm vulnerable to the attack and no one has publicly admitted independently discovering the attack). They may have also intentionally weakened others (there are conflicting reports on DES). It’s still unclear whether the null-dereference exploits introduced with SELinux were intentional and the NSA knew about that vulnerability class before anyone else or whether they were accidental.

          That’s, unfortunately, to be expected from a dual-mission agency. The organisation is tasked with both making sure that US communication (military, government, and civilian) is impervious to decryption by foreign actors and making sure that foreign communication is vulnerable to decryption by US intelligence agencies (and, for extra fun, that countries that are US allies this week are not vulnerable to interception by countries that are not US allies this week, but ideally that they are vulnerable to interception by the USA). This is a completely conflicting set of goals, which also means that a secret conversation with the NSA might be a deniable malicious intervention or a positive intervention from the NSA.

          1. 6

            I recognize your point in general, but cryptographers should generally abide by Kerchoffs’ principle. The NSA only acts this way because they genuinely believe that they are the only ones who hire skilled mathematicians; it’s an arrogance that the community should tear down.

          2. 4

            That’s all true, but arguably implies that the only reasonable way for NIST to execute their mission is to refuse the NSA’s help, and rely on civil society to review their work.

      6. 2

        It sounds more like someone at NIST had a promotion depending on getting something standardised for PQC

        That sounds like malice as far as I’m concerned. Perhaps incompetence by the others reviewing it, but I think it‘s hard to give NIST the benefit of the doubt either way

    14. 11

      Good grief. What happened to DJB?

      There’s a right way to publicize malfeasance. An incomprehensible 20,000 word rant ain’t it.

      1. 36

        I guess, but consider the alternative. Suppose I tell you, “NIST is lying to the public, at the behest of the NSA,” without any other details; you presumably would want details, evidence, an explanation of how I know about it, and a justification of my framing.

        As far as I can tell, the author’s only mistake here was to not lead with this part:

        In 2022, NIST announced plans to standardize a particular cryptosystem, Kyber-512. As justification, NIST issued claims regarding the security level of Kyber-512. In 2023, NIST issued a draft standard for Kyber-512. NIST’s underlying calculation of the security level was a severe and indefensible miscalculation. NIST’s primary error is exposed in this blog post, and boils down to nonsensically multiplying two costs that should have been added. How did such a serious error slip past NIST’s review process? Do we dismiss this as an isolated incident? Or do we conclude that something is fundamentally broken in the procedures that NIST is following?

      2. 7

        An incomprehensible 20,000 word rant ain’t it.

        It is probably a rant but why is it incomprehensible? To who?

        And everyone is free to summarize the problem in their own succinct form.

        1. 2

          You seem to find it comprehensible. Would you care to summarize it?

          If not, why?

          1. 5

            It’s already summarized in the comments.

            1. 2

              Really? I don’t agree.

              From a skimming of the post, along with skimming the comments here and on HN, the following summaries can be made:

              • DJB has lost it
              • NIST made a boo boo, but no biggie
              • NIST is again being very underhanded just like that one time in the 90s that DJB exposed[1]
              • This is the NSA destroying crypto

              That’s the point many, including me, are making. The post can be read in multiple ways. It’s not an effective way to make a case, or even show there is a case.

              —-

              [1] don’t quote me on that, the crypto wars were a long time ago.

              1. 8

                You missed “NIST screwed up, and then continued with standardization full steam while ignoring any issues that were raised”, which seems like the most reasonable take away, and definitely hampers trust in them and their processes, regardless of whether the screw up was intentional malice or just incompetence.

                1. 4

                  Again, if that was DJB’s point, he could have made it a lot more succinctly.

                  Look, my point isn’t that DJB is wrong per se, it’s that it’s really really hard to take what he’s saying seriously when he writes like a crank. He’s not doing his cause any favors.

                  1. 3

                    But the problem exists regardless of how poorly he is communicating it. And it affects all of us. If we ignore the problem because the only person who felt like going through the trouble to find this out is rather deranged these days, we’re not doing ourselves any favors.

                    1. 1

                      Can you point me to a source corroborating DJB’s claims?

              2. 3

                I mean, you either think that ratifying an inadequate standard based on an egregious miscalculation is bad or it isn’t. I don’t think we’re disagreeing on content here.

                1. 1

                  OK, I’ve added your viewpoint to the list of interpretations.

      3. 4

        What would you suggest is a better way to publicize this that hasn’t been tried (and failed) against NIST before? Multiple credible claims of malfeasance happen at nearly every NIST crypto decision these days.

        1. 12

          This text will do very little to convince anyone who has not made their mind up about what they believe about NIST and DJB. It’s surprising to me that he, as an academic of long standing, has put his name on something as disorganized as this.

          1. 8

            Meh, I highly doubt it’s written for a lobste.rs audience

            If another crypto researcher reads it and writes something either confirming or refuting it, then it will have been proven useful

            Not everyone has to weigh in on every topic

            1. 14

              Myself, and a number of other cryptography practitioners I spoke with, are not finding it a useful or coherent way to convey anything. In fact, a number of people—me included—have mostly stopped engaging because of the personal attacks, constant self-promotion, legal threats, and gish galloping.

              1. 17

                OK that’s interesting. What’s the best (non-DJB) background on this topic?

                I’m seeing lots of complaints about the tone, which I can acknowledge, but it would be nice to get some commentary on the substance.

                I didn’t read the post carefully, but I didn’t see personal attacks, legal threats, etc. Are you referring to this post, or other parts of this debate?

              2. 7

                Of course, djb being an unhinged lunatic doesn’t make the problems at hand any less real, so while not engaging with him I hope you and other cryptography practitioners are taking these things seriously.

            2. 5

              Meh, I highly doubt it’s written for a lobste.rs audience

              I don’t think this writing is particularly useful for any audience.

              If you know what’s going on here and agree with DJB, I think this would be better written like a reference rather than a narrative.

              For anyone else, the constant imputing of malicious intent and assuming that is the only explanation for certain facts makes this very difficult to read. It also makes me doubt the reliability of the document as a whole. As someone without a lot of knowledge of this matter, I don’t think I could confidently differentiate between common ground and speculation in this piece.

      4. 3

        It reads like “DJB gave a lecture and some poor soul of a grad student got to transcribe it.”

        1. 4

          God help his students if that’s the way he lectures.

    15. 7

      Ask yourself if needing to hide content for small screens and not larger screens means that content is as necessary as you initially thought.

      The article overall makes good points, but this sentence is just silly. “Ask yourself if having only £10 to spend on groceries means that fresh fruits are not as necessary as you initially thought.” Sometimes you’re forced by circumstances to prioritize things; that doesn’t mean that the things you had to deprioritize were necessarily less worthy than the other ones. It just means you were forced to prioritize.

      1. 6

        There’s a virtually infinite number of pixels to spend for content even on the smallest of screens. Small screen forces you to rearrange stuff but if you have to hide it entirely on a small screen is it still useful on the big one?

        1. 1

          If you consider scrolling then sure, that’s true. Otherwise, you have more like a 380×540 region of pixels to work with. Everything that doesn’t fit into that viewport is “below the fold,” or else tucked away in a navigation bar or menu.

          1. 1

            Is “fold” still a consideration? If anything in recent years I see more meaningless full screen images/videos above the fold than ever before.

      2. 4

        The majority of web traffic flipped to mobile years ago. Unless one is building something really niche, it’s long past time to design for mobile first, not as an afterthought.

        1. 1

          Note: The conclusion only holds if you’re seeking to maximize traffic by design appeal.

          1. 8

            I can sympathize with reactions against a world of websites that fish for eyeballs, but “only”? Perhaps we should talk substantively. For example, I’m working on an interactive web app for a municipal website. Maximizing traffic by design appeal is definitely not a goal. We are building for mobile. It takes a bit more time, but if we did not, we would be disenfranchising literally most of our visitors. I can think of many other examples where designing for mobile is not merely a crass hustle by a VC-funded startup.

            1. 5

              I think I would rephrase this as “the conclusion only holds if you’re seeking to maximize traffic on general purpose websites.” What I mean is no one should be surprised that the device everyone (obviously not EVERYONE, but the majority of most of our countries) carries with them 12-18 hours a day generates more web traffic than the fixed (or even mobile laptops, but those are still less easy to use at a stoplight than a phone) devices we used to use.

              HOWEVER, that doesn’t hold for all cases. For example, I don’t want to view my Grafana dashboards on my phone. I want to see the whole big board at once so I know what is going on. So things like blogs, municipality news websites, restaurant online ordering, general purpose instructions for how to get from here to there? Yeah, you probably want to either optimize for mobile or ensure that you handle both mobile and desktop nicely. For other things that are aimed at a subset of users that are likely on a desktop/laptop, I think you’d want to ensure it works well on a 4k display. If it also works on mobile, fine.

              This all comes down to knowing the target audience you want to reach and planning accordingly. My take away from the survey linked above is: your target audience is more likely than not going to try to use a mobile device to access it. Plan accordingly.

    16. 18

      My current job does this, and I don’t have a word for just how confusing it make things. It would be a very bad word though. Something evoking religious imagery of eternal suffering, perhaps a reference to Sisyphus.

      BUT - and this pains me to say - it’s also not wrong at all. These points are completely valid. Semantics change over time. Names become obsolete. Worse, having to name two very similar things that only differ by a tiny semantic bit leads to really terrible names that don’t make the situation any better.

      Also, let’s say you could even successfully rename things from a technical perspective, there’s the human perspective. Everyone is going to use the old name for 2 years anyway. Humans don’t have the ability to erase context overnight.

      Basically, against my desire, I firmly believe that at its limit, naming is futile. We shouldn’t purposefully obfuscate things with bad names, but there is no hope for good names either. Behavioral semantics is too information-dense to communicate with a few characters. It needs more bits of info to successfully be transmitted with low entropy.

      1. 8

        My approach is this: when you name a component like, SomethingSomething, and you’re explaining it to someone and they’re like “SomethingSomething?” And you respond, “You know, the X-doing service”.

        Then you should just rename it to “X-doing-service”. No matter how stupid or wrong it sounds.

        1. 5

          And what do you do when the service takes on or changes responsibility? I.e. when “X-doing-service” is no longer accurate.

          1. 7

            Then you make a new service. (Even if just by forking.)

      2. 7

        Three years ago I made the mistake of introducing the new search engine as Elasticsearch and I’ve been correcting people ever since that it’s just the database.

      3. 4

        ever deploy something on heroku? You get two random names like “dark-resonance” or “sparkling-star”. There you go. Always just take two random words.

        1. 5

          That’s too flippant for my personal taste.

        2. 4

          I use this pattern for branch names. It’s convenient. Rarely it discovers some offensive pair of words. Most often though it results in names that are completely useless and on a number of occasions I’ve struggled to find the right branch for something.

          1. 2

            Certainly better branch name than enforced ticketing system ID. You really think I’m gonna bother memorizing a string of integers or that I want to open & search JIRA just to find the ID?

        3. 4

          I’ve recently started doing that with my personal projects, using some random generator page I found. It basically spits out “whimsical adjective” “animal or funny object” word pairs. I cycle through them until I find one that sort of kind of matches the project. Examples:

          • glowing lamp: my effort to keep a restructured text engineering log book to make public

          • fancy hat: building an FOC-based brushless motor controller. Tenuous connection… a ball cap with a propellor on it is a fancy hat. The BLDC will be spinning a propellor on it.

          • lost elf: ESP32-based temperature sensor that’s going to live in the crawl space over the winter to make sure the heat tape on the water line is working

    17. 5

      Fwiw, I think D demonstrates that “C-like” and “functional” are completely compatible, but it points in an interesting direction:

      The important aspect for functional programming is not immutability per se, but parameter immutability. That is, it doesn’t matter if variables inside your functions are mutated, it only matters whether those mutations are visible from the caller. If they are invisible, then your function is completely compatible with a functional style.

      I think this also highlights that recursion is a red herring. Functional programming is associated with recursive algorithms because tail recursion is how you implement variable mutation in a language that doesn’t have it. But if variable immutability isn’t that important, recursion probably isn’t either.

      (Or maybe I’ve just defined away functional programming, I don’t know. I think at least, this shows that FP is two only tangentially related things: map, reduce etc. plus parameter immutability, and recursion plus variable immutability.)

      1. 3

        What you define in a bit of a roundabout way is usually referred to as local mutability, and Scala is famous for applying it inside functions where a mutable implementation is more clear, while leaving the APIs themselves mostly functional.

        I also believe that one should mostly stick with local mutability only.

      2. 3

        It sounds like you are also implying that values reachable through reference parameters can’t be mutated either. I.e. if “items” is a parameter, I can’t change “items[1].owner.name”. That’s a much bigger deal than parameter immutability — it pretty much implies data structures are immutable too.

        1. 2

          Not if the parameters are passed by value (essentially, deep-copied as necessary), or your language uses copy-on-write (COW) data structures, or similar.

        2. 1

          Yes, correct.

          It doesn’t exclusively imply that, but I’m saying that a language is functional to the degree that it’s the case. You can have situations where data structures are mutable but you exclusively pass around immutable views, for instance.

      3. 2

        There’s a ton of state around the program that a function could change, outside of parameters to a function. In fact, many functional programming languages model such a thing (IO monad).

        Functional programming is I think more about state management. Changes in state should be modelled as such.

        1. 3

          Sure, but regardless, if the function doesn’t make the state change visible from the outside, it doesn’t matter how it actually goes about fulfilling its function.

        2. 1

          State management yes, but don’t you think that goes for effects in general?

    18. 3

      In my early days I was super into getting rid of any and all boilerplate, but nowadays I think I have mostly seen through that. A little bit of boilerplate never hurts! It can actually make things more readable and inspectable in the long run, because which part of the code is being executed at a given point in time is clear and straightforward (as compared to using reflection, for example.)

      It’s partly why I’m against using reflection and macros even when they’re available. They can reduce boilerplate, but most of the time simpler is better until it gets really unwieldy and you keep running into the same hard-to-catch bugs - at that point you know the cut points along which you can build a sensible abstraction (grug brained developer approach) but then you should really evaluate whether you need to pull out the big guns, or can solve your problem someway else.

      1. 3

        Sufficient reflection and macros can turn one language into another language. If your first language has a strong macro system but a weak data model, for instance, then this can be a simple straight improvement. But you should definitely approach it with the trepidation and care of “I am creating a new language.”

        I’m a heavy advocate of reflection and boilerplate-reduction at work - the trick is that the reflection library 1:1 models the business rules that we were using anyways. And of course, if something is wrong, there’s always backdoors.

    19. 4

      Computers should be better than humans at intelligence, too. However, impressive or not, normalized or not, it cannot be denied that the future after computers are better (probably vastly better) than humans at thinking will be fundamentally, transformatively different from the present.

    20. 3

      Well, every single time I need to do this I find myself having to research SDL and relearn it all over again 🤦‍♂️. I know I need to init sdl and then… create a window, right? a renderer or a surface? how was it that I handled events? something double loop… I think? Or did I just waited for events?

      ChatGPT4 and/or large language models are like made for this. Just type in what type of C+SDL2 starter program you need and ey presto! Bob’s your uncle!

      1. 22

        Probably not exactly the case here as SDL is already lean compared to, say, electron apps, but in general it’s nice when humans take the time to simplify interfaces and create useful abstractions rather than embracing the boilerplate and having machines generate more and more of it.

        1. 5

          This in general is perhaps true, but with event loops, I’ve personally found that asking the user to write while (!done) is the right call, which leads to significantly easier to understand, debug, and introspect software.

      2. 6

        I’ve done it, got tired of doing it :-)

        1. 1

          That makes sense. :)

          I appreciate the simple C interface. It reminds me of how easy it was to get a pixel on screen with QBasic or Turbo Pascal 5.5. With shaders and especially Vulkan, the path to a single red pixel on screen is much longer.

          Although not as simple as sdlsimple, I made a package for Go that uses SDL2, with a somewhat similar purpose.

      3. 5

        I wonder how much pain this is going to cause in the long run. Having to type the boilerplate by hand shows you how much is redundant and encourages it to be factored out into reused libraries. Having tools that can generate it means that there’s less benefit to having clean APIs, right up until you need to change them. Maybe by then LLM-based refactoring tools will be good enough that you’ll be able to say ‘rewrite this program to use the v6 API instead of the v5 one’ and it will just work.

        1. 2

          Nobody types it by hand anyways, they copy an existing demo program.