Including methods into blocks - ruby

Anyone know how to get this to work if it's possible?
class Foo
def self.go(&block)
class << block
include Bar
end
puts "Within Foo#go: #{block.methods.include? 'baz'}"
block.call
end
end
module Bar
def baz
puts "I'm happily in my place!"
end
end
Foo.go {
puts "Within actual block: #{methods.include? 'baz'}"
baz
}
This gets the output:
Within Foo#go: true
Within actual block: false
NameError: undefined local variable or method ‘baz’ for main:Object
EDIT: when I print out the block's class in Foo#go, it's Proc, but when I print it out within the Proc, it's Object. Could this be related?

You can't do this. The reason for what you're seeing is that there are two different contexts here. One is the context of the block, which closes over the context where it's defined. The other is the context of the Proc object wrapper, which is just the same as any other object context and completely unrelated to the context of the block itself.
I think the closest you'll get is to instance_eval the block using a context object that has the methods you want, but then the block won't have access to the self that existed where it was defined. It's up to you whether that makes sense for the method you want to write.
The other option is to pass the block an actual receiver for the baz method.

You could use eval with Proc#binding:
module Bar
def baz
puts "hi from baz!"
end
end
def go(&block)
eval('include Bar', block.binding)
block[]
end
baz #=> NameError
go { baz } #=> "hi from baz!"
baz #=> "hi from baz!"
But unless you use a mixin/mixout framework (like mixico or mixology), you'll be placing the methods from the included module into the lexical scope, so they'll still be accessible once the block returns.
require 'rubygems'
require 'mixico'
module Bar
def baz
puts "hi from baz!"
end
end
def go(&block)
Module.mix_eval(Bar, &block)
end
baz #=> NameError
go { baz } #=> "hi from baz!"
baz #=> NameError
Here's a good article on different ways to use a DSL from within a block.

Another alternative, following on from rampion, is to dup the context of the block before mixing into it, this way you're not messing up the context after you've finished.
module Bar
def baz
puts "hi from baz!"
end
end
def go(&block)
dup_context = eval('self', block.binding).dup
dup_context.send(:include, Bar)
dup_context.instance_eval &block
end
Note this will only be useful to you if you're not running any mutator methods in the block

Related

Ruby refinements subtleties

