Again: yes, Haskell can be very self-determining in the task, otherwise it will just give an error instead of printing something that cannot be analyzed, can it?
What you cannot do in Haskell ever changes / reassigns a value to a variable. It just can't be said: if you once defined x as some value, x keep that value forever. The assumption is baked in the language itself and is used for a lot of use by the compiler for many optimizations, etc.
Now you are wondering why you can write let x = ... in general, am I not saying that this is impossible? The fact is that what you do there defines a new variable, which also has the name x , but that does not change anything about the old variable with the same name. You can expect it
Prelude> let x = 6 Prelude> let p = print x Prelude> let x = 7 Prelude> p
to get 7 , but it actually prints 6 because p is still referring to the old variable x , and not to the new one, which was only defined later.
Still confused? Perhaps less strange is something like
n :: Int n = 7 f :: IO () f = print $ replicate n "ha" ...
Itβs pretty reasonable that f will take 37 characters, not 7, and any call to f continues to repeat the line only 7 times. In the end, itβs β g , where n has that meaning,β but nothing else. Now let just where written the other way around. In particular, the 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 paragraph covers scope. Variables defined in external areas can be used, but local ones are preferred if found. So, let x = show x in ( print x ) can be considered by itself, the original x = 6 is hidden out of scope here. Therefore, the only way to define a definition is to refer to itself recursively.