Live data from Hacker News

A couple million lines of Haskell: Production engineering at Mercury

blog.haskell.org

231–240 of 249 posts

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

#231
post #100

Earlier quoted context omitted.

What you cannot do is compile-time safety guarantees, and in languages like Rust type system isn't strong enough to do some advanced compile-time guarantees (via types). So no, you cannot do this in basically any language (unless you turn it into Haskell).

Can you give some examples? What is Haskell’s type system capable of that you can’t express in rust?

Caveat emptor, I'm not a Haskeller, just an admirer. But Haskell let's you run functions over the type system itself very elegantly, so you can have a type that is derived from a type that composes three types that are derived from several functions' declared runtime behavior. This kind of type inference can provide arbitrarily rich information about anything in the entire program, letting you encode more than just range-types etc at compile time.

This comes with some design drawbacks. I think Rust's borrow checker would be implementable but unreasonable in Haskell: Haskell already does lazy-evaluation on types to enable its arbitrary depth of type expressivity. But the borrow checker also wouldn't really make sense for Haskell because the default programming model uses a GC. I think Linear Haskell might be a kind of Rust-in-Haskell, though.

Again, caveat emptor.

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

#234
post #100

Earlier quoted context omitted.

What you cannot do is compile-time safety guarantees, and in languages like Rust type system isn't strong enough to do some advanced compile-time guarantees (via types). So no, you cannot do this in basically any language (unless you turn it into Haskell).

Can you give some examples? What is Haskell’s type system capable of that you can’t express in rust?

I've spent long time writing Haskell and now write Rust professionally, so let me tell you something. The stuff where you really want to "program in types" lives beyond Haskell, in languages like Agda, Idris, some HoTT stuff etc. anyways.

In principle the more developed the type system is – the closer it to not distinct between types and values. Caviat is that its "type inference" gets worse.

So, in those more developed languages, you could have type-level proofs (guarantees) that your calculator produces correct results, as a proof, as theorems. That 2+3 will be 5, not as runtime test assertion, but as a theorem, that no other result is possible no matter what happens. Or that your parser will never fail on valid JSON etc. but nobody guarantees it's going to be a pleasant thing to write, maintain, and debug. And compile times will probably be terrible.

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

#235
post #232

I wish people'd report metrics like "40 type classes, 5000 functions, 300 instances, 20 monads, 14 functors, ..." And such instead of "lines of haskell".

I wish people'd report metrics like "transactions per second, 99.x% availability with N secs/mins p99 latency, ..." when describing how practically useful and how effective a real-world banking/transaction system is, which further proves the practicality of the programming language.

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

#236
post #224
post #140

Earlier quoted context omitted.

> Actually in modern Java you can simulate type classes approach with a mix of interfaces and default methods implementations. Can you? The beauty of traits/type classes is that you can attach them to any type - in a world where 90% of the functionality of any piece of software is supplied by dependencies - external types which you cannot change - this is a vital feature.

Simulate was the word, not map 1:1 the exact experience. It isn't pretty, but one can try to achieve a similar approach. https://godbolt.org/z/TjPha3obs Failing that, there are always Clojure, Kotlin and Scala on the JVM, which expose language features to achieve the same, which you naturally can mix and match with plain old Java.

Why the reference to “modern Java” then? Writing adaptor classes is not “modern Java” nor does it involve using “a mix of interfaces and default methods implementations”.

I was responding to your original claim and I’m well aware of such facilities in both Kotlin and Scala - having used both extensively. I was genuinely curious if the latest Java was in the process of adding support for trait/typeclasses - so I don’t understand why you’d bother to reply with something that completely changes your original claim.

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

#238
post #53

I don't believe I'm the target market (I'm plenty happy with my small CU), and seeing their billboards makes me want to never use them BUT: seeing this post and their culture and that they use Haskell is kinda changing my mind.

What’s wrong with their billboards?

Typical SF garbage billboards with either “AI” or something they think is clever but isn’t.

Mercury is pretty tame by comparison I’ll admit.

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

#239

Earlier quoted context omitted.

No. That is not what I am saying. I am saying there are contexts where you do not get value out of it and you can potentially decrease your productivity because it is more rigid. You have examples above if you want to read through. In no way I am saying it is useless. I just see niche uses for it compared to alternatives.

I read most of your comment as phrasing the things that make rust unique as being additional burdens relative to what you would prefer, which is fair, but often they are what I appreciate about the language. Explicit result types are a great example. Rigidity is a trade off: it can make initial development slower but refactors significantly easier, just as an example. I don’t think any of your examples show it to be…

> but also higher level things where you want performance but don’t want to worry about memory safety).

