5 ms·
What you say is correct, but let me try and explain a little further. The Haskell function `getChar` is used to get a character from stdin. It has the type `IO
by anchpop 6y ago
What you say is correct, but let me try and explain a little further. The Haskell function `getChar` is used to get a character from stdin. It has the type `IO Char` and is a totally, 100% pure. In fact, it isn't even a function, it's just a value.
You can write `getChar` as many times as you want, and you will get the same result every time, and it will have nothing to do with whatever character is being sent to stdin. Instead, what you will get is an instruction, saying "please get a character from stdin".
What the IO monad allows you to do is to compose instructions together. For example, you could write `getChar >> getChar". This makes a new instruction that says "please get a character from stdin, discard it, then get a character from stdin". The `>>` operator means "follow the instruction on the left, discard the result, then do the instruction on the right".
Your entire program is made by composing instructions like this, into one new huge instruction that specifies your entire program's behaviour. You assign that to the special name `main`. At runtime, the instruction is executed.
The only bit of compiler magic that's necessary is the code for interpreting the instructions and actually executing them. In theory, although haskell doesn't let you do this, you could write `getChar` yourself, and just return the same thing that `getChar` does, and it would behave identically.
This I feel is the simplest explanation for why monads are necessary in Haskell. They allow you to conveniently specify what you want your program to do, using only pure functions and values. Why do you want all values to be pure? Many don't because it's kind of a pain, but Haskell kind of exists to see if a lazy, pure functional language can be fun to use and many find that it is. Hope this helps someone!
- choeger 6y ago> In fact, it isn't even a function, it's just a value. What's the difference?
- dllthomas 6y agoFunctions are values, but values that are not functions are those that cannot be supplied an argument.
- ridiculous_fish 6y agoGreat description of IO, but I think it doesn't actually motivate monads. Your example of `getChar >> getChar` could be simply `[getChar, getChar]`. Why can't we just compose instructions into an ordinary list? The answer is that we can, and Haskell used to work this way [1]! So as 'chowells' observed the monad-part of IO is almost incidental. 1: https://stackoverflow.com/questions/17002119/haskell-pre-monadic-i-o https://stackoverflow.com/questions/17002119/haskell-pre-mon...
- a1369209993 6y ago> Your example of `getChar >> getChar` could be simply `[getChar, getChar]`. Why can't we just compose instructions into an ordinary list? Because you can't pass results from a earlier list element into the computation of a later element. You'd need a list with existentially-quantified element types such as: {-# LANGUAGE ExistentialQuantification, GADTs #-} foreign import ccall "exit" exit :: Int -> IO () infixr 1 :> data IOList a = forall b. (:>) (a -> Act b) (IOList b) data Act b where Getchar :: Act Char Print :: String -> Act () Exit :: Int -> Act () main' :: IOList () main' = (\_->Getchar) :> (\c->Print ("Got '"++[c]++"'\n")) :> (\_ -> Exit 0) :> undefined runio :: a -> IOList a -> IO () runio a (f:>fs) = flip runio fs =<< case f a of Getchar -> getChar Print s -> putStr s Exit n -> exit n main = runio () main' where f :> g :> h :> ... is your list of instructions (rather than f : g : h : ..., which doesn't allow the response type of f to be related to the argument type of g).
- thatcherc 6y agoAh, thanks - this is a really nice explanation. From reading a few "what is a monad" posts in the past, I get how the a monad can chain functions in sequence for imperative-style execution, but how the information like 'getChar' actually got in was never clear. It all comes down to the runtime I guess. I'm favoriting this comment so I can come back the next time I forget how all this works :)
- roenxi 6y agoAlthough true; I'm not sure much of that provides clarity. Eg, "Your entire program is ... one new huge instruction" - that is true of any program, and it doesn't sound like a useful abstraction for reasoning about programs. The idea that getChar returns an instruction rather than a character is similarly not an obvious improvement from getChar() returning a character. It is quite subtle for someone to complain that getChar() returning a char is suboptimal. Everything I've seen is monads are confusing because they solve a problem imperative languages don't think they have. As the article points out, function getDisplayPictureFromId(id) { const user = getUser(id) if (!user) return const profile = getProfile(user) if (!profile) return return getDisplayPicture(profile) } is totally fine and idiomatic in most imperative languages. Once a programmer starts chaining functions together (as functional programmers are wont to do) then the "if (!user) return" become a substantial blocker that needs a solution - which leads very quickly to monads and abstracting away IO.
- brabel 6y agoThat's not fine and idiomatic in any language I know. This would be: function getDisplayPictureFromId(id) { return getUser(id)?.profile?.displayPicture } If the language does not have the null-safe operator '?.' you just write it: function nullSafe(obj, callback) { if (obj) { return callback(obj) } return null } function getDisplayPictureFromId(id) { return nullSafe(getUser(id), (user) => nullSafe(user.profile, (profile) => profile.displayPicture) )) } Not as nice in this case, but with a few similar functions for composing other functions, you achieve similar benefits to Monads without having the complexity of higher-kinded types.