I hacked this past week, pairing with Pat Eyler on checkr, a static code analysis tool(lint) for Ruby.
We made some bad assumptions because of our lack of understanding of Procs.
UPDATE:
First of all, Proc is a terrible name. Instead, Block seems like a much more appropriate name to me than Proc.
I haven’t found an instance of yet where a Proc is more that just a lexical scoping block.
First I assumed that Procs where closures. They’re not!
I misunderstood closures,
This new example demonstrates that Procs are closures
a = Proc.new {b=10; Proc.new { puts b} }
a.call.call
Try the following code:
puts “————-Test1—————-”
foo = “value1″
bar = Proc.new { puts foo }
foo = “value2″
bar.call
————-Test1—————-
value2
If procs where true closures we would expect the output of Test1 to be value1.
To get the behavoir I wanted I needed to do something like this
puts “————-Test NEW1—————-”
bar = Proc.new { foo = “value1″; Proc.new { puts foo } }.call
foo = “value2″
bar.call
————-Test NEW1—————-
value1
I tried making closures using eval.
atempt1:
eval ‘val1 = “No love here.”; def a; puts val1; end; a;’
attemp2:
val1 = “Value1″
eval “def a; puts val1; end; a;”
Neither attempt worked.
I was way off here.
Using Module::define_method, I finally made some progress.
I was able to turn Procs into closures by creating methods out of them.
puts “————-Test2—————-”
class A
def add_method(name)
method_name = “method” + name.to_s
self.class.send(:define_method, method_name , Proc.new { puts method_name } )
end
end
a = A.new
a.add_method(:a)
a.add_method(:b)
a.methoda
a.methodb
————-Test2—————-
methoda
methodb
This is almost the same example as above with the Proc enclosed within a Proc(Test New1), except that functions or methods introduce a new “scope” so that outer lexical variables don’t bleed into the function.
puts “————–Test NEW2 ————–”
class A
b = 10
def c
b = 20
Proc.new { puts b}
end
b = 30
end
a = A.new
a.c.call
————–Test NEW2 ————–
20
puts “————–Test NEW2a ————–”
b = 10
c = Proc.new {
b = 20
Proc.new { puts b}
}.call
b = 30
c.call
————–Test NEW2a ————–
30
This last test(NEW2a) messed me up a little bit because, in Perl I would have written my in front of the second b which would have introduced a new lexical variable(storage location) for b. Of course if you dont add the my before the second b, Perl behaves the same as ruby in the test above. Likewise in Scheme the let form allows the declaration of a second variable b with a different storage location than the first b. What slipped my mind of course was that if you don’t introduce a let form in Scheme, once again you get the same behavoir as the Ruby example NEW2a.
Well, kindof…, but not really. Maybe we should call those psuedo closures.
Here is where Ruby surprised me again.
puts “————-Test3—————-”
class A
def add_method(name)
method_name = “method” + name.to_s
self.class.send(:define_method, method_name , Proc.new { puts method_name } )
method_name = “method” + name.to_s + “2″
self.class.send(:define_method, method_name , Proc.new { puts method_name } )
end
end
a = A.new
a.add_method(:a)
a.add_method(:b)
a.methoda
a.methodb
a.methoda2
a.methodb2
————-Test3—————-
methoda2
methodb2
methoda2
methodb2
This is the expected behavoir of closures.
In fact object-orientation comes from the fact that multiple funcitons(think methods)
can close over the same data(think member variables)
Oh and what you think is global scope is really just a class called main.
And yes it works the same there too.
puts “————-Test4—————-”
method_name = “methoda”
self.class.send(:define_method, method_name , Proc.new { puts method_name } )
method_name = “methoda2″
self.class.send(:define_method, method_name , Proc.new { puts method_name } )
puts self
puts self.class
methoda
methoda2
————-Test4—————-
main
Object
methoda2
methoda2
I think you’re getting a little confused over the concept of closures here. In your initial example, you create a variable foo in the top-level scope. In the proc, or block, or whatever Ruby calls them, you refer to a variable named foo.
Since no variable foo is defined within the block, the reference is lexically bound to the foo in the enclosing scope. Note that foo inside the block now refers to the variable, or storage location, of the outer scope’s foo. Thus it is perfectly consistent with the idea of closure for a subsequent change to the value stored in the outer scope’s variable foo to be visible within the block.
Closures mostly come into play when you create a variable at run-time, close over it with a lambda, block, or whatever, and return the resulting closure. You haven’t done this in your examples, so you’re not really testing for closures. Try making a function that returns an incrementor by closing over a counter variable that is created afresh upon function invocation and closing over it with a block that returns 1 plus the variable. Then create a couple of incrementors with your function, store them in variables, and ensure they increment independantly of one another.
Levi is definitely correct here.
I thought, incorrectly mind you, that closures copied the environments that they reference variables from.
This is not true.
Closures maintain pointers to the scopes they reference variables from, thereby keeping outer scopes alive and free from garbage collection.
Copy behavior, or at least copy on write indicative of continuations not closures, which are a whole other ballgame.
Here are some examples of closures that show that the environment is not copied only linked to.
Ruby:
class A
def add_method(name)
method_name = “method” + name.to_s
self.class.send(:define_method, method_name , Proc.new { puts method_name; method_name = “George” + name.to_s } )
method_name = “method” + name.to_s + “2″
self.class.send(:define_method, method_name , Proc.new { puts method_name } )
end
end
a = A.new
a.add_method(:a)
a.add_method(:b)
a.methoda
a.methodb
a.methoda2
a.methodb2
Perl:
#!/usr/bin/perl
sub a
{
$m = 10;
sub b
{
print $m . “\n”;
}
$m = 20;
sub c
{
print $m . “\n”;
}
}
a();
b();
c();
Scheme:
(define (a)
(define b 10)
(define (c) (printf “~a~n” b) (set! b 20))
(set! b 15)
(let ((d (lambda () (printf “~a~n” b))))
(c)
(d)
))
(a)
I’m not sure which implementation detail of continuations would suggest behavior that you were expecting. Continuations are sometimes implemented by storing a copy of the call stack (which makes sense, considering that the call stack contains references to the computation left to be completed) but no copy of the data store (the heap) is made. Since environments are sets of bindings of variables to locations in the data store, copying the environment doesn’t keep the data store itself from changing.
I am a little fuzzier on the details of continuations than closures, though, so perhaps I’m missing something.
You might want to check out Artima’s interview with Matz about this subject. (http://www.artima.com/intv/closures.html)