At the end of the day, this is a huge inner-framework anti-pattern over the same procedural syscalls that every other language handles procedurally. I shouldn't have to care what a monad is, and side effects? The whole point of I/O is side effects. It could be a no-op, idle process or CPU-burning busy loop if I didn't care about side effects.
From that doc:
"
So, in the end, has Haskell simply re-invented the imperative wheel?
In some sense, yes. The I/O monad constitutes a small imperative sub-language inside Haskell, and thus the I/O component of a program may appear similar to ordinary imperative code. But there is one important difference: There is no special semantics that the user needs to deal with. In particular, equational reasoning in Haskell is not compromised. The imperative feel of the monadic code in a program does not detract from the functional aspect of Haskell. An experienced functional programmer should be able to minimize the imperative component of the program, only using the I/O monad for a minimal amount of top-level sequencing. The monad cleanly separates the functional and imperative program components. In contrast, imperative languages with functional subsets do not generally have any well-defined barrier between the purely functional and imperative worlds."
So, basically, they acknowledge that their theoretical model has a huge impedance mismatch with what we write programs to do (I/O, eventually, somewhere). And that's fine, they can knock themselves out and I hope it's fulfilling for them. It's not for me.
Clearly you have no idea what you're talking about. I suggest first learning the language before spreading FUD about it.
Separation of pure and unpure components greatly simplifies the reasoning about the problem domain. Traditional imperative programs are often full of subtle bugs because calling a procedure can have arbitrary effect on your system. For example calling function with the same arguments can return different values based on arbitrary hard-to-track reasons (such as your OS's scheduler). It's just too much details to keep in your head.
But in Haskell pure functions are guaranteed to have same result with same input. It enables the programmer to create logically isolated blocks without messy interdependencies.
It's especially helpful for concurrent programming by freeing you from all the non-deterministic spaghetti.
Haskell has a steep learning curve, but it'll make you a better programmer in the long run.
"But in Haskell pure functions are guaranteed to have same result with same input." I'm pretty sure that same law applies to a pure function in any language, not just Haskell. #shitthatHNsays
In fairness, I could also say that hammers don't encourage carpentry best practices and as a result they cause many more smashed fingers than wood glue. The fact that a tool doesn't prevent counterproductive usage patterns doesn't automatically make it inappropriate for every job.
Point being, I/O doesn't square with that concept. For some reason, every time I point this out, I'm accused of not appreciating the beauty of pure functions. I love pure functions. Maybe they're not appreciating the ugly reality of I/O?
How much experience do you have composing I/O in Haskell to prove that I/O doesn't square with that concept? Others here have experience using Haskell with I/O, but you just seem to have a hypothesis without experience or examples to support it.
I'm seeing I/O as "side effects with possible random error conditions". I really don't see how that squares with functional purity.
It's late on EST but I promise if you put effort into explaining a higher-order take on this I'll put effort into reading and understanding it tomorrow. Have a good night.
I don't think they are acknowledging any impedance mismatch there. The point of that paragraph is to emphasize that Haskell retains the usual feel of imperative programming, with the important difference that the imperative bits of your code are cleanly separated from the pure bits due to the IO type.
There's no theoretical stuff here, it's just making sure that the caller knows about callees having side effects.
>At the end of the day, this is a huge inner-framework anti-pattern over the same procedural syscalls that every other language handles procedurally.
No, you have a tiny, very simple type that allows for type safe IO. It also happens to make haskell a more powerful imperative language than most imperative languages, as IO actions are first class and can be passed around and manipulated like anything else.
>So, basically, they acknowledge that their theoretical model has a huge impedance mismatch with what we write programs to do (I/O, eventually, somewhere)
No, they acknowledge that doing IO is so important that it should be done correctly. You are going to some pretty extreme mental gymnastics to misrepresent a language you want to hate.
> The I/O monad modifies the Universe that contains the set of all functions that comprise your program
That sounds rather complicated. My way of thinking of it is simpler than that. a -> IO b is just a function from a to b that can do some I/O. Nothing more complicated than that.
I recommend just regarding IO a as an action, or some description of an action, that returns a value of type a. And (>>=) constructs bigger actions by attaching continuations to actions.
It is (slightly) more complicated than that. Your return type is not b but IO b. Your callers will be performing functions that are defined on IO b with the result (such as bind)
The IO monad is just an action. Its a cons pair with a left and a right half. In an imperative language you could model the left half with a closure that takes no arguments, does I/O and produces a value.
The right half is usually empty, except when you use the `>>=` operator (or flatMap) on an existing action, e.g.
let c = a >>= f
That operator chains the function `f` can take the value produced by the first IO action and produce another IO action. After we apply that operator, the result `c` is a cons cell where the left part is the original action `a` and the right part is the function f which takes the value produced by a and produces the next action. Its sort of like a cons cell in regular lists, except the next value is provided by a function. A lazy cons cell, perhaps :)
The final result is a lazy chain of I/O cons cells (called "main", of course :P). Its passed to the Haskell runtime, which executes that chain as a recipe, alternating between doing I/O actions and evaluating the function to decide what to do next.
So what does this buy us? Mostly just referential transparency. What does referential transparency buy us? Easy refactoring. We can replace any expression with its value, even stuff like `putStrLn "test"`. We can say `let writeTest = putStrLn "test"` at the top of the file then write `do writeTest; writeTest` in main.
Another neat thing is that do syntax isn't limited to just IO, but works with anything that implements `flatMap` (and `unit`, which I forgot to mention). That means we can build our own imperative DSLs that produce IO-like monads which are then interpreted by our own interpreter, and the users of those DSLs can use the same do syntax. Which is pretty awesome. Here is a simple example: https://gist.github.com/tonymorris/b5dba9d7d877051d0164 and a much more complex one http://augustss.blogspot.com/2009/02/more-basic-not-that-any... :)
You have the option of two monads, Maybe and Either. Usually errors are handled with the Either monad. An operation may either return a result or an error. Together with do syntax the Either monad gives you multiple choices in handling errors.
You can handle every single error explicitly (as in Go) using pattern matching
eitherResultOrError = operation1 arg
case eitherResultOrError of
Left error -> handle error
Right result -> handle' result
Its also possible to chain multiple operations then check the error later. If an error occurs, the next operations in the chain will not execute.
let eitherResultOrError = do
x <- operation1 arg
y <- operation2 x + 1
z <- operation3 x y
case eitherResultOrError of
Left error -> handle error
Right result -> handle' result
Or simply use `orElse` to return a default value in case of errors.
For IO operations and other monadic actions, its best to use EitherT, ErrorT or MaybeT, which are monads that can add error handling to any other monad. To understand how these work, its probably best to implement MaybeT. Basically, they add another wrapper to other monads to redefine what the bind operator (`>>=`) does
But, for example, https://www.haskell.org/tutorial/io.html.
At the end of the day, this is a huge inner-framework anti-pattern over the same procedural syscalls that every other language handles procedurally. I shouldn't have to care what a monad is, and side effects? The whole point of I/O is side effects. It could be a no-op, idle process or CPU-burning busy loop if I didn't care about side effects.
From that doc:
" So, in the end, has Haskell simply re-invented the imperative wheel?
In some sense, yes. The I/O monad constitutes a small imperative sub-language inside Haskell, and thus the I/O component of a program may appear similar to ordinary imperative code. But there is one important difference: There is no special semantics that the user needs to deal with. In particular, equational reasoning in Haskell is not compromised. The imperative feel of the monadic code in a program does not detract from the functional aspect of Haskell. An experienced functional programmer should be able to minimize the imperative component of the program, only using the I/O monad for a minimal amount of top-level sequencing. The monad cleanly separates the functional and imperative program components. In contrast, imperative languages with functional subsets do not generally have any well-defined barrier between the purely functional and imperative worlds."
So, basically, they acknowledge that their theoretical model has a huge impedance mismatch with what we write programs to do (I/O, eventually, somewhere). And that's fine, they can knock themselves out and I hope it's fulfilling for them. It's not for me.