Using `defined?` at one-level expansion - ruby

The method defined? gives the result for the verbatim expression given as an argument. For example, the result of
defined? foo
is sensitive to whether foo is literally any defined variable/method. It does not make difference whether foo is defined as a string that is a valid (existing) expression:
foo = "Array"
or not:
foo = "NonExistingConstant"
Is it possible to make defined? be sensitive to the given argument expanded one level? That is, for foo = "Array", it should return "constant" and for foo = "NonExistingConstant", it should return nil. If so, how?

Since you need to check only constants:
['Array', 'NonExistentClass'].each do |name|
puts Object.const_defined?(name)
end
# >> true
# >> false

Related

When is a line of Ruby parsed? evaluated? executed?

I was a little surprised to discover that person is defined by the following line of code, even when params[:person_id] doesn't exist:
person = Person.find(params[:person_id]) if params[:person_id]
I kind of expected that Ruby would first check the if statement and only define person then. In practice it seems person is defined earlier than that but remains nil.
While investigating that I tried the following:
irb> foo
# NameError (undefined local variable or method `foo' for main:Object)
irb> if false
irb> foo = 'bar'
irb> end
irb> foo
# => nil
Initially foo is undefined. But then it gets defined, even though it's only referenced inside an if block that isn't evaluated.
I'm now guessing that the whole program gets parsed(?) and that a foo node is added to the Abstract Syntax Tree (i.e. defined). The program is then executed(?), but that particular line is skipped (not evaluated(?)) and so foo is nil (defined but not set to a value).
I'm not sure how to confirm or refute that hunch though. How does one go about learning and digging into the Ruby internals and finding out what happens in this particular scenario?
Answering my own question, Jay's answer to a similar question linked to a section of the docs where it is explained:
The local variable is created when the parser encounters the assignment, not when the assignment occurs
There is a deeper analysis of this in the Ruby Hacking Guide (no section links available, search or scroll to the "Local Variable Definitions" section):
By the way, it is defined when “it appears”, this means it is defined even though it was not assigned. The initial value of a defined [but not yet assigned] variable is nil.
That answers the initial question but not how to learn more.
Jay and simonwo both suggested Ruby Under a Microscope by Pat Shaughnessy which I am keen to read.
Additionally, the rest of the Ruby Hacking Guide covers a lot of detail and actually examines the underlying C code. The Objects and Parser chapters were particularly relevant to the original question about variable assignment (not so much the Variables and constants chapter, it simply refers you back to the Objects chapter).
I also found that a useful tool to see how the parser works is the Parser gem. Once it is installed (gem install parser) you can start to examine different bits of code to see what the parser is doing with them.
That gem also bundles the ruby-parse utility which lets you examine the way Ruby parses different snippets of code. The -E and -L options are most interesting to us and the -e option is necessary if we just want to process a fragment of Ruby such as foo = 'bar'. For example:
> ruby-parse -E -e "foo = 'bar'"
foo = 'bar'
^~~ tIDENTIFIER "foo" expr_cmdarg [0 <= cond] [0 <= cmdarg]
foo = 'bar'
^ tEQL "=" expr_beg [0 <= cond] [0 <= cmdarg]
foo = 'bar'
^~~~~ tSTRING "bar" expr_end [0 <= cond] [0 <= cmdarg]
foo = 'bar'
^ false "$eof" expr_end [0 <= cond] [0 <= cmdarg]
(lvasgn :foo
(str "bar"))
ruby-parse -L -e "foo = 'bar'"
s(:lvasgn, :foo,
s(:str, "bar"))
foo = 'bar'
~~~ name
~ operator
~~~~~~~~~~~ expression
s(:str, "bar")
foo = 'bar'
~ end
~ begin
~~~~~ expression
Both of the references linked to at the top highlight an edge case. The Ruby docs used the example p a if a = 0.zero? whlie the Ruby Hacking Guide used an equivalent example p(lvar) if lvar = true, both of which raise a NameError.
Sidenote: Remember = means assign, == means compare. The if foo = true construct in the edge case tells Ruby to check if the expression foo = true evaluates to true. In other words, it assigns the value true to foo and then checks if the result of that assignment is true (it will be). That's easily confused with the far more common if foo == true which simply checks whether foo compares equally to true. Because the two are so easily confused, Ruby will issue a warning if we use the assignment operator in a conditional: warning: found `= literal' in conditional, should be ==.
Using the ruby-parse utility let's compare the original example, foo = 'bar' if false, with that edge case, foo if foo = true:
> ruby-parse -L -e "foo = 'bar' if false"
s(:if,
s(:false),
s(:lvasgn, :foo,
s(:str, "bar")), nil)
foo = 'bar' if false
~~ keyword
~~~~~~~~~~~~~~~~~~~~ expression
s(:false)
foo = 'bar' if false
~~~~~ expression
s(:lvasgn, :foo,
s(:str, "bar"))
foo = 'bar' if false # Line 13
~~~ name # <-- `foo` is a name
~ operator
~~~~~~~~~~~ expression
s(:str, "bar")
foo = 'bar' if false
~ end
~ begin
~~~~~ expression
As you can see above on lines 13 and 14 of the output, in the original example foo is a name (that is, a variable).
> ruby-parse -L -e "foo if foo = true"
s(:if,
s(:lvasgn, :foo,
s(:true)),
s(:send, nil, :foo), nil)
foo if foo = true
~~ keyword
~~~~~~~~~~~~~~~~~ expression
s(:lvasgn, :foo,
s(:true))
foo if foo = true # Line 10
~~~ name # <-- `foo` is a name
~ operator
~~~~~~~~~~ expression
s(:true)
foo if foo = true
~~~~ expression
s(:send, nil, :foo)
foo if foo = true # Line 18
~~~ selector # <-- `foo` is a selector
~~~ expression
In the edge case example, the second foo is also a variable (lines 10 and 11), but when we look at lines 18 and 19 we see the first foo has been identified as a selector (that is, a method).
This shows that it is the parser that decides whether a thing is a method or a variable and that it parses the line in a different order to how it will later be evaluated.
Considering the edge case...
When the parser runs:
it first sees the whole line as a single expression
it then breaks it up into two expressions separated by the if keyword
the first expression foo starts with a lower case letter so it must be a method or a variable. It isn't an existing variable and it IS NOT followed by an assignment operator so the parser concludes it must be a method
the second expression foo = true is broken up as expression, operator, expression. Again, the expression foo also starts with a lower case letter so it must be a method or a variable. It isn't an existing variable but it IS followed by an assignment operator so the parser knows to add it to the list of local variables.
Later when the evaluator runs:
it will first assign true to foo
it will then execute the conditional and check whether the result of that assignment is true (in this case it is)
it will then call the foo method (which will raise a NameError, unless we handle it with method_missing).