There is a pretty good documentation of the current implementation of refinements in ruby here:
http://ruby-doc.org//core-2.2.0/doc/syntax/refinements_rdoc.html,
but there are some strange corner cases.
First, include module is orthogonal to using module (one include the instance method of module while the other activates the refinement). But there is a trick to include a refinement module itself, see
Better way to turn a ruby class into a module than using refinements?.
def to_module(klass)
Module.new do
#note that we return the refinement module itself here
return refine(klass) {
yield if block_given?
}
end
end
class Base
def foo
"foo"
end
end
class Receiver
include to_module(Base) {
def foo
"refined " + super
end
}
end
Receiver.new.foo #=> "refined foo"
Strangely this refinement module can't be used with using!
m=to_module(Base) {}
m.class #=> Module
using m
#=>TypeError: wrong argument type Class (expected Module)
So using only work on the enclosing module of the refinement modules.
Secondly I wanted to use the above yield trick to be able to pass a Proc to refine (even through it only accepts a block), without resorting to converting the Proc back to source as in
https://www.new-bamboo.co.uk/blog/2014/02/05/refinements-under-the-knife/.
But using yield as in the include example does not work:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
using ref_module1(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver1.new.bar #=> NoMethodError: super: no superclass method `foo'
We see that Receiver1 still use Bar#foo and not the refined method.
Howewer we can use module_eval instead:
def ref_module2(klass,&b)
Module.new do
refine(klass) {
module_eval(&b)
}
end
end
class Receiver2
using ref_module2(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver2.new.bar #=> "refined foo"
I don't quite understand why module_eval works here and not the yield method. Inside the refinement block, the 'default_definee' is the refinement module, so module_eval which puts the 'default_definee' to self='the refinement module' should not affect it. And indeed in the 'include' example at the beginning, I get the same result when I use module_eval or a direct yield.
Can anyone explain this behavior?
Context (or binding) is the reason why module_eval works and yield doesn't in your last set of examples. It actually has nothing to do with refinements, as demonstrated below.
Starting with module_eval:
class Foo
def run(&block)
self.class.module_eval(&block)
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts foo.hello # => "hello"
puts hello => # '<main>': undefined method 'hello' for main:Object (NameError)
In Foo#run we call module_eval on Foo. This switches the context (self) to be Foo. The result is much like we had simple defined hello inside of class Foo originally.
Now let's take a look at yield:
class Foo
def run
yield
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
yield simply invokes the block in its original context, which in this example would be <main>. When the block is invoked, the end result is exactly the same as if the method were defined at the top level normally:
class Foo
def run
yield
end
end
foo = Foo.new
def hello
"hello"
end
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
You might notice that foo seems to have the hello method in the yield examples. This is a side effect of defining hello as a method at the top level. It turns out that <main> is just an instance of Object, and defining top level methods is really just defining private methods on Object which nearly everything else ends up inheriting. You can see this by opening up irb and running the following:
self # => main
self.class # => Object
def some_method
end
"string".method(:some_method) # => #<Method: String(Object)#some_method>
Now back to your examples.
Here's what happens in the yield example:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
# like my yield example, this block is going to
# end up being invoked in its original context
using ref_module1(Base) {
def foo
"I'm defined on Receiver1"
end
}
def bar
# calling foo here will simply call the original
# Base#foo method
Base.new.foo
end
end
# as expected, if we call Receiver1#bar
# we get the original Base#foo method
Receiver1.new.bar # => "foo"
# since the block is executed in its original context
# the method gets defined in Receiver1 -- its original context
Receiver1.new.foo # => "I'm defined on Receiver1"
As for module_eval, it works in your examples because it causes the block to be run in the context of the new module, rather than on the Receiver1 class.

Making a method available within a block without changing its context?

I would like to create a class that does the following:
Its instance accepts a block.
During instance initialization, it executes certain actions, then calls the block, then executes more actions.
Within the block, another method from that class should be available.
Here is how i want it to work:
Foo.new do
puts "Hell, I can get you a toe by 3 o'clock this afternoon..."
bar
puts "...with nail polish."
end
I have managed to achieve it with the following class:
class Foo
def initialize(&block)
puts "This represents a beginning action"
instance_eval &block
puts "This symbolizes an ending action"
end
def bar
puts "I should be available within the block."
end
end
As you see, i use the instance_eval trick. It enables using bar within the block.
It works fine, but the problem here is that instance_eval makes current local context unavailable. If i use it from within another class, i lose access to that class' methods. For example:
class Baz
def initialize
Foo.new do
bar # -> Works
quux # -> Fails with "no such method"
end
end
def quux
puts "Quux"
end
end
The question is: how do i allow executing bar within the block without losing access to quux?
The only way that comes to my newbie mind is passing bar as an argument into the block. But that requires more typing, so i would like to aviod that if possible.
instance_eval does not consider the scope of where the block is called, so every method call is only relative to what is defined inside Foo.
So you have 2 options. Either
def initialize
baz = self
Foo.new do
bar # -> Works
baz.quux # -> Works
end
end
or
def initialize
puts "This represents a beginning action"
yield self
puts "This symbolizes an ending action"
end
....
def initialize
Foo.new do |b|
b.bar # -> Works too
quux # -> Works too
end
end
I am not sure which one would be better performance wise, but the option you pick is based on your own preference.
It works fine, but the problem here is that instance_eval makes
current local context unavailable
instance_eval() does no such thing. The code inside all blocks, i.e something that looks like:
{ code here }
can see the variables that existed in the surrounding scope at the time the block was CREATED. A block cannot see the variables in the surrounding scope at the time the block is EXECUTED. In computer science jargon, a block is known as a closure because it 'closes over' the variables in the surrounding scope at the time it is created.
What instance_eval does do is assign a new value to the self variable that the block closed over. Here is an example:
puts self #=>main
func = Proc.new {puts self}
func.call #=>main
class Dog
def do_stuff(f)
puts self
f.call
end
end
d = Dog.new
d.do_stuff(func)
--output:--
#<Dog:0x000001019325b8>
main #The block still sees self=main because self was equal to main when the block was created and nothing changed the value of that self variable
Now with instance_eval:
class Dog
def do_stuff(f)
puts self
instance_eval &f
end
end
d = Dog.new
d.do_stuff(func)
--output:--
#<Dog:0x000001011425b0>
#<Dog:0x000001011425b0> #instance_eval() changed the value of a variable called self that the block `closed over` at the time the block was created
You also need to realize that when you call a method and you don't specify a 'receiver', e.g.
quux()
...then ruby converts that line to:
self.quux()
So, it is important to know the value of the variable self. Examine this code:
class Dog
def do_stuff(f)
puts self #Dog_instance
instance_eval &f #equivalent to self.instance_val &f,
#which is equivalent to Dog_instance.instance_eval &f
end
end
Because instance_eval() sets the value of the self variable inside the block to instance_eval()'s 'receiver', the value of self inside the block is set equal to a Dog_instance.
Examine your code here:
puts self #=> main
Foo.new do
puts self #=>main
bar #equivalent to self.bar--and self is not a Foo or Baz instance
#so self cannot call methods in those classes
end
Examine your code here:
class Foo
def initialize(&block)
instance_eval &block #equivalent to self.instance_eval &block
end
end
And inside Foo#initialize() self is equal to the new Foo instance. That means inside the block self is set equal to a Foo instance, and therefore if you write the following inside the block:
quux()
That is equivalent to:
self.quux()
which is equivalent to:
Foo_instance.quux()
which means quux() must be defined in Foo.
In this answer:
class Baz
def initialize
puts self #=>Baz_instance
baz = self
Foo.new do
bar # -> Works
baz.quux # -> Works
end
end
def quux
puts "Quux"
end
end
b = Baz.new
...the bar and baz lines seem to have identical 'receivers':
puts self #=>Baz_instance
baz = self #To evaluate that assignment ruby has to replace the variable self
#with its current value, so this is equivalent to baz = Baz_instance
#and baz no longer has any connection to a variable called self.
Foo.new do
bar #=> equivalent to self.bar, which is equivalent to Baz_instance.bar
baz.quux #=> equivalent to Baz_instance.quux
end
But when instance_eval() executes that block, which is everything between the do and end, instance_eval() changes the value of self:
Foo.new do #instance_eval changes self inside the block so that self = Foo_instance
bar #=> equivalent to self.bar which is now equivalent to Foo_instance.bar
baz.quux #=> the block still sees baz = Baz_instance, so equivalent to Baz_instance.bar
end

Preventing methods from being re-defined

So, I need to make a method within a class protected from re-definition. I am not really sure how else to explain it so here's the code:
module Foo
def bar
p "lol"
end
end
Now, that's the original Foo#bar method and I need it to be like a constant. So, I did come up with a solution. That was to save the method in a Constant and detect when someone tried changing it it would simply re-load it from that constant (it was a Proc object):
module Foo
Original_bar = Proc.new { p "lol" }
def bar
Original_bar.call
end
def self.method_added(method_name)
if method_name == :bar
def Foo::bar
Original_bar.call
end
end
end
end
But this isn't completely safe since once could use the same "trick" I did to bypass method_added and I am not really fond of this, since it's not very ruby-like.
Tests
A normal test:
module Foo
def bar
p "lmao"
end
end
Foo::bar # => "lol"
And another test using the trick:
def Foo::bar
p "rofl"
end
Foo::bar # => "rofl"
tl;dr
How to make a method in Ruby "unredefinable" (if that's even a word)?
If you freeze the module that should prevent method being added to it.
Note that a c extension can unfreeze variables (see evil.rb for example.
You can make your code warn you that a method has been over-ridden by turning on warnings:
$VERBOSE = true
module Foo
def self.bar
p "lmao"
end
end
def Foo::bar
p "rofl"
end
(irb):9: warning: method redefined; discarding old bar
It may be possible, but not practical, to raise an exception when warnings are issued.
This won't warn you if you first undefine Foo.bar, however.

Ruby blocks with method_missing

Note, this is a follow up to my question here.
I'm trying to parse the following Tcl code:
foo bar {
biz buzz
}
In Tcl, foo is the method name, bar is the argument, and the rest is a "block" to be processed by eval.
Now here is my current implementation to this:
def self.foo(name, &block)
puts "Foo --> #{name}"
if block
puts "block exists"
else
puts "block does not exist"
end
end
def self.method_missing(meth, *args, &block)
p meth
p block
meth.to_s &block
end
tcl = <<-TCL.gsub(/^\s+/, "").chop
foo bar {
biz buzz
}
TCL
instance_eval(tcl)
Which outputs the following:
:bar
#<Proc:0x9e39c80#(eval):1>
Foo --> bar
block does not exist
In this example, when the block is passed up to the foo method, it does not exist. Yet in method_missing it does exist (at least it appears to exist). What's going on here?
Note, I am aware of ruby's precedence of parentheses and realize this works:
foo (bar) {
biz buzz
}
However, I want to have the parentheses omitted. So is this possible in ruby (without lexical analysis)?
You can do (I marked the lines I changed):
def self.foo args # changed
name, block = *args # changed
puts "Foo --> #{name}"
if block
puts "block exists"
else
puts "block does not exist"
end
end
def self.method_missing(meth, *args, &block)
p meth
p block
return meth.to_s, block # changed
end
That way, block will exist.
This has nothing to do with method_missing. You simply can't omit parentheses when passing block along with some parameters. In your case, Ruby will try to call bar method with block as an argument, and the result of it all will be passed to foo method as a single argument.
You can try this yourself by simplifying the method call (all the metaprogramming just obscures the real problem in your case):
# make a method which would take anything
def a *args, &block
end
# try to call it both with argument and a block:
a 3 {
4
}
#=>SyntaxError: (irb):16: syntax error, unexpected '{', expecting $end
# from /usr/bin/irb:12:in `<main>'
So the best solution I've found is to just gsub the string before processing it.
tcl = <<-TCL.gsub(/^\s+/, "").chop.gsub('{', 'do').gsub('}', 'end')
foo bar {
biz buzz
}
TCL

ruby: can a block affect local variables in a method?

I'm just learning ruby and trying to understand the scope of code executed in blocks. For example, I want to be able to create a block that affects the method that it is attached to, like so:
def test(&block)
block.call() if block_given?
puts "in test, foo is #{foo}"
puts "in test, bar is #{bar}"
end
test() {
foo="this is foo"
bar="this is bar"
}
In this case I don't want to have to modify the block at all -- I want to be able to write it using simple variable references and no parameters. Only by making changes to the 'test' method in the above example, is it possible to access the variables defined in the block?
Again, the goal is to leave the block unmodified, but be able to access the created variables from within 'test' after the block executes.
First of all, block.call() is done with yield, and you don't need the &block parameter that way.
You can't normally do what you want, blocks are bound when they are created, and inside the block you can see the local variables defined at that moment; the easiest way to do what you want, which is not how you will use blocks normally, is this:
def test()
foo = yield if block_given?
puts "in test, foo is #{foo}"
end
test() {
foo="this is foo"
}
But that's only a side effect because foo is "returned" by the block. If you instead do this:
def test()
foo = yield if block_given?
puts "in test, foo is #{foo}"
end
test() {
foo="this is foo"
"ha ha, no foo for you"
}
You'll notice that it does something different.
Here's more magic:
def test(&block)
foo = eval "foo", block.binding
puts foo
block.call
foo = eval "foo", block.binding
puts foo
end
foo = "before test"
test() {
foo = "after test"
"ha ha, no foo for you"
}
That would kind of work, but it breaks if you remove foo = "before test" because foo becomes a local variable in the block and does not exist in the binding.
Summary: you can't access local variables from a block, just the locals where the block was defined and the return value of the block.
Even this won't work:
def test(&block)
eval "foo = 'go fish'", block.binding
block.call
bar = eval "foo", block.binding
puts bar
end
because the foo in the binding is different from the local in the block (I didn't know this, thanks).
No, a block can't affect local variables in the place where it's called.
Blocks in Ruby are closures, which means they capture the scope around them when they are created. The variables that are visible when you create the block are the ones it sees. If there were a foo and bar at the top of your code, outside any method, that block would change those when it was called.
You can do what you want by being a little more verbose:
class Test
def foo(t)
#foo = t
end
def bar(t)
#bar = t
end
def test(&block)
self.instance_eval &block if block_given?
puts "in test, foo is #{#foo}"
puts "in test, bar is #{#bar}"
end
end
Test.new.test() {
foo "this is foo"
bar "this is bar"
}
You can create methods like attr_accessor that will generate apropriate setter (the foo and bar methods).
def test(&block)
foo = yield
puts "in test, foo is #{foo}"
end
test { "this is foo" }
prints in test, foo is this is foo
The value of yield is the value of the block.
You can also pass parameters to yield, which then can be accessed by the block using |param,another| at the block's beginning.
Also, check out procs.
foo = "this is foo"
p = Proc.new { "foo is #{foo}" }
p.call
Prints "foo is this is foo"
def test(p)
p.call
end
test p
Prints "foo is this is foo"
def test2(p)
foo = "monkey"
p.call
end
test2 p
Prints "foo is this is foo"

Resources