3 ms·
Monads as a concept in category theory are hard to understand without a bit of background. However, monads as a design pattern are not much more difficult than
by substation13 4y ago
Monads as a concept in category theory are hard to understand without a bit of background. However, monads as a design pattern are not much more difficult than singletons, strategy pattern, visitors etc. I think the reason many monad tutorials fail is that the authors have an interest in category theory, so they find it hard to target the level of their audience.
Here's my attempt for Java programmers.
Suppose that:
- Values (like `String` and `int`) can be put into "boxes" (like `Async<String>`)
- We can call the box a "monad" when we have a few static methods for operating on the boxes that are well behaved:
1. A method for constructing a box from a value:
Async<String> myAsync = Async.from("Hello, world. ");
2. A method for transforming a box into a new one with a possibly different value:
Async<int> anotherAsync = Async.flatMap(myAsync, myString -> Async.from(myString.length()));
The implementation of these two methods depends on the box we are talking about.
You may see these under different names, which makes things extra confusing:
1. "just", "from", "pure", "return"
2. "flatMap", "bind"
OK, but why would you want to do this?
Well, if you are doing a complex procedure that manipulates these boxes, then having these methods allows us to build that complex procedure out of simpler parts. It's highly compositional.
Additionally, the monad structure allows languages (not Java sadly) to give you really powerful syntactic sugar that avoids nesting. In Haskell this is called do-notation; F# has Computation Expressions. Many more traditional languages have a less general version in the form of async / await.
- chopin 4y agoWhere this can directly been seen in Java is the Optional class which has the methods you describe as instance methods. Though Java has no syntactic sugar for monads this allows chaining expressions (which I think in Haskell would be bind() chains). Java streams behave similarly (and form a kind of monad around Collections). Having monad (or functor) functions (flatMap, map,...) as static functions has the disadvantage that you can't chain them well.
- tremon 4y agoBut for such a monad to be valuable, wouldn't you also need 3a. A method for extracting the value from the box String myValue = Async.val(myAsync); Or, alternatively, to be able to re-use the entire standard library for your boxed values (which is not possible in Java, FAFAIK): 3b. Take a plain-valued function and lift it to accept boxed values: Func<Async<String>> asyncJoin = Func.lift<Async>(String.join);
- chopin 4y agoJava solves this with map as instance function (transfering the function inside the box). And there are several extraction functions (See Optional for an example).
- deleted 4y ago[deleted]
- btschaegg 4y agoMy take would be: Your 3a is certainly necessary in order for each individual monad to be useful, but it is not general (i.e it depends on the monad how you do it). Therefore, it's not part of the criteria of what it means to be a monad. Examples: - Async: Block and wait for your Async<T> to finish or fail. (Note that Async<T> can still be useful without that if you can simply register a final continuation callback with side effects) - Option: Check if there is a value and unwrap it if that's the case. Or, maybe more idiomatic: Some way to write a `match` statement ("if there's a value, do A. Otherwise do B. Both have to return the same result type") - List: Provide ways to check the length and access each individual value in the list. - Stream: Provide a way to wait for the next value. …and so on and so forth. Your 3b is implementable in terms of `bind` and `return` (and, in case your monad is also a functor, even in terms of `map`). Edit: To drive the point about 3a home a bit more: The reason why monads (and functors) as a pattern are interesting is that they provide the formalistic means to write "imperative" code by writing pure functional "in between" code: Do A …and then do B …and then do C Different monads essentially only influence what "and then" means. A lot of languages have syntactic sugar for this kind of "and then" lists, for example: Haskell: do notation F#: Computation expressions (e.g. `async` or `seq` blocks) Python, C#, Javascript: Async methods and functions C#: Inline SQL with LINQ Rust: `try` and `async` blocks You'll note that each of these notations return the final monad instance without unwrapping it. That's because "unwrapping" is either not general enough for the notation or not always desirable. I.e. an `async` method in C# returns a `Task<T>`, you'll have to block for the result yourself. So, the "Monad" pattern is only concerned with how you can compose operations, not with how you can get to the resulting value.
- substation13 4y ago