`defined?` and `unless` not working as expected

I was expecting the following snippet:
var = "Not Empty" unless defined? var
var # => nil
to return "Not Empty", but I got nil. Any insight into why this is happening?
This is one of the only moments in Ruby I would call actual WTFs.
You have to use
unless defined? var
var = :value
end
With the postfix syntax, the interpreter will internally nil-ify the value so it can reason about the variable, thus making it defined before the check is done:
# Doesn't print anything
unless defined?(foo) and (p(foo) or true)
foo = :value
end
# Prints nil
bar = :value unless defined?(bar) and (p(bar) or true)
Local variables are defined (as nil) at the point they are parsed. Definition of var2 precedes the condition. That makes var2 defined even when if the assignment is not executed. Then, the condition evaluates that var2 is defined, which retains the value nil for var2.

Position of "if" condition

I thought that:
do_something if condition
were equivalent to
if condition
do_something
end
I found a code that does not respect this rule.
if !(defined? foo)
foo = default_value
end
Here, foo takes default_value.
foo = default_value if !(defined? foo)
Here, foo takes nil. In the former code, I think if is executed first, and should be equivalent to:
foo = (default_value if !(defined? foo))
Is there any way to set to default_value if the variable is not defined?
General answer :
Some several comments want to use the ||= operator... Which will not work if foo is nil :
foo ||= default_value
will return the default value, while foo is defined.
I insist on using "not defined?", which is not equal to nil.
The Ruby way is
foo ||= default_value
But, of course
if (defined? foo)
foo = default_value
end
and
foo = default_value if !(defined? foo)
are different. You're not comparing the same thing.
In one you compare (defined? foo) and the other you compare !(defined? foo)
I think what you're really after is the following
if !(defined? foo)
foo = default_value
end
The two pieces of code are equivalent syntactically, but are different from the point of view of parsing. You are partially right that, "if is executed first", but that is only regarding syntax. Within parsing, the parsing order follows the linear order of the tokens. In Ruby, when you have an assignment:
foo = ...
then foo is assigned nil even if that portion of code is not syntactically evaluated, and that affects the result of defined?.
In order to write inline without having that problem, the way I do is to use and, or, &&, or ||:
defined?(foo) or foo = default_value

