Ruby conditions and defined? operator weird behavior - ruby

I want to set a variable if it's not already defined, so I write
if defined?(var).nil?
var = true
end
puts "[#{var}]"
This behaves as expected, it will print [true]. But if I want to simplify the snippet and write:
var = true if defined?(var).nil?
puts "[#{var}]"
It will print [].
What is the difference between these two snippets ?

A local variable is defined from the point that the first assignment to it is parsed. Therefore, in your second snippet, the variable is defined at the point where you are calling defined? (since Ruby is parsed like English, i.e. left-to-right, top-to-bottom), therefore the condition is always false, therefore the assignment never gets executed, therefore, the variable never gets initialized. Un-initialized local variables evaluate to nil and nil.to_s is the empty string.

Consider the following.
a #=> NameError (undefined local variable or method `a' for main:Object)
defined?(a)
#=> nil
a #=> NameError (undefined local variable or method `a' for main:Object)
As no local variable a exists defined? returns nil but does not create the variable.
b = 1
c = defined?(b)
#=> "local-variable"
puts "cat" if c
cat
d = nil
e = defined?(nil)
#=> "nil"
e.nil?
#=> false
c ("local-variable") is truthy (logically true) because it is neither nil or false (the latter being falsy).
a #=> NameError (undefined local variable or method `a' for main:Object)
a = true if false
#=> nil
a #=> nil
This last result is an odd characteristic of Ruby. As soon as the parser sees the beginning of the assignment (a =) it sets a equal to nil. I understand this is done for efficiency reasons.
Case 1
d = defined?(var1)
#=> nil
e = d.nil?
#=> true
var1 = true (since e #=> true)
var1
#=> true
Case 2
var2 =
# var2 is set equal to nil
d = defined?(var2)
#=> "nil"
e = d.nil?
#=> false
var2 = true if false
var2
#=> nil
puts "[#{nil}]"
[]

Related

Not able to do Class(argument) with eval in ruby

I have a function like this:
def check_if_correct_type(type, value)
# nil.test!
# eval(type.classify(value)) rescue return false
# true
case type
when "integer"
!!Integer(value) rescue return false
when "float"
!!Float(value) rescue return false
else
return true
end
true
end
A sample would be
check_if_correct_type("integer", "a")
I tried changing the function like this:
check_if_correct_type(type, value)
!!(eval(type.classify(value))) rescue return false
true
end
This is throwing errors. How do I fix this. I am fairly new to meta programming so kind of lost.
Update 1:
"adfadf".kind_of?(String) #=> true
123.kind_of?(String) #=> false
# The "Fixnum" class is actually used for integers
"adfadf".kind_of?(Fixnum) #=> false
123123.kind_of?(Fixnum) #=> true
12.3.kind_of?(Float) #=> true
"sadf".kind_of?(Float) #=> false
12.kind_of?(Float) #=> false
The above will not work for me as the kind_of? function will find the type of the object where as for me the answer requires to be like this:
check_if_correct_type("integer", "1221") #=> true
check_if_correct_type("float", "1.24") #=> true
check_if_correct_type("string", "asds12") #=> true
check_if_correct_type("float", "asdasd1.24") #=> false
where as
"1.24".kind_of?(Float) #=> false
That is why conversion works for me. Hope the question is more clear now.
Update 2:
This is what I get if I use public send.
!!public_send("integer".capitalize("1"))
ArgumentError: wrong number of arguments (1 for 0)
from (pry):4:in capitalize'
[5] pry(main)> !!public_send("integer".classify("1"))
ArgumentError: wrong number of arguments (1 for 0)
from /home/aravind/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/activesupport-4.2.0/lib/active_support/core_ext/string/inflections.rb:187:inclassify'
Note: classify is a part of Ruby on Rails and not Ruby.
I suggest you write your method as follows:
def correct_type?(type, str)
case type.downcase
when "integer"
!!to_integer(str)
when "float"
!!to_float(str)
else
raise ArgumentError, "type must be 'integer' or 'float'"
end
end
where to_integer(value) (to_float(value)) is a method that returns value.to_i (value.to_f) if value is the string representation of an integer (a float), else returns nil. The methods to_integer and to_float are useful because they tell you both whether the string can be converted to the given numerical type, and if it can, give you the numerical value.
Before considering how you might implement to_integer and to_float, I would like to call into question the need for correct_type?. Rather than:
str = "33"
if correct_type?("integer", str)
n = str.to_i
puts n
else
...
end
would it not be better to write:
if (n = to_integer("33"))
puts n
else
...
end
There are basically two ways to write the methods to_integer and to_float. The first is the approach you took:
def to_integer(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
Integer(s) rescue nil
end
def to_float(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
return nil if to_integer(s)
Float(s) rescue nil
end
to_integer("3") #=> 3
to_integer("-3") #=> -3
to_integer("+ 3") #=> 3
to_integer("cat") #=> nil
to_integer("3.14") #=> nil
to_integer(:cat) #=> ArgumentError: ArgumentError
to_float("3.14") #=> 3.14
to_float("-3.14") #=> -3.14
to_float("+ 3.14") #=> 3.14
to_float("cat") #=> nil
to_float("3") #=> nil
to_float(:cat) #=> ArgumentError: ArgumentError
The second approach is to use a regular expression:
def to_integer(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
s[/^[+-]?\s*\d+$/] ? s.to_i : nil
end
def to_float(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
return nil if to_integer(s)
s[/^[+-]?\s*\d+\.\d+$/] ? s.to_f : nil
end
to_integer("3") #=> 3
to_integer("-3") #=> -3
to_integer("+ 3") #=> 3
to_integer("cat") #=> nil
to_integer("3.14") #=> nil
to_integer(:cat) #=> ArgumentError: ArgumentError
to_float("3.14") #=> 3.14
to_float("-3.14") #=> -3.14
to_float("+ 3.14") #=> 3.14
to_float("cat") #=> nil
to_float("3") #=> nil
to_float(:cat) #=> ArgumentError: ArgumentError
There is no need to use eval to send a message. You can just use send instead:
def check_if_correct_type(type, value)
!!send(type.capitalize, value) rescue return false
true
end
Note: there is no method named classify anywhere in either the Ruby core library or the Ruby standard libraries. Note also that it is a very bad idea, to just blindly catch all exceptions.
I don't see a point of using metaprogramming for this example. You should avoid using it where there's no need for it. Generally speaking, your program's logic should be:
a) Check the type of the value entered.
b) Compare the type with the type entered as argument. Or in code:
def check_if_correct_type(type, value)
actual_type = value.class.name
return actual_type.downcase == type.downcase
end
p check_if_correct_type('string', 'test') #=> true
p check_if_correct_type('integer', 'test2') #=> false
This could could be made even shorter in one line, but did it in two to demonstrate more clearly what's going on.
If you want to check an object's class, the right way is this:
"adfadf".kind_of?(String) #=> true
123.kind_of?(String) #=> false
# The "Fixnum" class is actually used for integers
"adfadf".kind_of?(Fixnum) #=> false
123123.kind_of?(Fixnum) #=> true
12.3.kind_of?(Float) #=> true
"sadf".kind_of?(Float) #=> false
12.kind_of?(Float) #=> false
There is no reason to be using the Integer() or Float() methods to check for a type. Those are type conversion methods, they will convert other types to Float or Fixnum. If you do want to try to convert a type is convertable to Float or numeric, that is one way to do it, but there might be better ways.
In general, you should never plan on raising and rescuing an exception as part of ordinary program flow; one reason is because it is very slow. Exceptions should be used for errors and unusual/exceptional conditions, not routine conditions such that exceptions will be frequently raised.
And definitely don't start bringing eval into it, geez why would you do that?
This is how I have ended up solving my problem
def check_if_correct_type(type, value)
!!eval("#{type.classify}(value)") rescue return false
true
end
Sample output for this function is below incase you are wondering if it words or not
[25] pry(main)> value = "1"
=> "1"
[26] pry(main)> !!eval("#{type.classify}(value)")
=> true
[27] pry(main)> value = "a"
=> "a"
[28] pry(main)> !!eval("#{type.classify}(value)")
ArgumentError: invalid value for Float(): "a"
from (pry):28:in `eval'
[29] pry(main)> value = "1.4"
=> "1.4"
[30] pry(main)> type = "integer"
=> "integer"
[31] pry(main)> !!eval("#{type.classify}(value)")
ArgumentError: invalid value for Integer(): "1.4"
from (pry):31:in `eval'

Passing splat on nil as argument

All values for b below let me call a method with the *args syntax.
def some_method(a)
puts a
end
b = 1
some_method(*b) # => 1
b = false
some_method(*b) # => false
b = "whatever"
some_method(*b) # => "whatever"
With nil, I expected to get nil, not argument error:
b = nil
some_method(*b) # => ArgumentError: wrong number of arguments (0 for 1)
What is happening here?
The splat operator * first applies to_a to the object if it is not an array and to_a is defined on it. For numerals, falseclass, and strings, to_a is not defined, and they remain themselves. For nilclass, to_a is defined and returns an empty array. When they are splatted, the numerals, falseclass, and strings remain themselves, but the empty array becomes absence of anything. Also see an answer to this question.

Variable in else condition assumed nil value [duplicate]

This question already has answers here:
Confusion with the assignment operation inside a falsy `if` block [duplicate]
(3 answers)
Why is `a = a` `nil` in Ruby?
(1 answer)
Closed 8 years ago.
This is something strange I've figured out in ruby 1.9.3.
Here is the code:
>> r = true
>> if r
>> a = "hello"
>> else
>> b = "hello"
>> end
Now the value of a is "hello":
>> a
=> "hello"
And strangely the value of b is nil
>> b
=> nil
Since b is nowhere in the scene, it should be undeclared.
Why?
Variable declarations take effect even if the branch where the assignment takes place is never reached.
The reason for this is explained in the Ruby documentation, and it is valid for any version of Ruby, not just 1.9:
The local variable is created when the parser encounters the
assignment, not when the assignment occurs.
This means that if the parser sees an assignment in the code it creates the local variable even if the assignment will never really occur. In this case the value referenced by the variable is nil:
if true
a = 0
else
b = 0
end
p local_variables
# => [:a, :b]
p a
# => 0
p b
# => nil
The local variable is created when the parser encounters the
assignment, not when the assignment occurs:
=> foo
# NameError: undefined local variable or method `foo' for main:Object
=> if false
=> foo = "bar" # does not assign to foo
=> end
=> nil
=> foo
=> nil

ruby condition sequence

I was under the impression that conditions joined with && were executed in sequence, such that the following would return true:
a = "adasd"
> b = a && b.present?
=> false
Thoughts?
Thanks!
--Peter
note:
b = a
=> "adasd"
b.present?
=> true
When you say this:
b = a && b.present?
You're declaring b as a local variable but it will be nil until the right side of the assignment is evaluated. In particular, b will be nil when you call present? on it and the conjunction will be false making b false.
When you do this:
a = 'pancakes'
b = a
b.present?
b will have the value 'pancakes' when you call present? on it so you get a true return from b.present?.
As per the rails doc
present is checking for a variable to be non blank
As per assignment timing, ruby declares the variable in the scope as soon as it sees it, so b will be in the scope but with no value, so present will return false.
You should maybe compared this with defined?
a = "abc"
=> "abc"
defined? a
=> "local-variable"
defined? b
=> nil
b = defined? b
=> "local-variable"

Set Ruby variable if it is not already defined

In Ruby, how do you set a variable to a certain value if it is not already defined, and leave the current value if it is already defined?
While x ||= value is a way to say "if x contains a falsey value, including nil (which is implicit in this construct if x is not defined because it appears on the left hand side of the assignment), assign value to x", it does just that.
It is roughly equivalent to the following. (However, x ||= value will not throw a NameError like this code may and it will always assign a value to x as this code does not -- the point is to see x ||= value works the same for any falsey value in x, including the "default" nil value):
if !x
x = value
end
To see if the variable has truly not been assigned a value, use the defined? method:
>> defined? z
=> nil
>> z = nil
=> nil
>> defined? z
=> "local-variable"
>> defined? #z
=> nil
>> #z = nil
=> nil
>> defined? #z
=> "instance-variable"
However, in almost every case, using defined? is code smell. Be careful with power. Do the sensible thing: give variables values before trying to use them :)
Happy coding.
#variable ||= "set value if not set"
So false variables will get overridden
> #test = true
=> true
> #test ||= "test"
=> true
> #test
=> nil
> #test ||= "test"
=> "test"
> #test = false
=> false
> #test ||= "test"
=> "test"
As you didn't specify what kind of variable:
v = v
v ||= 1
Don't recommend doing this with local variables though.
Edit: In fact v=v is not needed
If the variable is not defined (declared?) it doesn't exist, and if it is declared then you know how you initialized it, right?
Usually, if I just need a variable whose use I don't yet know---that I know will never use as a Boolean---I initialize it by setting its value to nil. Then you can test if it has been changed later quite easily
x = nil
some code
if x do
[code that will only run if x has changed]
end
that's all.

Resources