I'm puzzled over the following code:
Proc.new do |a|
a.something "test"
puts a.something
puts "hello"
end
It doesn't throw any errors when it runs. However nothing is printed for either puts statement. I'm curious about the a.something "assignment". Perhaps this is a method call w/ parens omitted. What is happening in the above code?
Proc.new ... # create a new proc
Proc.new{ |a| ... } # a new proc that takes a single param and names it "a"
Proc.new do |a| ... end # same thing, different syntax
Proc.new do |a|
a.something "test" # invoke "something" method on "a", passing a string
puts a.something # invoke the "something" method on "a" with no params
# and then output the result as a string (call to_s)
puts "hello" # output a string
end
Since the last expression in the proc is puts, which always returns nil, the return value of the proc if it is ever invoked will be nil.
irb(main):001:0> do_it = Proc.new{ |a| a.say_hi; 42 }
#=> #<Proc:0x2d756f0#(irb):1>
irb(main):002:0> class Person
irb(main):003:1> def say_hi
irb(main):004:2> puts "hi!"
irb(main):005:2> end
irb(main):006:1> end
irb(main):007:0> bob = Person.new
#=> #<Person:0x2c1c168>
irb(main):008:0> do_it.call(bob) # invoke the proc, passing in bob
hi!
#=> 42 # return value of the proc is 42
irb(main):009:0> do_it[bob] # alternative syntax for invocation
hi!
#=> 42
Related
I was wondering how to pass a block to a method which will make the method return on yield.
The naive aproach doesn't work:
def run(&block)
block.call
end
run { return :foo } # => LocalJumpError
Wrapping in another proc has the same effect:
def run(&block)
proc { block.call }.call
end
run { return :bar } # => LocalJumpError
So I thought that the return statement is bound to the receiver of the current binding. However, trying it out with instance_eval proved me wrong:
class ProcTest
def run(&block)
puts "run: #{[binding.local_variables, binding.receiver]}"
instance_eval(&block)
end
end
pt = ProcTest.new
binding_inspector = proc { puts "proc: #{[binding.local_variables, binding.receiver]}" }
puts "main: #{[binding.local_variables, binding.receiver]}"
# => main: [[:pt, :binding_inspector], main]
binding_inspector.call
# => proc: [[:pt, :binding_inspector], main]
pt.run(&binding_inspector)
# => run: [[:block], #<ProcTest:0x007f4987b06508>]
# => proc: [[:pt, :binding_inspector], #<ProcTest:0x007f4987b06508>]
pt.run { return :baz }
# => run: [[:block], #<ProcTest:0x007f4987b06508>]
# => LocalJumpError
So the questions are:
How can this be done?
How is the return context tied to the return statement. Is this connection accessible via the language's API?
Was this implemented in such manner intentionally? If yes - why? If no - what are the obstacles to fix it?
I thought that the return statement is bound to the receiver of the current binding.
Only methods have an receiver. return is not a method:
defined? return #=> "expression"
Trying to invoke it as a method doesn't work:
def foo
send(:return, 123)
end
foo #=> undefined method `return'
trying it out with instance_eval proved me wrong
Though instance_eval evaluates the block in the context of the receiver (so you have access to the receivers instance methods and instance variables):
class MyClass
def foo(&block)
#var = 123
instance_eval(&block)
end
end
MyClass.new.foo { instance_variables }
#=> [:#var]
... it does not evaluate the block in the current binding (so you don't have access to any local variables):
class MyClass
def foo(&block)
var = 123
instance_eval(&block)
end
end
MyClass.new.foo { local_variables }
#=> []
How can this be done?
You could use eval, but that requires a string:
def foo
var = 123
eval yield
nil
end
foo { "return var * 2" }
#=> 246
Or by passing the binding to the block (again using eval):
def foo
var = 123
yield binding
nil
end
foo { |b| b.eval "return var * 2" }
#=> 246
return in a block returns from the enclosing method when the block is defined (ie, the closure in which the block is created). In your example, there is no enclosing block to return from, hence your exception.
This is easily demonstrated:
def foo(&block)
puts yield
puts "we won't get here"
end
def bar
foo { return "hi from the block"; puts "we never get here" }
puts "we never get here either"
end
puts bar # => "hi from the block" (only printed once; the puts in `foo` is not executed)
Return in a proc will immediately return out of the proc, not out of the method on the stack under the proc:
def foo(&block)
puts yield
puts "we will get here"
end
def bar
foo &->{ return "hi from the proc"; puts "we never get here" }
puts "we will get here too"
end
puts bar
# hi from the proc # puts from foo
# we will get here # puts from foo
# we will get here too # puts from bar
Because of these behaviors, there is no way to achieve your desired behavior, in which a return in the given block will execute a return in the method from which the block is invoked, unless the block was defined within that scope, since doing so would require one of the existing behaviors not work.
You could achieve something like this with throw...catch, which is kinda-sorta useful as a way to zip up the stack from an arbitrary depth, but you can't return arbitrary values with it:
def foo(&block)
yield
puts "we won't get here"
end
catch(:escape) do
foo &->{ throw :escape }
end
As we know, wo can pass a method to a iterator method by a &: prefix.
For example:
["a", "b"].map(&:upcase) #=> ["A", "B"]
def rettwo
2
end
["a", "b"].map(&:rettwo) #=> [2, 2]
Here is the problem, when I write a method, pass a method with &: prefix to it, I got a error message: "ArgumentError: no receiver given".
Let me show the code:
def a_simple_method &proc
puts proc.class # it shows `Proc`
proc.call
end
def a_iterator_method
puts yield
end
a_simple_method &:rettwo #=> ArgumentError: no receiver given
a_iterator_method &:rettwo #=> ArgumentError: no receiver given
What do I missing, How the map like method of Array handle it
Here's what works. Explanation below.
class String
def rettwo
self + self
end
end
def a_simple_method &proc
proc.call('a')
end
def a_iterator_method
yield 'b'
end
a_simple_method(&:rettwo) # => "aa"
a_iterator_method(&:rettwo) # => "bb"
The &: construct is called Symbol#to_proc. It turns symbol into a proc. This proc expects a receiver as a first argument. The remaining arguments are used to call the proc. You're not passing any arguments, hence the "receiver not given" error.
Here's a demonstration of additional arguments:
class String
def say name
"#{self} #{name}"
end
end
def a_simple_method &proc
proc.call('hello', 'ruby')
end
a_simple_method(&:say) # => "hello ruby"
Here's a definition of Symbol#to_proc from some blog post from 2008. Modern Symbol#to_proc seems to be implemented in C, but this can still help the understanding.
class Symbol
def to_proc
Proc.new { |*args| args.shift.__send__(self, *args) }
end
end
Here is an example:
class MyClass
end
obj = MyClass.new
obj.instance_eval do
def hello
"hello"
end
end
obj.hello
# => "hello"
obj.methods.grep "hello"
# => ["hello"]
MyClass.instance_methods.grep "hello"
# => []
MyClass's instance methods don't contain 'hello' method, so My question is where Ruby stores the method defined in instance_eval()?
Look at this:
obj = MyClass.new
def obj.hello
"hello"
end
obj.hello #=> "hello"
obj.singleton_methods #=> [:hello]
obj.methods.grep :hello #=> [:hello]
obj.instance_eval do
def hello2 ; end
end #
obj.singleton_methods #=> [:hello, :hello2]
As you can see instead of using instance_eval you can also define a method directly on an object. In both cases they end up in the object's singleton class (eigenclass), which can be accessed via obj.singleton_class in Ruby 1.9 and the class << self ; self; end idiom in Ruby 1.8.
Say that we have this class:
class Hello
def hello
#hello
end
def hello=(hello)
#hello = hello
end
def other_method
hello = 'hi!'
...
end
end
In this case,
h = Hello.new
h.other_method
puts h.hello
will write 'hi!', due to the interpretation of hello = as self.hello =. Is there a way to avoid this behaviour, giving to the hello = declaration a method-local scope (as, for example , var hello = would do with javascript code)?
EDIT I'm sorry, I was sure I had a problem for this reason! But now I have two other questions!
why hello = is not interpreted as self.hello =??? it is declared as an instance method...
why, even if I write
...
def other_method
self.hello = 'hi!'
end
end
h = Hello.new
h.other_method
puts h.hello
it returns nil??? now it should be an explicit assignment!
Thank you for the patience, I'm a bit confused! :-/
Actually this will not print hi! because hello = 'hi!' in other_method assigns to a local variable and is not interpreted as self.hello=.
If this is not the behaviour that you're seeing then there must be something else specific to your code that you haven't included in the question.
Answers to the follow up questions:
Within the body of a method identifier = value assigns a local variable (no method lookup is performed (so it's doesn't matter that there's a hello= method. You need the explicit self. to invoke the method.
When you write puts h.hello it prints 'hi!' which is the return value of h.hello but then returns nil which is the return value of puts.
It works for me:
$ irb
irb(main):001:0> class Hello
irb(main):002:1> def hello
irb(main):003:2> #hello
irb(main):004:2> end
irb(main):005:1> def hello=(hello)
irb(main):006:2> #hello = hello
irb(main):007:2> end
irb(main):008:1> def other_method
irb(main):009:2> self.hello = 'hi!'
irb(main):010:2> end
irb(main):011:1> end
=> nil
irb(main):012:0> h = Hello.new
=> #<Hello:0xb746b7cc>
irb(main):013:0> h.other_method
=> "hi!"
irb(main):014:0> puts h.hello
hi!
=> nil
I would like to pass an argument(s) to a method being defined using define_method, how would I do that?
The block that you pass to define_method can include some parameters. That's how your defined method accepts arguments. When you define a method you're really just nicknaming the block and keeping a reference to it in the class. The parameters come with the block. So:
define_method(:say_hi) { |other| puts "Hi, " + other }
... and if you want optional parameters
class Bar
define_method(:foo) do |arg=nil|
arg
end
end
a = Bar.new
a.foo
#=> nil
a.foo 1
# => 1
... as many arguments as you want
class Bar
define_method(:foo) do |*arg|
arg
end
end
a = Bar.new
a.foo
#=> []
a.foo 1
# => [1]
a.foo 1, 2 , 'AAA'
# => [1, 2, 'AAA']
...combination of
class Bar
define_method(:foo) do |bubla,*arg|
p bubla
p arg
end
end
a = Bar.new
a.foo
#=> wrong number of arguments (0 for 1)
a.foo 1
# 1
# []
a.foo 1, 2 ,3 ,4
# 1
# [2,3,4]
... all of them
class Bar
define_method(:foo) do |variable1, variable2,*arg, &block|
p variable1
p variable2
p arg
p block.inspect
end
end
a = Bar.new
a.foo :one, 'two', :three, 4, 5 do
'six'
end
Update
Ruby 2.0 introduced double splat ** (two stars) which (I quote) does:
Ruby 2.0 introduced keyword arguments, and ** acts like *, but for keyword arguments. It returns a Hash with key / value pairs.
...and of course you can use it in define method too :)
class Bar
define_method(:foo) do |variable1, variable2,*arg,**options, &block|
p variable1
p variable2
p arg
p options
p block.inspect
end
end
a = Bar.new
a.foo :one, 'two', :three, 4, 5, ruby: 'is awesome', foo: :bar do
'six'
end
# :one
# "two"
# [:three, 4, 5]
# {:ruby=>"is awesome", :foo=>:bar}
Named attributes example:
class Bar
define_method(:foo) do |variable1, color: 'blue', **other_options, &block|
p variable1
p color
p other_options
p block.inspect
end
end
a = Bar.new
a.foo :one, color: 'red', ruby: 'is awesome', foo: :bar do
'six'
end
# :one
# "red"
# {:ruby=>"is awesome", :foo=>:bar}
I was trying to create example with keyword argument, splat and double splat all in one:
define_method(:foo) do |variable1, variable2,*arg, i_will_not: 'work', **options, &block|
# ...
or
define_method(:foo) do |variable1, variable2, i_will_not: 'work', *arg, **options, &block|
# ...
... but this will not work, it looks like there is a limitation. When you think about it makes sense as splat operator is "capturing all remaining arguments" and double splat is "capturing all remaining keyword arguments" therefore mixing them would break expected logic. (I don't have any reference to prove this point doh! )
update 2018 August:
Summary article: https://blog.eq8.eu/til/metaprogramming-ruby-examples.html
In addition to Kevin Conner's answer: block arguments do not support the same semantics as method arguments. You cannot define default arguments or block arguments.
This is only fixed in Ruby 1.9 with the new alternative "stabby lambda" syntax which supports full method argument semantics.
Example:
# Works
def meth(default = :foo, *splat, &block) puts 'Bar'; end
# Doesn't work
define_method :meth { |default = :foo, *splat, &block| puts 'Bar' }
# This works in Ruby 1.9 (modulo typos, I don't actually have it installed)
define_method :meth, ->(default = :foo, *splat, &block) { puts 'Bar' }
With 2.2 you can now use keyword arguments:
https://robots.thoughtbot.com/ruby-2-keyword-arguments
define_method(:method) do |refresh: false|
..........
end