instance_eval's block argument(s)- documented? purpose?
Reading Ruby's source code, what I can interpret is:
instance_eval is executing this:
return specific_eval(argc, argv, klass, self)
which in turn runs:
if (rb_block_given_p()) {
if (argc > 0) {
rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc);
}
return yield_under(klass, self, Qundef);
}
You can see they pass Qundef
for the VALUES argument.
if (values == Qundef) {
return vm_yield_with_cref(th, 1, &self, cref);
}
In that particular line of code, they set manually argc (argument count) to 1 and the argument as "self". Later on the code that prepares the block sets the arguments to the block to these arguments, hence the first argument = "self" and the rest are nil.
The code that sets up the block arguments is doing :
arg0 = argv[0];
... bunch of code ...
else {
argv[0] = arg0;
}
for (i=argc; i<m; i++) {
argv[i] = Qnil;
}
Resulting in:
1.9.3p194 :006 > instance_eval do |x, y, z, a, b, c, d| x.class end
=> Object
1.9.3p194 :008 > instance_eval do |x, y, z, a, b, c, d| y.class end
=> NilClass
Why ? I have no idea but the code seems to be intentional. Would be nice to ask the question to the implementers and see what they have to say about it.
[Edit]
This probably is like that because the blocks you pass to instance_eval may or may not be crafted for it (code that depends on self being set to the class you want the block to modify), instead they may assume you are going to pass them the instance you want them to modify as an argument and in this way they would work with instance_eval as well.
irb(main):001:0> blk = Proc.new do |x| x.class end
#<Proc:0x007fd2018447b8@(irb):1>
irb(main):002:0> blk.call
NilClass
irb(main):003:0> instance_eval &blk
Object
Of course this is only a theory and without official documentation I can only guess.
Why does instance_eval succeed with a Proc but not with a Lambda?
instance_eval
is yielding self
(User
) to the lambda. Lambdas are particular about their parameters - in the same way methods are - and will raise an ArgumentError
if there are too few/many.
class User
code1 = Proc.new { |x| x == User } # true
code2 = lambda { |x| x == User } # true
define_method :test do
self.class.instance_eval &code1
self.class.instance_eval &code2
end
end
Relevant: What's the difference between a proc and a lambda in Ruby?
Ruby lambda's proc's and 'instance_eval'
From the docs
For procs created using lambda or ->() an error is generated if the
wrong number of parameters are passed to a Proc with multiple
parameters. For procs created using Proc.new or Kernel.proc, extra
parameters are silently discarded.
In your case both lamb
and proc
called with one parameter
From the docs of instance_eval
When instance_eval is given a block, obj is also passed in as the
block's only argument
instance_eval
is method of BasicObject
class and can be called within instance. So given block will have access for private methods for example.
class Test
def call
secret_number + 100
end
private
def secret_number
42
end
end
test = Test.new
show_secret = -> (obj) { puts secret_number }
test.instance_eval(&show_secret) # print 42
Without instance self
of current context will be passed as an argument. I think instance_eval
was designed more for calling it within objects.
From the docs of instance_eval
In order to set the context, the variable self is set to obj while the
code is executing, giving the code access to obj's instance variables
and private methods.
Passing around procs between different objects
You are looking for:
instance_eval(&data)
object.instance_eval
evaluates block, but replaces self
within that block (which would normally be self
of the context block was created in) with object
:
whoami = proc { self }
whoami.call => main
1.instance_eval(&whoami) => 1
Note however, that instance_eval
also passes an object as an argument to the block, which might be problematic if you want to pass a lambda:
whoami = lambda { self }
1.instance_eval(&whoami) #=> ArgumentError (wrong number of arguments (given 1, expected 0))
There is another similar method: instance_exec
. Not only it does not pass self as an argument:
whoami = lambda { self }
1.instance_exec(&whoami) #=> 1
but it additionally allows you to pass other arguments:
add_number = lambda { |number| self + number }
1.instance_exec(3, &add_number) #=> 4
Naturally, both methods need to be used with extra caution and very sparingly - they are nice when you want to declare class-wide hooks in a declarative manner to be executed on each instance. They should not be used as a mean of interaction between two objects, unless you really know what you are ding and can justify it does not validate encapsulation.
ruby access instance variable in instance_eval
After some search and advices from my friend i think i figured out the problem.
In ruby there is two Context
when your code running self
and binding
, when you work with local vars
or method
without set self.xxx
first thing will be checking is it in your binding
object as a local var
if not Ruby will think it's a method then search on your self
object to find its definition and invoke it.
Think this:
class A
def test
4
end
def use_variable
test = 5
test
end
def use_method
test = 5
self.test
end
end
a = A.new
a.use_variable # returns 5
a.use_method # returns 4
That's explained WHY
of instance_eval
as its document said instance_eval
just changed self
in the given block and NOT touch binding
so methods will be search on new self
, local vals still in same binding
object.
About instance_exec
i'm not very sure about this but seems like instance vars(with at prefix vars) it will be search on self
directly skip on binding
, so out of instance_exec
your @instance_arr
belongs to old self
and in instance_exec
block you got it as a new local var
in the new binding
of block(block has own scope) but the value of it actually is the reference of @instance_arr
so invoke method on the new local var
such like push
it will change both of them because they share same Array instance
, but when you assign a new Array instance
to the new local var
they are no longer refer same Array instance
that's the second WHY
.
How can I create a Perl subroutine that accepts more than one block?
I hope you realise that this is just code seasoning, and all you are achieving is a tidier syntax at the expense of clarity?
Perl won't allow you to pass more than one bare block to a subroutine, but the second actual parameter could be a call to a subroutine that also takes a single block and simply returns the code reference.
This program demonstrates. Note that I have chosen please
and also
as names for the subroutines. But you must use something that is both appropriate to your own code's functionality and very unlikely to clash with forthcoming extensions to the core language.
use strict;
use warnings;
sub please(&$) {
my ($code1, $code2) = @_;
$code1->();
$code2->();
}
sub also(&) {
$_[0];
}
please { print "aaa\n" } also { print "bbb\n" };
output
aaa
bbb
Rspec shared example: Access parameter in a helper method
This is standard Ruby scoping, the parameter passed to the block is available within the block's closure. So the evaluated string "parameter=#{parameter}"
being within the closure works just fine.
What you're trying to do the same as is this:
b = "Hi!"
def a
puts b
end
a()
# NameError (undefined local variable or method `b' for main:Object)
The solution is to wrap parameter in a let
, (note I strongly encourage using a different name to prevent confusion about precedence.) e.g.
let(:param) { parameter }
This is the same (roughly) as doing:
b = "Hi!"
def a
puts b
end
define_method(:b) { b }
a()
# Hi!
# => nil
Mixing keyword with regular arguments in Ruby?
A pseudo-regex for parameter lists in Ruby (this applies equally to methods, blocks and lambda literals) is something like this:
mand* opt* splat? mand* (mand_kw | opt_kw)* ksplat? block?
Here's an example:
def foo(m1, m2, o1=:o1, o2=:o2, *splat, m3, m4,
ok1: :ok1, mk1:, mk2:, ok2: :ok2, **ksplat, &blk)
Hash[local_variables.map {|var| [var, eval(var.to_s)] }]
end
method(:foo).arity
# => -5
method(:foo).parameters
# => [[:req, :m1], [:req, :m2], [:opt, :o1], [:opt, :o2], [:rest, :splat],
# [:req, :m3], [:req, :m4], [:keyreq, :mk1], [:keyreq, :mk2],
# [:key, :ok1], [:key, :ok2], [:keyrest, :ksplat], [:block, :blk]]
foo(1, 2, 3, 4)
# ArgumentError: missing keywords: mk1, mk2
foo(1, 2, 3, mk1: 4, mk2: 5)
# ArgumentError: wrong number of arguments (3 for 4+)
foo(1, 2, 3, 4, mk1: 5, mk2: 6)
# => { m1: 1, m2: 2, o1: :o1, o2: :o2, splat: [], m3: 3, m4: 4,
# ok1: :ok1, mk1: 5, mk2: 6, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, mk1: 6, mk2: 7)
# => { m1: 1, m2: 2, o1: 3, o2: :o2, splat: [], m3: 4, m4: 5,
# ok1: :ok1, mk1: 6, mk2: 7, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, mk1: 7, mk2: 8)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [], m3: 5, m4: 6,
# ok1: :ok1, mk1: 7, mk2: 8, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, mk1: 8, mk2: 9)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5], m3: 6, m4: 7,
# ok1: :ok1, mk1: 8, mk2: 9, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8, mk1: 9, mk2: 10)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: :ok1, mk1: 9, mk2: 10, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8,
ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13, k4: 14)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8,
ok1: 9, ok2: 10, mk1: 11, mk2: 12, k3: 13, k4: 14) do 15 end
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14},
# blk: #<Proc:0xdeadbeefc00l42@(irb):15> }
[Note: mandatory keyword arguments will be introduced in Ruby 2.1, all the rest already works.]
Restricting eval() to a narrow scope
Short answer: No. If it's in the global scope, it's available to anything.
Long answer: if you're eval()
ing untrusted code that really wants to read or mess with your execution environment, you're screwed. But if you own and trust all code being executed, including that being eval()
ed, you can fake it by overriding the execution context:
function maskedEval(scr)
{
// set up an object to serve as the context for the code
// being evaluated.
var mask = {};
// mask global properties
for (p in this)
mask[p] = undefined;
// execute script in private context
(new Function( "with(this) { " + scr + "}")).call(mask);
}
Again, I must stress:
This will only serve to shield trusted code from the context in which it is executed. If you don't trust the code, DO NOT
eval()
it (or pass it to newFunction()
, or use it in any other way that behaves likeeval()
).
Related Topics
Accessing the Child Instance in a Rabl Template
What's the Difference Between the Ruby Irb Prompt Modes
Ruby Error Reading in Certificate File with Openssl
Rails 5 - Using Polymorphic Associations - Rendering the Views
Running Selenium::Webdriver::Firefox Inside Xvfb from Ruby as Non-Root User
Test (With Rspec) a Controller Outside of a Rails Environment
Store Image in Database Using Rails Paperclip Plugin
How to Change Ruby to Version 1.9.3 (Again) with Rvm
What Does This Mean in Ruby Language
Ruby: Cannot Install Watir Gem on Windows
How to Find Out All the Dependencies of a Gem
Triple Single Quote VS Triple Double Quote in Ruby
Product Orders Between 2 Users
Ruby, Generate a Random Hex Color (Only Light Colors)
How to Get Gmail Oauth or Xauth Tokens with Omniauth
Cannot Install Any Ruby Gems on MAC Os - Ssl_Connect Error
Comparing Identical Datetime Objects in Ruby -- Why Are These Two Datetime.Now's Not Equal