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
Related
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).
I'm coding something in Ruby where, given a value foo output from a method call, I want to:
Return foo if foo is truthy
Log an error and return a default value if foo is falsy.
The simplest naive way to implement this is probably:
foo = procedure(input)
if foo
foo
else
log_error
default
end
but this feels overly verbose because foo is repeated three times, and this style is very imperative.
What's the cleanest, most idiomatic way to write this?
(Performance matters-- let's assume that foo is truthy in the vast majority of cases.)
Living off of Ruby's Perl heritage:
foo = procedure(input) and return foo
log_error
default
This is a rare case where Ruby's anonymous block is actually useful:
foo = procedure(input) || begin
log_error
default
end
You can write that if log_error returns a true value
foo || log_error && default
If not:
foo || (log_error; default)
For the sake of completeness (and since no one has posted it yet) I'll add my original attempt at an answer:
procedure(input) || proc {
log_error
default
}.call
I can't think of any reason to use this over #mwp's answer:
procedure(input) || begin
log_error
default
end
because of the extra overhead (performance and readability) involved in creating the Proc object. Though some quick benchmarking reveals that the performance difference should be negligible in most cases, since the Proc object doesn't seem to be created unless procedure(input) is falsy.
All things considered, I would go with:
foo = procedure(input)
return foo if foo
log_error
default
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
I have an inline if which isn't doing what i thought, and i've distilled it into a console example for clarity.
I thought that the inline if was syntactically the same as the multi-line if, but it looks like it isn't.
foo = "chunky"
(bar1 = foo) if (!defined?(bar1) && foo)
bar1
In this instance, bar1 ends up set to nil. If i restructure it to
foo = "chunky"
if !defined?(bar2) && foo
bar2 = foo
end
bar2
then it works - bar2 is set to "chunky" (i've used bar2 instead of bar1 in the second example to make sure i was using an undefined variable in each case).
Is it the case that the inline if always sets bar1 to something, defaulting to nil? I thought it just didn't evaluate the part before the if, if the if test returns falsy.
It fails because as soon as a bareword is seen on the left-hand side of an assignment, it's initialized to nil. Thus when you do (bar1 = foo) if (!defined?(bar1) && foo), bar1 will be nil in the defined? check. Here's a simplified example:
>> defined? foo
=> nil
>> foo = 1 if false
=> nil
>> foo
=> nil
>> defined? foo
=> "local-variable"
I have the following:
foo ||= []
foo << "bar"
And I am sure this can be done in one line, I just cannot find how.
Important is, that foo may, or may not exist. If it exists it is always an Array, if it does not exist, it must become an array and get a variable appended to it.
Like this:
(foo ||= []) << "bar"
The parenthesized bit returns foo if it already exists, or creates it if it doesn't, and then the << appends to it.
If you only want to add "bar" when foo is not already defined:
foo ||= ["bar"]
if you want to add "bar" regardless of whether or not foo already exists:
(defined? foo) ? foo << "bar" : foo = ["bar"]
However, in the latter case, I personally prefer the way the original code is written. Sure it can be done in one line, but I think the two line implementation is more readable.
What code are you writing where you're unsure if a local variable exists?
If it's something like
def procedural_method(array)
result ||= []
array.each do |array_item|
result << bar(array_item)
end
result
end
then you could try a more functional programming approach
def functional_programming_method(array)
array.map do |array_item|
bar(array_item)
end
end