<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hacker News: el_pollo_diablo</title><link>https://news.ycombinator.com/user?id=el_pollo_diablo</link><description>Hacker News RSS</description><docs>https://hnrss.org/</docs><generator>hnrss v2.1.1</generator><lastBuildDate>Mon, 27 Jul 2026 20:38:28 +0000</lastBuildDate><atom:link href="https://hnrss.org/user?id=el_pollo_diablo" rel="self" type="application/rss+xml"></atom:link><item><title><![CDATA[New comment by el_pollo_diablo in "We have proof automation now"]]></title><description><![CDATA[
<p>> a single proof covering the most precise description of the program's behavior is more compact<p>Yes, and a program is most compact when all modules have been merged, and all functions with a single caller inlined. We have compilers with LTO for that, though; we would never maintain source code in that form.<p>Snark aside, I get your point about restating the program code, but this can be alleviated by interactive proof assistants that largely reduce the length of proof scripts. This is mostly a matter of tooling.</p>
]]></description><pubDate>Mon, 27 Jul 2026 13:33:49 +0000</pubDate><link>https://news.ycombinator.com/item?id=49069500</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49069500</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49069500</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "We have proof automation now"]]></title><description><![CDATA[
<p>In my view, a major selling point of dependent types when it comes to reasoning, is that by bundling logical properties with a runtime value, they require no separate effort to prove the propagation of the logical properties as the value is moved around. That is why I mentioned them in the context of opaque types. This is especially useful with generics: when a type parameter is instantiated with a dependent type, all its occurrences instantly benefit from the strong typing.<p>They can also be used to enforce just enough constraints on the inputs of a function to make it total, but this comes down to the tradeoff between either leaving an error path in the program and proving its unreachability later, or not having this error path but immediately requiring the proof. In any case, as you mention, further properties can be proved later without altering the dependent type.<p>I do not intend to come off as overly negative about dependent types. They have their uses, but they can also bring a maintenance nightmare.</p>
]]></description><pubDate>Mon, 27 Jul 2026 13:22:12 +0000</pubDate><link>https://news.ycombinator.com/item?id=49069363</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49069363</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49069363</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "We have proof automation now"]]></title><description><![CDATA[
<p>I am not sure what you mean. Of course proving a new property generally implies reasoning on each elementary step of the program. My point is that, assuming you have already proved a property, proving a new one shouldn't lead you to alter the first proof. But it does if you carelessly use some logical tools like dependent types and single preconditions/postconditions/loop invariants.<p>For example, take Hoare's while rule (see <a href="https://en.wikipedia.org/wiki/Hoare_logic#While_rule" rel="nofollow">https://en.wikipedia.org/wiki/Hoare_logic#While_rule</a>). If you have already proved<p><pre><code>    {P∧B}S{P}
</code></pre>
and, in order to prove another property, some new invariant Q has to be propagated across this loop. It would be enough to prove<p><pre><code>    {Q∧B}S{Q}
</code></pre>
or even<p><pre><code>    {P∧Q∧B}S{Q}
</code></pre>
if the new proof builds upon the first one. But if your logical tool of choice insists on having a unique loop invariant, you must throw away your existing proof and prove<p><pre><code>    {P∧Q∧B}S{P∧Q}
</code></pre>
which is incomparable and uselessly mixes both concerns.</p>
]]></description><pubDate>Mon, 27 Jul 2026 12:52:36 +0000</pubDate><link>https://news.ycombinator.com/item?id=49068969</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49068969</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49068969</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "We have proof automation now"]]></title><description><![CDATA[
<p>I have already written it, and I will write it again: dependent types and total functions do not scale. Maintenance is terrible.<p>Suppose that you have managed to write a non-trivial piece of software with dependent types encoding all sorts of properties everywhere. The actual computation and proof are intermingled. Think of the author's innocent bound-check proof in the zstd decoder, but across the whole program, with more elaborate properties and longer proofs.<p>Suddenly, you realize that you need to prove a new property of your program. Can you keep your existing work and build on top of it? In general, no, you have to refine every dependent type everywhere by adding a new conjunct expressing a new invariant, and adapt every proof, as the new property is threaded in the existing program.<p>That is because dependent types (and other staples of naive approaches to proving program properties, like a unique invariant per loop) structure the program along the wrong dimension: they encourage grouping everything that concerns a value ("put this value in a dependent type that encodes everything known about it") or a program point ("write the precondition for this function as a big conjunction mixing all the concerns"), where it works much better, for long-term maintenance, to structure the development along concerns: computational parts of the program, basic functional properties and absence of UB, termination, other functional properties, security, etc., where each layer builds on top of the previous ones without requiring them to change.<p>That is not to say that dependent types do not have their use. Where they shine is at module boundaries. Consider a library that exposes an opaque type and operations on it. Users of the library can only build and modify values of that type through the library's API. This type should be a dependent type. If it needs to be refined at any point to encode a new invariant, this will have no impact on the library's users, since all they do is pass around values without interpreting them. However, inside the library, I would recommend unpacking/repacking the dependent type at the library's entry points and handling the concerns separately.</p>
]]></description><pubDate>Mon, 27 Jul 2026 07:58:24 +0000</pubDate><link>https://news.ycombinator.com/item?id=49066421</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49066421</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49066421</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Everything in C is undefined behavior"]]></title><description><![CDATA[
<p>> probably meaning on an address that’s a multiple of sizeof(int), but who knows<p>Sigh. s/sizeof(int)/_Alignof(int)/.<p>There are good reasons for an implementation to have sizeof(int) = _Alignof(int) and not a mere multiple of it, but if you are going to discuss subtle points and UB, just stick to the language guarantees.<p>> But let’s say you have a modern machine, where NULL is a pointer to address zero, and you actually have an object there.<p>You don't program in C on such a machine. Or maybe memory is virtualized, and it does not matter that your object lives at physical address zero, as long as you can map a non-zero virtual address to it.<p>> So how do you print an uid_t?<p><pre><code>    if ((uid_t)-1 < (uid_t)0) {
        // uid_t is signed
        printf("%" PRIdMAX, (intmax_t)id);
    } else {
        // uid_t is unsigned
        printf("%" PRIuMAX, (uintmax_t)id);
    }
