2 ms·
Practical programming is all about the effects. Without effects, maybe you can do very good mathematics, but write software you cannot. Unfortunately, effects
by catnaroek 10y ago
Practical programming is all about the effects. Without effects, maybe you can do very good mathematics, but write software you cannot.
Unfortunately, effects are quite unwieldy. The difficulty of reasoning about effectful programs grows faster than linearly on the number of effects that can happen in them, because effects interact with each other in all sorts of tricky ways. In the limit, reasoning rigorously about a program in which “anything can happen” is practically impossible.
The great insight of functional programming researchers and Haskell programmers is that you can use types to classify programs according to their possible effects, from which it follows that you don't need to worry about any other effects other than those sanctioned by the type. This is the essence of the whole monad business.
At this point, I need to introduce a little bit of terminology:
(0) A value is something you can bind to a variable. At this point, it doesn't matter whether “bind” means “let binding as in functional languages” or “imperative assignment”. Either works equally well.
(1) A computation is what happens when you evaluate an expression or execute a statement. Again, the difference between the functional and imperative versions doesn't really matter.
The difference between strict and lazy languages lies in what is considered a value:
(0) In strict languages (ML, Lisp, Java, Python, etc.), a value is the result of a finished computation. For instance, in Java, `x = 2 + 2;` means “bind the result of evaluating `2 + 2` to the variable `x`”. (In case pron ever reads this, I'm openly admitting he was right and I was wrong about this, when, in a previous flame war between us, he pointed out that `2 + 2` isn't `4`, but rather the former evaluates to the latter.) As a result, values form a pure sublanguage of any strict language: Since no computation can happen and everything is already its own final result, the statement “all computations are free of side effects, and successfully terminate with a result” is vacuously true.
(1) In lazy languages, however, unfinished computations are values in their own right. Compound with the inability of Haskell's type system to distinguish between terminating and non-terminating computations, this means any variable could be bound to a non-terminating computation. Non-termination is an effect (unlike most other effects, often an unwanted one), and this causes technical difficulties, on which I shall not elaborate further, other than to note that these difficulties can be sorted out with a “denotational semantics”, which relies on powerful mathematical tool called “domain theory”. However, instead of learning and using these tools, most Haskellers just pretend the technical difficulties didn't exist in the first place.
- wyager 10y ago> Non-termination is an effect While this is more of a philosophical than factual contention, that is as ridiculous as saying "the reduction of `(λx.x)4` is an effect". Non-termination is a property of a computation, not something that a program does to the outside world. The fact that, in Haskell, certain forms of non-termination "do something" to the computer is a consequence of the fact that real-world computers aren't actually turing machines or ideal lambda reduction machines, and sometimes the abstraction falls apart. There's nothing wrong with letting a computer churn away forever trying to reduce a divergent expression (with no effects!), but we just choose not to for practical reasons.
- tome 10y ago> Non-termination is a property of a computation, not something that a program does to the outside world You seem to be suggesting that IO is the only effect. State, Writer and Either are effects but they don't do anything to the outside world.
- catnaroek 10y ago> that is as ridiculous as saying "the reduction of `(λx.x)4` is an effect". What I'd do is consider the use of recursion to put your computation in a partiality monad. The identity function isn't recursive, so `(λx.x)4` is a pure computation. I'd also require a positivity check on recursive datatypes, to prevent users from sneaking nontermination into pure functions via datatype constructors. > Non-termination is a property of a computation, not something that a program does to the outside world. Non-termination gets in the way of producing a result, so it's an effect. > There's nothing wrong with letting a computer churn away forever trying to reduce a divergent expression (with no effects!) I wouldn't call it “wrong” either, because effects aren't intrinsically “wrong”.