Again: yes, Haskell very much can self-reference in assignment, otherwise this would just give an error instead of printing something undecipherable, wouldn't it?
What you can not do in Haskell, ever, is modify / re-assign the value of a variable. It's just totally out of question: if you've once defined x
as some value, x
will keep this value forever. An assumption baked into the very language, and used to great avail by the compiler for lots of optimisations etc..
Now you wonder why you can write let x = ...
again at all, didn't I just say this is not possible? The thing is, what you're doing there is define a new variable that also happens to have the name x
, but that doesn't change anything about the old variable with the same name. You might expect this
Prelude> let x = 6
Prelude> let p = print x
Prelude> let x = 7
Prelude> p
to yield 7
, but actually it prints 6
because p
is still referring to the old variable x
, not to the new one that was defined only later.
Still confused? Perhaps less strange is something like
n :: Int
n = 7
f :: IO ()
f = print $ replicate n "ha"
... -- much later, you've forgotten there was a global `n` up there...
g :: String -> String
g = take n
where n = 37
It's quite reasonable that f
will take 37 characters, not 7, while any call to f
continues to repeat the string only 7 times. After all, it's "g
, where n
has that value", but nothing else outside. Now, let
is just where
written the other way around. In particular, a do
notation or your GHCi prompt is actually syntactic sugar for something like this:
let x = 6
in ( print x >> ( let y = show x
in ( print y >> ( let x = show x
in ( print x )
)
)
)
Each paren encloses a scope. Variables defined in outer scopes can be used, but local ones are preferred if found. So let x = show x in ( print x )
can be considered completely on its own, the original x = 6
is shadowed out of scope here. Therefore, the only way the definition can work is refer recursively to itself.