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.
Related
This question already has answers here:
When to use 'self' in Ruby
(2 answers)
Closed 3 years ago.
I am trying to understand ruby objects and the self keyword.
Lets say I have a class:
class CashRegister
attr_accessor :total
def initialize(total)
#total = total
end
def add(amount)
self.total = self.total + amount
end
end
cr = CashRegister.new(5)
puts cr.total #=> 5
cr.add(10)
puts cr.total #=> 15
Say I remove the self keyword in the add method above:
def add(amount)
total = total + amount
end
I get an error:
cr = CashRegister.new(5)
puts cr.total #=> 5
cr.add(10) #=> this throws an error: ./lib/test.rb:28:in `add': undefined method `+' for nil:NilClass (NoMethodError)
I am assumed this is because I need the self keyword to refer to the instance variable, total
Say I have another similar class:
class School
attr_accessor :name, :roster
def initialize(name)
#name = name
#roster = {}
end
def add_student(student, grade)
roster[grade] = roster[grade] || []
roster[grade] << student
end
end
school = School.new("Jefferson High")
puts school.roster #=> {}
school.add_student("Sam", 10)
puts school.roster #=> {10=>["Sam"]}
Why did I not need self in add_student?
tl;dr: foo = value will always refer to a local variable foo not the method call self.foo=(value).
I am assumed this is because I need the self keyword to refer to the instance variable, total
No, total is a local variable, not an instance variable. #total is an instance variable. A local variable lives for the current scope, like a single method call. An instance variable sticks with the object.
You need the self keyword to refer to the method total=. Let's dive in.
attr_accessor :total declares two methods, total and total=. These are wrappers to get and set the instance variable #total. The following code does the equivalent.
def total
#total
end
def total=(value)
#total = value
end
Note that the method is named total=, this will become important in a moment.
With that in mind, let's look at your code.
def add(amount)
self.total = self.total + amount
end
(Almost) everything in Ruby is really a method call. The above is syntax sugar for calling the total= method on self.
def add(amount)
self.total=(self.total + amount)
end
Now what happens if we remove self like so?
def add(amount)
total = total + amount
end
In Ruby, self is optional. Ruby will figure out if total means the method total or the local variable total. The local variable takes precedence and assignment is always to a local variable.
total = total + amount works like so:
def add(amount)
total = self.total + amount
end
Assignment is always to a local variable.
To further illustrate, what if we declared total first?
def add(amount)
total = 23
self.total = total + amount
end
The existing local variable total takes precedence over the total() method. total + amount refers to the local variable total and so cr.add(10); puts cr.total will be 33.
total = ... will always refer to the local variable total. For this reason if you want to use method assignment you must explicitly use self.total=. In other cases you can drop the self. And avoid local variables with the same name as methods.
def add(amount)
# self.total = self.total + amount
self.total = total + amount
end
Why did I not need self in add_student?
Because there is no ambiguity. You're not assigning to a local variable roster.
def add_student(student, grade)
roster[grade] = roster[grade] || []
roster[grade] << student
end
roster[grade] is really self.roster.[]=(grade). You are calling self.roster which returns a Hash and then calling the []= method on that Hash to give it a new key/value pair.
Similarly, roster[grade] << student is self.roster.[](grade).<<(student). Get aHash, call [[]](https://ruby-doc.org/core/Hash.html#method-i-5B-5D) on it to retrieve theArrayand call [<<](https://ruby-doc.org/core/Array.html#method-i-3C-3C) on thatArray`.
def add_student(student, grade)
self.roster.[]=(grade) = self.roster.[](grade) || []
self.roster.[](grade).<<(student)
end
Yes, [], []=, and << are all methods names like any other.
A lot of Ruby mysteries go away once you understand the method calls under the syntax sugar and that things like [], []=, << and so on are method names.
Ok so I just started learning ruby and I'm making a Yhatzee game, now this is where I'm currently at:
class Yhatzee
def dices
#dices.to_a= [
dice1=rand(1..6),
dice2=rand(1..6),
dice3=rand(1..6),
dice4=rand(1..6),
dice5=rand(1..6)
]
end
def roll_dice
#dices.to_a.each do |dice|
puts dice
end
end
end
x = Yhatzee.new
puts x.roll_dice
Now the reason i typed .to_a after the array is i kept getting a "uninitialized variable #dices" error, and that seemed to fix it, i have no idea why.
anyways on to my question, i currently don't get any errors but my program still won't print anything to the screen. I expected it to print out the value of each dice in the array... any idea what I'm doing wrong? It seems to work when i do it in a procedural style without using classes or methods so i assumed it might work if i made the 'dices' method public. But no luck.
There are a few issues here. Firstly #dices is nil because it is not set anywhere. Thus when you call #dices.to_a you will get []. Also the dices method will not work either because nil does not have a to_a= method and the local variables you are assigning in the array will be ignored.
It seems a little reading is in order but I would do something like the following: (Not the whole game just refactor of your code)
class Yhatzee
def dice
#dice = Array.new(5){rand(1..6)}
end
def roll_dice
puts dice
end
end
x = Yhatzee.new
puts x.roll_dice
There are alot of additional considerations that need to be made here but this should at least get you started. Small Example of how I would recommend expanding your logic: (I did not handle many scenarios here so don't copy paste. Just wanted to give you a more in depth look)
require 'forwardable'
module Yahtzee
module Display
def show_with_index(arr)
print arr.each_index.to_a
print "\n"
print arr
end
end
class Roll
include Display
extend Forwardable
def_delegator :#dice, :values_at
attr_reader :dice
def initialize(dice=5)
#dice = Array.new(dice){rand(1..6)}
end
def show
show_with_index(#dice)
end
end
class Turn
class << self
def start
t = Turn.new
t.show
t
end
end
attr_reader :rolls
include Display
def initialize
#roll = Roll.new
#rolls = 1
#kept = []
end
def show
#roll.show
end
def roll_again
if available_rolls_and_dice
#rolls += 1
#roll = Roll.new(5-#kept.count)
puts "Hand => #{#kept.inspect}"
show
else
puts "No Rolls left" if #rolls == 3
puts "Remove a Die to keep rolling" if #kept.count == 5
show_hand
end
end
def keep(*indices)
#kept += #roll.values_at(*indices)
end
def show_hand
show_with_index(#kept)
end
def remove(*indices)
indices.each do |idx|
#kept.delete_at(idx)
end
show_hand
end
private
def available_rolls_and_dice
#rolls < 3 && #kept.count < 5
end
end
end
The main problem with this code is that you are trying to use the #dices instance variable inside of the roll_dice method, however you are not defining the instance variable anywhere (anywhere that is being used). You have created the dices method but you are not actually instantiating it anywhere. I have outlined a fix below:
class Yhatzee
def initialize
create_dices
end
def roll_dice
#dices.each do |dice|
puts dice
end
end
private
def create_dices
#dices = Array.new(5){rand(1..6)}
end
end
x = Yhatzee.new
x.roll_dice
I have done some simple refactoring:
Created an initialize method, which creates the #dice instance variable on the class initialization.
Made the 'dices' method more descriptive and changed the method visibility to private so only the class itself is able to create the #dice.
Cleaned up the creation of the dices inside of the #dice instance variable
I have omitted the .to_a from the roll_dice method, now that we create the variable from within the class and we know that it is an array and it will be unless we explicitly redefine it.
UPDATE
Although I cleaned up the implementation of the class, it was kindly pointed out by #engineersmnky that I oversaw that the roll would return the same results each time I called the roll_dice function, I have therefore written two functions which will achieve this, one that defines an instance variable for later use and one that literally just returns the results.
class Yhatzee
def roll_dice
#dice = Array.new(5){rand(1..6)} # You will have access to this in other methods defined on the class
#dice.each {|dice| puts dice }
end
def roll_dice_two
Array.new(5){rand(1..6)}.each {|dice| puts dice } # This will return the results but will not be stored for later use
end
end
x = Yhatzee.new
x.roll_dice
x.roll_dice # Will now return a new result
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.
How to pass an instance variable as an argument to a block?
This doesn't work in Ruby 1.9 (formal argument cannot be an instance variable).
awesome do |foo, #bar|
puts 'yay'
end
Ruby 1.9 makes block parameters local to the block. This also means that block parameters can no longer be global or instance variables. You can use closures for your purposes:
#foo = 10
1.times do |i|
puts #foo
end
# => "10"
UPDATE
Instead of using instance variables, local variable can help you:
foo = 10
1.times do |i|
puts foo
end
# => "10"
In such cases, you won't get problems related to code execution inside different context.
There are 3 cases here which could be relevant:
Instance variable of the definer of the block that references the instance variable (in the example the usage of awesome2.
Instance variable of the definer of the block that gives the instance variable as argument to the receiver (no chance to give it as argument to the block). This is usage of awesome.
Instance variable of the user of the block that gives the instance variable as argument to the block. This is usage of awesome3.
So lets try to implement both examples to see them. The example is lengthy, the relevant lines are:
awesome gets the instance variable as argument, which is then used by the receiver in the call of the block
awesome2 gets no instance variable, it is bound by Sender#use_in_block. No chance for the receiver to change that binding.
awesome3 gets the instance variable of the sender, but uses its own instance variable for calling the block
So depending on what you want to reach, one of the three is the better solution.
class Sender
attr_accessor :send_var
def call_a_block(var)
rec = Receiver.new(5)
#my_var = var
res = rec.awesome(#my_var) do |arg1|
arg1 + 3
end
res2 = rec.awesome3(#my_var) do |arg1|
arg1 + 3
end
p "Result of calling awesome with: 3 and #{#my_var} is #{res}"
p "Result of calling awesome3 with: 3 and #{#my_var} is #{res2}"
end
def use_in_block(var)
rec = Receiver.new(6)
#my_var = var
res = rec.awesome2 do
4 + #my_var
end
p "Result of calling awesome2 with: 4 and #{#my_var} is #{res}"
end
end
class Receiver
attr_accessor :rec_var
def initialize(var)
#rec_var = var
end
def awesome(arg1)
res = yield(arg1)
res * 2
end
def awesome3(arg1)
res = yield(#rec_var)
res * 2
end
def awesome2
res = yield
res * 2
end
end
s = Sender.new
s.call_a_block(7)
s.call_a_block(20)
s.use_in_block(7)
s.use_in_block(20)
Results are:
c:\Temp>ruby call.rb
"Result of calling awesome with: 3 and 7 is 20"
"Result of calling awesome3 with: 3 and 7 is 16"
"Result of calling awesome with: 3 and 20 is 46"
"Result of calling awesome3 with: 3 and 20 is 16"
"Result of calling awesome2 with: 4 and 7 is 22"
"Result of calling awesome2 with: 4 and 20 is 48"
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