Live data from Hacker News

A couple million lines of Haskell: Production engineering at Mercury

blog.haskell.org

91–100 of 249 posts

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

#91

I just wanted to leave a glowing comment about Mercury itself, since this is one of the few times I’ll be able to. I’ve been using Mercury for 5 years. In that time, I’ve been able to wire transfer money without having to worry it might disappear (functionally impossible at certain other banks), created hundreds of virtual debit cards each with their own limit and pulling from different accounts, created dozens of ac…

I badly want to try out Mercury, in fact I have an account already, but they don't yet support Zelle. I really hope their national bank charter application gets approved. (They applied for one last December.) Once they support Zelle, I'll drop U.S. Bank, as their app sucks and they don't have an API.

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

#92
post #84
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…

> 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 not readily support it because of its demands for static memory layout.

A very easy place to see this is the difference in decorator types between Rust and other languages like Java. Java's legacy File/reader API has you write things like `new PrintWriter(new BufferedWriter(new FileWriter("foo.txt")))`, where each layer adds some functionality to the base layer. The resulting value has principal type `PrintWriter` and can be used through the `Writer` interface.

The equivalent code in Rust would give you a value of type `PrintWriter>` which can only be passed to functions that expect exactly that type and not, say, a `PrintWriter>`. You would solve this by using a template function that takes a `T where T: Writer` parameter and gets compiled separately for every use-site, thus contributing to Rust's infamous slow build times.

It would be perfectly sane, and desirable for application code, to be able to pass around a PrintWriter value as an owned pointer to a PrintWriter struct which contains an owned pointer to a BufferedWriter struct which contains an owned pointer to a FileWriter struct. You could even have each pointer actually be to a Writer value of unknown size, and thus recover modularity.

In Rust, there is sometimes a painful and very fragile way to do this: have each writer type contain a Box, effectively the same as the Java solution above. This works, except that, if one day you want to add a method to the Writer trait that breaks dyn-compatibility, then you will no longer be able to do this, and will need to rewrite all code that uses this type.

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

#93
post #77
post #65

Earlier quoted context omitted.

>you would have to change the code at all call sites. Actually I think you can just change concrete argument `Foo` to type constraint in Haskell as well using a type class. So the function would be something like `foo :: ToMaybeFoo a => a -> .. ->`. And you would implment `ToMaybeFoo` instance for `Foo` and `Maybe Foo`. Agree that this is more involved than typescript, but you get to keep `null` away from your code..…

Admittedly I don't really understand your construction. But this solution, if it works, doesn't look practical enough that it could be routinely used in practice like Foo|Null could be. By the way, some languages even shorten "Foo|Null" to "Foo?" as syntax sugar. > but you get to keep `null` away from your code... I don't think this would be desirable once we have eliminated null pointer exceptions with untagged unio…

>Admittedly I don't really understand your construction.

It is quite simple. Instead of accepting a concrete type `Foo`, the function is changed to accept types that can be converted to `Option`. Since both `Foo` and `Option` can be converted to `Option`, the existing call sites that passes `Foo` would not require changing.

https://play.haskell.org/saved/g4idq2zv

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

#94
post #83

Earlier quoted context omitted.

(1) Higher order functions are pretty much the same as all the other languages you mentioned, using closure syntax? What was the problem you ran into? (2) In such situations the compiler (type system or borrow checker) is telling you that what you wanted to do has hidden bugs, and therefore refuses to compile. Usually that's a good thing. (3) &dyn Trait

(1) Oh sure, the syntax is easy. Getting it to borrow-check is somewhere between insane and impossible. As I said, I've had friends who are actual Rust experts give up trying. (2) No, it stems from a compiler limitation (imposed in large part by the need for static memory layout), not because there's anything intrinsically buggy about doing this. (3) Look up "dyn-compatibility", for the largest, but not the only, pro…

If your goal is to translate Haskell (or other garbage collected code) pattern-for-pattern into Rust, you will almost certainly burn out.

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

