Haskell uses monads to encompass IO actions because IO is so much more than the stdout and stdin. The stderr, file operations, accessing raw memory, network connections and all sorts of global states are the domain of the IO monad.
From how I understand this approach, these are effectful functions that have the size effect of printing on a prompt when first ran.
While it is true that they yield the same result back to the program on each invocation, so would OCaml invocation `Printf.printf "Hello, World!"`, it namely always returns `()`, but it is quite an effectful function, of course.
> Haskell uses the seriously complex machinery of monads to do I/O, supposedly without side effects (I don’t accept this).
The consensus of many highly intelligent people knowledgeable on the subject seems to be monads allow you to maintain a pure language while performing IO. This shouldn’t make it an incontestable fact, but probably should make disagreement worth more than a throwaway line — that is if you want to avoid looking like a crackpot.
> monads allow you to maintain a pure language while performing IO
I/O is by definition not pure, but the use of monads allow a strict separation between the pure and impure parts of the code. I guess you can say that monads allow you to have a guaranteed pure sublanguage.
Agreed, and AFAIK if you’re implementing IO, it’s either in the runtime or you’re doing FFI, so bizarrely (again AFAIK) all of those statements are accurate.
Edit: sorry I glossed over the last bit regarding a sub-language. The point is the whole language is considered (at least by many of its proponents) pure: monads + runtime + FFI are the gymnastics that allow one to perform IO in a pure language.
That code is 100% pure, it is a value of type IO (), it can be passed to pure functions, kept in lists. It is a pure description of some impure actions. This is run by the runtime. But within haskell is 100% pure
function main() {
let name = prompt('Hello, what is your name?');
alert('Hello, ' + name);
}
This function can also be passed around, kept in lists etc. without executing it. So you can also say it is a specification of impure actions. It only has effect when executed by the runtime. So within JavaScript I guess it is 100% pure?
You could almost say that, if there was any notion of purity in javascript. You don't get referential transparency within javascript so talking about purity in that case is really moot.
The main point is: Within haskell we have purity, if you have Int -> Int it is referentially transparent, it can't do anything but take inputs and produce outputs.
If we have an expression like
let x = y + z
y = 10 + z
z = 3
these are just expressions that are reduced to a type and a value.
IO () is a type, a higher kinded type that satisfies properties the same way a list does. This type has meaning and the meaning is some "Side effectful action producing something". we can manipulate, map and combine this type and it just represents some action to be performed, which is pure as it just represents the actions. The value is the actions to perform.
A haskell program is an expression that reduces to the type of an action IO ().
So while yes both the IO monad and the js can represent the same effects, it isn't equivalent when we are looking at something that was never pure to begin with and drawing the similarities of what they represent.
Of course you can have purity in JavaScript. This function is referentially transparent and pure:
(a, b) => a + b;
While this function is not pure:
(msg) => console.log(msg)
However by your definition I guess the above function is actually also pure, since it can be passed around and manipulated without executing it?
The cool thing about Haskell is that purity can be statically guaranteed by the type system and that unpure function are explicitly marked as such in their type signature.
That function isn't referentially transparent as enforced by the language/compiler, doing no effects in a function isn't the same as having a referentially transparent function. Nothing is stopping you from logging/introducing side effects in (a,b) => a + b; you chose not to which doesn't make it referentially transparent. Being enforced or statically guaranteed is what makes haskell have purity and js not have it.
monadic IO isn't pure in the same sense that even a pure Haskell function isn't pure (cuz it's usually executing on a register machine), but from a language semantics thing, it _is_ a pure thing!
The whole premise is that you end up with some main function which is a description of side effects, but you still have referential transparency and the like at a language level (so I/O doesn't need special treatment).
(there's a whole thing about "monad = IO" in laypeople discussion of Haskell which I think muddies this. There are loads of monads not about doing side effects, like list. But instead people think there's a link between monads and side effects, because we say "IO monad" instead of just "in IO" or something)
EDIT: it's v true that like.... side effects are side effects are side effects, so you can take that shortcut when talking about stuff. But if you want to get into the details about whether some expression is pure or not, you're brushing up against important semantics!
So in another subthread I compared two code samples. One in Haskell:
main = do
putStrLn "Hello, what's your name?"
name <- getLine
putStrLn $ "Hello " ++ name
And one in JavaScript:
function main() {
let name = prompt('Hello, what is your name?');
alert('Hello, ' + name);
}
My claim is that either both have side effects or neither. In both cases the definition of a function does not in itself have side effects, and the function can be passed around and manipulated without being executed. But when the function is executed, it causes side effects.
Haskell have the feature that the type system can statically verify if functions are pure or not. This is very cool, but it does not change the semantics or purity of a function, it just verifies it statically.
That's because every single line of code is about side effect.
That means that you have already eliminated all the unnecessary side effects (in both cases), and there is no extra work that could be made pure (also in both cases).
Haskell gives you good tools to limit number of side effects to minimum necessary and verify that it is so.
JavaScript? It's free for all and people talk all the time how developer discipline leads to more maintainance codebases. Examples give for that discipline? (Almost) all are about more purity.
Many are of the position that the IO monad is purely a type theoretical hack that allows one to control the order of effectful functions in Haskell that under the surface creates a vacuous data dependency that ensures the optimizer must sequence them in a particular order.
There is no consensus as to what the IO monad “is”; only what it's specific semantics with regard to execution order is.
But it serves it's purpose in that allows one to control the order of certain effects in another wise non strict language while semantically not necessarily relying on controlling the order of execution of function calls.
On the type level, the effect of the function is separated from the calling of the function and it's effect need not necessarily take place conceptually when the function be called; under the hood, it simply ensures that the optimizer cannot move out of order the effectful kernel of the function by way of this vacuous data dependency.
It’s easy to demonstrate that the IO monad is pure by reimplementing it in 100% pure code.
{-# LANGUAGE GADTs #-}
data IO' a where
Pure :: a -> IO' a
Bind :: IO' a -> (a -> IO' b) -> IO' b
PutStrLn :: String -> IO' ()
GetLine :: IO' String
-- more IO operations…
instance Functor IO' where
fmap f ma = Bind ma (Pure . f)
instance Applicative IO' where
pure = Pure
mf <*> ma = Bind mf (\f -> Bind ma (Pure . f))
instance Monad IO' where
(>>=) = Bind
The actual implementation in GHC is much more efficient, of course. GHC does all sorts of magic to hide the fact that it’s generating straight-line code instead of a tree-like data structure full of continuation functions. But it’s semantically equivalent.
You're right, but I think there's a distinction here that is easy to miss.
An IO action describes what to do, but something still has to do that action. A Haskell program on its own doesn't do anything; that's exactly why it's pure. The Haskell runtime interprets an IO value, unraveling it into the string of commands that comprise it. The runtime executes the commands produced by the Haskell program.
Haskell uses monads exactly because it lets you describe/express complex sequences of actions (for which later actions depend on earlier actions) without requiring that the machinery for actually performing those actions be part of the program too.
That would be equally true of a programming model where every program is a pure function from a list of input lines to a list of output lines. Everyone would agree that such a model is pure (if limiting), even though the function itself is just a function, and the language runtime takes care of the ugly low-level details of actually making the system calls that read lines from STDIN and write lines to STDOUT.
Purity doesn’t mean that effects don’t happen. It means that effects don’t happen as a side effect of mere expression evaluation.
> Purity doesn’t mean that effects don’t happen. It means that effects don’t happen as a side effect of mere expression evaluation.
I don't disagree? I was adding color to your explanation for other readers, not attempting to correct you.
The evaluation of a program merely reduces or simplifies it, transforming its structure without adding or removing information. When we can't reduce further without interacting with the environment, something else must mediate that interaction.
> That would be equally true of a programming model where every program is a pure function from a list of input lines to a list of output lines.
Indeed, Haskell used this model (`[Request] -> [Response]`) before switching to use an IO monad. It made for a poor architectural basis for complex programs.
But isn't it the case in any programming language that impure operations only have side effects when they are executed? You might as well argue that a JavaScript program on its own is pure - it is only when it is executed by the runtime it has side effects.
You can also pass a function around without executing it in JavaScript.
JS will print out "hello" and "world" and the value of io_actions[0] is undefined. In Haskell, the value of io_actions !! 0 is IO "world" which is just a value you can pass around in your program. Only when the Haskell runtime evaluates your code is when it will do the effect, which is to print "world". "hello" is never printed.
Naturally you can also pattern match on IO ():
f :: IO ()
f = case putStrLn "hello" of
_ -> putStrLn "world"
also only prints "world".
The IO type in Haskell is akin to IO (State World -> (State World, a)), ie. the state monad where the state threaded through the computation is the "real world". This can be demonstrated with the following:
f :: IO ()
f = IO $ \world -> case putStrLn "foo" of
IO _ -> case putStrLn "bar" of
IO g -> g world
If your point is that one can implement a Haskell like language in any turing complete programming language then sure, you are correct. GP's point however was that Haskell is purposefully designed to be written and thought about this way from the ground up. Ie. putStrLn "hello" is not a command that is executed verbatim when the runtime gets to its source position. It's an IO action and an IO action is just a value that can be passed around or, by virtue of IO being a Functor, mapped over, etc...
If you say that purity and referential transparency are not only reserved for Haskell then again technically sure, but the point of Haskell is that it makes these concepts the centre of its design. Instead of having to jump through hoops to write code where you can do the above you have to jump through hoops to do the opposite.
My point is just that input/output is not pure in Haskell. I believe it is an unfortunate misconception that obscures what the actual cool properties of Haskell is.
IO functions like putStrLn is curried by default, so you can pass them around without executing them. But this does not affect the purity of executing them. And it is not something special for Haskell, you can pass functions around without executing them in almost any language.
The cool thing about Haskell is that code is pure by default, and it is only due to special-casing by the language (the type of "main" being "IO ()") that you can even have operations with side-effects.
Haskell allows you to have parts of the program guaranteed pure, but the program is a whole is not pure if it does any input/output.
It will not be executed until the very last line of your app.
O_O
No seriously. If you wanted to model IO in say Java. Your whole program would be about constructing values. No I/O what so ever. Only do this then that then get a, then if a is this do x other.....
Whole Java app and I/O would only happen when at the very end you execute run method on that constructed value.
That'd not usual Java. After all in Java code itself can do control flow and I/O. Do why bother with those values. Need I/O on line ten out of 100. Do I/O immediately on line 10.
What I/O on Haskell is is side effect. Explicit side effect.
It's possible to write impure code in Haskell but that is not IO. It's when you use escape hatches that break type system guarantees.
> But isn't it the case in any programming language that impure operations only have side effects when they are executed?
That's a great question! This framing is indeed universally true, which suggests there's something less useful about one or more of the definitions it relies on. For my money, I'm betting on our definitions of "executed" being different.
What exactly does it mean to execute a program? For most of us practitioners, it means using a program for its intended purpose. I think this is closest to the meaning you're using. But this embodies everything at all about a program, so it is quite coarse grained.
Can we break "execution" into smaller pieces? Yes, at least conceptually. First, we can speak of "reduction": it's what happens when you simplify "1 + 1". Second, we can speak of "interaction": it's what happens when you substitute for "x" in "x + 1".
Reduction is purely internal: the program state evolves purely on the basis of the information it already has. We perform reduction in order to turn the program into a more useful form, much as in algebra we might put a linear expression into slope-intercept form.
Interaction operates explicitly on the exchange of information between two parties: the program and its environment. We perform interaction when we need to make some information known by one party available to the other.
Personally, I take my definition of "side-effect" to mean "requires interaction". For instance, take the (JavaScript) program `(x) => x + 2`. This program cannot be reduced; in order to reduce it further, we need to interact with it by providing an argument. That interaction transforms the program to, say, `((x) => x + 2)(42)`, and now we can reduce down to `44`, which cannot reduce and cannot interact.
Of course, if my program was `((x) => x + 2)(42)` from the beginning, there is no side-effect. At least, not one visible from the outside: the interaction between programs `(x) => x + 2` and `42` has been promoted to a reduction via composition.
This kind of interaction allows us to implement request-response protocols between the program and an environment, so long as we have a runtime to mediate the interaction. But it's also somewhat painful to author programs in this style: we have to return a callback all the way back to the top of the program whenever we want to interact.
If we add interaction primitives to the language itself, as in `prompt("Enter a number:")`, we gain the ability to interact from anywhere in the program, in exchange for the added complexity of a runtime that has to keep track of where in the program the interaction is occurring. This is great! But can we promote the `prompt` interaction to an internal reduction as we did with function application?
Well, in JavaScript, _kind of_. Since most symbols are dynamically looked up, you could overwrite `window.prompt` before running the inner program, and reset it afterward. But this impacts every call to `prompt`, not just the one (out of potentially many) you wanted to intercept. Providing an internal `prompt` interaction becomes a bit fraught; it's not very composable. And in other languages, you couldn't do this at all. (Dynamic lookup is itself a kind of primitive effect system, so you can backdoor a surprising number of things through it.)
Suppose that, instead of adding interaction primitives directly to the language, we stick with the original idea of "a returned top-level function is a request for interaction", but try to dress it up a bit more nicely. We still want to be able to perform interaction deep within the program, but we need some out-of-the-way machinery that propagates that interaction up to the top level for us, and manages remembering how to get back to where we were. In other words, we need to implement the system that was previously in the runtime in our program instead. Only the actual doin...
Your code is not an implementation, only a type theoretical description.
You have described the typological constraints the IO Monad conforms to, but you have not actually implemented any functions in it.
That is why some are of the position that it is merely a type system hack that fools the type system into thinking that it is pure as any other, whereas under the hood it requires coöperation from the optimizer with vacuous dependencies to ensure that all executions happen within the specified order.
The axiomatic functions of the IO Monad have to be provided as primitives by the compiler, they cannot be written as a purely library function, unlike, say `||` in Haskell which can actually be realized as a library function unlike in most languages where it must be a specially treated primitive.
You can write real code given only the definitions I gave:
someCode :: IO' ()
someCode = do
PutStrLn "What is your name?"
name <- GetLine
PutStrLn ("Hello, " ++ name ++ "!")
and you can run any such program, for example by translating it to the “real” IO:
runIO' :: IO' a -> IO a
runIO' (Pure a) = pure a
runIO' (Bind ma f) = runIO' ma >>= runIO' . f
runIO' (PutStrLn s) = putStrLn s
runIO' GetLine = getLine
main :: IO ()
main = runIO' someCode
or you can imagine an implementation of Haskell where IO is IO' and PutStrLn really is that data constructor and no such translation is necessary.
You might ask, what’s the point of separating it out this way, when obviously you still need to actually perform the effects at some layer? The point is to demonstrate that running code in the IO' monad (and, similarly, the IO monad) does not require “reaching into” the pure functions that define it and violating their purity; you just use them as black-box mathematical functions. All the equational reasoning you can do with pure functions applies equally well to the IO monad.
And then you still define them in terms of the actual primitive functions that are a hack.
This is entirely different, for, say, floating point arithmetic, which one could in theory simulate from the ground up by defining one's own float as a vector of binary states and manually implement floating point arithmetic on it.
> All the equational reasoning you can do with pure functions applies equally well to the IO monad.
Only because these primitive functions receive special treatment from the optimizer which ensures that they are not optimized in the same way and allowed to be executed in indeterminate order. — they are very much magical primitives that cannot be simulated ex nihilō.
No. runIO' does not define PutStrLn, it translates PutStrLn; there’s a difference. I can write and compile all the IO' code I want without runIO' ever existing, and it has the semantics it has as a data structure built from pure functions. I can write a pure function that computes which effects would happen for a given IO' action for a given sequence of inputs, and I can evaluate this function within the GHC REPL. None of this receives any special treatment from the optimizer.
-- action -> input -> (return, remainingInput, output)
simulateIO' :: IO' a -> [String] -> (a, [String], [String])
simulateIO' (Pure a) input = (a, input, [])
simulateIO' (Bind ma f) input = (b, input'', output ++ output')
where (a, input', output) = simulateIO' ma input
(b, input'', output') = simulateIO' (f a) input'
simulateIO' (PutStrLn s) input = ((), input, [s])
simulateIO' GetLine (line : input) = (line, input, [])
λ> simulateIO' someCode ["Anders"]
((),[],["What is your name?","Hello, Anders!"])
The fact that the real IO receives special treatment from the GHC optimizer is an implementation detail that’s necessary for the correctness of GHC’s optimizations, but assuming GHC was implemented correctly, this has no visible effect on how programs are evaluated. If you actually run them, the resulting effects should agree with the predictions of the pure function above.
This is beside the point. Haskell have a number of built-in unpure operations like putStrLn. You can use the built-in IO type to execute these unpure operations.
If you define your own type called IO you can't use it to execute unpure operations.
I've proposed an approach that is simple, equation oriented and trivial to implement. I think a substantive discussion off its merits is more useful than arguing about consensuses.The Haskell alternative is to write what are obviously commands in what looks like C and I don't see this as pure. Sometimes the emperor has no clothes.
That’s a fair point regarding comments on the actual design vs its presentation in prose.
My feedback regarding the design goals: As a working functional programmer, I find monads to be extremely helpful in many scenarios, I use them as much for validation, business logic, and parsers, as I do for IO. I also like how easily monads can be used to handle async with the same syntax as synchronous IO. Though I haven’t used Haxl yet, I find the idea of using applicative functors for free/automatic parallelism really interesting; I gather that a contributing factor for this being somewhat straightforward is because applicative is a superclass of monad. I also don’t believe monads are actually that complicated, just a scary name and many poor explanations floating around. All of this is to say I don’t find the design goals of eliminating monads very compelling.
Here is a question. Can you have an input whose prompt depends on the results of prior input calls?
If you can’t, then this only solves a rather limited subset of I/O, where the program is essentially a pure function whose input is the set of answers to a fixed set of prompts. It’s sufficient for some use cases, but most real programs need more interactivity than that.
If you can depend on prior inputs, on the other hand, then input() is not pure. Simply evaluating it has the side effect of displaying some text to the user.
(Arguably this would be true even if the prompt couldn’t depend on prior input, as long as it was the act of evaluating input() that caused the prompt to appear. An alternative would be to treat input() as a sort of macro, where the interpreter statically analyzes the program to determine every prompt for every input() call that could possibly be made, asks the user up front for responses to all of those, then, when input() is actually evaluated, just looks up the already-entered response. That would make input() pure. And maybe you could argue that even if input() does display a prompt only at the time it’s evaluated, it’s sort of morally equivalent to what I previously mentioned, just saving time by not asking unnecessary questions. But that goes out the window if questions can depend on prior answers.)
Monads don't make side effects in I/O go away, but they do help you manage them. Crucially, the IO monad describes a computation which must then be run. Computations are describable in side-effect-free code. Running them is handled automatically by the Haskell runtime, and can be done without introducing side effects into the language.
> And you end up writing stuff like main = do { putStrLn "Hello, World!" ; return () } which to me looks like C. There must be a more functional approach.
It looks imperative because that's what Haskell's do-notation was designed to. But it's merely syntactic sugar for the functional equivalent
If the author really doesn't like the "imperative" syntactic sugar then it's always possible to write equivalent code without it, but it quickly becomes painful for more complex situations.
'do notation' is just a more generalised version of what async/await does. No one wants to go back to writing async code with callbacks / continuations.
"Please don't post shallow dismissals, especially of other people's work. A good critical comment teaches us something."
He's a well-known language designer, famous for his work in dataflow, whose work was influential in the field. We should ask him to explain what he meant by that, before jumping to internet cheap shots.
If you really want your program's output to be a pure function of its input you can use the "interact" function in Haskell. I would recommend that the author try this and see the limitations, since it might help him start to understand why Haskell uses the IO monad.
[The original paper which introduced Monads to Haskell](https://www.microsoft.com/en-us/research/wp-content/uploads/...) is a fantastic read, and extremely easy to understand. It explains clearly why monads are a good option, and what problem they're meant to solve in Haskell.
In the article itself, the line:
> As it happens the computation will need the value of b before that of a. If this is a problem, we can write the definition of root1 as
Is actually quite interesting: the problem being described there is almost identical to one of the problems that Haskell had with I/O, which motivated the introduction of monads.
One question for the author: monads are not just a tool for IO but form a coherent abstraction over all side-effects. In my Haskell code, I personally have used monads for applications including parsing, fallible operations, IO, random number generation and testing, amongst others. For me, the most useful feature of monads is that they act as a coherent abstraction, allowing me to write code which can work over any of these domains. How does the author propose to replace this?
Sure, but to me the best things about monads are precisely those ‘other uses’. And if that means you still need monads, then why not use them for IO as well? That then gives you the advantage of unifying everything under the same abstraction.
Monads are not strictly necessary in any language, and don't have to cover I/O at all. They are instead the same interface to a useful abstraction. They may as such be used together with all helper-functions and types, for many situations where one needs to wrap and unwrap data/types into more complex representations, and back again. They remain pure, since the actual side effects happen lazily outside of expression evaluations (by magic!).
They can also assist in decoupling code from side-effects, while keeping code simpler and coherent. Like with Go, just more subtly, it seems Haskell directs the programmer towards better code design, by giving helpful errors from the type system. This is because the types ensures that you avoid mixing what should not be mixed. If there's alot of errors and progress is slow, this is to be expected at startup, as you will be directed to find the better code structure to build your shell on. Once the shell is more complete, it will be easier to incorporate the functional core in that, or vica versa. So in that area also, it is helpful and useful, once one groks the hints of linter and compiler errors (use an IDE for this).
Something looks wrong to me though: a = input(‘coefficient of x squared’) is not a function. Well, it's not a function in a mathematical sense - an entity that provides a mapping between two sets (with some bells and whistles but let's leave it aside for now). Since the result of this "function" does not depend in any way on the input parameter and can be anything, that doesn't look like a functional solution of anything, it's just declaring that the issue does not exist. I mean, most non-functional languages don't care their functions aren't functions, and they are completely fine with it. But if you specifically declare you're going for functional approach and mention Haskell and IO monads, maybe you should?
Lots of tedious back-and-forth in here about how many pure angels can dance on the head of a side-effecting IO monad, as usual.
The desirable properties that we are trying to enforce are that, firstly
x = expression
y = expression
...
is the same as
x = expression
y = x
...
and that, secondly
x = expression
...
is the same as
...
when x does not appear in ... .
These conditions are an order of magnitude more important than any considerations about what "purity" really means, what side effects are versus effects, whether there's an impure runtime below a pure language and whether IO is a "sublanguage".
How is this more functional than IO in Haskell? Your approach seems to be the same as I/O handling in Haskell minus the convenience of monads and plus caching. Not all that different.
55 comments
[ 3.3 ms ] story [ 68.3 ms ] threadWhile it is true that they yield the same result back to the program on each invocation, so would OCaml invocation `Printf.printf "Hello, World!"`, it namely always returns `()`, but it is quite an effectful function, of course.
If you just needs a fixed set of inputs without dependencies, conditionals, branching etc you indeed don’t need monads.
The consensus of many highly intelligent people knowledgeable on the subject seems to be monads allow you to maintain a pure language while performing IO. This shouldn’t make it an incontestable fact, but probably should make disagreement worth more than a throwaway line — that is if you want to avoid looking like a crackpot.
I/O is by definition not pure, but the use of monads allow a strict separation between the pure and impure parts of the code. I guess you can say that monads allow you to have a guaranteed pure sublanguage.
Edit: sorry I glossed over the last bit regarding a sub-language. The point is the whole language is considered (at least by many of its proponents) pure: monads + runtime + FFI are the gymnastics that allow one to perform IO in a pure language.
The main point is: Within haskell we have purity, if you have Int -> Int it is referentially transparent, it can't do anything but take inputs and produce outputs.
If we have an expression like
let x = y + z y = 10 + z z = 3
these are just expressions that are reduced to a type and a value.
IO () is a type, a higher kinded type that satisfies properties the same way a list does. This type has meaning and the meaning is some "Side effectful action producing something". we can manipulate, map and combine this type and it just represents some action to be performed, which is pure as it just represents the actions. The value is the actions to perform.
A haskell program is an expression that reduces to the type of an action IO ().
So while yes both the IO monad and the js can represent the same effects, it isn't equivalent when we are looking at something that was never pure to begin with and drawing the similarities of what they represent.
The cool thing about Haskell is that purity can be statically guaranteed by the type system and that unpure function are explicitly marked as such in their type signature.
The whole premise is that you end up with some main function which is a description of side effects, but you still have referential transparency and the like at a language level (so I/O doesn't need special treatment).
(there's a whole thing about "monad = IO" in laypeople discussion of Haskell which I think muddies this. There are loads of monads not about doing side effects, like list. But instead people think there's a link between monads and side effects, because we say "IO monad" instead of just "in IO" or something)
EDIT: it's v true that like.... side effects are side effects are side effects, so you can take that shortcut when talking about stuff. But if you want to get into the details about whether some expression is pure or not, you're brushing up against important semantics!
Haskell have the feature that the type system can statically verify if functions are pure or not. This is very cool, but it does not change the semantics or purity of a function, it just verifies it statically.
That means that you have already eliminated all the unnecessary side effects (in both cases), and there is no extra work that could be made pure (also in both cases).
Haskell gives you good tools to limit number of side effects to minimum necessary and verify that it is so.
JavaScript? It's free for all and people talk all the time how developer discipline leads to more maintainance codebases. Examples give for that discipline? (Almost) all are about more purity.
Many are of the position that the IO monad is purely a type theoretical hack that allows one to control the order of effectful functions in Haskell that under the surface creates a vacuous data dependency that ensures the optimizer must sequence them in a particular order.
There is no consensus as to what the IO monad “is”; only what it's specific semantics with regard to execution order is.
But it serves it's purpose in that allows one to control the order of certain effects in another wise non strict language while semantically not necessarily relying on controlling the order of execution of function calls.
On the type level, the effect of the function is separated from the calling of the function and it's effect need not necessarily take place conceptually when the function be called; under the hood, it simply ensures that the optimizer cannot move out of order the effectful kernel of the function by way of this vacuous data dependency.
An IO action describes what to do, but something still has to do that action. A Haskell program on its own doesn't do anything; that's exactly why it's pure. The Haskell runtime interprets an IO value, unraveling it into the string of commands that comprise it. The runtime executes the commands produced by the Haskell program.
Haskell uses monads exactly because it lets you describe/express complex sequences of actions (for which later actions depend on earlier actions) without requiring that the machinery for actually performing those actions be part of the program too.
Purity doesn’t mean that effects don’t happen. It means that effects don’t happen as a side effect of mere expression evaluation.
I don't disagree? I was adding color to your explanation for other readers, not attempting to correct you.
The evaluation of a program merely reduces or simplifies it, transforming its structure without adding or removing information. When we can't reduce further without interacting with the environment, something else must mediate that interaction.
> That would be equally true of a programming model where every program is a pure function from a list of input lines to a list of output lines.
Indeed, Haskell used this model (`[Request] -> [Response]`) before switching to use an IO monad. It made for a poor architectural basis for complex programs.
You can also pass a function around without executing it in JavaScript.
Naturally you can also pattern match on IO ():
also only prints "world".The IO type in Haskell is akin to IO (State World -> (State World, a)), ie. the state monad where the state threaded through the computation is the "real world". This can be demonstrated with the following:
prints "bar".If you say that purity and referential transparency are not only reserved for Haskell then again technically sure, but the point of Haskell is that it makes these concepts the centre of its design. Instead of having to jump through hoops to write code where you can do the above you have to jump through hoops to do the opposite.
IO functions like putStrLn is curried by default, so you can pass them around without executing them. But this does not affect the purity of executing them. And it is not something special for Haskell, you can pass functions around without executing them in almost any language.
The cool thing about Haskell is that code is pure by default, and it is only due to special-casing by the language (the type of "main" being "IO ()") that you can even have operations with side-effects.
Haskell allows you to have parts of the program guaranteed pure, but the program is a whole is not pure if it does any input/output.
It will not be executed until the very last line of your app.
O_O
No seriously. If you wanted to model IO in say Java. Your whole program would be about constructing values. No I/O what so ever. Only do this then that then get a, then if a is this do x other.....
Whole Java app and I/O would only happen when at the very end you execute run method on that constructed value.
That'd not usual Java. After all in Java code itself can do control flow and I/O. Do why bother with those values. Need I/O on line ten out of 100. Do I/O immediately on line 10.
What I/O on Haskell is is side effect. Explicit side effect.
It's possible to write impure code in Haskell but that is not IO. It's when you use escape hatches that break type system guarantees.
That's a great question! This framing is indeed universally true, which suggests there's something less useful about one or more of the definitions it relies on. For my money, I'm betting on our definitions of "executed" being different.
What exactly does it mean to execute a program? For most of us practitioners, it means using a program for its intended purpose. I think this is closest to the meaning you're using. But this embodies everything at all about a program, so it is quite coarse grained.
Can we break "execution" into smaller pieces? Yes, at least conceptually. First, we can speak of "reduction": it's what happens when you simplify "1 + 1". Second, we can speak of "interaction": it's what happens when you substitute for "x" in "x + 1".
Reduction is purely internal: the program state evolves purely on the basis of the information it already has. We perform reduction in order to turn the program into a more useful form, much as in algebra we might put a linear expression into slope-intercept form.
Interaction operates explicitly on the exchange of information between two parties: the program and its environment. We perform interaction when we need to make some information known by one party available to the other.
Personally, I take my definition of "side-effect" to mean "requires interaction". For instance, take the (JavaScript) program `(x) => x + 2`. This program cannot be reduced; in order to reduce it further, we need to interact with it by providing an argument. That interaction transforms the program to, say, `((x) => x + 2)(42)`, and now we can reduce down to `44`, which cannot reduce and cannot interact.
Of course, if my program was `((x) => x + 2)(42)` from the beginning, there is no side-effect. At least, not one visible from the outside: the interaction between programs `(x) => x + 2` and `42` has been promoted to a reduction via composition.
This kind of interaction allows us to implement request-response protocols between the program and an environment, so long as we have a runtime to mediate the interaction. But it's also somewhat painful to author programs in this style: we have to return a callback all the way back to the top of the program whenever we want to interact.
If we add interaction primitives to the language itself, as in `prompt("Enter a number:")`, we gain the ability to interact from anywhere in the program, in exchange for the added complexity of a runtime that has to keep track of where in the program the interaction is occurring. This is great! But can we promote the `prompt` interaction to an internal reduction as we did with function application?
Well, in JavaScript, _kind of_. Since most symbols are dynamically looked up, you could overwrite `window.prompt` before running the inner program, and reset it afterward. But this impacts every call to `prompt`, not just the one (out of potentially many) you wanted to intercept. Providing an internal `prompt` interaction becomes a bit fraught; it's not very composable. And in other languages, you couldn't do this at all. (Dynamic lookup is itself a kind of primitive effect system, so you can backdoor a surprising number of things through it.)
Suppose that, instead of adding interaction primitives directly to the language, we stick with the original idea of "a returned top-level function is a request for interaction", but try to dress it up a bit more nicely. We still want to be able to perform interaction deep within the program, but we need some out-of-the-way machinery that propagates that interaction up to the top level for us, and manages remembering how to get back to where we were. In other words, we need to implement the system that was previously in the runtime in our program instead. Only the actual doin...
You have described the typological constraints the IO Monad conforms to, but you have not actually implemented any functions in it.
That is why some are of the position that it is merely a type system hack that fools the type system into thinking that it is pure as any other, whereas under the hood it requires coöperation from the optimizer with vacuous dependencies to ensure that all executions happen within the specified order.
The axiomatic functions of the IO Monad have to be provided as primitives by the compiler, they cannot be written as a purely library function, unlike, say `||` in Haskell which can actually be realized as a library function unlike in most languages where it must be a specially treated primitive.
You might ask, what’s the point of separating it out this way, when obviously you still need to actually perform the effects at some layer? The point is to demonstrate that running code in the IO' monad (and, similarly, the IO monad) does not require “reaching into” the pure functions that define it and violating their purity; you just use them as black-box mathematical functions. All the equational reasoning you can do with pure functions applies equally well to the IO monad.
This is entirely different, for, say, floating point arithmetic, which one could in theory simulate from the ground up by defining one's own float as a vector of binary states and manually implement floating point arithmetic on it.
> All the equational reasoning you can do with pure functions applies equally well to the IO monad.
Only because these primitive functions receive special treatment from the optimizer which ensures that they are not optimized in the same way and allowed to be executed in indeterminate order. — they are very much magical primitives that cannot be simulated ex nihilō.
If you define your own type called IO you can't use it to execute unpure operations.
My feedback regarding the design goals: As a working functional programmer, I find monads to be extremely helpful in many scenarios, I use them as much for validation, business logic, and parsers, as I do for IO. I also like how easily monads can be used to handle async with the same syntax as synchronous IO. Though I haven’t used Haxl yet, I find the idea of using applicative functors for free/automatic parallelism really interesting; I gather that a contributing factor for this being somewhat straightforward is because applicative is a superclass of monad. I also don’t believe monads are actually that complicated, just a scary name and many poor explanations floating around. All of this is to say I don’t find the design goals of eliminating monads very compelling.
If you can’t, then this only solves a rather limited subset of I/O, where the program is essentially a pure function whose input is the set of answers to a fixed set of prompts. It’s sufficient for some use cases, but most real programs need more interactivity than that.
If you can depend on prior inputs, on the other hand, then input() is not pure. Simply evaluating it has the side effect of displaying some text to the user.
(Arguably this would be true even if the prompt couldn’t depend on prior input, as long as it was the act of evaluating input() that caused the prompt to appear. An alternative would be to treat input() as a sort of macro, where the interpreter statically analyzes the program to determine every prompt for every input() call that could possibly be made, asks the user up front for responses to all of those, then, when input() is actually evaluated, just looks up the already-entered response. That would make input() pure. And maybe you could argue that even if input() does display a prompt only at the time it’s evaluated, it’s sort of morally equivalent to what I previously mentioned, just saving time by not asking unnecessary questions. But that goes out the window if questions can depend on prior answers.)
Seems like the author doesn't understand what's going on.
It looks imperative because that's what Haskell's do-notation was designed to. But it's merely syntactic sugar for the functional equivalent
which btw is equivalent to simplyIf the author really doesn't like the "imperative" syntactic sugar then it's always possible to write equivalent code without it, but it quickly becomes painful for more complex situations.
'do notation' is just a more generalised version of what async/await does. No one wants to go back to writing async code with callbacks / continuations.
He's a well-known language designer, famous for his work in dataflow, whose work was influential in the field. We should ask him to explain what he meant by that, before jumping to internet cheap shots.
https://news.ycombinator.com/newsguidelines.html
In the article itself, the line:
> As it happens the computation will need the value of b before that of a. If this is a problem, we can write the definition of root1 as
Is actually quite interesting: the problem being described there is almost identical to one of the problems that Haskell had with I/O, which motivated the introduction of monads.
They can also assist in decoupling code from side-effects, while keeping code simpler and coherent. Like with Go, just more subtly, it seems Haskell directs the programmer towards better code design, by giving helpful errors from the type system. This is because the types ensures that you avoid mixing what should not be mixed. If there's alot of errors and progress is slow, this is to be expected at startup, as you will be directed to find the better code structure to build your shell on. Once the shell is more complete, it will be easier to incorporate the functional core in that, or vica versa. So in that area also, it is helpful and useful, once one groks the hints of linter and compiler errors (use an IDE for this).
[1] Impure as per https://news.ycombinator.com/item?id=27259900
The desirable properties that we are trying to enforce are that, firstly
is the same as and that, secondly is the same as when x does not appear in ... .These conditions are an order of magnitude more important than any considerations about what "purity" really means, what side effects are versus effects, whether there's an impure runtime below a pure language and whether IO is a "sublanguage".