HNHacker News
TopNewBestAskShowJobs

nh2

3,613 karma · joined March 29, 2014

https://github.com/nh2

[ my public key: https://keybase.io/nh2; my proof: https://keybase.io/nh2/sigs/Duv6hcXOkjZU5KWzJ8d01yeD7QKmhG9QpVMOb4ZMkeU ]

submissionscomments
nh2··on Deutsche Bahn "joke" is no longer funny
Just go to https://delaybahn.com/en and build your opinion on the data.

https://delaybahn.com/en/?fromId=A%3D1%40O%3DK%C3%B6ln+Hbf%4...

Click the "+30 min (28/30 days)" tags to see the delays of the last month.

nh2··on Go Concurrency Distilled
I haven't found a Go concurrency thing yet that hasn't long-existed in Haskell before.

I also find Haskell's concurrency in practice much easier to reason about than Go's, let me do a pitch:

In Haskell you can just fork a thread and block till it's done. Threaded, "async" logic just looks like blocking serial code (but isn't blocking). I feel like in typical channel-based Go code I have to jump and scroll a lot in the code because of all the message-passing instead of block-scoped "blocking-style" variable use, and that this makes it hard to conclude whether the whole thing terminates or deadlocks.

In Haskell, channels are considered low-level concurrency primitives you should only use when you have no clean high-level primitives for it. This is because they are not "structured" concurrency: When you send something into a channel, it is gone out of your scope, and you now need to track in your brain where it is, and who should consume that thing in the right way ("message-passing").