#95
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…

> Rust afficionados are some combination of people who came to Rust early and never learned traditional software design and don't know what they're missing This is definitely not the case and is unnecessarily insulting. The truth is that some things are harder in Rust but a) often those things are best avoided anyway (e.g. callbacks), and b) it's worth the trade-off because of the other good things it allows. Surely…

> is unnecessarily insulting.

I know that it's insulting! And it doesn't make sense, because I generally think Rust programmers are smart people. But right now, it's the only explanation I've got, so it is alas necessarily insulting. So please, please, please give me a better explanation that actually makes sense.

> The truth is that some things are harder in Rust but a) often those things are best avoided anyway (e.g. callbacks), and b) it's worth the trade-off because of the other good things it allows.

This sounds like the seeds of a better explanation, but it needs a lot more to actually suffice. E.g.: why are callbacks best avoided anyway, when they're virtually required for a large number of important programming patterns? (In more technical language: they're effectively the only way to eliminate duplication in non-leaf-expressions. In even more technical language: they're the way to do second-order anti-unification.)

> Surely as a Haskell user of all things you must understand that sometimes making things harder is worth the trade-off. Yeay everything is pure! Great for many reasons. Now how do I add logging to this deeply nested function?

And this is a great illustration of the difference. First, you will seldom find Haskell programmers trying to argue that, actually, things like deeply-nested logging that everyone wants are actually "best avoided anyway." Second, you'll actually get a solution if you ask about them -- in this case, to either use MTL-style, to use a fixed alias for your monad stack, or that unsafePerformIO isn't actually that bad.

BTW, similar to my unpleasant conclusion for Rust above, I have another unpleasant conclusion for Haskell: Haskell is incredible for medium-sized programs, but it has its own missing modularity features that make it non-ideal for large programs (e.g.: >50k lines). But this is a much smaller problem than it sounds because Haskell is so compact that, while many projects can be huge, very few individual codebases will need to approach that size.

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

#98
post #94
post #83

Earlier quoted context omitted.

(1) Oh sure, the syntax is easy. Getting it to borrow-check is somewhere between insane and impossible. As I said, I've had friends who are actual Rust experts give up trying. (2) No, it stems from a compiler limitation (imposed in large part by the need for static memory layout), not because there's anything intrinsically buggy about doing this. (3) Look up "dyn-compatibility", for the largest, but not the only, pro…

If your goal is to translate Haskell (or other garbage collected code) pattern-for-pattern into Rust, you will almost certainly burn out.

It seems to be a common reflex of Rust advocates that, whenever an issue with using the language is asked about, the response is "That's just a garbage-collected code pattern" followed by "and therefore you shouldn't want it." It's happened multiple times in this thread. [Edit: and both the times I was thinking of were from you, so need to weaken that conclusion]

Aside from having vibes of "I've chosen to get hit weekly in the face with a baseball bat, but have learned to like it, and so should you" it's also seldom true.

All three of these examples are also quite easy to do with C and C++. It's not about garbage collection.

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

#99
post #89
post #81

Earlier quoted context omitted.

That's pretty interesting. I was thinking about starting a new pet project and was considering doing it in Rust to learn as I never tried anything with it and after some small pocs I had the feeling it was too verbose to my taste, but wasn't sure it was just me and/or my lack of experience with Rust. Still, wonder if it's still worth it to give a shot considering other positive elements of the language.

Rust is definitely very verbose. I think it's a fine choice -- probably even the best choice -- if you're doing systems code or if performance is your most important feature. If not, I would pass.

> performance

or less ressource hungry software

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

#100
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…

This isn't specific to Rust or Typescript. You can do this in basically any language. Imagine you have to distinguish between unescaped and escaped strings for security purposes. Even with a dynamically typed language, you can keep escaped strings as an Escaped class, with escape(str)->Escaped and dangerouslyAssumeEscaped(str)->Escaped functions (or static methods). There's a performance cost to this, so that's a tra…

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).
Post reply on HN