7 ms·
Can someone give me a practical example of when one would want to use a monad? Typically I don't want side-effects to all live in one thing. It's cute but "we
by sova 5y ago
Can someone give me a practical example of when one would want to use a monad? Typically I don't want side-effects to all live in one thing. It's cute but "we want to change the world," don't we?
- anchpop 5y agoTwo common examples from the Ocaml world are promises through the LWT library and options. If you just wanted to open a file, maybe you would write something like this: run(create_file("filename")) Here `create_file("filename")` is a pure value, no side effects. It's a value that represents the concept of creating `"filename"` (and maybe returning a handle). When passed to `run`, that value is inspected and only then the actual action it corresponds to, creating a file, is executed. What if you want to do something with the file? That looks like this: let create_then_delete = and_then(open_file("filename"), (handle) -> delete_file(handle)) run(create_then_delete) `create_then_delete` represents the concept of opening a file and giving it to `(handle) -> delete_file(handle)`. If you want to actually do that, you pass `create_then_delete` to `run`. Functions with a type signature similar to `and_then` turn out to be pretty common, so it's nice to have a way to talk about them. That's basically what monads are for. In OCaml, all functions that have a similar type signature can use the new "let-monadic syntax", which basically lets you rewrite and_then(open_file("filename"), (handle) -> delete_file(handle)) to let\* handle = open_file("filename"); delete_file(handle) Which is a lot more readable if you ask me. Any type that has a function like `and_then` works with this syntax, and those types called monads.
- massung 5y agoI believe that most descriptions and blogs about monads completely miss the point. Monads are a tool. And yes, they can be used for things like having side effects in pure code or allowing for the chaining of functions, but those are just tangential benefits. Fundamentally, monads are just a way to categorize data, and the downstream results of that data. In the end, this gives us the ability to codify what we - as programmers - already know, and therefore depend on. Consider the following mad-lib: > Any value computed using the result of ____ must also - by definition - be the result of ____. Now, let's plug in a simple example with "asynchronous function": > Any value computed using the result of an asynchronous function must also - by definition - be the result of an asynchronous function. If I call a function call `httpGet` that returns `Future<Response>` and then I take the response and parse the HTML into a DOM object, I know - as a programmer - that the DOM object is of type `Future<DOM>`. I know this whether or not the programming language I'm using is dynamically typed, supports higher-kinded types, or not. Whether or not you prefer to live in Haskell land, Go land, Clojure land, or Flatland, monads are just a tool for you - the programmer - to categorize your data and codify (at compile time and/or runtime) what you already know.
- sova 5y agoFlatland? great book. Surely monads are more than truisms! Forgive me, I must have missed the point.
- massung 5y agoLOL. Fair enough. :-) I just find that when I read most people discussing "why monads" it usually degrades into code examples and the OP still left asking "yeah? so? I could already do X? what did that do for me?" Similar to how a C++ programmer trying to tell a C programmer about std::vector might leave the C programmer is just saying "yeah? so? void* works just fine." I was attempting to steer the discussion in a different direction: about taking what you know and codifying it, which allows you to be sure - at compile and/or runtime - about what's happening. To take it back to your original question: > Can someone give me a practical example of when one would want to use a monad? Others have given good examples. But, I'll try and put it into the context of my reply by making up a contrived example. Let's say you have a hash map and want to look up a key's value. The function can return the value stored or a default if the key doesn't exist. But, you can't tell the difference between your default value being returned or the same value already existing. One solution would be to return multiple values, including a boolean indicating whether or not it's the default. But, presumably (since we care in this contrived example), downstream decisions and further data needs to know if the key was present. So, you need to keep passing along both values all the time. After all, per the original reply - any value derived later from the returned value also belongs to the category of "maybe derived from a default value/missing key". Instead of constantly passing around the value, a flag, and maybe even the missing key, you could return and pass around a struct of those 3 values. But, now you have a problem: all the existing functions you want to pass your value to don't understand your struct. Additionally, if you want to remain true to the "truism", those functions need to also return your struct, which they don't. Monads are the pattern created for solving this exact problem. Monads define an interface for... 1. Taking a value and returning your monad type 2. Taking a function (F) and returning a new function that can take your struct, extract the original value from it, pass it to F, and return a new struct with the return value wrapped as well. From there, you get all the other benefits people have discussed elsewhere in this thread "for free". Whether you're working with a strongly typed language like Haskell (and using the compiler to keep you honest) or a dynamic language like Python (and keeping yourself honest at runtime), the problem and pattern holds true. I intentionally picked a goofy, contrived example to stay clear of all the common examples people usually give (futures, IO, errors, option, etc.) to show how you might have a very different use-case that can still benefit.
- endgame 5y agoMonads 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.
- louthy 5y agoSome examples: * Lists - you don't need to manually for-each over the items, the monad will do the unwrapping for you, reducing boilerplate * Optional values - you don't need to check for null/no-value, the monad will do that for you and end the computation if an expected value is missing * State passing - constantly passing a Context type as an argument through your pure functions? Look no further than the State monad, it will do it for you * Environment/Configuration - don't want to deal with global variables for configuration? Look no further than the Reader monad, it will automagically carry your config to you. This can also be used as a pure dependency-injection system, with none of those hideous DI frameworks and tools. * Asynchrony - fed up of callback functions, and the boilerplate of dealing with the continuations? Use a promise/task/async monad, it will deal with the sequencing of your operations properly. This is an interesting one because monadic-asynchrony has inspired the async/await systems in C#, JS, and beyond. So, if you've ever used async/await and thought "wow, that was convenient", it's because they're monads. * Error handling - Exceptions are effectively gotos, why not have something a bit more powerful? The Either monad, Validation monad, etc will handle errors, groups of errors, etc. * Output aggregation - Want to build some output over the duration of your operation (building a list of strings, generating a document, summing some values, etc.), use the Writer monad to collect output and aggregate it using any available monoid pattern. * Parsers - Want to declaratively build a parser and not have to worry about error handling, position in the stream, etc? Get the Parsec parser-monad on the case. * DSLs - making a DSL to capture subsystems in your code-base? Use the Free monad with a sum-type to create a ready-to-go domain-specific language. Simply provide the interpreter. Want to combinations of all of the above? Many monads can be composed to create a super-monad with the behaviours of both; they're call monad-transformers, and everything "just works". Ultimately, it's about capturing common patterns and using the same interface to work with the monadic types (in Haskell it's called `do` notation, in C# it's called LINQ). They significantly reduce boilerplate, and allow for very succinct and declarative code.
- lmm 5y ago> Typically I don't want side-effects to all live in one thing. It's cute but "we want to change the world," don't we? I don't really understand the argument here? Usually we want to properly separate concerns and make sure each thing has one responsibility. Monads are a big help with that. Any time you have a secondary concern that you don't want to be dealing with explicitly in your straight-through code is a good use case. Any time you would be tempted to use aspect-oriented programming, or a global/thread-local variable, or similar hacks (and IMO they're still hacks when they're built into the language, e.g. exceptions). E.g. authorization, audit logging, database transactions, error handling - rather than having to use some magical decorator/annotation/macro that breaks all the rules of your language, by using monads you can write code that uses these things as plain old functions working on plain old values, but there's an extensive library of standard operations (e.g. traverse, *M variants of functions like cataM) that you can use to make working with them almost as lightweight as if you were using the magic way.