|
|
|
|
|
by nextaccountic
37 days ago
|
|
> A good example of (2) is realizing that futures, Rust's Result and Option, and Python's list/stream/etc compressions are almost the same thing, from a certain angle. Why the "almost"? They are all monads (I suppose you meant list comprehensions) One thing to note is that lists have two natural monads (kinda). One is the usual List monad in Haskell (which does the same thing as list comprehension), but the other is actually an almost monad like your point (3)! So Haskell solves this by defining the "other" monad with a newtype over lists (called ZipList), and actually gives it only an Applicative interface (which is like monad, but with a restricted comprehension - values can't depend on past values so it has no "memory") This happens with integers in a more honest way, they have two "natural" implementations of monoids (actually infinite, but two stand out): you can add integers and you can multiply integers. In this case Haskell decided to not implement Monoid directly because one is not "more natural" than the other, but instead gives two newtypes, Sum and Product |
|
> Why the "almost"? They are all monads (I suppose you meant list comprehensions)
Sort of. In the right language, with the right implementation, any of these can be monads. In practice, JavaScript Promises aren't quite monads, and everything in Rust is a bit complicated because we have things like FnOnce vs Fn, and so on. And even when Rust has something that's conceptually a monad, you can't actually "impl Monad" for it, because the type system isn't quite expressive enough.
In general I think it's mistake to accidentally implement something that's not quite an algebraic or categorical structure. This often means that your design is just a bit "off" of a much cleaner design. But sometimes, like in Rust, you know why you can't use the clean mathematical structure.