Many programming languages make a distinction between expressions and commands.
Like other languages it makes the distinction, and like other languages it has its own slightly idiosyncratic notion of what the difference is. The IO monad is just a device to help make this distinction.
There is no room for anything like a print command here because a print command doesn't return a value, it produces output as a side-effect
It's easy to use: you just write do and then follow it by an indented list of commands. Here's a complete Haskell program:
Note also that commands take arguments that can be expressions. So print (2*x) is a command, but 2*x is an expression. Again, little different from a language like Python.
So here's an interesting feature of Haskell: commands can return values. But a command that returns a value is different from an expression with a value.
We have to use <- instead of let ... = ... to get a returned value out of a command. It's a pretty simple rule, the only hassle is you have to remember what's a command and what's a function.
To introduce a list of commands, use do.To return a value from a command, use return.To assign a name to an expression inside a do block use let.To assign a name to the result of a command, use <-.
what's a command and what's an expression? If it has any chance of doing I/O it must be a command, otherwise it's probably an expression.
Everything in Haskell has a type, even commands. In general, if a command returns a value of type a then the command itself is of type IO a.
eturn is simply a function of type a -> IO a that converts a value of type a to a command of type IO a.
5. The type of a sequence of commands is the type of the last line.
The type of an if statement is the type of its branches. So if you want an if statement inside a do block, it needs to be a command, and so its branches need to be commands also. So it's
If something is of type IO a then it's a command returning an value of type a. Otherwise it's an expression. That's the rule.
following the rules above it's completely impossible to put an executed command inside an expression.
As the only way to do I/O is with commands, that means you have no way to find out what Haskell is doing inside expressions.
If the type isn't IO a, then you can sleep at night in the confidence that there are no side effects.
One last thing. Much of what I've said above is false. But what I hope I have done is describe a subset of Haskell in which you can start some I/O programming without having a clue what a monad is.
The idea of capturing imperative computations in a type of (immutable) values is lovely. And so is the general pattern we call "monad".
Semantically, FRP's concurrency is fine-grained, determinate, and continuous.
Dynamic/evolving values (i.e., values "over time") are first class values in themselves. You can define them and combine them, pass them into & out of functions. I called these things "behaviors".
In software design, I always ask the same question: "what does it mean?".
It's been quite a challenge to implement this model correctly and efficiently, but that's another story.
The basic idea behind reactive programming is that there are certain datatypes that represent a value "over time". Computations that involve these changing-over-time values will themselves have values that change over time.