6 ms·
My problem with understanding monads was as follows: I had no problem with understanding the mathematical definition, it was easy to check that a given monad ve
by Rerarom 7y ago
My problem with understanding monads was as follows: I had no problem with understanding the mathematical definition, it was easy to check that a given monad verifies the axioms.
No, my problem was this one: everyone was saying "hey, you know if you have a purely functional language, it would be impossible to have IO, since e.g. a read function would have different outputs every time. We solve that with monads."
But that didn't feel like an explanation. I couldn't draw a line from the monad axioms to dissolving that impossibility. It was like saying "Einstein says we can't do FTL, but we can solve that with monads".
- deleted 7y ago[deleted]
- goto11 7y agoThe solution is not really depending on monads. You pass "the world" as a parameter to read. If you pass a different world, you will get a different output. All function which interact with the world get a "world" parameter and returns a new modified "world". So now all IO operations are pure! Monads are used as a trick to hide the "world" argument, so you can't cheat by e.g by reusing the same world twice. Maybe something like ownership in Rust could be used for the same effect?
- ourlordcaffeine 7y agoI thought Monads were needed for IO to make sure IO operations happened in the correct order?
- theaeolist 7y agoYes, this is the correct answer. For everything else you don't need monads. Monads impose an evaluation order, which Haskell by default does not have, since it is lazy.
- gmfawcett 7y agoMonads do not impose an evaluation order in Haskell. Monads let you thread state between computations, but that state may be lazily evaluated out-of-order. The belief that monads sequence IO operations is why new Haskell programmers have difficulty writing functions like "open file, read file contents, close file, and return contents". They assume the contents will be read before the file closes, and not when the contents are lazily evaluated up the call chain (leading to a "can't read closed file" error).
- deleted 7y ago[deleted]
- goto11 7y agoNo, you can force operations to execute in linear order without the use of monads, as long as one operation depends on the output of the previous. But you have to be careful not to reuse values. Imagine if the IO operations took an explicit world parameter instead of using a monad: main :: World -> World main world1 = let world2 = putStrLn world1 "Hello, what's your name?" let (world3, name) <- getLine world2 let world4 = putStrLn world3 ("Hey " ++ name ++ ", you rock!") in world4 The IO operations would be executed in the expected order due to the dependency of the "world" output of the previous operation. Monads not necessary. But if you accidentally used world2 twice then you would split the universe into two timelines. A monad can avoid this issue by hiding the world parameter and passing it on behind the scenes.
- sullyj3 7y agowhat actual concrete, real world, non sci-fi sounding problem does "splitting the timelines" represent?
- gmfawcett 7y agoA less poetic way of stating it: if you re-use an already-used world state, you are violating a logical constraint of the system. No timelines will be violated, you just have an invalid program. World states are supposed to consumed once only: they are "linearly typed" in fancy language. But Haskell cannot directly enforce the linear-type constraint on world states, so in theory these states could be used more than once. Fortunately, the IO monad encapsulates (hides) the states, so that you can't make this mistake in your application code -- you can't reuse what you can't access. World-states in Haskell aren't first-class values. They aren't actual objects that are being passed around during the execution of the program. They are more like tokens that exist during type checking, but are erased when the program is compiled or interpreted. Another way of phrasing it is that Haskell interpreters/compilers have specialized support for the IO monad: during type checking, it acts as if these states exist, but they have no operational meaning in the executed program.
- sullyj3 7y ago
- comex 7y agoYep, if you have linear types then you could model IO with functions that explicitly accept and return the world.
- codeflo 7y agoThe way most people explain this is completely backwards. Monads don't actually solve the I/O problem. So forget about monads and just consider how to mathematically model I/O. Take something like getStrLn (which reads a line from STDIN). What is "getStrLn", as a mathematical object? It's not a string, rather, it's some kind of "I/O operation" that when executed, returns a string. Haskell calls such an object an "IO String". Similarly, the expression (putStrLn "foo") is an I/O operation that when executed, doesn't return anything. Haskell calls this an "IO ()". Once you develop an intuition of I/O operations, you can see how you might want to combine atomic I/O operations into larger and larger I/O operation, feeding the output from one as input into the other. Or how you could treat a plain Int as an "empty" I/O operation that when executed, simply returns the Int. And how your entire program, its "main" entry point, can be modeled simply as one large I/O operation. You can do all of that, and write every possible kind of I/O code in Haskell, without ever learning about monads. What are monads for, then? They generalize the structure of this approach, with its (>>=) and return operators, and apply it to other cases that don't have anything to do with I/O. But this step is completely optional! This is very similar to how the idea of a mathematical "field" generalizes the arithmetic operators (+, -, *, /) and applies them to objects that don't look anything like the rational numbers. You don't have to learn about fields to successfully use rational numbers. And we would never dream of explaining the abstract idea of fields to people before we introduce them to rational numbers. Yet almost every I/O tutorial in Haskell begins with a half-baked explanation of monads.
- AnimalMuppet 7y agoLearn the concrete, then abstract? That's sound wisdom about teaching most anything, in my opinion.
- Rerarom 7y agoThat is how I finally got it, because I knew what groups/rings/fields were but did not realize at first that monads were just another abstraction like those and not an ontologically fundamental category. The way monads are usually presented leads you to believe that there is something magical in those axioms that somehow makes the impossible happen.
- mbrock 7y agoActually I/O in general is something that almost always comes from outside the language, so to speak. How do you define a file writing function in C? You have to appeal to some already existing magic syscall which actually cannot be formally defined in the base language. It has no real semantics in the base language, it’s a magic function-like thing that happens to have a “side effect.” Same with Haskell, only that the base language semantics absolutely forbids functions with side effects, so you have to think of a different way to model it. The IO type is that way. It models effects not as side effects of function application but with these explicitly sequenced command values that are returned from the main function. It’s just a different formalism. Just like how math equations don’t themselves have any physical effects but can still model physical systems in different ways. Computer languages don’t do I/O, they model and represent, with different semantics and restrictions.
- Rerarom 7y agoYou are right, but this is something that is not usually emphasized. I think I could have been persuaded back then that with enough cleverness you could write printf out of if's and while's :) Also, some years ago I even thought -- but not consciously -- that you could implement preemptive multitasking just from software (you can do it trivially via emulation, but it's slow and most importantly it's not what happens in actual OSes, where the code you write is the actual code that runs on the CPU). I think when I began reading about assembly and OS development, I was expecting the solution to appear at some point. But again, I was not aware of it (the moment I became, I realized how absurd it was), so I got disappointed in assembly without even knowing why.
- mikorym 7y agoAccording to one of my lecturers, who knows both Haskell and is a Category Theorist, and according to some of my fellow students at the time, monads in functional programming should not be seen as strict versions of the category theoretical concept. The key concepts for FP are: you are able to reference system state, you are able to leverage composibility better than non-FP languages, you can abstract and prove things around how your program should behave. I have a copy of Category Theory for Programmers that I haven't read in earnest, but I would think that the section on monads can clarify the main differences between the mathematical monad and the FP monad.
- deleted 7y ago[deleted]
- jhanschoo 7y ago> According to one of my lecturers, who knows both Haskell and is a Category Theorist, and according to some of my fellow students at the time, monads in functional programming should not be seen as strict versions of the category theoretical concept. Indeed, e.g. lists are monadic, but it is more natural to talk about maps from one list to another, and folding. But neither is a fundamental monad operation. The natural operation for monads in the example of lists is flatmap, which takes in a function (X -> List Y), and a List X, and returns a List Y. The `map` function is a less powerful sibling of flatmap, and the `fold` operation is more powerful and general than flatmap. So whereas CT monads distinguish one (admittedly very powerful) notion of composability, monad-like notions in FP may find other notions of composability more natural. Of course, the main draw of using monads in FP is with the way it quarantines off state and context.
- joppy 7y agoThere’s an incredibly straightforward and readable paper by Simon Peyton Jones (one of the creators of Haskell and GHC) which explains how Haskell deals with IO, exceptions, and concurrency. It also explains why they settled on this, rather than some other design. In my opinion, it is the best explanation of the IO monad (specifically IO) out there. Even just reading the first 10 or so pages is completely worthwhile. Paper: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/mark.pdf https://www.microsoft.com/en-us/research/wp-content/uploads/...
- the_af 7y agoHere's another upvote for Tackling the Awkward Squad. A really nice paper. No surprise, since the author is the great Simon Peyton Jones :)
- ratww 7y ago> hey, you know if you have a purely functional language, it would be impossible to have IO, since e.g. a read function would have different outputs every time. We solve that with monads. But that didn't feel like an explanation. In a pure language you can’t have functions that return the contents of a file, or prints to stdout, or anything like that. So what do you do? Instead of performing those computations in you program, you have to return (in the main function) a bunch of lambdas chained together that will be executed by the runtime that called the main function in the first place. Inside this chain of lambdas you will probably call the rest of your program. The Haskell function you can call that “prints a string” doesn’t do much: it just returns some dummy structure (similar to an AST node, but you can chain your pure functions there too!) that will be read and executed later by the runtime. All the work is done later! Your “main” in Haskell returns immediately, it doesnt do much. It does zero IO. This is similar to how promises work in JavaScript: you return a structure instead of the result. Also similar to how React works (If you know its internals) but don’t let that distract you!!! This is how the IO monad works. People later discovered that this kind of structure could be used to a lot of stuff, and other monads (State monad, Maybe monad, Free monad) were born. But these others don’t use the IO runtime. Also look up “functional core imperative shell”, this is how IO monad and the IO runtime works.
- sullyj3 7y agothe way the impossibility is resolved is that the language itself isn't responsible for doing io, but rather it is responsible for (purely) constructing a value which represents an io computation to be performed. this value is constructed by using the bind operator to combine simpler io computation values together. that computation is then actually executed by the runtime system, which is conceptually external to the language itself. The language knows about evaluation of pure expressions, the runtime knows about execution of effectful actions.
- crimsonalucard 7y agoYou're referring to the axioms defined by the type class. The actual math definition is pretty deep and requires knowledge and intuition on what a functor is and what a natural transformation is.
- Rerarom 7y agoYeah, but I know what those are, I have a math background.
- crimsonalucard 7y agoThe execution of the main function has side effects. All io functions have side effects that are only observable by main. The print function itself is a side effect. Yes I'm talking about Haskell. What the io monad does is force the user seperate pure functions and impure functions via type checking. I think it's sort of incorrect to say Haskell is pure. It's more that only from a certain perspective Haskell is pure.
- Dolores12 7y agohere is a good wiki resource that helped me to understand the use of monads. https://wiki.haskell.org/IO_inside https://wiki.haskell.org/IO_inside