I have a method which takes a code block.
def opportunity
#opportunities += 1
if yield
#performances +=1
end
end
and I call it like this:
opportunity { #some_array.empty? }
But how do I pass it more than one code block so that I could use yield twice, something like this:
def opportunity
if yield_1
#opportunities += 1
end
if yield_2
#performances +=1
end
end
and:
opportunity {#some_other_array.empty?} { #some_array.empty? }
I am aware that this example could be done without yield, but it's just to illustrate.
You can't pass multiple blocks, per se, but you can pass multiple procs or lambdas:
Using 1.9 syntax:
opportunity ->{ #some_array.empty? }, ->{ #some_other_array.empty? }
and in the method itself:
def opportunity(lambda1, lambda2)
if lambda1.()
#opportunities += 1
end
if lambda2.()
#performances += 1
end
end
Related
I've two loops in two different methods which look very similar. I wanted to abstract most of their logic in a Proc.new
This works
def matches_base?
proc_exec = Proc.new do |subclass, breakpoint|
# next and return are meant to act inside the loop and quit it if needed
response = process_match(subclass)
next if response == :continue
return true if response == false
return response
end
subclasses(BASE_NAMESPACE).each do |subclass|
proc_exec.call(subclass)
end
false
end
The obvious issue here is the proc_exec is defined inside the method itself, but I want to use it in another method
def matches_breakpoints?
breakpoints.fetch.each do |breakpoint|
# I want to include the proc_exec here too
end
false
end
So I just tried to extract it at the class level like so
This does not work
def proc_exec
Proc.new do |subclass, breakpoint|
response = process_match(subclass)
next if response == :continue
return true if response == false
return response
end
end
def matches_base?
subclasses(BASE_NAMESPACE).each do |subclass|
proc_exec.call(subclass)
end
false
end
Then I could have called it like proc_exec.call from within both instance methods. Currently it throws
LocalJumpError:
unexpected return
I tried many tricks such as instance_eval or instance_exec without success. I'm out of solution right now.
Easily executable, simplified example of what I want below.
class MyLoops
def proc_exec
Proc.new do |num|
next if num == 1
# we want this `return` to act in the method context
# as it would do if I defined it inside a method directly
return if num == 4
puts "Current number : #{num}"
end
end
def method_a
[0,1,2].each do |num|
proc_exec.call(num)
end
false
end
def method_b
[3,4,5].each do |num|
proc_exec.call(num)
end
end
# this `false` below should never be reached ; that's the trick
false
end
loops = MyLoops.new
loops.method_a
loops.method_b
You can't have your cake and eat it too. If you want return from the proc to abort the method, it must be in the method's lexical scope* (which is another way to say "it must be defined within the same method").
An alternative is to have proc/lambda return a "stop" value, which caller will use to abort its execution.
(Your experiments with instance_eval/instance_exec were misdirected, sadly. Those methods only change current self. This problem has nothing to do with current self, but rather current lexical scope, in which return is executed.)
* The error you're getting, it is caused by return trying to return from a method that is no longer running (proc_exec).
Basically I want something like this:
some_method do
method1()
method2()
method3()
end
where between each of these 3 methods, there would be sleep 5, I just don't want to repeat sleep 5 after each and every method call. Is there a way to define some_method such as, between each method call, a particular method like sleep 5 is being executed? I don't want to pollute my code by inserting a bunch of sleep commands after each and every method call.
If, as in the example, the methods take no arguments or blocks, and the return values are not used, you could do the following.
module M
def method1
puts "method 1"
end
def method2
puts "method 2"
end
def method3
puts "method 3"
end
end
class A
include M
def method4
puts "method 4"
end
def execute_methods
M.instance_methods.each { |m| send m; sleep 5 }
end
end
A.new.execute_methods
# method 1
# method 2
# method 3
One advantage of including a module containing the methods to be executed is that you can add, remove or rename methods in the module without changing any other code. This technique might be potentially useful, for example, when executing a series of validation methods that each return true or false:
def validate(obj)
M.instance_methods.all? { |m| send m, obj }
end
This is also an example of methods that have arguments and whose return values are used.
Note, I don't know Ruby, so this will be in a javascripty flavour of pseudocode, and won't make use of any "special features" of Ruby.
This is probably the simplest way to achieve what you're looking for:
function execute_in_sequence(delay, fs) {
foreach(f in fs) {
f();
sleep(delay);
}
}
Then you can use it like:
function p() {
print(1);
}
execute_in_sequence(500,
[p, p, p]
);
You could also get rid of the explicit list using variadic functions, which could let you write it as:
execute_in_sequence(500,
p,
p,
p
);
In this example, p is pretty simple. To do this with functions that require arguments, you would need to wrap the calls in a lambda first.
Also note that the obvious flaw is that you can't return any values this way. All of the functions must operate soley through side effects, which limits the use of this.
Here's one possible some_method implementation.
Parameters are the methods names as symbol (i.e :method1 for method1()), and if a block is given, it's executed after each method call.
def method1
puts "Method1"
end
def method2
puts "Method2"
end
def method3
puts "Method3"
end
def some_method(*methods)
methods.each{|m|
send m
yield if block_given?
}
end
some_method(:method1, :method2, :method3) do
puts " Waiting 5 sec."
sleep 5
end
It returns :
Method1
Waiting 5 sec.
Method2
Waiting 5 sec.
Method3
Waiting 5 sec.
With some tweaks and more logic, you could also send parameters to the different methods and saves what the methods return in an Array.
I came across this code:
class RandomSequence
def initialize(limit,num)
#limit,#num = limit,num
end
def each
#num.times { yield (rand * #limit).floor }
end
end
i = -1
RandomSequence.new(10,4).each do |num|
i = num if i < num
end
Is it the case that the each method is called only once and will compute four different values, and then for each of those values we execute code block between do and end? Is my understanding of control flow correct?
Your understanding is close. The random number will be generated, then a block yielded, then another generated, etc 4 times. You can verify this easily by adding puts statements into you blocks to see when they are executed.
class RandomSequence
def initialize(limit,num)
#limit,#num = limit,num
end
def each
puts "in each"
#num.times { yield (rand.tap {|x| puts "Generated #{x}" } * #limit).floor }
end
end
i = -1
RandomSequence.new(10,4).each do |num|
puts "in block"
i = num if i < num
end
Outputs
in each
Generated 0.6724385316643955
in block
Generated 0.8906983274750662
in block
Generated 0.49038868732214036
in block
Generated 0.38100454011243456
in block
The each method on the RandomSequence class is called once. In it #num.times which creates an Enumerator. The Enumerator is iterated over and the block with the yield statement is called (with the argument to it ignored).
The yield statement calls the block that's passed to the each method passing the value of (rand * #limit).floor. In your code the block is not bound to a variable, i.e you could get a reference to the block by doing:
def each(&block)
#... do stuff with block, e.g. block.call("some args")
end
which can be useful at times.
A bit off topic, but one thing I found scary with Ruby starting out is that a return statement returns the flow of execution from where it was defined.
def create_proc
puts "Creating proc"
Proc.new do
puts "In proc!"
return "some value" # notice the explicit return
end
end
def do_stuff
my_proc = create_proc
my_proc.call # This will cause a runtime error
end
If the explicit return is removed everything works there is no error... Lesson being that in ruby you should probably avoid using explicit returns.
I have two ways I might need to call some code using a block.
Option 1:
foo()
Option 2:
block_function do
foo()
end
How do I switch between the two of these at runtime? I really don't want to do the following, because foo() is actually a whole lot of code:
if condition then
foo()
else
block_function do
foo()
end
end
def condition_or_block_function
if condition
yield
else
block_function { yield }
end
end
condition_or_block_function do
foo() # which is really a lot of code :)
end
Or as others suggested, make the foo() bunch of code an actual method and write what you wrote in the OP.
More generic version as #tadman suggests:
def condition_or_block condition, block_method, *args
if condition
yield
else
send(block_method, *args) { yield }
end
end
condition_or_block(some_condition, some_block_yielding_method) do
foo() # which is really a lot of code :)
end
#Christian Oudard added a comment specifying the specific problem, optionally decorating a code block with div do...end with Erector. This suggests another approach:
class BlockWrapper
def initialize(method=nil)
#method = method
end
def decorate
#method ? send(method) { yield } : yield
end
end
wrapper = BlockWrapper.new( condition ? nil : :div )
wrapper.decorate do
#code block
end
What is the proper way in ruby to call a method from within itself to rerun
In the sample below when #dest_reenter is equal to yes I would want the b_stage method to execute again
def b_stage
if #dest_reenter == 'yes'
#dest_reenter = nil
b_stage
end
end
That is how you do recursion, but using those instance variables isn't the way to go. A better example would be something like this:
def b_stage(i)
if i < 5
puts i
i += 1
b_stage(i)
end
end
If you call b_stage(0), the output will be
0
1
2
3
4
Use a separate method:
def go
...
middle_thing(true)
end
def middle_thing(first_time)
next_page unless first_time == true
parse_page
end
def parse_page
...(parsing code)
middle_thing(false)
end