Scope of assignments with lambda

This isn’t a question – it’s just a comment that I find it awesome and
only slightly mysterious that a lambda can reference the variable it is
assigned to in its body, such as p here:

p = lambda {|n| puts “bing #{n}” ; p.call(n-1) if n > 0 }
p.call(5)
bing 5
bing 4
bing 3
bing 2
bing 1
bing 0
=> nil

I guess that’s the power of a real lexical closure. Very handy.

Fearless F. wrote in post #988575:

This isn’t a question – it’s just a comment that I find it awesome and
only slightly mysterious that a lambda can reference the variable it is
assigned to in its body, such as p here:

p = lambda {|n| puts “bing #{n}” ; p.call(n-1) if n > 0 }
p.call(5)
bing 5
bing 4
bing 3
bing 2
bing 1
bing 0
=> nil

I guess that’s the power of a real lexical closure. Very handy.

For a real eye-opener, see this discussion from 2007.
https://groups.google.com/d/topic/comp.lang.ruby/OlHsBJoud_0/discussion

Fearless F. wrote in post #988575:

This isn’t a question – it’s just a comment that I find it awesome and
only slightly mysterious that a lambda can reference the variable it is
assigned to in its body,

How do recursive methods grab you?

def factorial(n)
if n == 1
1
else
n * factorial(n-1)
end
end

puts factorial(3)

–output:–
6

You might want to ponder the difference between your example and a
recursive method.

7stud – wrote in post #988636:

How do recursive methods grab you?

You might want to ponder the difference between your example and a
recursive method.

@7stud: Pardon if I wasn’t clear in my OP, but I was asking about
recursion. [For the record, Lisp was one of my first programming
languages, and I often find it easier to think in terms of recursion
than in iteration. I’d be more impressed if you wrote your factorial
routine as a lazy-eval’d stream, ala Abelson & Sussman SICP. ;)]

What I was surprised about was the scoping of variables in assignment
statements. I guess it makes sense. Assume that p is previously
undefined, then a reference on the RHS to p like this:

p = p + 1

will raise an error. But wrapped inside a lambda, the reference to p is
deferred, so it all works:

p = lambda { |n| p.call(n-1) if (n > 0) }

As I said, that’s the power of a lexical closure.

Content preview: On 22.03.2011 01:47, Albert S. wrote: > Ruby U.
wrote
in post #988632: >> For a real eye-opener, see this discussion from
2007.
>>
https://groups.google.com/d/topic/comp.lang.ruby/OlHsBJoud_0/discussion
> > Thanks. Here’s an alternative link, for those who have trouble
with the
> above: […]

Content analysis details: (-2.9 points, 5.0 required)

pts rule name description



-1.0 ALL_TRUSTED Passed through trusted hosts only via SMTP
-1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1%
[score: 0.0000]
X-Cloudmark-Analysis: v=1.1
cv=vUpxTctd+kpWCBtSXXIkt5ll4Z8E5Qu9nLREXC/hfIo= c=1 sm=0
a=aofHTkXiRO8A:10 a=ecd9y14Xgu0A:10 a=IkcTkHD0fZMA:10 a=1XWaLZrsAAAA:8
a=s5ue6VMcvd2SwaUa7CYA:9 a=MuuHW-lmGfA3qC-griTi7eTKR3oA:4
a=QEXdDO2ut3YA:10 a=HpAAvcLHHh0Zw7uRqdWCyQ==:117
Mime-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit
Precedence: bulk
Lines: 12
List-Id: ruby-talk.ruby-lang.org
List-Software: fml [fml 4.0.3 release (20011202/4.0.3)]
List-Post: mailto:[email protected]
List-Owner: mailto:[email protected]
List-Help: mailto:[email protected]?body=help
List-Unsubscribe: mailto:[email protected]?body=unsubscribe
Received-SPF: none (Address does not pass the Sender Policy Framework)
SPF=FROM;
[email protected];
remoteip=::ffff:221.186.184.68;
remotehost=carbon.ruby-lang.org;
helo=carbon.ruby-lang.org;
receiver=eq4.andreas-s.net;

On 22.03.2011 01:47, Albert S. wrote:

Ruby U. wrote in post #988632:

For a real eye-opener, see this discussion from 2007.
https://groups.google.com/d/topic/comp.lang.ruby/OlHsBJoud_0/discussion

Thanks. Here’s an alternative link, for those who have trouble with the
above:

Thanks that you mention it, I was able to see that groups was loading
the page but then replaced it’s content shortly afterwards.

  • Markus

Ruby U. wrote in post #988632:

For a real eye-opener, see this discussion from 2007.
https://groups.google.com/d/topic/comp.lang.ruby/OlHsBJoud_0/discussion

Thanks. Here’s an alternative link, for those who have trouble with the
above:

http://www.ruby-forum.com/topic/131109

Fearless F. wrote in post #988777:

7stud – wrote in post #988636:

How do recursive methods grab you?

You might want to ponder the difference between your example and a
recursive method.

@7stud: Pardon if I wasn’t clear in my OP, but I was asking about
recursion.

My point was that a recursive method seems harder to explain than your
lambda example. As you detailed, your lambda example can be explained
by a closure.

In the case of a recursive method, how do you explain being able to call
a name that doesn’t even exist yet? Is it because what’s really
happening is something like this:

factorial = Method.new(Object) do
if n == 1
1
else
n * factorial(n-1)
end
end

…and therefore a closure can explain why it works? In ruby, you can
certainly do something this:

MyClass = Class.new do
def greet
puts ‘hi’
end
end

MyClass.new.greet

–output:–
hi

7stud – wrote in post #988797:

@7stud: Pardon if I wasn’t clear in my OP, but I was asking about
recursion.
My point was that a recursive method seems harder to explain than your
lambda example. As you detailed, your lambda example can be explained
by a closure.

Yeah, well, I apologize, but that was my typo. I meant “I wasn’t
asking about recursion”. (And no, recursion doesn’t seem particularly
difficult to explain, does it?!? :slight_smile:

  • ff

Fearless F. wrote in post #988777:

What I was surprised about was the scoping of variables in assignment
statements. I guess it makes sense. Assume that p is previously
undefined, then a reference on the RHS to p like this:

p = p + 1

will raise an error. But wrapped inside a lambda, the reference to p is
deferred, so it all works:

p = lambda { |n| p.call(n-1) if (n > 0) }

As I said, that’s the power of a lexical closure.

“The scoping of variables in assignment” is exactly the same in both
cases.

p = p + 1 does not fail because p is undefined. p is defined. Its
value is nil. The error results from NilClass#+ not existing. This has
nothing to do with “the power of the lexical closure”.

Local variables are discovered at compile time. p gets defined at the
beginning of the scope, before the line “p = p + 1”, and it’s
assigned the value of nil. Try

p = p.to_i + 1

and observe the non-error. Even better, try

def foo
puts local_variables #=>bar
bar = 99
end

The reference to p inside the lambda is not deferred. At compile-time,
the p inside the lambda is bound to the local variable p, just as the
p on the right-hand side of “p = p + 1” is bound to the local variable
p. There is nothing special going on as a result of the lambda being
present.