Reputation: 771
I’m reading “Get programming with Haskell”, from Will Kurt. At the end of Lesson 3, which is about lexical scope, the author writes:
Using a
let
expression and a lambda function aren’t exactly the same thing under the hood. For example, the following code will cause an error if you try to run it:counter x = let x = x + 1 in let x = x + 1 in x
To prove that
let
and lambda aren’t identical, rewrite the counter function exactly as it is here, but use nested lambdas instead oflet
.
Here’s my solution, which works as I expect it to do:
counterLambda x = (\x -> (\x -> x) (x + 1)) (x + 1)
-- counterLambda 2 == 4
However, as the author suggests, if I run counter 2
in ghci, it hangs forever (using GHC 8.8.3).
What’s happening under the hood?
PS: It works when I properly name the variables.
counter x = let a = x + 1 in let b = a + 1 in b
-- counter 2 == 4
Upvotes: 3
Views: 1019
Reputation: 2044
In both the lambdas and the let
s, each x
shadows the one before. The difference is in the scope of the shadow. The scope of a lambda argument is limited to the lambda, but the scope of a let binding is the entire let ... = ... in ...
structure, thus let x = x + 1
defines x
in terms of itself, while (\x -> x) (x + 1)
shadows x
in terms of the unshadowed x
. Let me demonstrate the shadow scopes of each of your implementations by adding a number to each x
:
counterLambda x0 = (\x1 -> (\x2 -> (\x3 -> x3) x2) (x1 + 1)) (x0 + 1)
counter x0 = let x1 = x1 + 1 in let x2 = x2 + 1 in x2
Now it should be clear why these are different. In the lambda version, x1
is assigned x0 + 1
, while in the let
version, x1
is assigned x1 + 1
, which does not terminate.
Upvotes: 3