9 ms·
What Is IO Monad? (2018) [video]
- zamalek 5y agoI was raised on OOP, and at my ripe old age I really want to grok monads. This video did me a great service in that direction.
- ipnon 5y agoYou've probably been using IO monads as long as you've been programming: [Unix pipes as IO monads](https://www.okmij.org/ftp/Computation/monadic-shell.html https://www.okmij.org/ftp/Computation/monadic-shell.html)
- brigandish 5y ago> cat v | cmd is cmd < v I have never seen an `is` operator in a shell so my understanding stopped right there, which is a pity as I felt I was getting somewhere.
- jcranmer 5y agoWhat it's saying is that the shell command `cat v | cmd` is equivalent to `cmd < v`--if you look carefully, the 'is' is in a different typeface (a serif font) than the commands (monospace).
- brigandish 5y agoAh, my mistake (or my poor, abused laptop leering eyes). Thanks, I thought I was being introduced to some abstruse shell programming on top of monads!
- ipnon 5y agoThe monad as used in Haskell is really just a functional replacement for the semicolon: `doFoo(); logBar(); requestBaz()` in Haskell is `doFoo >> logBar >> requestBaz`. If you want to get really fancy you can pipe the inputs: `doFoo | logBar | requestBaz` becomes `doFoo >>= logBar >>= requestBaz`. Haskell suffers from the fact that endless mathematical analysis is enjoyable to a certain breed of programmer known as the "mathematician".
- tremon 5y agoWhat you're saying applies to the IO Monad only, using the IO side-effect for sequencing purposes. I'm not sure is applies to the myriad of other monads available in Haskell.
- _y5hn 5y agoIt's not that far off, if you think of side-effects / any magical processing happening at evaluation per bind-function (semi-colon ;). It's so because monads follow the exact same pattern by definition, but the different types of monads just inject some additional behaviour and constraints as well. Different patterns from this are no longer monadic. The "magic" part of this is why different types of monads don't compose well. Of course, this is overly simplified but might help for understanding.
- usrbinbash 5y ago> `cat v | cmd` is equivalent to `cmd < v` https://askubuntu.com/questions/172982/what-is-the-difference-between-redirection-and-pipe https://askubuntu.com/questions/172982/what-is-the-differenc...
- mrslave 5y agoHaskell for Imperative Programmers [0] is the best material I've come across recently for Haskell beginners. [0] https://www.youtube.com/playlist?list=PLe7Ei6viL6jGp1Rfu0dil1JH1SHk9bgDV https://www.youtube.com/playlist?list=PLe7Ei6viL6jGp1Rfu0dil...
- bedobi 5y agoShameless plug for something I wrote :P # Lambdas, Functions, Functors and Monads Even most OOP programmers are now familiar with map and, having used it quite a bit, have an intuitive understanding of what any given call to map will do. Does anyone know a bit more about map, what it is and where it came from? ## Category Theory A lot of Functional Programming derives from a field of Mathematics called Category Theory. Very briefly, Category Theory is the study of functions, where "function" refers to a process that associates to each element of a set X a single element of a set Y. Pure functions have only inputs and outputs- no access to anything other than what was sent in, and no ability to mutate what was sent in or generate other side effects. Now, I suck at math, but fortunately we don't need to know math to learn more about concepts programming languages have borrowed from Category Theory. Learning about it can make us better programmers if we go from an intuitive, but maybe a bit vague, understanding of things like map to fully understanding them! ## Functor (map) A *Functor* is anything that can be mapped over. Map, the defining function of a Functor, is quite simple. interface Functor<A> { fun <B> map (function: (A) -> B) : Functor<B> } This just says > Given a Functor of A, when map is called with a function of A to B, return a Functor of B ### An example This is all just a bunch of mumbo jumbo, but in our day to day programming, Functors (or potential Functors) are everywhere! Lists are a great candidate for a concrete example. class BoringList<A>(val backingList : List<A>) { } The above class is just a wrapper (with no added behavior) around a list. Let's make it do something interesting by declaring it a Functor. class InterestingList<A>(val backingList : List<A>) : Functor<A> { override fun <B> map(function: (A) -> B): InterestingList<B> { //implement - DON'T CHEAT by simply calling map on backingList } } fun main(args: Array<String>) { //experiment } Example implementation (very imperative, but that's ok) class InterestingList<A>(val backingList : List<A>) : Functor<A> { override fun <B> map(function: (A) -> B): InterestingList<B> { //the current InterestingList is of type A but we need to return //a InterestingList of type B, so first, make a new backingList //of type B val transformedList = ArrayList<B>() for (item in backingList) { //apply the provided function to each item val transformedItem = function(item) //add them to the transformedList of type B transformedList.add(transformedItem) } //return an InterestingList of type B return InterestingList(transformedList) } } fun main(args: Array<String>) { val interestingList = InterestingList(listOf("foo", "bar")) //when calling map on InterestingList<String> with a function: (String) -> Int, we get InterestingList<Int> val mappedInterestingList = interestingList.map { string -> string.length } } ### Other examples Now, we've used List as an example, but remember: Functors (or potential Functors) are everywhere! It's easy to think of Functors as "containers", but it's more useful to think of them as a "computational context", since eg Options and Futures can also have map. * The Functor of List applies the function to all elements in the list * The Functor of Option applies the function if it's non-empty. (or rather, any function applied to Nothing is still Nothing) * The Functor of Future applies the function to a result once a result is available. ### Functor laws Now, can someone think of a few important things to keep in mind when implementing map? Eg, what if your implementation of map for a List always returned an empty list, or a list twice the size of the given list? Or what if it mutated the items it mapped over, or called a bunch of other god knows what functions? That would be not so good! So when implementing Functors you actually have to observe the Functor Laws. There is nothing mysterious about these laws; their role is to guarantee map behaves sanely and actually performs a mapping operation (as opposed to some other nonsense). The first law is: map id = id id is the identity function, which returns its argument unaltered. The first law states that mapping id over a functorial value must return the functorial value unchanged. Next, the second law: map (g . f) = map g . map f It states that it should not matter whether we map a composed function or first map one function and then the other (assuming the application order remains the same in both cases). ## Monad (flatMap) A *Monad* is anything that can be flatMapped over. Sounds scary! It's actually surprisingly easy to understand, but can be a little more complex to implement. Don't feel bad if you don't get it right! FlatMap, the defining function of a Monad, is quite simple. interface Monad<A> { fun <B> flatMap (function: (A) -> Monad<B>) : Monad<B> } This just says > Given a Monad of A, when flatMap is called with a function of A to Monad B, return a Monad of B If you recall Functor interface Functor<A> { fun <B> map (function: (A) -> B) : Functor<B> } Which just says > Given a Functor of A, when map is called with a function of A to B, return a Functor of B The two are EXACTLY the same except instead of a function of A to B, it's a function of A to Monad B. ### An example Let's return to the concrete example of the list: if you call map on your list with a function of A to List B, what will you end up with? You would end up with a list of lists! And all flatMap does is "flatten" that into a single list. interface Monad<A> { fun <B> flatMap (function: (A) -> Monad<B>) : Monad<B> //yes! fun <B> flatMap (function: (A) -> Monad<B>) : Monad<Monad<B>> //no! } Implementing is left to the overachievers. ### Other examples Remember: just like Functors, Monads (or potential Monads) are everywhere! * The Monad of List flattens lists of lists. * The Monad of Option allows chaining many potentially absent things together, short circuits if any of them are empty, and returns a flattened end result. * The Monad of Future allows chaining together computations that don't have a result yet, and returns a flattened end result. ### The Monad laws Just like when implementing map for Functors the implementation must satisfy the Functor Laws, when implementing flatMap for Monads, the implementation must satisfy the Monad Laws. I will not get into them here, but for anyone who's interested this is a great resource https://en.wikibooks.org/wiki/Haskell/Understanding_monads https://en.wikibooks.org/wiki/Haskell/Understanding_monads ## A missed opportunity As we've seen, map and flatMap really shouldn't be these random functions that sometimes get added to things and sometimes not. Functional programming languages usually have an interface Functor, and an interface Monad etc which all classes that implement map and flatMap extend, and there's no reason why it shouldn't be like that in Java or Kotlin as well. Unfortunately, neither Java nor Kotlin do this, which is kind of a missed opportunity. ## But why? Most of us instinctively reached for looping language constructs like `for` and `while`. So since we're so used to that, where's the benefit in all this new map, flatMap etc nonsense? It's difficult to understand, hard to read etc etc. A few differences could be pointed out. ### An example //this looks fine but will actually blow up, can you tell why? int[] array = {1, 2, 3}; int[] doubled = int[array.size]; for (int i = 0; i <= array.size; i++){ doubled[i] = array[i] \* 2; } //even if it didn't blow up, there's still a lot of noise about *how* to //double that is completely besides the point of *what* we actually want, //which is simply to multiply the items by 2 #### Imperative vs Declarative loops describe in detail how to do something while map hides implementation details and let's you focus on what to do. And since describing in detail how to do something isn't required, the risk of bugs in the how part is completely eliminated. #### Stateful vs Stateless loops are literally stateful operations where we're, well, looping through a collection item by item (in a certain order) using something to keep track of how far we've looped. Sometimes the state matters to what's happening inside the loop, eg when we find what we're looking for and return early. Mapping operations have no such state, so the risk of bugs due to state are completely eliminated. #### Meaningless vs Meaningful loops have no real meaning. Eg, it's possible to loop through things and do nothing, mutate the items as you loop through them, mutate the collection you're looping through, return something from an enclosing method... Loops can do anything. Map on the other hand is a specific thing, has a specific use case, does no more and no less, makes guarantees as to what it does and doesn't do etc etc. This allows us to reason about code more effectively than with loops where we have to play compiler and debugger and keep a bunch of state in mind all the while trying to understand what's actually being done inside. These are just a few examples, I'm sure more could be thought of!
- exdsq 5y agoA monad is just a monoid in the category of endofunctors
- cryptonector 5y agoso what's the big deal?
- js8 5y agoI think monads are what we would ordinarily call "environment" or "context" of execution. Monad just describes how to compose the interactions with the context. Usually this is some serialization of these interactions but can be other things. IO monad is where the environment (context of execution) is just your entire operating system. In imperative languages, this is a normal state of affairs, but functional programmers view that with similar suspicion that most programmers of today view global variables. That's why in general "different monads don't compose", because it's like trying to compose two different environments (as we know, for example, porting between different OSes is not trivial). You can define it (with monad transformers) but it's often a lot of work.
- hither_shores 5y agoMore precisely: the composition of two different monads isn't always a monad. `[IO Int]` is a perfectly sensible type, you just can't flatten a `[IO [IO Int]]`.
- tromp 5y agoIsn't that what joinMMT [1] is for ? [1] https://hackage.haskell.org/package/TypeCompose-0.9.14/docs/Control-Compose.html#v:joinMMT https://hackage.haskell.org/package/TypeCompose-0.9.14/docs/...
- tome 5y agoThat particular function won't work because IO is not Traversable.
- js8 5y agoI think (I am not sure) that you cannot define monad composition (transformer) with IO because IO is purposely open-world (so that Haskell can use C libraries and OS calls) and as such is not entirely formally defined. If we defined it as a closed-world, i.e. these are the side effects that you can do with IO and nothing else, then it might become possible to compose with other monads.
- grumpyprole 5y agoOne advantage of monads is that they can communicate via the type that a function is not pure. I remember a serious bug in a trading system (distributed over multiple time zones), that was caused by Java's toString reading the system locale from the OS. In Haskell, such a function would have a type that makes it clear the function is not pure, e.g. toString : Date -> IO String. Monads are not the only way of tracking effects in the type, but they are one way. For example, Java has "checked exceptions", which also represent an effect in the type system. Monads also allow one to assemble a computational ready for execution later. One can therefore write an Async library, without needing baked-in language support.
- cryptonector 5y agoNote that using a monad does not imply the code is impure. Using the IO monad does.
- grumpyprole 5y agoYes and some would argue that all Haskell functions are pure even those that return computations in the IO monad. One can use referential transparency to reason about the IO monad values also.
- cryptonector 5y agoSure, you can treat I/O functions as World -> World functions, but unlike other pure functions, the compiler can't compute them at compile time when they are applied to constant inputs. A program consisting entirely of pure function applications to constants can be evaluated at compile-time to just the constant(s) it would output, while a program that calls I/O functions cannot be (assuming those calls to I/O functions are not dead code).
- ModernMech 5y agoIf you've ever used a language like Rust that has sum types, and you've gotten a Option<T> from a function and wanted to put it into some other function that takes a T, then you have a good intuition for what a monad is. Monads unwrap the T from the sum type so you can chain those kinds of function calls. This is relevant for IO because it can maybe fail.
- skavi 5y agoI think the more important part in the case of the IO monad is not that it might fail, but that it’s a side effect in a lazy language. Not super sure though. Haven’t used much Haskell.
- kaba0 5y agoLaziness is besides the point, scala (and pretty much every language) can do laziness without monads, it is as simple as if private variable == null, then initialize, and then return that variable on every getter call. It is “needed” due to pureness - without haskell’s unsafe, there is no other way to do side-effects[1]. But you are otherwise right. The reason it is beneficial in FP languages is that the optimizer can reason very well about these pure expressions. [1] though execution itself can be considered a side-effect, and haskell code can throw exceptions, so it is more like compartmentalizing the majority of effects
- dllthomas 5y agoOn the contrary, purity is needed because of laziness! https://www.microsoft.com/en-us/research/publication/wearing-hair-shirt-retrospective-haskell-2003/ https://www.microsoft.com/en-us/research/publication/wearing... Not, mind you, merely the possibility of laziness; as you say, you can do that without monads or purity. But with pervasive laziness it becomes difficult to reason about the precise order in which thunks will be evaluated and be sure that (say) the warning will be printed before the missiles are fired instead of after. With purity it (mostly) doesn't matter the precise evaluation order, so (again, in the setting of pervasive laziness) we want that wherever we can have it! But at some point we need to actually fire those missiles and print those messages. The Monad interface for IO, in chaining together effectful computations with bind, makes the input of later computations depend on the output of earlier computations such that the regular data dependencies between the thunks ensure that things happen in the order we asked for.
- kaba0 5y agoI gave a try explaining it the easiest way I could here: https://news.ycombinator.com/item?id=28844533 https://news.ycombinator.com/item?id=28844533
- cryptonector 5y ago> To understand Monads, we have to first understand Functors, Monoids and the Applicative type classes (type classes are more or less the same as interfaces in Java, C++ and the like). The parenthetical is extremely important too. What looks like a function call in Haskell is dispatched based on the types of its arguments (yes, functions take just one argument, but be that as it may). This is important to understand. The "methods" available depend on the types of the values being passed to them.
- kaba0 5y agoI tried to write Java-style method invocation wherever possible.
- cryptonector 5y agoRight, but when reading Haskell, one has to know this. It's critical.
- exdsq 5y agoI’m with Hitler on this https://youtu.be/ADqLBc1vFwI https://youtu.be/ADqLBc1vFwI
- danmur 5y agoThat just gets better and better
- exdsq 5y agoI remember it every few months and get a decent laugh from it each time!
- frob 5y ago`Monad is just a cute name for flatMap` Hahaha! I swear this is exactly how I feel halfway through every monad tutorial I find.
- skavi 5y agowell, bind (>>=) literally is just flatMap.
- ilovecaching 5y agoI spent several years learning and using Haskell professionally and I have to say it was a waste. Almost nothing has carried over to what companies use or want day to day, nor did it really help me to understand computers at a deeper level Erlang taught me about concurrency patterns which are actually useful in the real world, C/Rust taught me the abstractions presented by the hardware, locking, etc. but Haskell taught me something completely irrelevant to furthering my career or my skill as a software engineer. I think that it’s an interesting side hustle and if people enjoy playing with it more power to them, but most of Haskell is driven by language enthusiasts and researchers who have no eye for practicality. Everything is done for its own sake, such as whether something can be done through derivation via HKTs etc. A lot of it comes down to it being an excuse to play with math rather than write programs. The way that it’s oversold as the solution to all bugs is based on an understanding of society that doesn’t match with reality, nor the needs of the modern programmer. So what is an IO Monad? It’s a clever trap to get you to spend your time getting a poor man’s degree in category theory when you could be studying how computers really work.
- bedobi 5y agoBit too harsh but I can see where you're coming from. Kotlin with the Arrow library for Functional Programming is a nice balance between nice guarantees and correctness of Haskell and practicality IMO.
- kqr 5y agoI have the opposite experience. A year of using Haskell for my side projects taught me more about how to make code production ready than years of Java. But I suppose it depends on the environment you end up with. All the Java I did was with people where you could get away with murder in the code. All the Haskell I did was with people who carefully consider network failure, memory usage, readability, timeouts, pathological logging loops, etc., and would push back on code that isn't easy to use, maintain, and troubleshoot.
- ilovecaching 5y agoWriting good code is mostly language agnostic. Where the brain power in computing tends to flow to is hard or interesting problems (I say tends because money is also a factor), so you're going to find smart people concentrated in different places. I've been very fortunate to have worked with many groups containing the best and brightest in the software industry and I was never held back by letting go of Haskell.
- ObiWanFrijoles 5y agoNice to see Tsoding on the front page. He has another great video that covers a bunch of topics while making a JSON parser with only the Haskell standard library: https://www.youtube.com/watch?v=N9RUqGYuGfw https://www.youtube.com/watch?v=N9RUqGYuGfw
- dimator 5y agoReally just an epic, prolific streamer. Super technical, super deep dives, and on top of all that, an excellent educator. Here is a series he did on developing a Forth-like language from scratch, up until it's self compiling (and a few more videos after that): https://www.youtube.com/watch?v=8QP2fDBIxjM&list=PLpM-Dvs8t0VbMZA7wW9aR3EtBqe2kinu4 https://www.youtube.com/watch?v=8QP2fDBIxjM&list=PLpM-Dvs8t0... Here is a series on doing advent of code in HolyC in templeOS: https://www.youtube.com/watch?v=MMqd-6wNJQ4&list=PLpM-Dvs8t0VZNUvTX1pqfpI_tMkhWCLYL https://www.youtube.com/watch?v=MMqd-6wNJQ4&list=PLpM-Dvs8t0... Just look at the breadth of videos, from openGL to Excel: https://www.youtube.com/c/TsodingDaily/playlists https://www.youtube.com/c/TsodingDaily/playlists I do wish he would do revisit more haskell :)
- _query 5y agoHaskell is a bit famous for being very academic and being only for language enthusiasts and researchers. But that image is outdated by now. The Haskell ecosystem has improved quite a lot in recent years: E.g. there's now Haskell Language Server which provides very nice autocompletion in Editors like VSCode. Very recently support for dot-notation has been added, so you can now write `someValue.someField` as in other languages. In the web dev space we now have IHP, which is Haskell's take of Laravel/Rails/Django https://ihp.digitallyinduced.com/ https://ihp.digitallyinduced.com/ (Disclaimer: I'm founder of the company that makes IHP) So if you have checked out Haskell in the past already, you might want to give it another try! :)
- blahblarblar 5y agoDid they fix strings?
- _query 5y agoYes, there's now `Data.Text` https://hackage.haskell.org/package/text-2.0/docs/Data-Text.html https://hackage.haskell.org/package/text-2.0/docs/Data-Text.... which is widely supported
- misja111 5y agoHuh, Data.Text has been around forever. And the datatype that you will find in almost any Haskell tutorial is still 'String'. It is really a pain that any beginner in the Haskell language is learning what seems the obvious datatype for strings, i.e. String, and later has to unlearn that again.
- stingraycharles 5y agoIt’s been a while since I worked with Haskell. What’s the issue with Strings, I’m guessing it must be Unicode-related?
- 5y ago
- frogulis 5y agoIMO one of the best monads for explaining the concept is async promises, especially for JS programmers who haven't always had the luxury of async/await. If we have a long-running process, we can block the thread and wait for it to return us a result, but that's far from ideal. Instead we return a "promise", a structure that we can provide with a callback (using the `then` method on the promise) to manipulate the result of our asynchronous processing. The types look something like this: `doSomeAsync(T1): Promise<T2>` `Promise<T2>.then(T2 -> T3): Promise<T3>` Let's look at the type of monad `bind`, `>>=` in Haskell: `M a -> (a -> M b) -> M b` The types of `bind` and `then` are very similar! `Promise` is (approximately) a monad. The fact that we can't just "get" the result of the Promise and instead need to provide a callback is a core part of my understanding of the use of monads in Haskell -- they are used in Haskell to represent the result of a non-deterministic effect. You can't (or shouldn't) just get the value out of a monad, in the same way that you can't just get "the" value out of an array -- it doesn't make much sense to talk about. The use of monads to represent non-deterministic effects is a software engineering choice. The "monad" itself is just the structure of computation where you have a wrapped thing `M a`, to which you apply a "callback" `a -> M b`, to receive a monadic result `M b`. There are also some laws which a well-behaved monad needs to follow. The point of this is that even though that `b` might never eventuate, or might come about later, or might be multiple values, the `M b` can be treated as a plain-old function result and the type checker is able to make stronger guarantees. Pretty handy when your language is based around pure functions. Side note: async/await is pretty much do-notation for Promises. You write imperative-looking code and it gets magically turned into asynchronous callbacks for you.
- frogulis 5y agoI added some content and fixed short errors to turn this into a webpage here: https://frogulis.net/writing/async-monad https://frogulis.net/writing/async-monad
- pgorczak 5y agoYeah, Promise really helps illustrating these concepts. Also while being less explicit in the syntax, the combination of Promise and Promise.then forms a functor that - maps type x to type Promise<x> - maps function x -> y to function Promise<x> -> Promise<y>
- usrbinbash 5y agoThe thing is, in the time it took me to finish watching this video, I already hacked together a working prototype of what I need in plain procedural stateful Python.
- blippage 5y agoThe best explanation I've read about monads was actually in a blog, written in Python. When you jettison the burrito analogies, category theory and all that stuff, it's actually not all that bad. I can't find the page, unfortunately. In essence, think of a monad as something that takes a callback function. The callback function returns something. The monad calls the callback, and then does something based on the result. Once you get that into your head, it becomes a lot easier to understand what's going on.
- bradrn 5y agoYes! This is exactly how I prefer to explain monads to non-Haskellers. A monad is just a unified abstraction for a certain type of callback which pops up in a bunch of different places (promises, optionals, randomness etc.). In some ways, it’s almost like a formalised version of an design pattern in the OOP sense. There’s really no need for category theory or strained analogies to understand or use monads in normal Haskell code. (Of course, the theoretical underpinnings are still very interesting in and of themselves! They’re also a source for new patterns — things like free monads and comonads are very, very useful indeed. But it’s entirely possible to program Haskell without knowing a thing about category theory; most Haskellers don’t.)