Is it possible to have Methods inside Methods? - ruby

I have a method inside of a method. The interior method depends on a variable loop that is being run. Is that a bad idea?

UPDATE: Since this answer seems to have gotten some interest lately, I wanted to point out that there is discussion on the Ruby issue tracker to remove the feature discussed here, namely to forbid having method definitions inside a method body.
No, Ruby doesn't have nested methods.
You can do something like this:
class Test1
def meth1
def meth2
puts "Yay"
end
meth2
end
end
Test1.new.meth1
But that is not a nested method. I repeat: Ruby does not have nested methods.
What this is, is a dynamic method definition. When you run meth1, the body of meth1 will be executed. The body just happens to define a method named meth2, which is why after running meth1 once, you can call meth2.
But where is meth2 defined? Well, it's obviously not defined as a nested method, since there are no nested methods in Ruby. It's defined as an instance method of Test1:
Test1.new.meth2
# Yay
Also, it will obviously be redefined every time you run meth1:
Test1.new.meth1
# Yay
Test1.new.meth1
# test1.rb:3: warning: method redefined; discarding old meth2
# test1.rb:3: warning: previous definition of meth2 was here
# Yay
In short: no, Ruby does not support nested methods.
Note also that in Ruby, method bodies cannot be closures, only block bodies can. This pretty much eliminates the major use case for nested methods, since even if Ruby supported nested methods, you couldn't use the outer method's variables in the nested method.
UPDATE CONTINUED: at a later stage, then, this syntax might be re-used for adding nested methods to Ruby, which would behave the way I described: they would be scoped to their containing method, i.e. invisible and inaccessible outside of their containing method body. And possibly, they would have access to their containing method's lexical scope. However, if you read the discussion I linked above, you can observe that matz is heavily against nested methods (but still for removing nested method definitions).

Actually it's possible. You can use procs/lambda for this.
def test(value)
inner = ->() {
value * value
}
inner.call()
end

No, no, Ruby does have nested methods. Check this:
def outer_method(arg)
outer_variable = "y"
inner_method = lambda {
puts arg
puts outer_variable
}
inner_method[]
end
outer_method "x" # prints "x", "y"

The Ruby way is to fake it with confusing hacks that will have some users wondering "How in the fuck does this even work?", while the less curious will simply memorize the syntax needed to use the thing. If you've ever used Rake or Rails, you've seen this kind of thing.
Here is such a hack:
def mlet(name,func)
my_class = (Class.new do
def initialize(name,func)
#name=name
#func=func
end
def method_missing(methname, *args)
puts "method_missing called on #{methname}"
if methname == #name
puts "Calling function #{#func}"
#func.call(*args)
else
raise NoMethodError.new "Undefined method `#{methname}' in mlet"
end
end
end)
yield my_class.new(name,func)
end
What that does is define a top-level method that creates a class and passes it to a block. The class uses method_missing to pretend that it has a method with the name you chose. It "implements" the method by calling the lambda you must provide. By naming the object with a one-letter name, you can minimize the amount of extra typing that it requires (which is the same thing that Rails does in its schema.rb). mlet is named after the Common Lisp form flet, except where f stands for "function", m stands for "method".
You use it like this:
def outer
mlet :inner, ->(x) { x*2 } do |c|
c.inner 12
end
end
It is possible to make a similar contraption that allows for multiple inner functions to be defined without additional nesting, but that requires an even uglier hack of the sort you might find in Rake's or Rspec's implementation. Figuring out how Rspec's let! works would get you a long way towards being able to create such a horrible abomination.

You can do something like this
module Methods
define_method :outer do
outer_var = 1
define_method :inner do
puts "defining inner"
inner_var = outer_var +1
end
outer_var
end
extend self
end
Methods.outer
#=> defining inner
#=> 1
Methods.inner
#=> 2
This is useful when you're doing things like writing DSLs which require sharing of scope between methods. But otherwise, you're much better off doing anything else, because as the other answers said, inner is redefined whenever outer is invoked. If you want this behavior, and you sometimes might, this is a good way to get it.

