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
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).
I am creating an Elevator object with an instance that can only go between floors one and twelve. The code works for the up and down instance methods, but I cannot get the elevator to not go above floor 12 or below floor 1. I tried using unless #floor >= 12, but there was a syntax error. I'm sure it is simple, but I am new to Ruby.
Here is the code that works:
class Elevator
##count = #floor
#The Constructor Method
def initialize(floor) #floor is an instance variable of the Elevator object.
#floor = floor
cheery_greeting
end
def cheery_greeting
puts "Hello my friend! would you like to go up or down?"
end
def self.notify()
"You are now on floor #{##count}"
end
#accessor methods
def go_up
#floor += 1
notify
end
def go_down
#floor -= 1
notify
end
I want to add a break so that it stops iterating when we reach floor twelve, so I wrote this, but it wouldn't even puts.
def floor_limit
if ##count == 12
puts "You're Way Too High!"
end
I also tried:
def go_up
unless #floor >= 12
#floor += 1
notify
end
You're mixing class instance variables with instance variables here and that's going to lead to trouble and confusion. If you're new to Ruby I strongly advise you to avoid using class instance variables, they just lead to a lot of mess. Instead focus on making each instance as self-contained as possible.
To make this more Ruby you can do a few things:
class Elevator
# Define a valid range for this elevator
FLOOR_RANGE_DEFAULT = (1..12)
# Expose the current floor and range as properties
attr_reader :floor
attr_reader :range
def initialize(floor, range = nil)
#floor = floor.to_i
#range = (range || FLOOR_RANGE_DEFAULT).to_a
end
def inspect
#range.map do |floor|
if (#floor == floor)
'[%d]' % floor
else
' %d ' % floor
end
end.join(' ')
end
end
Then your up and down code can check limits and reject if that's an invalid operation. First separate the moving code from the code that interprets up or down:
def up
move(#floor + 1)
end
def down
move(#floor - 1)
end
def move(target)
if (#range.include?(target))
#floor = target
end
#floor
end
Now you have a framework that you can build on. By using simple things like Ruby's range feature you can make a very adaptable class that can handle situations like elevators that have other limits:
e = Elevator.new(1, (1..20))
Or that stop only on odd floors:
e = Elevator.new(1, (1..20).select(&:odd?))
Or skip the 4th, 13th and 14th:
e = Elevator.new(1, (1..20).to_a - [ 4, 13, 14 ])
It doesn't take more code, it just takes the right code.
The following should do the trick.
def go_up
#floor += 1 unless #floor >= 12
notify
end
Or like this:
def go_up
#floor += 1 if #floor < 12
notify
end
They are both pretty intuitive so it's up to you.
This is a pretty detailed explanation on when to use unless.
And here is a standard if/else/unless tutorial.
I'm currently learning Ruby and hit some behavior I don't quite understand. In the example code below, I have two until loops, each in their own method. When executing until_test it outputs an loop of 10 20 10 20 10 20 forever but when executing second_until_test it behaves as I expect it to, outputing only 10 20. It seems that for some reason the way the code is now, I'm not able to change variables passed as parameters. I know the answer to this is likely very simple but I've failed to figure it out or find the answer on here after searching a while. What is the proper way to pass parameters successfully as I'm trying to do in until_test?
Thanks in advance!
def until_test
num = 10
until num == 20
do_this(num)
end
end
def second_until_test
num = 10
until num == 20
puts num
num = 20
puts num
end
end
def do_this(variable)
puts variable
variable = 20
puts variable
end
Your problem here is namespace... In second_until_test, num is valid for the method, so it will be changed inside the untill loop.
In until_test, you are passing num as an argument to another method, which will not directly change the passed object, unless you assert num to the return value of the method:
def until_test
num = 10
until num == 20
num = do_this(num)
end
end
def do_this(variable)
puts 20
variable = 20
# this method has to return the value for num
variable # or return variable, or return 20...
end
TLDR: until_test's num does not change value, that's why it will loop forever.
I fixed you problem here:
def until_test
num = 10
until num == 20
num = do_this(num)
end
end
def second_until_test
num = 10
until num == 20
puts num
num = 20
puts num
end
end
def do_this(variable)
puts variable
variable = 20
puts variable
return variable
end
until_test
The other answers are correct. The reason you get a forever loop in until_test is because your do_this method doesn't return your modified variable. Calling puts doesn't return the value of the parameter passed, but nil, so that means, what you are assigning num is nil rather than the desired modified output :)
Anyways, just sharing an another way to kill the cat :)
In ruby, there is something that you call an instance variable. Any modification to the variable that is made within your script, be it other methods, will change the value of the variable. It can be simply declared by prepending an # to the variable. You can also use $ to make it global. Thanks Eric for pointing that out hahaha...
Implementing this on your code will look like this:
#num
def until_test
#num = 10
until #num == 20
do_this(#num)
end
end
def second_until_test
#num = 10
until #num == 20
puts #num
#num = 20
puts #num
end
end
def do_this(variable)
puts variable
variable = 20
#num = variable
puts variable
end
The selected answer is the best one, although Jason gives an alternative technique.
To clarify on Jason's answer, an instance variable is accessible to all the methods defined in an object's method, including nested methods.
class Dog
def dog_things
#dog = "Rex"
def bark
puts "#{#dog} goes 'bark, bark'"
end
bark
end
end
Dog.new.dog_things
=> "Rex goes 'bark, bark'"
And in Ruby, even if you haven't defined any classes or objects, you are nonetheless, always in an object. It's called main and it's an object of the Object class.
puts self
=> main
puts self.class
=> Object
The difference between instance and global variables is that if you define a class, the instance variable you set before creating the class is not available in the class (although it can have another instance variable with the same name).
Global variables, however, once defined, are accessible everywhere, in all classes and outside classes.
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 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