> [To lib authors] Nobody is obviously in charge in the way a fast-moving production team would mean "in charge," and that creates understandable hesitation around making breaking changes, even when experience has taught us better ways to design these systems. > This is not a complaint about volunteer maintainers. It is simply one of the ambient risks of building serious systems on a smaller ecosystem. And so instead…
A couple million lines of Haskell: Production engineering at Mercury
131–140 of 249 posts
Re: A couple million lines of Haskell: Production engineering at Mercury
#132> 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…
You can't enforce purity on the type level in TypeScript and IIRC neither in Rust.
I believe `const` functions in Rust are actually be guaranteed to be pure, though I haven't followed that feature closely and there may be nuances.
In most languages purity is a norm rather than enforced by static analysis. I definitely agree that it's much safer to assume that an arbitrary Haskell function is pure than it is to assume that of an arbitrary TypeScript function.
Re: A couple million lines of Haskell: Production engineering at Mercury
#133Earlier 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.
Re: A couple million lines of Haskell: Production engineering at Mercury
#134Earlier 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…
Re: A couple million lines of Haskell: Production engineering at Mercury
#135Earlier quoted context omitted.
> 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.…
> why are callbacks best avoided anyway Look up "callback hell". Basically they encourage spaghetti. > you'll actually get a solution if you ask about them You got solutions to your problems didn't you? Macros are a perfectly reasonable thing to use in Rust, even if they are best avoided where possible. Exactly like unsafePerformIO. If you were expecting Rust to work perfectly in every situation... well it doesn't. G…
Ah. I think you're confusing the general idea of a callback with one particular style of use. "callback hell" refers to the deep indentation that occurs when trying to program in monadic style in languages without syntactic support for monads. It was mostly solved by adding async/await syntax, aka syntactic support for the continuation monad. "Callback hell" is not spaghetti in any deep sense, merely syntactically cumbersome.
But a "callback" is a more general term, sometimes a synonym for "function parameter," sometimes for more narrow kinds of function parameter (e.g.: void function, invokable once). Many people will refer to the function argument of the `map` function as a callback, but no-one would refer to that as "callback hell."
Callbacks are quite universal, and most uses do not lead at all to callback hell. I've engaged with this topic a little bit at https://us16.campaign-archive.com/?u=8b565c97b838125f69e75fb... , above the header "Serious Business."
> You got solutions to your problems didn't you?
Mostly no :(
And when I did, I largely got it by figuring stuff out myself, while being told by multiple Rust experts that I either shouldn't care about the verbosity and lack of modularity, or that if I have a problem like "using the interface instead of the implementation" it must be because I'm a Haskeller.
Well, my ultimate solution was to start working on a new product, and to not use any Rust, except for some performance-heavy libraries. With the first product, the market had changed too much by the time we were ready for prime-time, and I'd put somewhere between 25% and 70% of the reason for that delay on our choice to start building new parts of the backend in Rust.
> Macros are a perfectly reasonable thing to use in Rust, even if they are best avoided where possible. Exactly like unsafePerformIO.
Good comparison!
> Despite that it's still probably the best language we have for a surprisingly large range of domains.
I agree with this. I just don't agree that that list of domains has a very large intersection with the set of applications.
Re: A couple million lines of Haskell: Production engineering at Mercury
#136Earlier 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…
I have to sit with the specific example, but a PrintWriter struct which owns a Box and has no generics should be quite doable I'd think?
This was one of the example approaches I gave. This works...at first. The problem is that, if you want to add a new function to the Writer trait which makes Writer no longer dyn-compatible, such as, say, any async function, then you can no longer write `Box` and need to rewrite all code that uses it.
(although you can dig under the hood and specify a pinned-down Future type, covering one kind of awfulness with another)
Re: A couple million lines of Haskell: Production engineering at Mercury
#137> 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…
“Parse don’t validate“ seems like the same idea https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va... You do not need Haskell for that eg it works in Python (via pydantic, attrs data classes)
Re: A couple million lines of Haskell: Production engineering at Mercury
#138Looks to me like you can build amazingly robust pieces of software with functional programming.
However, I am divided.
I have a backend that works in NiceGUI for a product. It does the job. The code is reasonable and MVVM. The most important task it does is connecting to a websocket per customer and consume data to present some analytics.
I will not have a great deal of customers, maybe in the tens or maximum hundreds visiting the website.
I also want REPL and/or hot reload, but I am aware that as I grow features (users admin panels, more analytics, etc) maybe functional programming can do a good job transforming data pipelines.
But Haskell or Ocaml are static. I guess if I want something later that grows and scales and is still dynamic Clojure or Elixir should be a good choice. But at the same time I am afraid that if at some point I need to refactor, things will go wrong.
Currently I use Python with Mypy. All is written in the backend: the frontend is generated by NiceGUI from the backend.
Re: A couple million lines of Haskell: Production engineering at Mercury
#139> 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…
In my backend system I represent users with different variant states to avoid a lot of unrepresentable states.
As for underutilization, I think only functional languages, Rust and C++ support variants and that might be one reason: people just make blobs of state and choose which fields to use instead of encoding states and make some combinations unrepresentable. Javascript, Java, C# or Python do not have Variant types to the best of my knowledge.In Ocaml and Haskell and with pattern matching they are very natural. In Rust with enums, same. In C++, they are so so but still usable compared to the others that do not have.
In my load tests I even went, since I launch thousands of clients, with a boos.MSM to drive the test behavior. One state machine per user.
Re: A couple million lines of Haskell: Production engineering at Mercury
#140Earlier quoted context omitted.
Eli5: Haskell type classes are not classes (like Java or PHP classes); they are comparable to Rust traits -- which are different from PHP traits which are comparable to Java/C# interfaces (with default impls; if you just want contracts you have... PHP interfaces). A fundamental difference is that you can instantiate/implement a type class (or Rust trait) for any* type, compared to interfaces where each class declares…
That conflates type classes with extension types, in type theory. Actually in modern Java you can simulate type classes approach with a mix of interfaces and default methods implementations. In C# you can have the experience more straightforward with extensions types introduced in C#13. Then we have yet another way to approach type classes in Scala, with traits and implicits. And so on, as I haven't yet run out of ex…
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.