Non executed line (blocked by "if false") still affects results [duplicate] - ruby

This question already has answers here:
Ruby instance method & conditional local variable assignment with same name
(2 answers)
Closed 3 years ago.
I wanted to use the classical ||= re-assignment (cf Set Ruby variable if it is not already defined) with ActiveInteraction pretty much like in https://github.com/AaronLasseigne/active_interaction/issues/395
However by testing different syntaxes in ActiveInteraction I stumbled upon a much more peculiar issue that happens even in vanilly Ruby.
A non-executed line (blocked by a if false) can still have a major impact on the rest of the code:
class A
attr_accessor :a
def run
(puts defined? a; a) if true
end
def run2
(puts 'change a'; a = 0) if false
puts defined? a
a
end
end
x = A.new
x.run # "method"; nil
x.run2 # "local-variable"; nil
x.a = 5
x.run # "method"; 5
x.run2 # "local-variable"; nil
Can anyone explain if this is a bug or a feature? And if a feature: how come? It seems very odd.
EDIT: Thanks to the answer of #Sergio Tulentsev I managed to find that my question is pretty much a duplicate of Ruby instance method & conditional local variable assignment with same name with a different focus for the title name.

[is this] a bug or a feature?
Neither. It's a... peculiarity. What happens is, when parser sees assignment to local variable in the code, it goes ahead and adds the name to the scope (starting from that line, possibly shadowing other names, like your method here). With default value of nil. If the actual assignment is then never executed, the new local variable is still in scope and still evaluates to nil.
This is documented in https://docs.ruby-lang.org/en/2.5.0/syntax/assignment_rdoc.html#label-Local+Variables+and+Methods.

Related

Pass by reference or pass by copy - Ruby Modules [duplicate]

