Q: Is it true that the return value of any Haskell function depends only on its arguments?
A: Yes, barring tricks like unsafePerformIO. But you can write perfectly good programs that do IO without these tricks anyway.
Q: Okay, genius. What about functions like getLine, which reads a line from the console?
A: getLine isn't a function.
Q: What?
A: All functions in Haskell have types like "a -> b" for some a and b. But you'll notice that the type of getLine doesn't have an arrow in it, it's just "IO String".
Q What do you mean, it's not a function? I can call it right here, by doing "do name <- getLine"!
A: That's not the syntax for calling functions in Haskell, that's monadic do notation. The syntax for calling functions is "foo x y".
Q: This is outrageous. So you're saying that getLine is just an inert value?
A: Yes.
Q: But what is that value? What's inside the type "IO String"? I'd always assumed that it was a kind of wrapper around a String, with some type system nastiness so people don't misuse it.
A: No, I'm afraid it can't be anything as simple as that. And it certainly can't contain a String, because then getLine couldn't be the same value all the time.
Q: Wait, you're saying that it doesn't even contain a String? Then what is it, really?
A: The implementation of IO types is kind of private to the runtime. But you can imagine that for any Haskell type X, "IO X" is a syntax tree of a side-effecting C program that produces an X.
Q: I see. So getLine really is the same value all the time. But how do you "run" the syntax tree that's hidden inside getLine, to actually get a String?
A: You generally don't. Instead you combine them together into one large tree and call it "main", and the runtime takes it from there.
Q: That seems like a lot of work. At the very least, why doesn't the language add some special syntax for combining syntax trees, so people don't have to deal with plumbing?
A: Yeah, that's what monadic do notation was invented for. Unfortunately, it was a bit too successful and some people started confusing it with actual imperative code :-)
"and then the runtime takes it from there" is of course the right answer, but no one upon first encountering Haskell understands the distinction between "the language" and "the runtime", so all the insistence that "the language" is "purely functional" is utterly misleading.
Furthermore, the claim that "getLine is really the same value all the time" is false. If that were the case, then any operation on getLine would return the same value, which it does not.
Edit: And in any case, the question is not whether "getLine" is a function, but whether "notpurelyFunctional" is. It has the signature of a function, but its return value certainly doesn't depend only on its arguments, or we would be able to say if value1 == value2 was true or not, and we can't.
Basically, any claim with the word "really" in it evaluates to false (in philosophy claims about what is "really" or "really really" true are sometimes enhanced by what is known as "a certain percussive emphasis.")
I think monads are an incredibly clever way of working side-effects into a language while maintaining an interesting level of functional purity. But the way the concepts are presented are just not conducive to conveying them to newbs, who will rightly call bullshit based on what words like "the language" are normally construed to mean.
> but its return value certainly doesn't depend only on its arguments
Note that value1 and value2 are not, and cannot possibly be, return values of notPurelyFunctional. The type of notPurelyFunctional says its return value has type IO Double, but value1 and value2 have type Double. IO Double is not Double, doesn't contain a Double, and cannot be converted to Double by a function call.
It sounds like you still think that the syntax "do x <- foo" means something like "let x be the result of some function call involving foo". Actually it can't be anything like that, it translates to something more compicated.
> If that were the case, then any operation on getLine would return the same value
Yes, and moreover, any operation on notPurelyFunctional 5.0 always returns the same value.
You have my honest word that I have some experience with Haskell and believe these things to be 100% true, with no wishy-washy philosophical caveats. I know exactly how to write an interpreter in which the machine representation of something like "notPurelyFunctional 5.0" will be immutable bit-for-bit, without using any dirty tricks like pointers to mutable memory. And I know that it's immutable in actual Haskell implementations as well.
A: Yes, barring tricks like unsafePerformIO. But you can write perfectly good programs that do IO without these tricks anyway.
Q: Okay, genius. What about functions like getLine, which reads a line from the console?
A: getLine isn't a function.
Q: What?
A: All functions in Haskell have types like "a -> b" for some a and b. But you'll notice that the type of getLine doesn't have an arrow in it, it's just "IO String".
Q What do you mean, it's not a function? I can call it right here, by doing "do name <- getLine"!
A: That's not the syntax for calling functions in Haskell, that's monadic do notation. The syntax for calling functions is "foo x y".
Q: This is outrageous. So you're saying that getLine is just an inert value?
A: Yes.
Q: But what is that value? What's inside the type "IO String"? I'd always assumed that it was a kind of wrapper around a String, with some type system nastiness so people don't misuse it.
A: No, I'm afraid it can't be anything as simple as that. And it certainly can't contain a String, because then getLine couldn't be the same value all the time.
Q: Wait, you're saying that it doesn't even contain a String? Then what is it, really?
A: The implementation of IO types is kind of private to the runtime. But you can imagine that for any Haskell type X, "IO X" is a syntax tree of a side-effecting C program that produces an X.
Q: I see. So getLine really is the same value all the time. But how do you "run" the syntax tree that's hidden inside getLine, to actually get a String?
A: You generally don't. Instead you combine them together into one large tree and call it "main", and the runtime takes it from there.
Q: That seems like a lot of work. At the very least, why doesn't the language add some special syntax for combining syntax trees, so people don't have to deal with plumbing?
A: Yeah, that's what monadic do notation was invented for. Unfortunately, it was a bit too successful and some people started confusing it with actual imperative code :-)