Forum: Ruby Proc.call with custom bindings

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
F581c43402ef82f64043b3a1ceb1599a?d=identicon&s=25 Adam Strzelecki (ono)
on 2009-04-14 17:11
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,
--
Adam Strzelecki
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2009-04-15 11:34
Adam Strzelecki wrote:
> 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/3c1187699a80e0c...

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

Regards,

Brian.
F581c43402ef82f64043b3a1ceb1599a?d=identicon&s=25 Adam Strzelecki (ono)
on 2009-04-15 12:05
> 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/3c1187699a80e0c...
>
> 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,
--
Adam
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2009-04-15 12:17
Adam Strzelecki wrote:
> 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.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-04-15 14:04
(Received via mailing list)
2009/4/14 Adam Strzelecki <ono@java.pl>:
> 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
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2009-04-15 14:19
Robert Klemme wrote:
>> 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?

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
D7463bd611f227cfb2ef4da4a978a203?d=identicon&s=25 Christopher Dicely (Guest)
on 2009-04-16 01:49
(Received via mailing list)
On Wed, Apr 15, 2009 at 3:17 AM, Brian Candler <b.candler@pobox.com>
wrote:

>
> 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.
This topic is locked and can not be replied to.