Live data from Hacker News

The compiler is your best friend

blog.daniel-beskin.com

41–50 of 149 posts

Re: The compiler is your best friend

#41

> How many times did you leave a comment on some branch of code stating "this CANNOT happen" and thrown an exception? Did you ever find yourself surprised when eventually it did happen? I know I did, since then I at least add some logs even if I think I'm sure that it really cannot happen. I'm not sure what the author expects the program to do when there's an internal logic error that has no known cause and no defini…

This is what rust's `unreachable()!` is for... and I feel hubris whenever I use it.

You should prefer to write unreachable!("because ...") to explain to some future maintenance engineer (maybe yourself) why you believed this would never be reached. Since they know it was reached they can compare what you believed against their observed facts and likely make better decisions.

But at least telling people that the programmer believed this could never happen short-circuits their investigation considerably.

Re: The compiler is your best friend

#42

I'm not a fan of the recent trend in software development, started by the OOP craze but in the modern day largely driven by Rust advocates, of noun-based programming, where type hierarchies are the primary interface between the programmer and the code, rather than the data or the instructions. It's just so... dogmatic. Inexpressive. It ultimately feels to me like a barrier between intention and reality, another abstr…

The kind of noun-based programming you don’t like is great for large teams and large code bases where there is an inherent communication barrier based on the number of people involved. (N choose 2 = N*(N-1)/2 so it grows quadratically.) Type hierarchies need to be the primary interface between the programmers and the code because it communicates invariants on the data more precisely than words. It is dogmatic, because that’s the only way it could work for large teams.

When you are the only programmer, this matters way less. Just do whatever based on your personal taste.

Re: The compiler is your best friend

#43
For another perspective on "lying to the compiler," I enjoyed the section on Loopholes in Niklaus Wirth's "Good Ideas, Through the Looking Glass"[1]. An excerpt:

Experience showed that normal users will not shy away from using the loophole, but rather enthusiastically grab on to it as a wonderful feature that they use wherever possible. This is particularly so if manuals caution against its use.

[...]

The presence of a loophole facility usually points to a deficiency in the language proper, revealing that certain things could not be expressed.

Wirth's use of loophole most closely aligns with the unchecked casts that the article uses. I don't think exceptions amount to lying to the compiler. They amount more to assuming for sake of contradiction, which is not quite lying (e.g., AFSOC is a valid proof technique, but proofs can be wrong). Null as a form of lying is not the fault of the programmer, that's more the fault of the language, so again doesn't feel like lying.

[1] https://people.inf.ethz.ch/wirth/Articles/GoodIdeas.pdf

Re: The compiler is your best friend

#44
So his view is from a programmer / developer. That's fine.

I had an issue on my local computer system yesterday; manjaro would not boot with a new kernel I compiled from source. It would freeze, at the boot menu, which I never had before. Anyway. I installed linuxmint today and went on to actually compile a multitude of things from source. I finally finished compiling mesa, xorg-server, ffmpeg, mpv, gtk3 + gtk4 - and the prior dependencies (llvm etc...). So I am almost finished finally.

I had to invest quite a lot of time hunting for dependencies. Most recent one was glad2 for libplacebo. Turns out "pip install glad2" suffices here. But getting that wasn't so trivial. The project project at pip website was virtually useless; respectively I installed "pip install glad" which was too old. Also took me perhaps one full minute or more to realise it.

I am tapping into LFS and BLFS webpage (Linux from scratch), which helps a lot but it is not perfect. So much information is not described and people have to know what they are doing. You can say this is fair, as this is more for advanced users. Ok. The problem is ... so many things that compilers do, is not well-described; or at the least you can not easily find high quality documentation. Google search is almost virtually useless now; AI just hallucinates and flat out lies to you often. Or tells you things that are trivia and you already know it. We kind of lose quality here. It's as if everything got dumbed down.

Meanwhile more and more software is required to build other software. Take mesa. Now I need not only LLVM but also the whole spirv-stack. And shaderc. And lots more. And also rust - why is rust suddenly such a huge dependency? Why is there such a proliferation of programming languages? Ok, perhaps C and C++ are no longer the best language, but WHY is the whole stack constantly expanding?

We worship complexity. The compilers also become bigger and bigger.

About two days ago I cloned gcc from https://github.com/gcc-mirror/gcc. The .tar.xz sits at 3.8 GB. Granted, regular tarball releases are much smaller, e. g. 15.1.0 tar.xz at 97MB (at https://ftp.gnu.org/gnu/gcc/?C=M;O=D). But still. These things become bigger and bigger. gcc-7.2.0.tar.xz from 9 years ago had a size of 59M. Almost twice the size now in less than 10 years. And that's really just like all the other software too. We ended up worshipping more and more bloat. Nobody cares about size. Now one can say "this is just static code", but this is expanded and it just keeps on getting bigger. Look at LLVM. How to compile this beast: https://www.linuxfromscratch.org/blfs/view/svn/general/llvm.... - and this will only get bigger and bigger and bigger.

