Live data from Hacker News

A couple million lines of Haskell: Production engineering at Mercury

blog.haskell.org

121–130 of 249 posts

Re: A couple million lines of Haskell: Production engineering at Mercury

#121

Earlier quoted context omitted.

> some pretty hacky incantations type NewType = T & { readonly __brand: Name }; I don't really see a big problem here?

EDIT: previously the example in the parent comment was: type NewType = T & { __brand__ : Symbol } --- This seems wrong; the type spelled `Symbol` refers to the boxed interface for symbols[0]. I suspect you meant to write `unique symbol` there, but it can't be used in that position. I'm not sure if `NewType` in your comment is supposed to stand in for a specific newtype (in which case it probably doesn't need to be ge…

I was on the Tube and wanted to get my reply in before entering a tunnel. I already corrected it whilst I was underground.

I think the point still stands - is this really a big problem? I guess I couldn't recite the syntax from memory, because I usually use a utility type for this

Re: A couple million lines of Haskell: Production engineering at Mercury

#122
post #72

Earlier quoted context omitted.

That is a very unusual Rust experience. I find "application code" very pleasant to write in Rust. Of course there are things that aren't as ergonomic in Rust as in other languages (e.g. callbacks) but that's true of pretty much any language.

I have heard this reaction from others before. One of the Rust expert friends I consulted with told me "I'm not convinced you're not trying to write Haskell-style code in Rust;" I told him the patterns I was struggling with were both trivial and common in Java. The things I found quite difficult or impossible in Rust were to me pretty basic patterns for modularity and removing duplication that it's really shocking th…

The most confusing thing that can happen with something like tokio is the failure-at-distance you can get from writing a non-Send future somewhere in the depths of a call stack and then having to figure out why your top-level spawn isn’t working. There’s a non-default lint I highly recommend turning on when working with tokio: clippy::future_not_send. Forces all your futures to be Send, unless you opt out, which really helps keep the reasoning local when you run into errors.

FWIW I write primarily rust, and I do not agree with the advice given in your second point, so I’d take it with a grain of salt were I you.

Re: A couple million lines of Haskell: Production engineering at Mercury

#123
post #92
post #84

Earlier quoted context omitted.

> difficult or impossible in Rust were to me pretty basic patterns for modularity Many things are plainly not permitted, either because the borrow-checker isn't clever enough, or the pattern is unsafe (without garbage collection and so on). Many functional/Haskell patterns simply can not be translated directly to Rust.

That "and so on" is doing a lot of work. You may accept rejecting garbage collection as a reasonable trade-off, but the bulk of the cost is coming from a much more aggressive tradeoff Rust is making with is at odds with the goals of most application code. A deeply-baked assumption of Rust is that your memory layout is static. Dynamic memory layout is perfectly compatible with manual memory management, but Rust does n…

You can usually manage dyn compatibility issues in my experience by writing a base trait that is not dyn compatible and then an Ext trait that is, which is auto implemented for all implementers of the base trait. You see this pattern all over the place, including with several of the buffer traits you mentioned.

Mostly, this works out well enough: dyn compatibility pretty much just insists your methods can in fact work with just a reference to an unknown variant of the type.

Re: A couple million lines of Haskell: Production engineering at Mercury

#124

Earlier quoted context omitted.

EDIT: previously the example in the parent comment was: type NewType = T & { __brand__ : Symbol } --- This seems wrong; the type spelled `Symbol` refers to the boxed interface for symbols[0]. I suspect you meant to write `unique symbol` there, but it can't be used in that position. I'm not sure if `NewType` in your comment is supposed to stand in for a specific newtype (in which case it probably doesn't need to be ge…

Right. Besides getting this incantation right, as gp did only after editing their comment, you also have to cast to create values of NewType. But generally you want to avoid casting in typescript if you care about type safety, so now everybody has to remember the rule that in this particular circumstance it's the right thing to do. There are helper libraries to ease this (zod supports branded types, I think?), but I…

[deleted]

Re: A couple million lines of Haskell: Production engineering at Mercury

#125
post #72

Earlier quoted context omitted.

I have heard this reaction from others before. One of the Rust expert friends I consulted with told me "I'm not convinced you're not trying to write Haskell-style code in Rust;" I told him the patterns I was struggling with were both trivial and common in Java. The things I found quite difficult or impossible in Rust were to me pretty basic patterns for modularity and removing duplication that it's really shocking th…

The most confusing thing that can happen with something like tokio is the failure-at-distance you can get from writing a non-Send future somewhere in the depths of a call stack and then having to figure out why your top-level spawn isn’t working. There’s a non-default lint I highly recommend turning on when working with tokio: clippy::future_not_send. Forces all your futures to be Send, unless you opt out, which real…

Thanks! Very helpful. Pain is still there, but surfaced early.

Re: A couple million lines of Haskell: Production engineering at Mercury

#126

Earlier quoted context omitted.

EDIT: previously the example in the parent comment was: type NewType = T & { __brand__ : Symbol } --- This seems wrong; the type spelled `Symbol` refers to the boxed interface for symbols[0]. I suspect you meant to write `unique symbol` there, but it can't be used in that position. I'm not sure if `NewType` in your comment is supposed to stand in for a specific newtype (in which case it probably doesn't need to be ge…

Right. Besides getting this incantation right, as gp did only after editing their comment, you also have to cast to create values of NewType. But generally you want to avoid casting in typescript if you care about type safety, so now everybody has to remember the rule that in this particular circumstance it's the right thing to do. There are helper libraries to ease this (zod supports branded types, I think?), but I…

Yeah we just use Zod’s branded type and that pretty much handles it. No casts, use a refinement then slap a brand on it.

Re: A couple million lines of Haskell: Production engineering at Mercury

#127
I worked on a somewhat similar system in a fringe language (Scheme, and later, Racket) that got huge, but that remained manageable and high-velocity over a long period by a small team.

We didn't create many bugs, and usually functionality could be added very rapidly (e.g., we were the first to achieve a certain certification for hosting sensitive data on AWS).

Though occasionally functionality had to be added more slowly, because we had to write from scratch what would be an off-the-shelf component in a more popular platform. But once we did it, it worked, and we were back to our old velocity, and not slowed by the bloat and complexity of dozens of off-the-shelf frameworks. We could also adapt rapidly because we controlled a manageable platform, which is how we were able to move fast to AWS when there was a need.

The system also had some technical bits of architectural secret sauce from the start (for complex data, and Web interaction), which enabled a lot of rapid development of functionality, and also set the tone for later empowering smartness.

One difference with our system, from the Haskell fintech, was that our team size was very small (only 2-3 software engineers at a time, and someone who managed all the ops). So we didn't have the challenges of hundreds of people trying to coordinate and have a coherent system while getting their things done. Instead, there was usually one person doing more technical and architectural changes to the code, and a prolific other person doing huge amounts business logic functionality for complex processes.

With careful use of current/near-term LLM-ish AI tools, software development might find some related efficiencies of very small and incredibly effective teams. But the model that comes to mind is having a small number of very sharp thinkers keeping things on an empowering and manageable path -- not churning massive bloat to knock off story points and letting sustainability be someone else's problem.

Re: A couple million lines of Haskell: Production engineering at Mercury

#128
post #110
post #87

Earlier quoted context omitted.

I think Rust would be fine for application code if it kept the borrow checker, but had greater allowance for dynamically-sized variables, or even garbage collection. The reason calling things through an interface is so tough in Rust is because doing so requires having a pointer to a value of unknown size, which involves either heap allocation or alloca(), neither of which are very happy in Rust. Many of the other thi…

Exactly, and that is why after Rust's break into mainstream, several garbage collected languages are trying to mix advanced type systems with their approach to garbage collection (GC, RC, a mix of both, whatever). This is also not strange for those in the Rust community with type systems experience, hence the Roadmap 2026 proposals for a more ergonomic experience. Thus we have Linear Haskell, Swift 6 ownership, D own…

Interesting. But when I search `"roadmap 2026" rust`, I only get results for the video game.

Am familiar with Linear Haskell (and actually went on a walk through Tokyo with one of the authors just a few months ago). IIRC: still no resources allocated to add the things that would actually make it useful. Had not been aware of most of those, except the dependently-typed ones. Cool to know about the others. Yay linear types becoming known.

You seem interesting, and I'm curious about your background now. All I can find so far is that you're from Europe and used to lead C++ in some major corporation.

Re: A couple million lines of Haskell: Production engineering at Mercury

#129
post #55
post #7

> Haskell gives you tools to encode these incantations in types so they cannot be forgotten. This is, for my money, the single most valuable thing the language offers a production engineering organization. Haskell is admittedly, probably the most powerful widely (or even somewhat widely) used language for doing this, but this general pattern works really well in Rust and TypeScript too and is one of my very favorite…

> works really well in Rust and TypeScript too And of course Rust and TypeScript were heavily influenced by Haskell... they just don't mention it and call things differently, to avoid the "monads are scary, I need to write a tutorial" effect. Though it's less about monads and more about things like type classes. Imitation is the sincerest form of flattery.

Rust has typeclasses so that can't be it.

Re: A couple million lines of Haskell: Production engineering at Mercury

#130
post #92

Earlier quoted context omitted.

That "and so on" is doing a lot of work. You may accept rejecting garbage collection as a reasonable trade-off, but the bulk of the cost is coming from a much more aggressive tradeoff Rust is making with is at odds with the goals of most application code. A deeply-baked assumption of Rust is that your memory layout is static. Dynamic memory layout is perfectly compatible with manual memory management, but Rust does n…

You can usually manage dyn compatibility issues in my experience by writing a base trait that is not dyn compatible and then an Ext trait that is, which is auto implemented for all implementers of the base trait. You see this pattern all over the place, including with several of the buffer traits you mentioned. Mostly, this works out well enough: dyn compatibility pretty much just insists your methods can in fact wor…

Good suggestion. I think started doing that kind of thing towards the end of my days with Rust. It's been close to a year now, and don't remember how well it worked out.
Post reply on HN