>>> map print ["hello","world","!"]
<interactive>:2:1:
No instance for (Show (IO ())) arising from a use of `print'
Possible fix: add an instance declaration for (Show (IO ()))
In a stmt of an interactive GHCi command: print it
What's going on here? Let's look at the type of (map print) to find out: >>> :t (map print)
(map print) :: Show a => [a] -> [IO ()]
(map print) is a function which takes a list of values of type a -- such that a can be shown as a String; hence the Show a => constraint -- and returns a list of values of type IO () (pronounced IO unit). These are monadic values representing computations that perform the actual IO. Hence, (map print) is a pure function which carries no side effects.So, what the heck do we do with this strange list of IO ()s? Well, one answer is to pass them to sequence_:
>>> sequence_ (map print ["hello","world","!"])
"hello"
"world"
"!"
Ahhh, so now we get to the impure function: sequence_! Actually, sequence_ is a pure function as well. Its type is: >>> :t sequence_
sequence_ :: Monad m => [m a] -> m ()
sequence_ merely takes a list of monadic values and combines them into a single monadic value, discarding any of the elements' return values and returning () instead.So if everything is a pure function, how do we actually perform the side effects? The simplest way to think of it is that our whole program is a bunch of pure functions which construct a single value representing all of the side effects that will take place over the lifetime of the program. This single value is called main:
main :: IO ()
main = sequence_ (map print ["hello","world","!"])
With this idea in mind, we can think of Haskell's runtime as taking this single value main and performing the side effects specified throughout our program.So when people say that some functions in Haskell are "impure" they mean that they produce IO actions that, if sequenced, will depend upon or cause IO effects. Thus, both
map print ["hello","world","!"] :: [IO ()]
and mapM_ print ["hello","world","!"] :: IO ()
are equally pure or impure: They both produce actions that have side effects if sequenced. It's just that the first must be sequenced differently than the second since it produces a list of actions and not a singleton action. Since singletons can be sequenced with (>>) and (>>=) you can insert them directly into do notation, which makes many people believe that they are somehow different in terms of purity. (But they are not.)While using the type system to implement an effects system is theoretically elegant, I think it's a beautiful hack that has made the language fussier and more obtuse in practice.
At the end of the day most of us have differing opinions on what constitutes simplicity and elegance. It's certainly true that a "pure" annotation like you're proposing is a much smaller change to introduce in an imperative setting. I recollect D or Rust or something is doing this. But in the functional programming context the monadic solution is more general, and a two-function type class with 3 (IIRC) algebraic laws is not considered an overbearing amount of complexity, though there are of course interesting alternatives with their own merits.
Function can manage state of variable destructively if the variable is declared unique (there can't be other references, so there can't be side effects from destructively modifying the value).
It's easy to understand. It opens more avenues for fast code, and it keeps the purity.
Looking at Clean's documentation it certainly seems nice, but I know of no other language that had adopted Uniqueness typing.
[Clojure's Transients (http://clojure.org/transients) perhaps?]
Haskell allows you to annotate classes of side effects such as "changes state" or "might throw exception", not necessarily the full IO monad, so annotating as pure doesn't make sense.