5 ms·
> 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 intelli
by aranchelk 5y ago
> 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.
- goto11 5y ago> 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.
- aranchelk 5y agoAgreed, 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.
- goto11 5y agoI disagree. This code: main = do putStrLn "Hello, what's your name?" name <- getLine putStrLn $ "Hello " ++ name is not pure in any meaningful sense. At least not any more pure than the similar code in any other language.
- papamonad 5y agoThat 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
- goto11 5y agoThen so is this code: 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?
- papamonad 5y agoYou 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.
- goto11 5y agoOf 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.
- 5y ago
- thewakalix 5y agoI'd say it's more that IO is a sublanguage. The type (Int -> IO Int) seems more specialized than the type (Int -> Int), for example.
- rtpg 5y agomonadic 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!
- goto11 5y agoSo 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.
- tome 5y agoI wrote down the properties that we acutally care about when we talk about purity at https://news.ycombinator.com/item?id=27261487 https://news.ycombinator.com/item?id=27261487
- przemo_li 5y agoThat'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.
- Blikkentrekker 5y agoThat's not a consensus at all. 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.
- anderskaseorg 5y agoIt’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.
- Twisol 5y agoYou'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.
- bade 5y agoI'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.
- aranchelk 5y agoThat’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.
- comex 5y agoHere 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.)
- bitwize 5y agoMonads 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.