3 ms·
Thanks to all for the answers! I want to make sure that I'm understanding this bit correctly: >A better metaphor is to think of values of type IO a as fragmen
by clementi128 8y ago
Thanks to all for the answers!
I want to make sure that I'm understanding this bit correctly:
>A better metaphor is to think of values of type IO a as fragments of a script in some other (imperative) language. The Haskell program merely computes the script, and the runtime then takes care of actually executing it. In this sense, effects in Haskell are not "side" -- they are first class citizens. What order the fragments of the imperative program are computed in (or how many times) is independent of the text of the final program.
In Wadler's paper, the monadic output example looks like this:
type M a = (Output, a)
type Output = String
unit :: a → M a
unit a = (“ ”, a)
(*) :: M a → (a → M b) → M b
m * k = let(x, a) = m in
let(y, b) = k a in
(x ++ y, b)
out :: Output → M ()
out x = (x, ())
where each function, instead of returning a simple numeric value, returns a tuple of a string (the output to be printed) and a number (the value to be returned). If you wrote this function out in Haskell, you would have access the output text in the form of a lazy list of strings.
If we can think of IO values as fragments of an imperative script that are handled at runtime, then that makes me think that the Haskell compiler doesn't use the strategy presented in Wadler's paper (in the case of printing output, returning a lazy list of strings to be printed), and instead just performs the specified side effects directly, knowing that semantics of the IO monad guarantee that things will be sequenced correctly. Do I have this right?
- lobster_johnson 8y agoRight, IO does not collect the results this way. putStr for strings is declared like so: putStr :: String -> IO () Here, () is the unit type, which is similar to null/nil in other languages. In other words, I/O functions like putStr etc. don't return anything. putStr internally calls lower-level functions that actually write to a file descriptor. Some people get into Haskell thinking that functions can't have side effects because Haskell is "pure", but that's not right. Rather, as the parent commenter say, they can have all the side effects they want, and the consistency and purity of the program's execution is preserved through the use of monads. Haskell is considered a pure functional language where everything is immutable, but that's not true, either. A lot of the GHC standard library uses mutation (e.g. with IORefs) for performance reasons. For example, putStr ends up writing to an internal byte buffer that is modified directly.
- zenhack 8y agoMore or less, yeah. Most of the language-aware optimization in ghc happens in an intermediate form that still carries the types, and then ultimately it spits out code that just has side effects as a result of evaluation like every other language. The code you sketch out above is basically the Writer Monad, which exists in the libraries, and does see use, but it's not IO -- as I mentioned above the semantics of IO don't entirely just fall out of the monadic structure. But I find the computed-imperative-program description lends itself to thinking about IO values as values in a way that thinking about them as type system goop to tame side effects doesn't; for example, the program: import Data.List (intersperse) import Control.Concurrent (threadDelay) main = sequence_ $ intersperse (threadDelay 1000000) (map print [1..]) will print out the natural numbers, one per second until you kill it. It works by constructing a (lazily evaluated) list of actions to perform, which alternately print out a number or wait for one second. sequence_ has type [IO a] -> IO (), and it just performs each action in the list in sequence (its type is actually a bit more general than this, but..). This kind of code is really weird to think about from the "tamed side-effects" model, but very natural from the "computing a script" model.