The Ultimate Customer Requirement: Haskell
Description
A screenshot of a tweet from the user 'chreke' (@therealchreke). The tweet, posted on April 18, 2024, presents a satirical 'uncomfortable truth'. The text reads: 'Uncomfortable truth: The customer doesn’t care about your “product”. They don’t care about your “solution”. The only thing they care about is whether it is written in Haskell or not'. The image has a clean, standard Twitter UI layout with black text on a white background. The humor is deeply ironic, playing on the well-known reality that customers are almost always completely indifferent to the underlying technology stack of a product. It satirizes the tendency of some developers, particularly those in niche communities like functional programming, to become so enamored with their chosen tools that they jokingly project their own priorities onto the end-user, creating an absurd scenario where a customer's primary concern is the use of a specific, academically-oriented language like Haskell
Comments
60Comment deleted
Of course the customer cares if it's written in Haskell. How else would they know to file a bug report titled 'Unexpected lazy evaluation caused the heat death of the universe'?
Enterprise sales funnel these days: Lead → Demo → Realise it’s written in Haskell → Closed-Won - because nothing screams “low risk” like a monad stack with fourteen type parameters
After 20 years of explaining monads to stakeholders, I've finally realized the real monad was the friends we confused along the way - and they still just want their CSV exports to work
This perfectly captures the eternal struggle between engineers who want to rewrite everything in their favorite pure functional language and stakeholders who just want the damn feature shipped. Spoiler: The customer actually cares whether it works, scales, and doesn't bankrupt them in cloud costs - but sure, let's have another 3-hour architectural debate about monads and type safety while the competitor ships in Python
Enterprise RFP: “Must be scalable, secure, and written in Haskell” - translation: we’ll trade ROI for IO, but only if it typechecks
Pitch: “Haskell gives you compile-time guarantees.” Client: “Great - can you also give us compile-time candidates?”
Haskell: Where customers demand purity until their monadic business logic hits production impurity
Uncomfortable truth: The customer doesn't care about your "product". They don't care about your "solution". The only thing they care about is whether it is written using microservices or not 🥺 Comment deleted
Thia is so λx.λy.x Comment deleted
This is so... "K"? What? Comment deleted
True This is so true Comment deleted
ah Comment deleted
I love untyped lambda calculus 😈 Comment deleted
can't stop obsessing over how 0 = [] = false holds both in JavaScript and lambda calculus Comment deleted
At least not in Rust Comment deleted
Rust is the Haskell of the future. Comment deleted
Rust is not Haskell, at all Comment deleted
maybe Idris, but not Rust, totally not Rust Comment deleted
Rust is haskell brother that is just a little less crazy Comment deleted
They're obviously very different languages, but Rust took a lot of inspiration from Haskell for the functional aspects of the language. Comment deleted
I'm afraid I don't understand. Rust looks as far from functional programming as possible to me. I'd concede that it likely inherited typeclasses from Haskell, which certainly influenced some decisions, but it doesn't have monads and generally makes writing code in functional style quite unfriendly, exactly because it doesn't have monads Comment deleted
Doesnt have monads? The two most commonly used types in Rust are the Option and Result types. Both of which are monads. Comment deleted
They might be monads by a formal definition, but they don't really compose like monads typically do. There's ? that kinda makes it look like Rust has do notation, but this illusion breaks down when you use iterators of results or something similar Comment deleted
I should probably have said that Rust has monads but not the concept of a monad. There isn't even a generic return, much less a bind Comment deleted
Using iterators of either options or results is about as seamless as you could possibly want. Largely because they will seamlessly convert themselves into iterators. Comment deleted
There's try_find, try_collect, try_for_each, try_reduce, and I think many more methods to come, because iterators and results simply don't compose unless std explicitly hacks that in Comment deleted
Also, async functions are kinda monadic too, and yet they're their own beasts, different from every other monad in existence Comment deleted
Don't even get me started on async iterators... Comment deleted
I lack the explicit functional programing experience to understand exactly what you mean by compose in this context. But in most cases you'll just flatten, flat_map, or map them. Comment deleted
Say I have impl Iterator<Item = Result<T, E>> and I want to find the first T that satisfies a predicate, but also abort on first error. Imperatively, I want for result in iterator { let value = result?; if predicate(value) { return Ok(Some(value)); } } Ok(None) How do I implement this in functional style? Comment deleted
In that case you'd use the find_map function. It would definitely be a little awkward. let collection: Vec<Result<T, E>> = ... // For example let result: Option<Result<T, E>> = collection.into_iter().find_map(|result| match result { Ok(t) => { if t == value {Some(Ok(t))} else {None}}, Err(_) => Some(result) }); Comment deleted
oof Comment deleted
Result<Option<T>, E> would probably be more idiomatic, but I see what you mean Comment deleted
I just think that this would look a lot better in a language with first-class monad support Comment deleted
I'm certain there's probably a much better way to do this. I'm just away from my development machine currently and it's past midnight. Lol Comment deleted
let result: Option<Result<T, E>> = collection.into_iter().find_map(|result| match result { Ok(t) if t != value => None, _ => Some(result) }); like this maybe. looks cringe though Comment deleted
I'm pretty sure that's not valid syntax. But it seems like you're on the right track. I'm going to ask some people who know better than I do. Comment deleted
fn try_find<T, E>( mut it: impl Iterator<Item = Result<T, E>>, mut predicate: impl FnMut(&T) -> bool, ) -> Option<Result<T, E>> { it.find_map(|result| match result { Ok(value) if !predicate(&value) => None, _ => Some(result), }) } this compiles https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d06fa19e9590bbb9510ce3a9cabe6fe3 Comment deleted
bro you are making it hard for yourself this can be way more simplified you can just use is_ok_and and then to simplify the code even more Comment deleted
It has to be the other way around because the input could be empty. Comment deleted
That'd just be Ok(None), I suppose Comment deleted
But I can just add a .transpose() to the mess; still not great implementation-wise though Comment deleted
Who cares about monads? Monoids in the endofunctor category is all we need! Comment deleted
+ diversity, inclusivity and equality Comment deleted
Damn, I love to create holy war in the chat 🍿😎 Comment deleted
Ah, right, with the advanced pattern matching. Comment deleted
I dont actually play around with that much. Comment deleted
I'd argue they just complicate the code in this particular case Comment deleted
Ah, There's a much simpler way to do this. collection.into_iter().find(|result| result.is_err() || result == Ok(value)) Comment deleted
I wanted a combinator that takes a predicate, not an expected value. So it'd be result.is_err() || predicate(result.as_ref().unwrap()) probably. A bit less simple Comment deleted
then replace result == Ok(value) with predicate(result.unwrap()) Comment deleted
Yeah. Still worse than a hypothetical magical monad combinator though Comment deleted
I think iter.find(|result| result.is_err() || predicate(result.unwrap())) is pretty ok all things considered. Very usable, and definitely cleaner than the loop based version. Comment deleted
Add an .as_ref(), but yeah, sure. That's honestly better than I imagined. Short-circuiting to the rescue Comment deleted
You could probably simplify it more by using matches!() instead of a match. But I'm not certain how exactly you'd convert that. Comment deleted
Though the Rust discord points out that in most cases if you end up with a collection of Results you probably made a poor decision about program structure earlier in your code. Comment deleted
I don't have a collection of results, I have an iterator of results. Say, a directory iterator yielding Result<DirEnt>. Comment deleted
You could make it a lot cleaner too depending on how much flexibility you have for your predicate. Comment deleted
If the predicate accepted a result or an iterator instead of a single value then you could probably just do iter.find(predicate) Comment deleted
iterator is probably the way I'd go. Comment deleted