> our ancestors insisting on eating toxic things like mushrooms and spoiled food
Presumably this didn't happen because are ancestors were dumb, but rather because the ability to process slightly spoiled food is a significant advantage in an environment where fresh food is sometimes scarce?
It's still a wild logical leap to me that we went from this observation, that maybe the bottom half of users never learn how the software works, to the idea that we should build software that is actively learning-hostile. In the old era, we established consistent UI patterns: this kind of button does X, this kind of button behaves like Y, here are the tools to solve your problem. Maybe not everyone learned the patterns, but those that did became wildly productive. Now: who cares if buttons are recognizable as such, your bottom half of users tap all over the place anyway, so design for them exclusively. It's good to enable more people, but we also lost something in the process.
I cynically ascribe that to a change in the market/ecosystem, from "personal computer as democratized means of production" to the (more profitable) "device we control to sell content and subscriptions to you".
The root problem is so-called startup/growth hacker culture: you start from zero user. There are a lot of potential users out there. Far more than your current users. So, the best way to grow your business is not to retain your current users, but to grab new users.
In this scenario, you only need to cater to the most clueless users. There is no need to make an app learnable as you don't even intent to keep the users long enough for them to actually learn how your app works.
They say it's a modified version Source Sans, but then don't compare it with Source Sans to show the differences. The only comparison is with their previous, unrelated font (Whitney SSm). Why?
I seem to be mostly unaware, at least, it's not self-explanatory to me why a Rust rewrite would be that baffling. What's the verdict on the Bun rewrite, and how does that relate to pnpm's decision? Also, wasn't Bun switching from Zig to Rust instead of from TypeScript to Rust like pnpm? I get the impression that many web infrastructure projects have been switching to more native languages.
I expect the pnpm maintainers to be familiar with it because bun is one of the other three or four major JavaScript package managers. To put it another way, if you gathered up the 100 people in the world working on the most similar problems to pnpm’s own work, like 15 or 20 of them would be working on bun.
I believe the objective verdict on the rewrite is “actually, everything went better than expected”, but it was and seemingly still is quite controversial and engendered a fair bit of bad blood, which I would have thought would be undesirable or demotivating to maintainers.
I think that's somewhat overly reductive. A significant portion (maybe 1/3-1/2?) of the article is devoted to describing mechanisms by which an ABI could be evolved, including a new (?) mechanism implemented in the author's Clang fork and submitted to the C committee ([0] in the blog post, currently on revision 8 [1]). Sure, it isn't a perfect solution, but as the author says:
> at least we’ll finally have the chance to have that discussion [about breaking ABI] with our communities, rather than just being outright denied the opportunity before Day 0.
Because it imagines that no library other than libc has an ABI that depends on intmax_t.
Suppose I have a libfoo that has a public function that takes an intmax_t parameter. Or that has a public struct with an intmax_t field. It will be compiled for a particular definition of intmax_t. If you try to link it with a program that uses a different definition, it will fail.
The article's solution with the "MY_LIBC_NEW_CODE" define cannot work because no existing C code knows about "MY_LIBC_NEW_CODE".
The proposed mechanism is somewhat useful to a library that wants to provide multiple incompatible implementations of a function. (But this is mostly only interesting for libc implementations that need to handle historic incompatibilities between all the various Unix specs. Other libraries can just give their new, incompatible function a new name.) It's useless if you want to make an incompatible change to a type definition.
> Because it imagines that no library other than libc has an ABI that depends on intmax_t.
I don't get quite the same impression. The sense I get is more that such a change would basically need to happen "bottom-up":
> Some of [the scenarios that aren't fixed by this proposal] are just the normal dependency management issues. If you build a library on top of something else that uses one of the changed types (such as intmax_t or something else), then you can’t really upgrade until your dependents do.
> <snip>
> For those of us in large ecosystems who have to write plugins or play nice with other applications and system libraries, we’re generally the last to get the benefits.
In which case the benefit of the proposal (as far as I understand) is that such bottom-up changes can occur without forcibly breaking other consumers.
It was never really possible to compile a library with one C compiler and expect it to link against code produced by another C compiler, unless both compilers happen to agree on specific ABI details that are either defined outside the C standard or not at all.
And to be honest, this sort of compiler-specific ABI interoperability is a non-problem that doesn't need solving, it's at most relevant for software developers of closed source libraries who distribute the libraries as precompiled blobs. But those must be stamped out for different target-triples anyway.
Compiler interoperability is the only ABI related problem area that's remotely in the scope for the C standard, but IMHO not even that (it's really not a problem that needs solving).
Ultimately any ABI discussions need to happen between CPU and OS vendors, compiler toolchains implement whatever comes out of those discussions.
> Compiler interoperability is the only ABI related problem area that's remotely in the scope for the C standard
idk, given how ABI impacts the evolution of C I think it's not unreasonable to provide a mechanism by which ABI can be evolved even if it's not specifically for compiler interop.
> Ultimately any ABI discussions need to happen between CPU and OS vendors, compiler toolchains implement whatever comes out of those discussions.
I think part of the article author's reasoning for proposing this feature is that toolchains have implemented something like this feature to try to address ABI issues and that the rest of the ecosystem could benefit from a similar technique.
Huh, compiler interoperability is extremely useful. It usually works by ABI groups defining a common ABI for each architecture and compilers then following these ABIs (although not required a compilers that does not is poor)
It's useful for closed-source developers who want to sell their libraries as precompiled binary blobs for static linking, but as soon as MSVC is involved that idea of "one library for different compilers" is out the window anyway ;)
For areas like application plugins via DLLs it's the OS ABI that matters.
I don't understand this comment. There are well-defined ABIs for many platforms. Sometimes there is a defacto ABI defined by the first compiler supporting this platform, but generally there ABI groups that agree on a common ABI for each architecture. This then allows code compiled by different compiler to be linked against common libraries - not just glibc. I can compile my projects with either clang, gcc, or some other compiler and link statically or dynamically against all the same libraries distributed by my distribution regardless of which compiler was used to compile them. This is extremely useful.
The issue is that a programmer is allowed to declare a function on its own without including the header, but then a function aliasing feature would not be visible and does not help. But if we waived this allowance, then a simple macro would do the job as well.
I think the most recent revision of the proposal basically says it not helping for such use cases is intentional? e.g., from Section 4.4.1. Standard Library Redeclaration [0]:
> Thankfully, we are not particularly concerned about the ability to upgrade this [user-redeclared stdlib] function: users who are declaring Standard Library functions without including the header like this are doing this strictly as experts. They have a strong expectation of what symbol they are getting from their distribution. Transparent aliases are meant to be used for functions which rely on type definitions or structures which may change, prompting the need to provide updated global variables and updated functions without breaking old binaries.
> <snip>
> Therefore, we do not do anything to support or inhibit such declarations. Implementations looking to keep such declarations working from older versions of code should consider leaving those old symbols within their binary artifacts (system tables, shared/static libraries, etc.) to continue supporting such a use case; this proposal is not going to address it or the myriad of other issues around this (such as strong/weak symbols and other attributes/aliasing issues).
To be fair, that section is talking about the stdlib specifically, but nothing jumps out to me as precluding it applying to libraries in general.
Conceptually intmax_t is a generic type of the form intmax_t<T>. Since C does not have generics, the T is chosen by the compiler during compile time.
But this means that the first time you compile any shared library with an intmax_t parameter or return value in one of its functions, you have permanently baked in the type parameter T to whatever the compiler chose it to be at that moment in time.
You cannot retroactively change intmax_t even if you change the symbols, because intmax_t runs into the same problem any generics system does, you cannot retroactively add instantiations for future types that were not explicitly compiled into the dynamic library.
Even if C gets generics and intmax_t would become obsolete either way, because you don't need intmax_t<T>, you can just have T.
intmax_t is only interesting for choosing the T and even then it is only interesting inside function implementations and never in their signatures.
So my conclusion is that intmax_t was a failed attempt at trying to be "clever" with the idea of introducing generics without introducing generics. This is an idea that is so doomed that anyone trying to rescue it, didn't really understand the problem with intmax_t.
> This is an idea that is so doomed that anyone trying to rescue it, didn't really understand the problem with intmax_t.
The C committee doesn't want to rescue intmax_t. If they had a time machine most members would prevent it ever being added. But it was and now it can't be removed; it takes decades to get breaking changes into the standard.
intmax_t is a thornier problem than time_t and needs a more comprehensive approach that requires some careful assistance from the C standard itself. There's no completely fixing the mess. The goal is to find an acceptable solution that allows everybody to begin to move past intmax_t given the various technical and practical constraints. The goal is not to find a solution that allows for expanded use of intmax_t, at least not in the standard, though it may be the case it can never completely go away.
I think the bigger issue is time_t, and I have, in fact wrote comments that literally state the code is "best before Jan 19, 2038." Said code may never be recompiled since it will trigger regulatory certification.
Even this is too simplistic. While C compilers normally use the OS ABI, there is nothing that requires them to do this, and other languages don't. Of course, when you need to call functions provided by the OS, you have to do so following the OS ABI; but calls between functions written in your own language, even in different libs, don't need to follow this same rule.
I think in the background of article's premises is an argument about classical vs. intuitionistic logic, rather than only about the merits of putting stuff in the kernel vs. outside.
Isabelle seems to use classical logic and set theory. Classical logic is often simpler, but when you do the "hard toil" (as the article puts it) of building recursive functions on set theory, all you've really done is to nonconstructively prove the existence of a set of pairs with certain properties. Good luck evaluating such an abstract "existence" with any concrete argument. Whereas intuitionistic logic as used by Coq is more complicated, but that's in part because its notion of "function" is an actual procedure in your computer that can accept an argument and produce a result.
At least that's to the best of my understanding; it's been a while since I have looked at any of this, so feel free to make corrections.
Isabelle is actually a "logical framework", so it supports intuitionistic logic, actually its meta theory is intuitionistic higher-order logic.
So this is not because of the logic, it is because of the mindset. Intuitionistic logic is usually championed by people who want to emphasise computation over reasoning, and that is why they build computation as one their reasoning steps into their kernel. They don't have to do that. They do it deliberately, because it aligns with what they like, and how they like to think about logic.
A light switch not working for 30 seconds, or a door not opening for 30 seconds, even if only sporadically, is a significant loss of life quality, no qualifiers needed.
> A light switch not working for 30 seconds, or a door not opening for 30 seconds
I dunno if its just me, but having a manual override for such important/basic stuff is kind of a must for me. As much as I enjoy smart home stuff, having anything critical only available via that is just stupid.
There's a widespread misconception (not shared by the article author) that floating point arithmetic is "imprecise" in the sense that the result is off by some amount of random noise. On the contrary, IEEE floating point results are precisely specified to produce the closest representable value to the mathematically exact result.
For efficiency reasons, library functions (and sometimes, sadly, hardware implementations) don't always guarantee this (closest representable) exact result. That's fine if the function is then called "approximate_inverse_square_root" or something. Sometimes being off by some epsilon is a good tradeoff for 10x efficiency. I'm unhappy when such a tradeoff is smuggled into my math library without warning.
I checked Rust's source code to confirm the article's claim that it doesn't have a precise log function, and indeed, here's the implementation:
I'm sure other math libraries aren't better. But this is not a correct implementation of arbitrary-base logarithm. A function like that perhaps shouldn't even be offered in the standard library at all (since it's so trivial to begin with), or at the very least, not with that name. If a programmer wants to opt-in to a fast but slightly wrong value, they should do so explicitly, in my opinion.
Thank you! This point has been driving me mad for the last couple of years.
While answering a sibling comment, I found a paper analyzing various libms for their precision: https://homepages.loria.fr/pzimmermann/papers/glibc238-20230... (2023). I only skimmed it, but it looks like only LLVM's libm guarantees 0.5 ulp for every supported single-precision operation, and everyone else is wildly off. That's a pretty good result, though -- it means there's at least one reasonably compliant libm :)
Another sibling comments says that guaranteeing 0.5 ulp for double-precision operations is nigh impossible (and then another says it is after all). I don't have the knowledge to confirm which is true, but it's possible that this is the best we can get.
Table--Maker's dilemma [0] says that we probably can't get correct rounding for some functions. That said, you can still calculate every function within 1 ULP by using extended precision (for example the double-double arithmetic, which uses two doubles to represent a single fp number) and rounding.
But I don't think anyone does this in practice. Simulations don't need perfect precision, anything involving real data has to deal with measurement errors which are usually much larger than f64 ULP scales, and then there's configurable arbitrary-precision for when it's really needed.
> On the contrary, IEEE floating point results are precisely specified to produce the closest representable value to the mathematically exact result.
Not sure about Rust, but C++ only guarantees that for four arithmetic operations and `sqrt`. Not `pow`, not `log`. Not even when when all inputs/outputs are integers. Sometimes you can even get `(int)pow(10, 2) == 99`, which I believe is fully standard-compliant.
Yeah, those operations (+fma) are the only ones for which IEEE 754 mandates correct rounding.
Calculating correct rounding for other functions without hardware support is both challenging to implement and is often really slow. And that's before one gets into special functions. Or, even worse, inverses of special functions. Sometimes you might be lucky when those don't over/underflow around the edges of the domain
It's still challenging to implement with hardware support but you don't notice because it challenges Intel instead of you. There is a reason that no instruction set since x87 implements transcendentals - and x87 did them in microcode.
Older numerical libraries such as cephes had to work with heterogeneous non-IEEE floating point implementations: you can still see remnants of VAX tests in the docs, for example. That, too, must've been quite a struggle
I am curious what the correct algorithm would be, if for no other reason than to find out how on god's green earth it could be slower than using a division as an erstwhile shortcut. 8I
In this particular case, there never exists any need to compute a logarithm of an arbitrary base, so such a function must not exist in a library.
If someone had the weird idea to write some formula with logarithms in a random base they should convert that formula to use binary logarithms, because there is no advantage in using another base.
Nonetheless, if one would want to include such a function, or any other function, one could reduce the argument to some small range using mathematical properties of the function and then some polynomial approximation should be determined, in order to use it to compute the function values.
If you determine a polynomial approximation for a function, whichever function it is, you can compute the entire function faster and more accurately than when you compute the function as a composition of other functions, one or more of which are computed by their own polynomial approximations.
In order to ensure that the function is rounded correctly, one must determine how many polynomial terms are needed and how big must be the numbers used for storing the coefficients.
For a function with a single argument, like "log2", it is much easier to do an exhaustive search to find all the argument values that would need more polynomial terms or more bits in the coefficients, to guarantee correct rounding.
For functions with 2 arguments, it can be difficult or impossible to find all problematic argument pairs, unless the function has some special mathematical properties that would allow a formal proof about the accuracy needed in polynomial evaluation to ensure correct rounding.
Regardless of what? Programming is solved, I hear, with all the 100x productivity PhD-level automated coding loops they have going. Don't make excuses for them when they disprove their own bullshit.
Presumably this didn't happen because are ancestors were dumb, but rather because the ability to process slightly spoiled food is a significant advantage in an environment where fresh food is sometimes scarce?
reply