Instance_Eval's Block Argument(S)- Documented? Purpose

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 new Function(), or use it in any other way that behaves like eval()).



Related Topics



Leave a reply



Submit