Proc.call with custom bindings

A

Adam Strzelecki

If it possible to call Proc instance with custom bindings. I am aware
that Proc gets its bindings at the moment it is declared (created). And
I can access them with proc.bindings, but is there a way to do
Proc#bindings= or to do something similar to Kern.eval(src, bindings)
2nd param?

The reason I am asking, is that I am thinking about improving
performance of template engines that generate Ruby code upon parsing
template, and then call eval(@precompiledsrc) upon each render of
template.
I believe it would be faster if instead of generating just Ruby code
template engines such as ERB or Haml were doing at precomilation stage
also:

@proc = eval("Proc.new { #{@precompiledsrc} }")

and upon each request (render) calling:

@proc.bindings = call_locals
@proc.call

Instead of just calling eval(@precompiled, call_locals) upon each render
(request).

We could spare CPU cycles that are parsing all over again same Ruby
code.

BTW. I know there's instance_eval, but it doesn't set local scope
variables for Proc.

Best regards,
 
B

Brian Candler

Adam said:
If it possible to call Proc instance with custom bindings.

I don't think so, and in the general case this would be very difficult
(e.g. it could change dynamically whether 'foo' represents a local
variable or a method call)
The reason I am asking, is that I am thinking about improving
performance of template engines that generate Ruby code upon parsing
template, and then call eval(@precompiledsrc) upon each render of
template.

See how Rails solves this problem:

http://github.com/rails/rails/blob/...onpack/lib/action_view/template/renderable.rb

Scroll down to the private 'def compile!' at the bottom.

Regards,

Brian.
 
A

Adam Strzelecki

I don't think so, and in the general case this would be very difficult
(e.g. it could change dynamically whether 'foo' represents a local
variable or a method call)

Yeah, I just got the same reply at Haml group on this subject. AFAIK
this may be due locals in Ruby are optimized and not accessed by symbol
but address (stack offset?), so injecting different locals would cause
change in AST/VM code, which is no go for already compiled proc.

So think this explains my problem:
---- cut ----
proc = Proc.new do
if defined? s
puts "s = #{s}"
else
puts "s is undefined"
end
end
proc.call
s = 1
proc.call
---- cut ----

We got twice "s is undefined" even it is defined at second call.

But here:
---- cut ----
s = 'a'
proc = Proc.new do
if defined? s
puts "s = #{s}"
else
puts "s is undefined"
end
end
proc.call
s = 'b'
proc.call
---- cut ----
Defining s before defining proc, makes the local var alive and it is
defined in both 2 calls, moreover we change its value! (s = 'b' at the
second call)
So this is just the way Ruby works.
See how Rails solves this problem: http://github.com/rails/rails/blob/...onpack/lib/action_view/template/renderable.rb

Scroll down to the private 'def compile!' at the bottom.

Yeah this is it. This is same way Haml does inside
Haml::Engine#render_proc I think I will stick to that method for optimal
performance.

Best regards,
 
B

Brian Candler

Adam said:
So think this explains my problem:
---- cut ----
proc = Proc.new do
if defined? s
puts "s = #{s}"
else
puts "s is undefined"
end
end
proc.call
s = 1
proc.call
---- cut ----

We got twice "s is undefined" even it is defined at second call.

The local variable/method ambiguity is resolved statically when code is
parsed, before anything is executed. Example:

def x
"hello"
end

if false
x = "world"
end

puts x # nil

At the "puts x", within the current scope there has been a (potential)
assignment to x, and therefore it is already decided that x is a local
variable. When it is executed, no assignment actually took place, so it
carries the default value nil.

Similarly:

puts y # NameError
y = nil

There has been no (potential) assignment to y lexically before the puts
statement, and therefore it is statically decided that y must be a
method call, i.e. self.y()

This logic may be a little surprising at first, but it means that you
don't have to declare variables, and it also means you don't have to
provide an empty set of parentheses when invoking a method which takes
no arguments.

Regards,

Brian.
 
R

Robert Klemme

2009/4/14 Adam Strzelecki said:
If it possible to call Proc instance with custom bindings. I am aware
that Proc gets its bindings at the moment it is declared (created). And
I can access them with proc.bindings, but is there a way to do
Proc#bindings= or to do something similar to Kern.eval(src, bindings)
2nd param?

The reason I am asking, is that I am thinking about improving
performance of template engines that generate Ruby code upon parsing
template, and then call eval(@precompiledsrc) upon each render of
template.

Why would anyone want to do that? The source of the template does not
change for each invocation of the template so there is no point in
parsing the unchanged template over and over again. Instead, one would
reasonably read the template once (or once after each detected change)
and create Ruby code which is simply invoked via a method or block
call. Which is what Rails seems to be doing (it creates a method)
according to the reference Brian posted.

Kind regards

robert
 
B

Brian Candler

Robert said:
Why would anyone want to do that?

I think he's saying that he had found a template engine which stored the
string and eval'd it each time it needed to be rendered; he wanted to
improve on that situation.

I think vanilla ERB falls into that category.

irb(main):001:0> require 'erb'
=> true
irb(main):002:0> e = ERB.new("<%= foo %>")
=> #<ERB:0xb7c90498 @src="_erbout = ''; _erbout.concat(( foo ).to_s);
_erbout", @filename=nil, @safe_level=nil>
irb(main):003:0> lambda { foo = 123; e.result(binding) }.call
=> "123"
irb(main):004:0> lambda { foo = 456; e.result(binding) }.call
=> "456"

Source of ERB#result:

def result(b=TOPLEVEL_BINDING)
if @safe_level
th = Thread.start {
$SAFE = @safe_level
eval(@src, b, (@filename || '(erb)'), 1)
}
return th.value
else
return eval(@src, b, (@filename || '(erb)'), 1)
end
end
 
C

Christopher Dicely

There has been no (potential) assignment to y lexically before the puts
statement, and therefore it is statically decided that y must be a
method call, i.e. self.y()

This logic may be a little surprising at first, but it means that you
don't have to declare variables, and it also means you don't have to
provide an empty set of parentheses when invoking a method which takes
no arguments.

Well, discussing the issue of determining whether it is a local
variable or method statically at parse time vs. dynamically at run
time, what it means is not so much that but "Ruby runs some amount
faster than it would with dynamic determination". You don't need the
static resolution of method/local variable ambiguity to avoid empty
parens or variable declarations, but static resolution at parse time
makes it quicker than it would be if it was dynamically resolved at
run time.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,821
Latest member
AleidaSchi

Latest Threads

Top