So, back to the "are compilers your best friend"? I am not sure. We seem to have the problem of more and more complexity getting in at the same time. And everyone seems to think this is no issue. I believe there are issues. Take slackware; basically it was a one person maintains it. This may not be the primary reason, but slackware slowed down a lot in the last some years. Perhaps maintaining all of that requires a team of people. Older engineers cared about size due to constraints. Now that the constraints are less important, bloat became the default.

Re: The compiler is your best friend

#45
> How many times did you leave a comment on some branch of code stating "this CANNOT happen" and thrown an exception?

My code is peppered with `assert(0)` for cases that should never happen. When they trip, then I figure out why it happened and fix it.

This is basic programming technique.

Re: The compiler is your best friend

#46
> A common pattern would be to separate pure business logic from data fetching/writing. So instead of intertwining database calls with computation, you split into three separate phases: fetch, compute, store (a tiny ETL). First fetch all the data you need from a database, then you pass it to a (pure) function that produces some output, then pass the output of the pure function to a store procedure.

Does anyone have any good resources on how to get better at doing "functional core imperative shell" style design? I've heard a lot about it, contrived examples make it seem like something I'd want, but I often find it's much more difficult in real-world cases.

Random example from my codebase: I have a function that periodically sends out reminders for usage-based billing customers. It pulls customer metadata, checks the customer type, and then based on that it computes their latest usage charges, and then based on that it may trigger automatic balance top-ups or subscription overage emails (again, depending on the customer type). The code feels very messy and procedural, with business logic mixed with side effects, but I'm not sure where a natural separation point would be -- there's no way to "fetch all the data" up front.

Re: The compiler is your best friend

#47
post #8
post #4

Earlier quoted context omitted.

A comment "this CANNOT happen" has no value on itself. Unless you've formally verified the code (including its dependencies) and have the proof linked, such comments may as well be wishes and prayers. Yes, sometimes, the compiler or the hardware have bugs that violate the premises you're operating on, but that's rare. But most non pure algorithms (side effects and external systems) have documented failure cases.

> A comment "this CANNOT happen" has no value on itself. I think it does have some value: it makes clear an assumption the programmer made. I always appreciate it when I encounter comments that clarify assumptions made.

Importantly, specifying reasoning can have communicative value while falling very far short of formal verification. Personally, I also try to include a cross reference to the things that could allow "this" to happen were they to change.

Re: The compiler is your best friend

#48

> How many times did you leave a comment on some branch of code stating "this CANNOT happen" and thrown an exception? Did you ever find yourself surprised when eventually it did happen? I know I did, since then I at least add some logs even if I think I'm sure that it really cannot happen. I'm not sure what the author expects the program to do when there's an internal logic error that has no known cause and no defini…

Ideally, if you can convince yourself something cannot happen, you can also convince the compiler, and get rid of the branch entirely by expressing the predicate as part of the type (or a function on the type, etc.) Language support for that varies. Rust is great, but not perfect. Typescript is surprisingly good in many cases. Enums and algebraic type systems are your friend. It'll never be 100% but it sure helps fil…

Yes, this has been my experience too! Another tool in the toolbox is property / fuzz testing. Especially for data structures, and anything that looks like a state machine. My typical setup is this:

1. Make a list of invariants. (Eg if Foo is set, bar + zot must be less than 10)

2. Make a check() function which validates all the invariants you can think of. It’s ok if this function is slow.

3. Make a function which takes in a random seed. It initializes your object and then, in a loop, calls random mutation functions (using a seeded RNG) and then calls check(). 100 iterations is usually a good number.

4. Call this in an outer loop, trying lots of seeds.

5. If anything fails, print out the failing seed number and crash. This provides a reproducible test so you can go in and figure out what went wrong.

If I had a penny for every bug I’ve found doing this, I’d be a rich man. It’s a wildly effective technique.

Re: The compiler is your best friend

#49
Great read. C# has the concept of nullable reference types[1] which requires you to be explicit if a variable can be null and the compiler is aware of this. I would love to see a similar feature in languages like TypeScript and Go.

[1]: https://learn.microsoft.com/en-us/dotnet/csharp/nullable-ref...

Re: The compiler is your best friend

#50

Earlier quoted context omitted.

If such an error happens, that would be a compiler bug. Why? Because I usually do checks against the length of the array or have it done as part of the standard functions like `map`. I don't write such assumptions unless I'm really sure about the statements, and even then I don't.

Do you really have code that's if array.Len > 2 { X = Y[1] } For every CRUD to that array? That seems... not ideal

Yes. Unless there’s some statement earlier that verify that the array has 2 items. It’s quick to do, so why not do it?
Post reply on HN