There are no nested methods. All are instance methods are only defined as instance methods after running the method above them
irb(main):001:0>
irb(main):002:1* class Test1
irb(main):003:2* def meth1
irb(main):004:3* def meth2
irb(main):005:3* puts "Yay"
irb(main):006:2* end
irb(main):007:3* def meth3
irb(main):009:4* def meth3_3
irb(main):010:4* puts "Third level indented method"
irb(main):012:2* end
irb(main):013:1* end
irb(main):014:0> end
=> :meth1
irb(main):015:0> Test1.new.meth3_3
Traceback (most recent call last):
4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
3: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
2: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
1: from (irb):15
NoMethodError (undefined method `meth3_3' for #<Test1:0x0000562ae163ce48>)
Did you mean? meth1
irb(main):016:0> Test1.new.meth3
Traceback (most recent call last):
5: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
3: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
2: from (irb):15
1: from (irb):16:in `rescue in irb_binding'
NoMethodError (undefined method `meth3' for #<Test1:0x0000562ae1328658>)
Did you mean? meth1
method
irb(main):017:0> Test1.new.meth2
Traceback (most recent call last):
5: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
3: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
2: from (irb):16
1: from (irb):17:in `rescue in irb_binding'
NoMethodError (undefined method `meth2' for #<Test1:0x0000562ae163df78>)
Did you mean? meth1
method
irb(main):018:0> Test1.new.meth1
=> :meth3
irb(main):019:0> Test1.new.meth3_3
Traceback (most recent call last):
4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
3: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
2: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
1: from (irb):19
NoMethodError (undefined method `meth3_3' for #<Test1:0x0000562ae2568688>)
Did you mean? meth3
irb(main):020:0> Test1.new.meth3
Method Drei
=> :meth3_3
irb(main):021:0> Test1.new.meth3_3
Third level indented method
=> nil
irb(main):022:0>
initialy if your check the instance methods, you get:
irb(main):019:0> Test1.instance_methods
=> [:meth1, :dup, ...]
After running them in steps:
> Test1.instance_methods
=> [:meth3_3, :meth3, :meth1, :meth2,...]

:-D
Ruby has nested methods, only they don't do what you'd expect them to
1.9.3p484 :001 > def kme; 'kme'; def foo; 'foo'; end; end
=> nil
1.9.3p484 :003 > self.methods.include? :kme
=> true
1.9.3p484 :004 > self.methods.include? :foo
=> false
1.9.3p484 :005 > kme
=> nil
1.9.3p484 :006 > self.methods.include? :foo
=> true
1.9.3p484 :007 > foo
=> "foo"

Related

Unable to call ruby lambda

I'm having issue in calling ruby lambda as below.
I'm having a function which accepts a Proc. And lambda definition as below.
def call_proc_with_arg(&b)
b.call(1)
end
lam = -> (a) { puts "printing the argument #{a}"}
And when I try to pass the lambda to function as,
call_proc_with_arg(lam)
I'm getting the error,
Traceback (most recent call last):
6: from /usr/bin/irb:23:in <main>' 5: from /usr/bin/irb:23:in load'
4: from /Library/Ruby/Gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in <top (required)>' 3: from (irb):118 2: from (irb):118:in rescue in irb_binding'
1: from (irb):52:in `call_block'
ArgumentError (wrong number of arguments (given 1, expected 0))
Whereas the function works with block,
call_proc_with_arg {|a| puts "printing the argument #{a}"}
What am I missing with lambda?
You need to use the following syntax to call the method passing the block, as you defined b as block in the method signature:
call_proc_with_arg(&lam)
The only way you could use call_proc_with_arg(lam) is if you treat the argument not as a method block, but as a first-class argument:
def call_proc_with_arg(b)
b.call(1)
end
call_proc_with_arg(lam)
Each syntax has its own advantage/disadvantage, and it depends on the use case. If you want to leverage blocks, then the first one is the most common one. In such case, you can also use a more common yield syntax:
def call_proc_with_arg(&b)
yield 1
end
instead of referencing the block b variable directly.
The second one would be necessary if you want to pass more than one lambda as parameter, as a single method cannot take multiple blocks.
You can call it different ways
lam = -> (a) { puts "printing the argument #{a}"}
# method with parameter as lambda
def call_proc_with_arg(b)
b.call(1)
end
call_proc_with_arg(lam)
# method without parameters
def call_proc_with_arg
yield 1
end
call_proc_with_arg(&lam)
# method without parameters with named block
def call_proc_with_arg(&b)
b.call(1)
end
call_proc_with_arg(&lam)

Define one method in the other method. Ruby [duplicate]

I have a method inside of a method. The interior method depends on a variable loop that is being run. Is that a bad idea?
UPDATE: Since this answer seems to have gotten some interest lately, I wanted to point out that there is discussion on the Ruby issue tracker to remove the feature discussed here, namely to forbid having method definitions inside a method body.
No, Ruby doesn't have nested methods.
You can do something like this:
class Test1
def meth1
def meth2
puts "Yay"
end
meth2
end
end
Test1.new.meth1
But that is not a nested method. I repeat: Ruby does not have nested methods.
What this is, is a dynamic method definition. When you run meth1, the body of meth1 will be executed. The body just happens to define a method named meth2, which is why after running meth1 once, you can call meth2.
But where is meth2 defined? Well, it's obviously not defined as a nested method, since there are no nested methods in Ruby. It's defined as an instance method of Test1:
Test1.new.meth2
# Yay
Also, it will obviously be redefined every time you run meth1:
Test1.new.meth1
# Yay
Test1.new.meth1
# test1.rb:3: warning: method redefined; discarding old meth2
# test1.rb:3: warning: previous definition of meth2 was here
# Yay
In short: no, Ruby does not support nested methods.
Note also that in Ruby, method bodies cannot be closures, only block bodies can. This pretty much eliminates the major use case for nested methods, since even if Ruby supported nested methods, you couldn't use the outer method's variables in the nested method.
UPDATE CONTINUED: at a later stage, then, this syntax might be re-used for adding nested methods to Ruby, which would behave the way I described: they would be scoped to their containing method, i.e. invisible and inaccessible outside of their containing method body. And possibly, they would have access to their containing method's lexical scope. However, if you read the discussion I linked above, you can observe that matz is heavily against nested methods (but still for removing nested method definitions).
Actually it's possible. You can use procs/lambda for this.
def test(value)
inner = ->() {
value * value
}
inner.call()
end
No, no, Ruby does have nested methods. Check this:
def outer_method(arg)
outer_variable = "y"
inner_method = lambda {
puts arg
puts outer_variable
}
inner_method[]
end
outer_method "x" # prints "x", "y"
The Ruby way is to fake it with confusing hacks that will have some users wondering "How in the fuck does this even work?", while the less curious will simply memorize the syntax needed to use the thing. If you've ever used Rake or Rails, you've seen this kind of thing.
Here is such a hack:
def mlet(name,func)
my_class = (Class.new do
def initialize(name,func)
#name=name
#func=func
end
def method_missing(methname, *args)
puts "method_missing called on #{methname}"
if methname == #name
puts "Calling function #{#func}"
#func.call(*args)
else
raise NoMethodError.new "Undefined method `#{methname}' in mlet"
end
end
end)
yield my_class.new(name,func)
end
What that does is define a top-level method that creates a class and passes it to a block. The class uses method_missing to pretend that it has a method with the name you chose. It "implements" the method by calling the lambda you must provide. By naming the object with a one-letter name, you can minimize the amount of extra typing that it requires (which is the same thing that Rails does in its schema.rb). mlet is named after the Common Lisp form flet, except where f stands for "function", m stands for "method".
You use it like this:
def outer
mlet :inner, ->(x) { x*2 } do |c|
c.inner 12
end
end
It is possible to make a similar contraption that allows for multiple inner functions to be defined without additional nesting, but that requires an even uglier hack of the sort you might find in Rake's or Rspec's implementation. Figuring out how Rspec's let! works would get you a long way towards being able to create such a horrible abomination.
You can do something like this
module Methods
define_method :outer do
outer_var = 1
define_method :inner do
puts "defining inner"
inner_var = outer_var +1
end
outer_var
end
extend self
end
Methods.outer
#=> defining inner
#=> 1
Methods.inner
#=> 2
This is useful when you're doing things like writing DSLs which require sharing of scope between methods. But otherwise, you're much better off doing anything else, because as the other answers said, inner is redefined whenever outer is invoked. If you want this behavior, and you sometimes might, this is a good way to get it.
There are no nested methods. All are instance methods are only defined as instance methods after running the method above them
irb(main):001:0>
irb(main):002:1* class Test1
irb(main):003:2* def meth1
irb(main):004:3* def meth2
irb(main):005:3* puts "Yay"
irb(main):006:2* end
irb(main):007:3* def meth3
irb(main):009:4* def meth3_3
irb(main):010:4* puts "Third level indented method"
irb(main):012:2* end
irb(main):013:1* end
irb(main):014:0> end
=> :meth1
irb(main):015:0> Test1.new.meth3_3
Traceback (most recent call last):
4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
3: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
2: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
1: from (irb):15
NoMethodError (undefined method `meth3_3' for #<Test1:0x0000562ae163ce48>)
Did you mean? meth1
irb(main):016:0> Test1.new.meth3
Traceback (most recent call last):
5: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
3: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
2: from (irb):15
1: from (irb):16:in `rescue in irb_binding'
NoMethodError (undefined method `meth3' for #<Test1:0x0000562ae1328658>)
Did you mean? meth1
method
irb(main):017:0> Test1.new.meth2
Traceback (most recent call last):
5: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
3: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
2: from (irb):16
1: from (irb):17:in `rescue in irb_binding'
NoMethodError (undefined method `meth2' for #<Test1:0x0000562ae163df78>)
Did you mean? meth1
method
irb(main):018:0> Test1.new.meth1
=> :meth3
irb(main):019:0> Test1.new.meth3_3
Traceback (most recent call last):
4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
3: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
2: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
1: from (irb):19
NoMethodError (undefined method `meth3_3' for #<Test1:0x0000562ae2568688>)
Did you mean? meth3
irb(main):020:0> Test1.new.meth3
Method Drei
=> :meth3_3
irb(main):021:0> Test1.new.meth3_3
Third level indented method
=> nil
irb(main):022:0>
initialy if your check the instance methods, you get:
irb(main):019:0> Test1.instance_methods
=> [:meth1, :dup, ...]
After running them in steps:
> Test1.instance_methods
=> [:meth3_3, :meth3, :meth1, :meth2,...]
:-D
Ruby has nested methods, only they don't do what you'd expect them to
1.9.3p484 :001 > def kme; 'kme'; def foo; 'foo'; end; end
=> nil
1.9.3p484 :003 > self.methods.include? :kme
=> true
1.9.3p484 :004 > self.methods.include? :foo
=> false
1.9.3p484 :005 > kme
=> nil
1.9.3p484 :006 > self.methods.include? :foo
=> true
1.9.3p484 :007 > foo
=> "foo"

Is there a way to tell if `method_missing` was explicitly called?

From within the method definition, is there a way to tell if the method method_missing was explicitly called or was called as a hook method?
With the method initialize, it is possible to tell if it was called explicitly or was called as a hook by doing the following:
class A
def initialize
puts caller
end
end
When called as a hook, the method initialize has a line with new:
a = A.new
# => ...:in `new'
# => ...
# => ...:in `<main>'
When called explicitly, it does not have such line (unless it is explicitly called from new):
a.send(:initialize)
# => ...
# => ...:in `<main>'
But when I do a similar thing with method_missing, I cannot distinguish the two cases:
class A
def method_missing *;
puts caller
end
end
a.foo
# => ...
# => ...:in `<main>'
a.method_missing
# => ...
# => ...:in `<main>'
You could check the first argument:
class A
def method_missing(name = nil, *)
if name
puts "called via `#{name}'"
else
puts "called directly"
end
end
end
A.new.foo
#=> called via `foo'
A.new.method_missing
#=> called directly
Unlike initialize, which is invoked explicitly via the Class#new, the BasicObject#method_missing is invoked by the Ruby interpreter:
Invoked by Ruby when obj is sent a message it cannot handle. symbol is
the symbol for the method called, and args are any arguments that were
passed to it. By default, the interpreter raises an error when this
method is called. However, it is possible to override the method to
provide more dynamic behavior.
Kernel#caller will not include those locations in the execution stack trace from where Ruby interpreter invoked the method_missing if that's what you are looking for.

Get Method Arguments using Ruby's TracePoint

I'm able to get access to a Ruby method's arguments using the TracePoint API:
def foo(foo_arg)
end
trace = TracePoint.trace(:call, :c_call) do |tp|
tp.disable
case tp.method_id
when :foo, :sub
method = eval("method(:#{tp.method_id})", tp.binding)
method.parameters.each do |p|
puts "#{p.last}: #{tp.binding.local_variable_get(p.last)}"
end
end
tp.enable
end
trace.enable
foo(10)
# => foo_arg: 10
However when I try this with a c method call, I get an error.
"foo".sub(/(f)/) { $1.upcase }
script.rb:20:in `method': undefined method `sub' for class `Object' (NameError)
from script.rb:20:in `<main>'
from script.rb:8:in `eval'
from script.rb:8:in `block in <main>'
from script.rb:20:in `<main>'
This looks like it happens because of a discrepancy between the binding returned when using a C method call and regular Ruby method call.
In the Ruby case tp.self is equal to tp.binding.eval("self") is main however in the C case tp.self is "foo" and tp.binding.eval("self") is main. Is there a way to get the arguments passed into a method using TracePoint for both Ruby and C defined methods?
As you point in your question and as it documented in ruby documentation, tp.self returns a traced object, which have a method method you are looking for.
I think you should use
method = tp.self.method(tp.method_id)
instead of
method = eval("method(:#{tp.method_id})", tp.binding)
UPDATE. Some explanation regarding your last paragraph in question. tp.self in first case (when you call foo) is point to main, because you define foo method in main context and it points to String object in second case because sub is defined there. But tp.binding.eval("self") returns main in both cases because it returns a calling context (not a 'define' context as you expect) and in both cases it is main.
UPDATE (in reply to comment) I think that the only way to do this is to monkey patch sub and all other methods that you are interesting for. Code example:
class String
alias_method :old_sub, :sub
def sub(*args, &block)
old_sub(*args, &block)
end
end
trace = TracePoint.trace(:call, :c_call) do |tp|
tp.disable
case tp.method_id
when :sub
method = tp.self.method(tp.method_id)
puts method.parameters.inspect
end
tp.enable
end
trace.enable
"foo".sub(/(f)/) { |s| s.upcase }
One big drawback is that you can't use $1, $2, ... vars in your original blocks. As pointed here where is no way to make it works. However you can still use block parameters (s in my example).

Remove Kernel Array method

I would like to know how to remove the method Kernel.Array.rand. When the user tries to call it, it should give an error; any kind of error would do.
I tried as below. I tried Kernel.Array and Kernel::Array instead of Random, but they did not work too.
class << Random; self; end.send :remove_method, :rand
Using my IRB I saw that:
2.0.0-p195 :028 > Kernel.Array.rand
ArgumentError: wrong number of arguments (0 for 1)
from (irb):28:in `Array'
from (irb):28
And was even available in the autocomplete with tab.
Tried the rand because was necessary to avoid any use of random method. So I need to remove also sample and shuffle from the Array.
But look what I get:
class << Array; self; end.send :remove_method, :sample
NameError: method `sample' not defined in Class
from (irb):31:in `remove_method'
from (irb):31
So, I would still know how to remove a method from the Array, in that case should be related to the Kernel.Array.
You can use undef_method (the difference with remove_method is that undef_method will walk up the inheritance chain)
rand # => 0.3417719504956065
Kernel.send :undef_method, :rand # private method, have to use `send`
rand # ~> -:5:in `<main>': undefined local variable or method `rand' for main:Object (NameError)
Update
Ah, you are confused. There is a Kernel::Array method, which is completely different from Array the class. Also, there's no need to obfuscate your code with those eigenclass constructs. You can do simply this:
module Kernel
undef_method :rand
end
class Array
undef_method :sample
end
rand # ~> -:9:in `<main>': undefined local variable or method `rand' for main:Object (NameError)
[1, 2].sample # ~> -:10:in `<main>': undefined method `sample' for [1, 2]:Array (NoMethodError)

Resources