</code></pre>
> It’s not rare for the denominator to come from untrusted input.<p>It's not rare for the array index to come from untrusted input.<p>It's not rare for the supposedly valid UTF-8 string to come from untrusted input.<p>...<p>Why single out division? This problem affects every partially defined operation. In the case of division at least, everyone learned in school that thou shalt not divide by zero. Adding two untrusted integers and forgetting that signed overflow is UB, not defined as a modulo? Your average programmer is much less likely to see that coming.<p><pre><code>    > unsigned char a = 0xff;
    > unsigned char b = 1;
    > unsigned char zero = 0;
    > bool overflowed = (a + b) == zero;
    >
    > unsigned char a = 0x80;
    > uint64_t b = a << 24;
</code></pre>
Please. Convert your operands to wide enough types before the operation. Convert your results back to narrow enough types to compensate for integer promotion to wider types than you would have liked. Do that consistently, and you're good.<p>Here:<p><pre><code>    unsigned char a = 0xff;
    unsigned char b = 1;
    unsigned char zero = 0;
    bool overflowed = (unsigned char)(a + b) == zero;

    unsigned char a = 0x80;
    uint64_t b = (uint32_t)a << 24;</code></pre></p>
]]></description><pubDate>Wed, 20 May 2026 15:59:14 +0000</pubDate><link>https://news.ycombinator.com/item?id=48209876</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=48209876</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48209876</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Int a = 5; a = a++ + ++a; a =? (2011)"]]></title><description><![CDATA[
<p>Type punning via unions is not UB in C in general, but it is in C++ IIRC.<p>I write "in general" because, as with other forms of memory reinterpretation (memcpy or copy through a character type), evaluating a trap representation triggers UB.</p>
]]></description><pubDate>Thu, 14 May 2026 21:20:01 +0000</pubDate><link>https://news.ycombinator.com/item?id=48141406</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=48141406</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48141406</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Box to save memory in Rust"]]></title><description><![CDATA[
<p>So there are now two ways to represent the same state: None or Some(struct whose fields are all None). Even though one of these representations is never produced by the deserialization routine, anyone could construct it if the constructor is public. And even if they don't, the different representations will show up in pattern matching as separate paths for every access to the field. This looks like a good opportunity to make these types (optimized for storage) private, and to define public view objects/accessors (optimized for usage) on top of them that merge equivalent representations.</p>
]]></description><pubDate>Mon, 27 Apr 2026 09:27:15 +0000</pubDate><link>https://news.ycombinator.com/item?id=47919458</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=47919458</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=47919458</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "The pleasures of poor product design"]]></title><description><![CDATA[
<p>This reminds me of Jacques Carelman's Catalogue d'objets introuvables. Highly recommended. It has already been mentioned on HN: <a href="https://news.ycombinator.com/item?id=9789216">https://news.ycombinator.com/item?id=9789216</a></p>
]]></description><pubDate>Wed, 18 Mar 2026 10:32:45 +0000</pubDate><link>https://news.ycombinator.com/item?id=47423831</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=47423831</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=47423831</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "AI will make formal verification go mainstream"]]></title><description><![CDATA[
<p>Capturing invariants in the type system is a two-edged sword.<p>At one end of the spectrum, the weakest type systems limit the ability of an IDE to do basic maintenance tasks (e.g. refactoring).<p>At the other end of the spectrum, dependent type and especially sigma types capture arbitrary properties that can be expressed in the logic. But then constructing values in such types requires providing proofs of these properties, and the code and proofs are inextricably mixed in an unmaintainable mess. This does not scale well: you cannot easily add a new proof on top of existing self-sufficient code without temporarily breaking it.<p>Like other engineering domains, proof engineering has tradeoffs that require expertise to navigate.</p>
]]></description><pubDate>Wed, 17 Dec 2025 10:01:04 +0000</pubDate><link>https://news.ycombinator.com/item?id=46300081</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=46300081</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=46300081</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Rouille – Rust Programming, in French"]]></title><description><![CDATA[
<p>Years ago the research team behind OCaml released Chamelle, a version of the language localized in French, as an April fool's joke:<p><a href="https://gallium.inria.fr/blog/ocaml-5/" rel="nofollow">https://gallium.inria.fr/blog/ocaml-5/</a></p>
]]></description><pubDate>Fri, 31 Oct 2025 07:16:15 +0000</pubDate><link>https://news.ycombinator.com/item?id=45769183</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=45769183</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=45769183</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Greenland’s national telco, Tusass, signs new agreement with Eutelsat"]]></title><description><![CDATA[
<p>I would put the emphasis on a different word:<p>> This article is made and published by Anna Hartz, <i>which</i> may have used AI in the preparation<p><i>Which</i>, not <i>who</i>. They're not even sure the author is human!</p>
]]></description><pubDate>Wed, 22 Oct 2025 08:31:11 +0000</pubDate><link>https://news.ycombinator.com/item?id=45666287</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=45666287</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=45666287</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "I write type-safe generic data structures in C"]]></title><description><![CDATA[
<p>Head and tail make sense for persistent lists in functional languages with value semantics, yes.<p>The intrusive, mutable, doubly-linked loops with reference semantics under discussion are quite different. Although all entries behave identically in the structure itself, one of them is _used_ differently, as a standalone anchor point, while the others are embedded in the list's "elements".</p>
]]></description><pubDate>Thu, 03 Jul 2025 14:24:19 +0000</pubDate><link>https://news.ycombinator.com/item?id=44455379</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=44455379</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=44455379</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "I write type-safe generic data structures in C"]]></title><description><![CDATA[
<p>> Are you saying the only real way to program is with generic data structures?<p>Certainly not. As I said, the experienced programmer knows when (not) to use them. Some programs are better off without them, such as... most of the low-level software I write (security-critical operating systems).<p>> Data tends to follow a particular path and there is probably 1 red black tree and it’s a core feature of the application. If that’s true then it’s not a big deal to write that core feature of your application.<p>"It's not a big deal" are famous last words. Maybe it is not a big deal, but unless you have hard constraints or measurements that confirm that you would not be served well enough by an existing solution, it may very well be the reason why your competitors make progress while you are busy reinventing, debugging, maintaining, and obtaining certification for a wheel whose impact you overestimated.<p>> It avoids premature optimization and code bloat because you tend to use complex data structures when they have a need.<p>Avoiding code bloat? Agreed, a custom solution cannot be worse than a generic one. Avoiding premature optimization? Quite the contrary: going straight for the custom solution without first confirming, with actual measurements, that the generic solution is the unacceptable bottleneck, is textbook premature optimization.<p>I am sorry but I do not understand what you are getting at.</p>
]]></description><pubDate>Wed, 02 Jul 2025 21:12:39 +0000</pubDate><link>https://news.ycombinator.com/item?id=44448891</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=44448891</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=44448891</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "I write type-safe generic data structures in C"]]></title><description><![CDATA[
<p>You mean the downside that we also already know, i.e. that there are some situations where a custom data structure would be superior for various reasons (e.g. smaller footprint)?<p>Experienced programmers know when to reuse a generic library and when to roll out their own. We all know that.<p>Yet you dismiss generic red black trees because there is no realistic single program that uses 10 key/value combinations. Not only is this false, but as I illustrated in my reply, a single program is not the relevant scope to decide whether to use a generic implementation. And as someone who has written a red black tree library, I am definitely in favor of reusing an implementation unless you have an excellent reason, which "I do not need 10 different instances in my program" or "my favorite language and its standard library only have arrays built in" most definitely are not.</p>
]]></description><pubDate>Wed, 02 Jul 2025 09:28:04 +0000</pubDate><link>https://news.ycombinator.com/item?id=44441657</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=44441657</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=44441657</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "I write type-safe generic data structures in C"]]></title><description><![CDATA[
<p>Absolutely. Wrapping the distinguished entry point in a new structure type equipped with a thin type-safe wrapper API that covers the most common use case is the way to go.</p>
]]></description><pubDate>Tue, 01 Jul 2025 10:31:51 +0000</pubDate><link>https://news.ycombinator.com/item?id=44432450</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=44432450</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=44432450</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "I write type-safe generic data structures in C"]]></title><description><![CDATA[
<p>Not to mention that they insist on calling every entry of the list a "list head", which makes no sense (hysterical raisins, maybe?). The structure is made of a uniform loop of entries, one of which is used as the actual head & tail, or entry point into the structure.</p>
]]></description><pubDate>Tue, 01 Jul 2025 09:30:58 +0000</pubDate><link>https://news.ycombinator.com/item?id=44432116</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=44432116</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=44432116</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "I write type-safe generic data structures in C"]]></title><description><![CDATA[
<p>None, but that is not my point. Before C23, fn() already meant the same thing as fn(void) <i>in function definitions</i>, which the situation under discussion here.<p>C23 changed what fn() means outside a function definition.</p>
]]></description><pubDate>Tue, 01 Jul 2025 06:27:13 +0000</pubDate><link>https://news.ycombinator.com/item?id=44431157</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=44431157</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=44431157</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "I write type-safe generic data structures in C"]]></title><description><![CDATA[
<p>Sure, but it is also very common for C programs to contain data structures that have one use in the program, and could still be instances of a generic type. You mentioned red black trees, which are a perfect example of that.</p>
]]></description><pubDate>Tue, 01 Jul 2025 06:19:28 +0000</pubDate><link>https://news.ycombinator.com/item?id=44431120</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=44431120</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=44431120</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "I write type-safe generic data structures in C"]]></title><description><![CDATA[
<p>If, by "situation", you mean the development of a small program with so many constraints that using existing libraries is out if the question, then yes.<p>Otherwise, that seems unwise to me. Not every user of a generic type has to be generic. A major selling point of generic types is that you write a library once, then everyone can instantiate it. Even if that is the only instance they need in their use case, you have saved them the trouble of reinventing the wheel.<p>No colleague of mine may need 10 different instances of any of my generic libraries, but I bet that all of them combined do, and that our bosses are happy that we don't have to debug and maintain 10+ different implementations.</p>
]]></description><pubDate>Tue, 01 Jul 2025 06:10:34 +0000</pubDate><link>https://news.ycombinator.com/item?id=44431079</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=44431079</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=44431079</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "I write type-safe generic data structures in C"]]></title><description><![CDATA[
<p>Sure, but your alternative code incorrectly assigns to (list)->payload. You have many other options. Without typeof, you can if(0) the assignment, or check type compatibility with a ternary operator like 1 ? (item) : (list)->payload and pass that to _list_prepend, etc. With typeof, you can store item in a temporary variable with the same type as (list)->payload, or build a compound literal (typeof(*(list))){.payload=(item)}, etc.</p>
]]></description><pubDate>Mon, 30 Jun 2025 23:10:18 +0000</pubDate><link>https://news.ycombinator.com/item?id=44428902</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=44428902</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=44428902</guid></item></channel></rss>