How can you make a ruby function accept either an individual item or collection of items and do the same thing?

I'm trying to write a function that would apply the function to just one item if it's not a collection, or else apply that function to each of the collections elements. For example:
replace spaces with underscores
foo 'a bear' => 'a_bear'
foo ['a bear', 'a bee'] => ['a_bear', 'a_bee']
Is this possible?
It depends on how you define "collection". The most natural option would probably be either "any Enumerable" or even "anything with an each method". However this leads to a problem because Strings are Enumerable, too - in ruby 1.8, at least.
If you only need it to work with arrays, it's easy:
def foo(x)
if x.is_a? Array
x.map {|item| foo(item)}
else
# Do something with x
end
end
Personally I would use variable args:
def foo(*args)
args.each { |arg| puts arg }
end
foo("bar") # bar
foo("bar", "foobar") # bar \n foobar
foo(*%w(bar foobar)) # bar \n foobar
a = ["bar", "foobar"]
foo(*a) # bar \n foobar
foo("baz", *a) # baz \n bar \n foobar
a = "bar"
foo(*a) # bar
If you don't know whether or not your argument is a string or an array then just prepend it with a *.
I find this gives the maximum flexibility when dealing with arrays which might instead be a single value as I can enter them as just arguments if I am initializing the array or safely pass in the variable if I know it will either be an array or a single argument. It will choke on hashes though.
You may be interested in the splat operator
def foo(x)
[*x].map {|item| item.gsub(" ", "_")}
end
Unfortunately, this'd return foo("a bear") as ["a_bear"], rather than "a_bear" without the array.
Not sure if I'm misreading the question or not. The below will make it so a function will treat either a single element or an array of elements the same way. Just array-ifies the argument if it's not already an array, and undoes that at the end if necessary.
def foo(x)
x = [x] unless x.is_a? Array
# do array stuff to x
return result.size > 1 ? result : result.first
end

Checking if a variable is defined?

