Changing structure of Ruby proc/block - ruby

I am not sure if this is possible in Ruby, but in case someone knows a good solution.
I'd like to change the structure of a block, replacing particular nodes in it with other code structures. Much like macros.
For example, say I have an unevaluated code block
some_method do
foo
bar
end
Then I define some_method like
def some_method(&block)
...
end
In some_method, I really would like to replace "bar" in block with something else, e.g. with baz.
I want to do the replacement w/o evaluating the block, because ultimately I am passing the block around to other places.
Doable? or no?
I can think of fairly complicated answers: e.g. I can pass the block around with an additional closure that defines replacement for bar, and uses method_missing and continuation to replace bar with baz when bar is evaluated. But is there a simpler way?
Thanks.

Ruby doesn't have dynamic scoping and doesn't have macros, so unless you wrap the block in a function taking bar as a parameter, and pass that function around, I don't think you can substitute code like that. You can use eval of course, but I wouldn't recommend it=)

This is the simplest way i can think of:
class Base
def some_method(&block)
self.instance_eval(&block)
end
def foo; puts 'foo'; end
def bar; puts 'bar'; end
end
class Replacement < Base
def foo; puts 'baz'; end
end
Base.new.some_method do
foo
bar
end
Replacement.new.some_method do
foo
bar
end
output:
foo
bar
baz
bar

Do aliases and Procs help?
def foo; p 'foo'; end
def bar; p 'bar'; end
def sm(&block)
##Block = Proc.new {
alias :oldbar :bar
def bar; p 'baz'; end #redefine
block.call
alias :bar :oldbar #restore
}
yield #prints foo,bar
end
sm do
foo
bar
end
def later(&block)
yield
end
def delayedEx
later { ##Block.call}
end
delayedEx #prints foo,baz
bar #prints bar (unchanged)
This prints "foo bar foo baz bar", i.e: bar does something different in the block, but retains its original behavior outside.

def some_method(a_proc=Proc.new{puts "Bar"}, &block)
a_proc.call
yield
end
p1 = Proc.new{puts "Baz"}
some_method{puts "a block"}
some_method(p1){puts "a block"}

Related

Ruby messages to self

If I run the following Ruby code:
class Foo
def foo=(something)
puts "It's a #{something}"
end
def foo_assign
self.foo = "bar"
end
end
f = Foo.new
f.foo_assign
The output is: It's a bar.
On the other hand, if I run the code:
class Foo
def foo=(something)
puts "It's a #{something}"
end
def foo_assign
foo = "bar"
end
end
f = Foo.new
f.foo_assign
There is no output. How can that be given that foo and self.foo are equivalent?
foo = "bar" is ambiguous in the foo_assign method. Ruby thinks you are trying to assign a local variable and not call the method, so that's what it does for you. (imagine setting bar = "foo" on the next line, it would still work, even though there is no bar= method). In cases where it's ambiguous what you are trying to do or call, you have to use self.. Someone better than I might be able to explain this all better or more clearly, but that's what is happening here.

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

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

Including methods into blocks

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

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