For example, in Stolon, a high-availability Postgres orchestrator written in Go (https://github.com/sorintlab/stolon), I found the logic for failover with multiple channels and various timeouts very difficult to reason about when investigating failover bugs. I'm pretty sure that would read much easier in Haskell (see below how).

In Haskell, you can start 2, or N, things in parallel, and easily wait till they are done. You can invoke parallel `map` easily.

    results <- mapConcurrently f mylist
If f throws on any element, the whole map throws, and other threads get cancelled automatically as expected.

You can get bounded, steaming parallelism, easily.

You can set time limits to function calls writing

    timeout 1000 (myIoFunction ...)
You can cancel any thread or computation, at any time. The same timeout function can cancel blocking IO operations, such as reading from the terminal or sockets, without having pass around `Context` objects like in Go (which, if you forget it, just makes things hang or deadlock).

You wrap the 2 words "timeout 1000" around your function and done.

Concurrency _composes_ in Haskell. You can write

    res :: Maybe (Maybe a) < timeout a (timeout b (myIoFunction ...))
and the returned type tells you cleanly at which level the cancellation occured (no mixing into the same `error` type.

You can build trees of parallel operations that live and die together.

And there are no data races (because mutability is a very explicit thing), and I'm not even mentioning STM here (which allows you to do database-style transactions across variables) because that's already pointed out in another post.

As a composed example, in Haskell you can write:

    timeout 1000 (race (downloadUrl ...) (forever (putStrLn "Still loading ...")))
and that will do exactly what you think it should, with correct Ctrl+C cancellability, and good developer ergonomics.

If you enjoy concurrency, give Haskell a shot!

nh2··on Go Concurrency Distilled
Haskell had preemptive M:N green-threading before Go was invented.

I believe Go didn't originally have it, and added it in 2020, 14 years after Haskell.

nh2··on Evolving programming languages in the AI era
Please do the world a favour:

> integer literals become i32, float literals become f32

Don't use the shitty small sizes that have introduced countless bugs over the last decades, as a language default.

Especially if you care about correctness.

i32 is all over the examples.

Use 64 bits, like Python and Haskell; even JavaScript and thus TypeScript got floats right defaulting to f64.

nh2··on PipePipe: NewPipe hard fork implementing SponsorBlock
> Set a sleep timer for bedtime listening

Killer feature!

nh2··on Writing Parquet files using Haskell
There are companies like IOHK or Mercury that hired large swathes (50?) of Haskell programmers in relatively short time, so I don't think bus factor of the programmer market is an issue. Of course it's still an issue if a company hires 1 or 2 Haskell devs only, and those depart.

> from the past three years [..] the latest I’ve seen were Opus-4.6 and GPT-5.5 struggle a lot with standard GHC Haskell

If your experience is from years ago, I totally get that. For my standards, LLMs were pretty bad before 2026.

I haven't tried any serious LLM Haskell before 2026-02, because it was just not a timesaver.

I had good experiences with Opus-4.6 already on Haskell, such as it having detail knowledge on async exceptions, allowing it to immediately pinpoint difficult bugs that me and other experts missed, e.g. https://github.com/snoyberg/conduit/pull/530

But most of my experience is from Opus >= 4.8. I found it excellent at writing difficult TemplateHaskell and Generics implementations, such as oneshotting Postgres JsonPath queries derived from lens-like field accessors on Haskell types.

Note it's still not great at writing general high-quality "engineering" stuff, e.g. Opus 5.0 writes functions that refer to things out of their scope, such as:

    requireFooOr400 :: MyMonad m => Maybe Foo -> m Foo
    requireFooOr400 x = case x of
      Nothing -> throwHttp400 "Cannot perform [very specific action from the caller]: the inputs have no foo"
      Just foo -> return foo
However, that's not a lack of Haskell understanding, and it does that for all languages, e.g. in Python it keeps writing implementation details into API docstrings instead of function body comments.
nh2··on Show HN: Avoid smooth spinners, use low-FPS spinners
> I’ve only ever heard non-frontend developers call them “spinners”

Such as these non-frontend devs?

https://getbootstrap.com/docs/5.3/components/spinners/

nh2··on Show HN: Avoid smooth spinners, use low-FPS spinners
Probably no.

Does "Square ring" really look jerky/retro?

nh2··on Show HN: Avoid smooth spinners, use low-FPS spinners
I'm not describing anything about screen refresh rates, which I'm pretty sure are 60 Hz on my laptop even when the CPU+GPU are at 0% and don't draw anything. Compute != monitor refresh.
nh2··on Show HN: Avoid smooth spinners, use low-FPS spinners
The damage tracking doesn't seem to be effective at reducing the computational cost. Measurement:

Opening https://nh2.me/low-fps-spinners/smooth-spinner.html it doesn't matter if I zoom the spinner to 25% or 500%: Despite the 400x difference in pixels redrawn, the "GPU Process" CPU usage in the "Browser" tab of the Chromium Shift+Esc task manager is equally high.

It only scales down the cost when reducing the FPS.

nh2··on Show HN: Avoid smooth spinners, use low-FPS spinners
But it does. The GPU rerenders your entire screen with all its pixels, even if you change only one. The only thing you can do to avoid that is to not ask the GPU to render a frame. For that you need to reduce FPS.
nh2··on Show HN: Avoid smooth spinners, use low-FPS spinners
Avoiding unnecessary browser repaint of the rest of the page is a basic technique and often already done correctly. If people don't do that, it's just ultra-bad.

But do really have the CPU/GPU go idle, you need to not use them; the only solution to that is to show still images, aka reduce FPS.

nh2··on Writing Parquet files using Haskell
Hiring for Haskell is very easy, unless you need to hire more than 100 people on the spot, or don't offer remote. I've been doing Haskell hiring for 10 years, with more than 50 interviews, the applicants are always great, each time we had to pick the best of multiple candidates.

Cannot confirm functional programming being inefficient token-wise, or worse at being generated than other paradigms. Claude is great at Haskell.

The difficult parts about Haskell, such as understanding type checker error messages, are gone thanks to LLMs.

Type-safe, side-effect free code degrades correctness a lot less under heavy LLM action in my experience.

nh2··on Show HN: Avoid smooth spinners, use low-FPS spinners
I don't think so. This is just what happens when you redraw the screen.

Doing it 30x more frequent than needed costs more, no matter if it's only 1 pixel.

This problem is not isolated to me or to just one machine. Check out the chromium bug about the tab loading spinner, for example. It is a "simple" spinner as well.

Or the Zed editor, which spams screen redraws even when nothing changes at all, so it has high GPU usage for still picture.

nh2··on C++26: Trivial infinite loops are no longer undefined behaviour
Of course the infinite loop should run as expected.

It breaks the most fundamental debugging expectations (such as "delete code until problem disappears") if the fundamental, minimal building blocks of a language, when on their own, do random rubbish.

To understand a program that does something, better first understand a program that does nothing.

As a fan of sensible analogies:

You put a salad bowl with vinegar into the fridge and notice that when you do that, the fridge stinks afterwards. You try again without the vinegar, then without the salad. In C++ world, upon receiving the empty bowl, the fridge detonates ("it is not useful"), blowing up your house. That is not OK.

nh2··on Jemalloc 5.4.0
Generally yes, but the wording of "instantly" begs for the following pedantic remark:

This is controlled by jemalloc settings `dirty_decay_ms`, `muzzy_decay_ms`, and their interaction with `background_thread`.

`dirty_decay_ms` currently defaults to 10 seconds, so it's not that instant.

That is important e.g. for single-threaded programs that start other programs, such as my Python example: If it starts a subprocess before the 10 seconds elapse after `free()`, Python (and jemalloc) do not run, and get no chance to return memory to the OS.

In such cases, either enable `background_thread`, or set the `_decay_` values to `0` to ensure immediate return to the OS upon `free()`. (This costs some performance.)

See e.g. https://github.com/jemalloc/jemalloc/issues/2688

nh2··on Jemalloc 5.4.0
Memory allocation behaviour has visible impact also for users of managed languages, and the behaviour of software for end users.

In our Python program, a bit of numpy processing of large pictures led to 100 GB not being returned to the OS by glibc's default allocator and the machine running out of memory shortly after. With jemalloc's reliable memory return settings, those problems disappear.

nh2··on Ubuntu 26.10 completes transition to Rust-based coreutils
The point is that such bugs shouldn't exist in the first place.

Using recursion on unbounded inputs on a programming language that doesn't support that (which are most) is an extremely classical mistake that really should be known to all programmers, especially those of low level languages that care about safety.

Every time you call something recursively you should be thinking "how deep is this?".

nh2··on The case against JPEG XL
Do you have an example comparison?
nh2··on Terpstra Keyboard
> oh, it seems that the lumatone is the commercialized version essentially

From the same makers or others?

On the website I can find no info whether this project is concluded or not.

nh2··on The creator of Jujutsu has joined ERSC
Thanks, the animation is gone now and the problems are gone as well.

Good choice in my opinion, as I hadn't even noticed the animation existed until I looked for something that might make the scrolling slow.

nh2··on The creator of Jujutsu has joined ERSC
This website has unbearable scroll performance on mobile.

Is it the animation at the top?

nh2··on Stripe said to abandon $50B pursuit of PayPal
Justified fear. A former flat mate of mine reported an unexpected outgoing transfer to his UK bank and they blocked his account for 3 months. He was essentially unbanked. He had to pay his rent in cash after getting it from an ATM with a credit card (which cost a lot extra and hit limits). Resolving it took months even though we lived right next to a branch of that bank, where he went every day.
nh2··on We found a division by zero bug in FFmpeg with a vibecoded fuzzer
In Haskell they are a little less annoying. It is just easier to reason about (including proving) pure functions.
nh2··on Maiao: Gerrit-style code review workflow for GitHub, GitLab, Gitea, others
Code review tools should really compare with reviewable.io, which supports proper review of every-commit in a PR, with force pushes, making sure all changes get read, and comment sign-off and disposition, making sure no comment remains unaddressed.

In contrast to Gerrit and Phabricator, it needs not "Change IDs" inserted in your commits (easier workflow just using git) and "just works" to review whole branches.

It seems to me that "1 PR = 1 commit = 1 review" and "stacked PRs" workflows are just workarounds for not properly having implemented that as Reviewable has. Am I not seeing something?

Reviewable's main drawback is being for Github only and not open source.

nh2··on Malicious Rust crate Arrayref runs a build-time payload
You have just reinvented "Safe Haskell" from 2012.

It guarantees that pure functions are pure.

https://www.microsoft.com/en-us/research/publication/safe-ha...

https://downloads.haskell.org/ghc/latest/docs/users_guide/ex...

nh2··on Stripe to Buy OpenRouter for $7B
> Within certain limits and certain models

For those that hadn't read yet (on https://aws.amazon.com/service-terms/):

"Certain models" means "only AWS's own trained models" (Nova etc), so not e.g. Bedrock Claude or GPT.

"Certain limits" means they don't defent if the input to the model is the IP.

nh2··on A Preview of DuckDB v2.0
If you merge PRs that have commit mesages like this, it's easy to arrive at 10000 commits in 6 months:

    rename to NodePointer instead
    format
    Revert "format"
    Revert "rename to NodePointer instead"
    rename to OptionalNodePtr
    woops
    update comment
    slot renames
    more renames
Source: https://github.com/duckdb/duckdb/pull/23605

If every Ctrl+S is a commit, it'll go up fast.

"woops"!

nh2··on Self hosted email continues to steeply decline
I agree Spam is the hardest. Setting up a mailserver is pretty easy today, e.g. with NixOS-simple-mailserver you can do it with a few lines and a few minutes of config in your DNS provider, and maintenance is minimal.

But I self-host email for ~20 years, and I have not managed to get rid of Spam without not also getting false positives.

I first used SpamAssistant, in later years Rspamd, but I feel like they are just not good enough. Also I find Rspamd config pretty incomprehensible.

Hosted email like also does not solve this, e.g. GMail filters way much (e.g. important company correspondence leading to orders almost being lost because they landed in our GMail spam, so I had to turn off the spam filter entirely).

nh2··on Self hosted email continues to steeply decline
Not in my experience.

My self-hosted email filters too little spam, while GMail etc filter too much (e.g. important company correspondence leading to orders almost being lost because they landed in our GMail spam).

Page 1 of 25Next →