How does the assignment operator work in Ruby? - ruby

I'm writing a simple piece of code in ruby, but it's not working the way I expect it to at all. I think this problem comes from a misunderstanding of how ruby works, specifically, how the assignment operator works, relative to other languages. Here's what I've written:
#instance1 = #clock
#clock.tick!
#clock.tick!
#clock.tick!
#instance2 = #clock
puts " instace1.seconds: #{#instance1.seconds}, instance2.seconds: #{#instance2.seconds}"
'Clock' is a class and has a value, seconds, a method 'tick!' which increases seconds by one, and a method 'seconds' which returns the seconds value. Seconds is initalized as 0.
Now when I run this code, the output is:
" instace1.seconds: 3, instance2.seconds: 3"
But the output I would expect is:
" instance1.seconds: 0, instance2.seconds: 3"
Because, I've assigned intance1 the values which #clock had before I changed clock, and I did nothing to modify #instance1 thereafter.
To me this implies that ruby assigns objects as pointers in some contexts, and that there's implicit dereferencing going on. What are these contexts? (class variables?, large objects? )
How do I make an explicit assignment? In other words, how do I dereference a variable?
Like, in C, I would do something like:
*instance1 = *c
(although it's been a long time since pointer-arithmatic, so that's a rough example

Ruby assigns by reference, not by value. What you can do is #instance1 = #clock.dup or #instance1 = #clock.clone.

Related

Ruby: Idiom for "create and increment"

I am looking for a concise way to deal with the following situation: Given a variable (in practince, an instance variable in a class, though I don't think this matters here), which is known to be either nil or hold some Integer. If it is an Integer, the variable should be incremented. If it is nil, it should be initialized with 1.
These are obvious solutions to this, taking #counter as the variable to deal with:
# Separate the cases into two statements
#counter ||= 0
#counter += 1
or
# Separate the cases into one conditional
#counter = #counter ? (#counter + 1) : 1
I don't like these solutions because they require to repeat the name of the variable. The following attempt failed:
# Does not work
(#counter ||= 0) += 1
This can't be done, because the result of the assignment operators is not an lvalue, though the actual error message is a bit obscure. In this case, you get the error _unexpected tOP_ASGN, expecting end_.
Is there a good idiom to code my problem, or do I have to stick with one of my clumsy solutions?
The question is clear:
A variable is known to hold nil or an integer. If nil the variable is to be set equal to 1, else it is to be set equal to its value plus 1.
What is the best way to implement this in Ruby?
First, two points.
The question states, "If it is nil, it should be initialized with 1.". This contradicts the statement that the variable is known to be nil or an integer, meaning that it has already been initialized, or more accurately, defined. In the case of an instance variable, this distinction is irrelevant as Ruby initializes undefined instance variables to nil when they are referenced as rvalues. It's an important distinction for local variables, however, as an exception is raised when an undefined local variable is referenced as an rvalue.
The comments largely address situations where the variable holds an object other than nil or an integer. They are therefore irrelevant. If the OP wishes to broaden the question to allow the variable to hold objects other than nil or an integer (an array or hash, for example), a separate question should be asked.
What criteria should be used in deciding what code is best? Of the various possibilities that have been mentioned, I do not see important differences in efficiency. Assuming that to be the case, or that relative efficiency is not important in the application, we are left with readability (and by extension, maintainability) as the sole criterion. If x equals nil or an integer, or is an undefined instance variable, perhaps the clearest code is the following:
x = 0 if x.nil?
x += 1
or
x = x.nil? ? 1 : x+1
Ever-so-slightly less readable:
x = (x || 0) + 1
and one step behind that:
x = x.to_i + 1
which requires the reader to know that nil.to_i #=> 0.
The OP may regard these solutions as "clumsy", but I think they are all beautiful.
Can an expression be written that references x but once? I can't think of a way and one has not been suggested in the comments, so if there is a way (doubtful, I believe) it probably would not meet the test for readability.
Consider now the case where the local variable x may not have been defined. In that case we might write:
x = (defined?(x) ? (x || 0) : 0) + 1
defined? is a Ruby keyword.

Does + in front of a variable in ruby ever do anything?

foo = 1
p +foo
This example code prints 1 just like if the + was not there. I know - in front of a variable gets the opposite of what the variable was (-29 becomes 29) but is there any case where a variable with a + in front of it ever does anything or can I safely remove it every time I see it? To clarify this a bit I am asking no specifically about numbers assigned to variables but any datatype in ruby.
+ is both a unary operator (one argument) and a binary operator (two arguments). It is defined on the Numeric class. Unary operators are defined using # suffix to differentiate from the binary operator.
Unary Plus—Returns the receiver.
This is the source for the method:
num_uplus(VALUE num)
{
return num;
}
So to answer your question,
Does + in front of a variable in ruby ever do anything?
NO, for Numeric values.
I just looked it up for strings and yes it does do something for frozen strings.
If the string is frozen, then return duplicated mutable string.
If the string is not frozen, then return the string itself.
static VALUE
str_uplus(VALUE str)
{
if (OBJ_FROZEN(str)) {
return rb_str_dup(str);
}
else {
return str;
}
}
is there any case where a variable with a + in front of it ever does anything
Yes. Every time. It calls the +# method.
or can I safely remove it every time I see it?
No, you can't. It will change the semantics of your code: before, it will call the +# method, after, it won't.
Whether or not that changes the outcome of your program, depends on what that method is doing. The default implementation for Numeric#+# simply returns self, but of course someone could have monkey-patched it to do something different.
Also, String#+# does something more interesting: if self is a frozen string, it will return an unfrozen, mutable copy; if self is already mutable, it returns self.
Other objects in the core library don't have a +# method, so they will usually raise a NoMethodError. If you remove the call, they won't; that is also a behavioral change.

Overwrite a variable without repeating the name

Is there a method to overwrite variable without copying its name? For example, when I want to change my_var = '3' to an integer, I must do something like this:
my_var = my_var.to_i
Is there way to do this without copying variable's name? I want to do something like this:
my_var = something_const.to_i
For numbers there exists +=, -= etc, but is there universal way to do this for all methods ?
There is no way to covert a string to an integer like that, without repeating the variable name. Methods such as String#upcase! and Array#flatten! work by mutating the object; however, it is not possible to define such a method like String#to_i! because we are converting the object to an instance of a different class.
For example, here is a (failed) attempt to define such a method:
# What I want to be able to do:
# my_var = "123"
# my_var.to_i! # => my_var == 123
class String
def to_i!
replace(Integer(self))
end
end
my_var = "123"
my_var.to_i! # TypeError: no implicit conversion of Fixnum into String
...And even if this code were valid, it would still offer no performance gain since a new object is still being created.
As for your examples of += and -=, these are in fact simply shorthand for:
x += 1
# Is equivalent to:
x = x + 1
So again, there is no performance gain here either; just slightly nicer syntax. A good question to ask is, why doesn't ruby support a ++ operator? If such an operator existed then it would offer performance gain... But I'll let you research for yourself why this is missing from the language.
So to summarise,
is there universal way to do this for all methods?
No. The special operators like +=, -=, |= and &= are all predefined; there is no "generalised" version such as method_name=.
You can also define methods that mutate the object, but only when appropriate. Such methods are usually named with a !, are called "bang-methods", and have a "non-bang" counterpart. On String objects, for example, there is String#capitalize! (and String#capitalize), String#delete! (and String#delete), String#encode! (and String#encode), .... but no String#to_i! for the reasons discussed above.

How to unfreeze an object in Ruby?

In Ruby, there is Object#freeze, which prevents further modifications to the object:
class Kingdom
attr_accessor :weather_conditions
end
arendelle = Kingdom.new
arendelle.frozen? # => false
arendelle.weather_conditions = 'in deep, deep, deep, deep snow'
arendelle.freeze
arendelle.frozen? # => true
arendelle.weather_conditions = 'sun is shining'
# !> RuntimeError: can't modify frozen Kingdom
script = 'Do you want to build a snowman?'.freeze
script[/snowman/] = 'castle of ice'
# !> RuntimeError: can't modify frozen String
However, there is no Object#unfreeze. Is there a way to unfreeze a frozen kingdom?
Update: As of Ruby 2.7 this no longer works!
Yes and no. There isn't any direct way using the standard API. However, with some understanding of what #freeze? does, you can work around it. Note: everything here is implementation details of MRI's current version and might be subject to change.
Objects in CRuby are stored in a struct RVALUE.
Conveniently, the very first thing in the struct is VALUE flags;.
All Object#freeze does is set a flag, called FL_FREEZE, which is actually equal to RUBY_FL_FREEZE. RUBY_FL_FREEZE will basically be the 11th bit in the flags.
All you have to do to unfreeze the object is unset the 11th bit.
To do that, you could use Fiddle, which is part of the standard library and lets you tinker with the language on C level:
require 'fiddle'
class Object
def unfreeze
Fiddle::Pointer.new(object_id * 2)[1] &= ~(1 << 3)
end
end
Non-immediate value objects in Ruby are stored on address = their object_id * 2. Note that it's important to make the distinction so you would be aware that this wont let you unfreeze Fixnums for example.
Since we want to change the 11th bit, we have to work with the 3th bit of the second byte. Hence we access the second byte with [1].
~(1 << 3) shifts 1 three positions and then inverts the result. This way the only bit which is zero in the mask will be the third one and all other will be ones.
Finally, we just apply the mask with bitwise and (&=).
foo = 'A frozen string'.freeze
foo.frozen? # => true
foo.unfreeze
foo.frozen? # => false
foo[/ (?=frozen)/] = 'n un'
foo # => 'An unfrozen string'
No, according to the documentation for Object#freeze:
There is no way to unfreeze a frozen object.
The frozen state is stored within the object. Calling freeze sets the frozen state and thereby prevents further modification. This includes modifications to the object's frozen state.
Regarding your example, you could assign a new string instead:
script = 'Do you want to build a snowman?'
script.freeze
script = script.dup if script.frozen?
script[/snowman/] = 'castle of ice'
script #=> "Do you want to build a castle of ice?"
Ruby 2.3 introduced String#+#, so you can write +str instead of str.dup if str.frozen?
frozen_object = %w[hello world].freeze
frozen_object.concat(['and universe']) # FrozenError (can't modify frozen Array)
frozen_object.dup.concat(['and universe']) # ['hello', 'world', 'and universe']
As noted above copying the variable back into itself also effectively unfreezes the variable.
As noted this can be done using the .dup method:
var1 = var1.dup
This can also be achieved using:
var1 = Marshal.load(Marshal.dump(var1))
I have been using Marshal.load(Marshal.dump( ... )
I have not used .dup and only learned about it through this post.
I do not know what if any differences there are between Marshal.load(Marshal.dump( ... )
If they do the same thing or .dup is more powerful, then stylistically I like .dup better. .dup states what to do -- copy this thing, but it does not say how to do it, whereas Marshal.load(Marshal.dump( ... ) is not only excessively verbose, but states how to do the duplication -- I am not a fan of specifying the HOW part if the HOW part is irrelevant to me. I want to duplicate the value of the variable, I do not care how.

Modifying module level variables in an anonymous array in Ruby

I am in the midst of learning Ruby and thought I was clever with the following piece of code:
[#start,#end].map!{ |time| time += operation == :add ? amount : -(amount) }
where #start, #end are two module level variables, operation can be one of :add or :sub, and amount is an float amount to adjust both #start and #end by.
Granted it only saves me a line of code, but why doesn't this approach work, and how can I get something similar that does?
(My expected output is for #start/#end to be modified accordingly, however unit tests show that they stay at their original values.)
It's important in Ruby to remember the distinction between variables and the objects they hold. Simply setting a variable will never change the object referenced by that variable. When you do a += b, it's just shorthand for a = a + b. So you're assigning a new value to the variable a, not changing the object that used to be there or changing any other references to that object. So changing the variable time doesn't change #start.
In order to assign to an instance variable, you need to actually assign to that instance variable. Here's a way to do what you were looking for:
operation = :+
amount = 12
#start, #end = [#start, #end].map {|time| time.send(operation, amount)}
You'll notice that we're not faffing around with that :add and :sub business either — we can just pass the actual name of the message we want to send (I used + in this case, but it could be anything).
If you had a big, dynamically generated list of ivars you wanted to set, it's only a little bit more complicated. The only difference there is that need to get and set the ivars by name.
ivars = [:#start, :#end, :#something_else]
operation = :+
amount = 12
ivars.each {|ivar| instance_variable_set(ivar, instance_variable_get(ivar).send(operation, amount))}
The += operator changes the value of time but it returns the old value of time, therefore the right code is:
#start,#end = [#start,#end].map!{ |time| time + (operation == :add ? amount : -amount) }
EDIT
Updated the code to actually change #start and #end.
The addition operation in the block doesn't modify 'time', it returns a new value. So the elements in the array aren't modified, they're replaced.

Resources