▲ | kriops 4 days ago | |||||||||||||||||||||||||||||||
Monads are well-defined, though. Monoids in the category of endofunctors, anyone? The problem is when implementations aren’t actually monads at all. The same goes for other functional concepts. I wrote a blog about Java’s Optional::map here: https://kristofferopsahl.com/javas-optional-has-a-problem/ It’s the same kind of problem, where naming signals something the implementation is not. (Am I allowed to link my own blog btw?) | ||||||||||||||||||||||||||||||||
▲ | whstl 4 days ago | parent | next [-] | |||||||||||||||||||||||||||||||
> The problem is when implementations aren’t actually monads at all Exactly, this was my point, it wasn't clear. The original definition, and Haskell's implementation are good in itself. Monads in Haskell are not that difficult or too abstract. It was Monad tutorials and partial implementations missed the mark, like in your example. Myself, similarly, I've seen way too many Option<T> implementations in Typescript that are less safe than if (value !== null) {}, because they replace a static check with an exception in runtime. | ||||||||||||||||||||||||||||||||
▲ | lock1 4 days ago | parent | prev | next [-] | |||||||||||||||||||||||||||||||
Unrelated with GP post: What's wrong with Java's Optional? IIRC it doesn't fulfill monad axiom, but I don't think there's a huge problem with it. By the time you're using Option<>-like, I don't think you should use bare `null` at all in your project. Mixing Option<>-like and bare `null` sounds like playing with fire. Also, if you're using Java 17+ (`record` in your example), you're probably better off writing your own Option<T> to support sum-type matching & product-type destructuring. | ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
▲ | ekidd 4 days ago | parent | prev [-] | |||||||||||||||||||||||||||||||
Yes, monads are abstract, but the definition is also very precise. Specifically (using C++/Rust notation for parameterized types), if we have a type "M<T>", we also need:
...and finally the magic bit:
This, in turn, allows defining what you really want:
...where the mapping creates an "extra" layer of M<...>, and then we flatten it away immediately.(There are other rules than ones I listed above, but they tend to be easy to meet.) Once you have flatMap, you can share one syntax for promises/futures, Rust-style Return and "?", the equivalent for "Option", and a few dozen other patterns. Unfortunately, to really make this sing, you need to be able to write a type definition which includes all possible "M" types. Which Rust can't do. And it also really helps to be able to pick which version of a function to call based on the expected return type. Which Rust actually can do, but a lot of other low- and mid-level languages can't. So monads have a very precise definition, and they appear in incomplete forms all over the place in modern languages (especially async/await). But it's hard to write the general version outside of languages like Haskell. The main reason to know about monads in other languages is that if your design is about 90% of the way to being a monad, you should probably consider including the last 10% as well. JavaScript promises are almost monads, but they have a lot of weird edge cases that would go away if they included the last 10%. Of course, that might not always be possible (like in many Rust examples). But if you fall just barely short of real monads, you should at least know why you do. (For example, Rust: "We can't have real monads because our trait system can't quite express higher-order types, and because ownership semantics mean our function types are frankly a mess.") |