How can I check whether a variable is defined in Ruby? Is there an isset-type method available?
Use the defined? keyword (documentation). It will return a String with the kind of the item, or nil if it doesn’t exist.
>> a = 1
=> 1
>> defined? a
=> "local-variable"
>> defined? b
=> nil
>> defined? nil
=> "nil"
>> defined? String
=> "constant"
>> defined? 1
=> "expression"
As skalee commented: "It is worth noting that variable which is set to nil is initialized."
>> n = nil
>> defined? n
=> "local-variable"
This is useful if you want to do nothing if it does exist but create it if it doesn't exist.
def get_var
#var ||= SomeClass.new()
end
This only creates the new instance once. After that it just keeps returning the var.
The correct syntax for the above statement is:
if (defined?(var)).nil? # will now return true or false
print "var is not defined\n".color(:red)
else
print "var is defined\n".color(:green)
end
substituting (var) with your variable. This syntax will return a true/false value for evaluation in the if statement.
defined?(your_var) will work. Depending on what you're doing you can also do something like your_var.nil?
Try "unless" instead of "if"
a = "apple"
# Note that b is not declared
c = nil
unless defined? a
puts "a is not defined"
end
unless defined? b
puts "b is not defined"
end
unless defined? c
puts "c is not defined"
end
WARNING Re: A Common Ruby Pattern
the defined? method is the answer. See the accepted answer above.
But watch out... consider this common ruby pattern:
def method1
#x ||= method2
end
def method2
nil
end
method2 always returns nil.
The first time you call method1, the #x variable is not set - therefore method2 will be run. and
method2 will set #x to nil.
But what happens the second time you call method1?
Remember #x has already been set to nil. But method2 will still be run again!! If method2 is a costly undertaking this might not be something that you want.
Let the defined? method come to the rescue:
def method1
return #x if defined? #x
#x = method2
end
As with most things, the devil is in the implementation details.
Use defined? YourVariable
Keep it simple silly .. ;)
Here is some code, nothing rocket science but it works well enough
require 'rubygems'
require 'rainbow'
if defined?(var).nil? # .nil? is optional but might make for clearer intent.
print "var is not defined\n".color(:red)
else
print "car is defined\n".color(:green)
end
Clearly, the colouring code is not necessary, just a nice visualation in this toy example.
You can try:
unless defined?(var)
#ruby code goes here
end
=> true
Because it returns a boolean.
As many other examples show you don't actually need a boolean from a method to make logical choices in ruby. It would be a poor form to coerce everything to a boolean unless you actually need a boolean.
But if you absolutely need a boolean. Use !! (bang bang) or "falsy falsy reveals the truth".
› irb
>> a = nil
=> nil
>> defined?(a)
=> "local-variable"
>> defined?(b)
=> nil
>> !!defined?(a)
=> true
>> !!defined?(b)
=> false
Why it doesn't usually pay to coerce:
>> (!!defined?(a) ? "var is defined".colorize(:green) : "var is not defined".colorize(:red)) == (defined?(a) ? "var is defined".colorize(:green) : "var is not defined".colorize(:red))
=> true
Here's an example where it matters because it relies on the implicit coercion of the boolean value to its string representation.
>> puts "var is defined? #{!!defined?(a)} vs #{defined?(a)}"
var is defined? true vs local-variable
=> nil
It should be mentioned that using defined to check if a specific field is set in a hash might behave unexpected:
var = {}
if defined? var['unknown']
puts 'this is unexpected'
end
# will output "this is unexpected"
The syntax is correct here, but defined? var['unknown'] will be evaluated to the string "method", so the if block will be executed
edit: The correct notation for checking if a key exists in a hash would be:
if var.key?('unknown')
Please note the distinction between "defined" and "assigned".
$ ruby -e 'def f; if 1>2; x=99; end;p x, defined? x; end;f'
nil
"local-variable"
x is defined even though it is never assigned!
defined? is great, but if you are in a Rails environment you can also use try, especially in cases where you want to check a dynamic variable name:
foo = 1
my_foo = "foo"
my_bar = "bar"
try(:foo) # => 1
try(:bar) # => nil
try(my_foo) # => 1
try(my_bar) # => nil
Also, you can check if it's defined while in a string via interpolation, if you code:
puts "Is array1 defined and what type is it? #{defined?(#array1)}"
The system will tell you the type if it is defined.
If it is not defined it will just return a warning saying the variable is not initialized.
Hope this helps! :)
Leaving an incredibly simple example in case it helps.
When variable doesn't exist:
if defined? a then "hi" end
# => nil
When variable does exist:
a = 2
if defined? a then "hi" end
# => "hi"

Resources