Doesn't that mean it's pure?
Edit:
I'd define pure in this sense as the same thing as referentially transparent, meaning f(x) will always return the same thing for a given x.
This is clearly the case with `println`.
(println "Hi there")
is clearly not the same program as nil
as the former causes output to the console, while the latter does not -- an observable difference.Using Scheme (because I don't know Clojure that well)
(define (foo x)
(let ([y 0])
(set! y 10)
(+ x 1)))
is a referentially transparent program. Although it contains a side-effect (the assignment to y) this effect is not observable outside `foo`, and thus any call to `foo` can be substituted with its result. constantly_unit = f >> constantly_unit
If you unpack that a bit it might translate as "if we throw away the return value of a function, it is exactly the same as if nothing is happening at all".If your notion of equality differentiates "println" and "constantly_unit" then we cannot call "println" pure.
Note that this is a very powerful notion of purity. It's so powerful as to render Haskell impure as if we have the function
loop x = loop x
then `loop >> constantly_unit` never returns and is therefore easy to distinguish from `constantly_unit` itself. This just drives home that non-termination is an effect itself!This would mean that non-termination is not a property of the function, but of the compiler/runtime, in that they failed to notice that loop was a partial function called with an input value for which it was not defined!
var a=f(0); a=f(1)
a=f(1)
however, these are not the same when f=println.Even if `println` returned a value, it would still be considered pure as long as that value is strictly calculated from its input parameter and that parameter alone (say, the size of the string).
Another often used criterion to determine purity of a function is whether inlining it everywhere in your program produces a similar program. There again, `println` passes this test.
The only way `println` could not be pure is if you call it with the same string twice and it returns a different value.
var a = println("Hello ")
a = println(" World")
This should output "Hello \n World\n"
However, assuming println is pure, we can optimize this to just var a = println(" World")
Which produces a different output. We could also convert: var a=println("Hello")
var b=println("Hello")
into var a=println("Hello")
var b=aYes the relationship between input and output is clearly defined, though. Your point of view raises a good point about precision when talking about purity and functions.
A pure function always returns the same result because it only depends on its input parameters. An impure IO function does not.
Is breaking the kernel something I should actually consider likely?
Of course there is lots of code that has side effects in implementation but not in their interface. Like malloc, or a read only data structure that has an internal cache it updates.
> Doesn't that mean it's pure?
Unfortunately not because it also modifies global state (the state of the console). A pure function implies that it is referentially transparent, meaning that:
a = foo()
b = a + a
Is the exact same as: b = foo() + foo()
But if foo() modifies global state, then this statement is not true. In this case the difference is printing something twice vs. once. If a function is pure, a compiler can optimize the second example into the first example.For a function to be pure, it must always return the same value given the same input and not modify any state that is observable outside of its definition.
Maybe a better example function is one that deletes data from your hard drive, or sens control signals to a robot that locks the doors or changes the A/C or starts some industrial process, releasing toxic chemicals.
The point is println does have a side effect, it's just not a very interesting one.
Or maybe the definition of side effect is what is causing the problem, after all - executing any function does have physical side effects - electrons move, electromagnetic styate chabnes, ambient temperature changes. Maybe the problem is where we draw the line for observable side effects.
You need to use monads to get around Haskell's limitations imposed by the decision to target complete purity just to do simple things like I/O but no matter what I/O is still not pure (something like readline can never be pure even if it wanted to) and thus you're, in one way or in another, forced to separate the static, pure and functional parts of your program and its dynamic part with side-effects.
Haskell does it with monads which, as a concept in itself, is a generic way to reason about state, but IMHO the exactly best part of Clojure is that it offers several ways to manage dynamic state in a controlled way without forcing you to go 100% pure or 100% impure. Why break that, except as a mental exercise?
* IO doesn't have to be wrapped in a monad, there are other models
* Clojure cannot handle IO/side effects very well (compared to any language with effect typing)
* Input and output are not pure, but the `IO` type itself is pure. Rather, constructions of the IO type are pure and then the runtime can interpret IO values impurely.
* The entire point is to separate the impure from the pure. It's not that you're forced to, it's that you desire to.
* Monads are not, in themselves, generic ways to reason about state. They are far more general.
* Anything that is not 100% pure is 100% impure. Without purity guarantees you cannot trust code you call upon to not do side effects. This breaks local reasoning.
Consider, do you really think that code was lacking local reasoning before the likes of Haskel? It is arguable that things were more difficult then, but the argument is still out that things are easier now.
What I haven't claimed is that no other fragment of code in another language can be pure. In nearly any language (1+1) is pure. My point was that literally any impurity inside of a fragment of code makes the whole thing impure (in most cases) and therefore destroys local reasoning.
The "in most cases" bit above is important because there are ways to "purify" a code fragment so that code which uses it cannot witness the impurity inside and therefore it restores local reasoning "above" that level. The ST monad is such an example.
It is certainly worth exploring other paradigms to understand the advantages and disadvantages they bring.
I really don't get why this point is not made clear in all Haskell discussions up-front, but instead Haskellers insist on repeating the empty mantra "Haskell is a pure language" as if they were using the term "language" in the standard way rather than redefining it in a carefully constructed way so as to make their claim true.
This is terrible pedagogy on par with people who introduce negative generalized temperature or resistance without first explaining that they are generalizing the concept. And it obscures one of the coolest aspects of Haskell, which is that it is an impure language (using "language" in the standard way, which always includes the runtime) that has very cleverly packaged its impurity such that reasoning about the code still gets most of the advantages of purity.
"Haskell has a pure syntax that is interpreted by an impure runtime" is more accurate and far less confusing.
Disclaimer: I only has read the first comments.
This doesn't really apply to Clojure because Clojure is strictly evaluated--except when you're explicitly working with a lazy stream data structure. If you were to put side-effectful expressions inside of a lazy stream, you would have a hard time controlling when they are evaluated, especially since Clojure "chunks" lazy streams by default in groups of 32 as an optimization.
Here's a SO question demonstrating what happens when you mix laziness and side-effects and don't understand the implications--you find yourself trying to restrict the optimizations you allow the compiler to do (which will hurt performance): http://stackoverflow.com/questions/3407876/how-do-i-avoid-cl...