4 ms·
Monads are not "for side effects". Monads are a programming pattern to facilitate code reuse. Any time you have a type constructor M (like List<_>, say) that su
by endgame 5y ago
Monads are not "for side effects". Monads are a programming pattern to facilitate code reuse. Any time you have a type constructor M (like List<_>, say) that supports the following operations:
1. pure :: T -> M<T>, and
2. Any one of the following three (you can derive any of these from any other)
join :: M <M<T>> -> M<T>
bind :: (M<A>, A -> M<B>) -> M<B> -- sometimes called "andThen"
pipe :: (A -> M<B>, B -> M<C>) -> (A -> M<C>)
If they interact with each other in a "sensible" way (the "monad laws"), you have a Monad.
---
What things fit this abstraction? I think the most familiar one to mainstream programmers would be Promises. If you have a Promise<Promise<A>>, you can join them together to get a Promise<A>. If you have a Promise<A> that you need to do some further work on, using the bind operation with a function (a -> Promise<B>) lets you "bind a name" to the result of the first promise and return a new Promise<B>.
If you'd like to be explicit about null-checks, then the Option<A> type lets you build up a computation without writing `if (foo != null)` everywhere. A variant, Result<E, A>, lets you stop on the first error. Tracking explicit error information in this way has been invaluable when taming a large ruby codebase - see the dry-monads project.
Any time you want to describe interactions with a system, this abstraction gives you something like the "command design pattern" on steroids. I have used Transaction<A> types to representing database transactions, ensuring that arbitrary IO doesn't happen inside a transaction (you can't unsend an email if the transaction rolls back). A function of type ((Connection, Transaction<A>) -> IO<Result<E,A>>) executes the DB operations inside a DB transaction and either returns the transaction result or an error.
None of this is "we want to change the world lol". I want to be able to notice the common elements between several type constructors, and be able to work on them using a common vocabulary, instead of reinventing wheels.
- sova 5y agoThanks for taking the time to explain this to a novice. So are the <T>, <B>, and <C> types ? Clojure has collections like [vectors], {maps}, '(lists/seq), #{sets} and a bunch of primitives that can fill up those collections, so I'm not certain how this relates exactly... Are promises and futures always monads?
- endgame 5y agoMany languages (C++ templates, C#, Java, among others) write things like List<T> to mean a "list where every item is of type T". This feature is usually called something like "generics" or "parametric polymorphism". It's not haskell notation but I hoped that it would give an easier flavour. > Are promises and futures always monads? Only if you can construct the necessary operations (or if they are provided), and they relate to each other correctly. For example, if you have a value m of type M<A>, (join (pure m)) should be identical to m. The language you're using also needs to be able to usefully do something with this fact, or it's just trivia. In Haskell, we can define a type class called Monad and write operations that work on any monad, without knowing or caring which one is in use (that's the caller's job to decide). This would be hard-to-impossible to do in a less expressive language like C.
- Zababa 5y ago> So are the <T>, <B>, and <C> types ? Clojure has collections like [vectors], {maps}, '(lists/seq), #{sets} and a bunch of primitives that can fill up those collections, so I'm not certain how this relates exactly... They are. It's the generic notation that you'll find in Java, C#, Rust, etc.. They are statically typed languages, so instead of just saying [vector] like in Clojure, you would say Vector<T>, where T is the type of the things in the vector. Vector<T> by itself is not a type. You can think of it as a function that takes a type and returns a type: with Int, you would get Vector<Int>, with String, you would get Vector<String>. This isn't limited to collections: Option<T> represents a value that may or may not be there, Result<R, E> represents something that can be either a result, or an error. For the other collections, {maps} would be Map<A, B>, lists would be List<T>, sets would be Set<T>. Note that T, A or B don't mean anything specifically, they are variable names. Usually people use very abstract names for generics because they are very abstract. But you could also do List<Element> or Map<Key, Value>. > Are promises and futures always monads? Not all the time. For example, JavaScript promises aren't monads. There's a relatively popular discussion about it here: https://github.com/promises-aplus/promises-spec/issues/94 https://github.com/promises-aplus/promises-spec/issues/94. As another example of a monads, there Option<T>, called Maybe<T> in Haskell. It's used for a value that could be there. Java has Optional that looks like it, but isn't a monad since it doesn't follow the monad laws: https://www.sitepoint.com/how-optional-breaks-the-monad-laws-and-why-it-matters/ https://www.sitepoint.com/how-optional-breaks-the-monad-laws.... A good way to think about monads is to think about them as mathematical objects. They respect some rules, and thus have some properties. When you're implementing something in a language, like promises, you can choose to have them respect the monad rules, so that they can have the monad properties. You can also not do that (like they did in JavaScript), and so people won't be able to use them like monads.