Well, at the cost of having a straight jacket. Result without option for exception handling is an example. You need to refactor all the way up if you notice that suddenly when refactoring you needed a Result bc a new error appears that could not happen before or you need to preventively spam Result everywhere since the start. You need to handle those all the stack up. The borrow checker is also rigid. I do know why it exists. I understand its value. I am just talking about the toll it imposes while coding, and wondering if it is a good default (I think most of the time it is not, but when you need it, it is invaluable, however these cases are a minority).

Another insight is that when you really go low-level, most of the time you are working with unsafe interfaces probably. At that time, you are using unsafe and now you have to satisfy Rust's borrow checker. How? By hand. So you lost part of the value proposition.

Can you recover it? Yes. How? By reviewing that code. But if I have to review that code, what is better from choosing a language (in this situation I mean, there are situations where Rust is the better choice) where I can understand the invariants in unsafe code better and anyway I have linters and a lot of established guidelines that are not difficult to follow? And by not difficult to follow I mean they are embedded in tooling like clang-tidy, not that I can follow because I know a lot.

So for me it is not so obvious at all, especially in the presence of quite a few unsafe blocks. If you want it safe, at that time, you are starting to compete with other unsafe languages: you need human review anyways... if there is tooling in Rust for unsafe blocks (I can imagine there could be something), that improves things competitively for Rust in unsafe blocks. But if you need careful review, you are stuck again in the non-magical real world: things are safe if you checked absolutely everything.

> Rigidity is a trade off: it can make initial development slower but refactors significantly easier, just as an example.

That is certainly true. It is also true that in areas where you put this extra effort and quickly refactor, it makes things more difficult.

Refactoring, if you mix it with unsafe, needs a much more strict review than just pretending things are safe because you refactored and put things behind an unsafe interface and present it as safe.

I am not convinced at all this is what you need in most scenarios. The productivity impact is relatively high IMHO.

OTOH, if I really want correctness (real correctness!) but not absolute full speed, I think I can reach to Ocaml (very practical) or Haskell (this one is also a bit too rigid actually sometimes).

So I am left in a situation where Rust just seems to be appealing for places where the most absolute memory safety is needed. But memory safety is still a composed characteristic of a running program: you have to take into account unsafe interfaces, bindings, etc.

So the only way to get real safety is anyways to review everything (if that is what you really want to deliver), probably proving your code, which anyway requires human intervention. Did we ever (even if less often) see crashes for invariant violations in code advertised as safe in Rust? Certainly yes. I acknowledge it is usually an improvement, but still not a guarantee.

So if it is not a guarantee and I can reach other tools where anyway the guarantee is there through GC or other mechanisms and where it is not I am equal to Rust, then, why bother?

Probably the only place where I see Rust appealing is where you need both max. performance and absolute memory safety (but you will still need the kind of reviews I mentioned if you spam unsafe and interact with bindings anyway). Those are niche cases, not the norm.

I see like a suboptimal choice to write much of the application code in Rust, even when you need speed, compared to C++. C++ has very good tools for compile-time programming, expression templates, good warnings and linters, a big ecosystem and it is way more voluble (exceptions and results can be used, invariants in unsafe code are easier to follow since a borrow checker does not need to be satisfied "by hand").

So I am not sure at all Rust is the reply for a more or less mainstream general-purpose application language.

I think that Rust is valuable in things like OS hardened interfaces, etc. But even there CVEs were found! Right? https://news.ycombinator.com/item?id=46302621

There is no magic bullet here, but I do know that when coding in Rust, the productivity toll I am paying is not negligible and I can reach for tools and techniques that make me very close or equal to that productivity.

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

#240

It's a double-edged sword. Two million lines is a major feat. It's also represents a significant maintenance burden. The advantages to Haskell are theoretically obvious. The downsides are harder to intuit. The temptation is to model _everything_ as types. The codebase itseld becomes a _business specification_, not an application. Every policy change is a major refactor (some of which are shockingly high-touch thanks…

You can do a great job of navigating that as long as you have some experienced engineers with taste building the core pieces. You can't have it all, but you can have a lot . I interned at Jane Street years ago and they seemed to do a great job of walking that line (in OCaml rather than Haskell, but same difference). They moved remarkably quickly despite working in an area with a lot of inherent complexity and where r…

Any particular stories illustrating that that you can share?
Post reply on HN