This question already has answers here:
Is Ruby pass by reference or by value?
(14 answers)
Closed 9 years ago.
I'm not sure what happens when you pass an object to a module method. Does the object gets passed by reference or by copy? Like in this example:
module SampleModule
def self.testing(o)
o.test
end
end
class SampleClass
def initialize(a)
#a = a
end
def test
#a = #a + 1
end
end
sample_object = SampleClass.new(2)
3.times do
SampleModule.testing(sample_object)
end
p sample_object # => #<SampleClass:somehexvalue #a=5>
seems to be pass-by reference. Really unclear about this.
All variables in Ruby are references to objects. You cannot "pass by value" versus "pass by reference" in the same way as you have that choice in C, C++ or Perl. Ruby in fact forces pass by value, there are no options to do otherwise. However, the values that are sent are always references to objects. It's a bit like using C or C++ where all member variables are pointers, or using Perl where you must work with references at all times, even when working with simple scalars.
I think that it is this separation of variable from object data that is confusing you.
A few points:
Variable allocation never over-writes other variables that may point to the same object. This is pretty much the definition of pass-by-value. However this isn't meeting your expectations that object contents are also protected.
Instance variables, and items in containers (e.g. in Arrays and Strings) are separate variables, and if you send a container you can alter its content directly, because you sent the reference to the container, and that includes the same variables for its contents. I think this is what you mean by "seems to be pass-by reference"
Some classes - including those representing numbers, and Symbol - are immutable i.e. there are no change-in-place methods for the number 4. But conceptually you are still passing a reference to the singular object 4 into a routine (under the hood, for efficiency Ruby will have the value 4 encoded simply in the variable's memory allocation, but that is an implementation detail - the value is also the "pointer" in this case).
The simplest way to get close to the "pass by value" semantics you seem to be looking for with SampleModule is to clone the parameters at the start of the routine. Note this does not actually cause Ruby to change calling semantics, just that in this case from the outside of the method you get the safe assumption (whatever happens to the param inside the method stays inside the method) that you seem to want:
module SampleModule
def self.testing(o)
o = o.clone
o.test
end
end
Technically this should be a deep clone to be generic, but that wouldn't be required to make your example work close to a pass-by-value. You could call SampleModule.testing( any_var_or_expression ) and know that whatever any_var_or_expression is in the rest of your code, the associated object will not have been changed.
If you really want to be anal on vocabulary, Ruby passes references to (mutable) objects by value:
def pass_it(obj)
obj = 'by reference'
end
def mutate_it(obj)
obj << ' mutated'
end
str = 'by value'
pass_it(str)
mutate_it(str)
puts str # by value mutated
You can work around issues that may arise from this by using dup or clone (note that both do shallow copies) and freeze.
Everything in Ruby is passed by reference:
class Test
attr_reader :a
def initialize(a)
#a = a
end
end
s = "foo"
o = Test.new(s)
o.a << "bar"
o.a #=> "foobar"
s #=> "foobar"
o.a.equal? s #=> true
In your code, the fact that you are passing an object to a module method doesn't change anything; sample_object is already a reference to the new object SampleClass.new(2)

Assigning an undefined variable in a one line condition [duplicate]

This question already has answers here:
Ruby if vs end of the line if behave differently?
(5 answers)
Closed 8 years ago.
In Ruby, why can you write :
# b is not defined yet.
#
if b = true
a = b
end
# => a = true
But not a one-liner :
a = b if b = true
# => NameError: undefined local variable or method `b' for main:Object
Because the Ruby interpreter "creates" a local variable when it sees an assignment.
In the second case, it hasn't yet seen the assignment, so the variable doesn't exist when the expression is parsed.
To be more precise, a method is first parsed into an internal representation, and then, perhaps, the code will eventually be called and actually executed.
Variables are "created" in that parsing pass. It's really more a matter of declaration, it just means that the interpreter becomes aware of them. They won't be created in the sense of being given space or a value until the surrounding method is called by someone.

Ruby knows 'myvar' is a variable in myvar = 0 if false

I'm learning Ruby and I like playing with irb to discover new features and tricks. Today I was playing with variables and methods because I wanted to know which one took preference in front of the other one. Everything looked fine until I tried this:
def test
puts "hello"
end
test = "bye" if false
puts test
I was expecting this to return "hello" , but it doesn't. So, I suppose the parser is treating 'test' as a variable instead of as a method. I have two questions:
Is my assumption correct?
Is there any way to know if something is a variable or a method? Some method like test.is_variable?
test = "hello" if false
p test #=> nil
The local variable test is created anyway (with default value nil), and given that local variables overshadow methods with the same name, that's the value you get. Just an hour ago someone got bitten by a subtle variation of the theme. And don't you think this only happens with one-liner conditionals:
if false
test = "hello"
end
p test #=> nil
That's because Ruby defines variables when they are parsed (and not when they are executed).
There are at least two methods that help: methods and local_variables. I wouldn't recommend using them in real world programs, but they might be useful when learning Ruby.

Ruby: Setting a global variable by name

I am trying to dynamically set (not create, it already has to exist) a global ruby variable in a method. The variable name is determined from the passed symbol. What I am currently doing is the following:
def baz(symbol)
eval("$#{symbol}_bar = 42")
end
$foo_bar = 0
baz(:foo)
puts $foo_bar # => 42
But to me, this kind of feels very wrong. Is this the way to do this? Or can it be done differently? Also, I don't know how evals perform in ruby. Does it run much slower than
$foo_bar = 42
The method looks fine to me. This guy says that eval efficiency is much worse, though the post is 3 years old.
I will point out that this method suggests you have a lot of global variables, which is generally a code smell if the code base is significant.
If you can use an instance variable instead, there is Object#instance_variable_set.
def baz(symbol)
instance_variable_set("##{symbol}_bar", 42)
end
Note that it only accepts variable names that can be accepted as an instance variable (starting with #). If you put anything else in the first argument, it will return an error. For the global variable counterpart to it, there is a discussion here: Forum: Ruby
Either way, you also have the problem of accessing the variable. How are you going to do that?

defined? in Ruby works in irb but not in my class file

I was writing a small Heap implementation and upon creating my Node class I noticed some weird behaviour. I wanted to call defined?(x) to ensure x was defined, then check if x was an Integer, before storing it in the Node's value class variable. In IRB I can call
defined?(x) and the result is nil.
However, in the class, I try this:
def change_value value
#value = value if defined?(value)
end
and the result when I call the change_value with a random letter, let's say 'e', is the standard undefined local variable or method error. Again, in IRB it seems to work fine and I am wondering if I have some kind of environment issue or if this is not the 'best' way to check if value is really there.
Thanks.
(Edit: DigitalRoss has since cleaned up the question's formatting, so the comment on formatting and the initial re-write may no longer apply. I'll leave them in until I get some feedback from the OP.)
That's a nearly unreadable way to write a method and it doesn't even parse on my machine (Ruby 1.8.7).
I'm assuming you mean the following:
def change_value(value)
#value = value if defined?(value)
end
This works fine when I call it, but it's incorrect. nil and undefined are two different beasts in Ruby; value will always be defined in that context because it's a declared method parameter. I suspect what you are really after is:
def change_value(value)
#value = value unless value.nil?
end
Note that some people would simply write this as:
def change_value(value)
#value = value if value
end
because nil is "falsy". However, this form conflates nil and false, so it's not a good habit to get into.
This isn't the best way to check if it's really there. If change_value is called without providing any parameters, you would get:
ArgumentError: wrong number of arguments (0 for 1)
Instead, you might want to check to make sure value is not nil. Of course you can do this a variety of ways:
if !value.nil?
#...
end
if value
# this will be exeuted if value is either not `nil` or not `false`
end
Best of luck!
Would it have to do with calling the method from the class instance and not the object instance?

Resources