Ruby statement modifiers behave differently than conditional statements
ryanangilly.com
ryanangilly.com
>> a = 1
=> 1
>> unless defined? a
>> b = 5
>> end
=> nil
>> defined? b
=> "local-variable"
>> b
=> nilThis is not a bug.
"a = 5 unless defined? a" is not meant to be equivalent to this:
unless defined? a
a = 5
end
It is supposed to be (and is) equivalent to this: a = unless defined? a
5
end* a =
a is defined. It doesn't matter at all what comes after (as long as it keeps the expression syntactically valid) - a is defined. The only question remains is what will end up getting assigned to it. Try a = asdfgasdfweradsf
it will complain there is no asdf... but a is defined. >>> a=b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>>
I think that follows the POLA... ra:~$ irb
irb(main):001:0> defined? a
=> nil
irb(main):002:0> defined? b
=> nil
irb(main):003:0> a = b
NameError: undefined local variable or method `b' for main:Object
from (irb):3
irb(main):004:0> a
=> nil
irb(main):005:0> b
NameError: undefined local variable or method `b' for main:Object
from (irb):5
irb(main):006:0> defined? a
=> "local-variable"
irb(main):007:0>
'a' gets defined. # let a=b;;
Error: Unbound value b
# a;;
Error: Unbound value a
#
Haskell: Prelude> let a=b
<interactive>:1:6: Not in scope: `b'
Prelude> a
<interactive>:1:0: Not in scope: `a'
Prelude>
Scheme: guile> (define a b)
Backtrace:
In standard input:
1: 0* (define a b)
standard input:1:1: In expression (define a b):
standard input:1:1: Unbound variable: b
ABORT: (unbound-variable)
guile> a
ERROR: Unbound variable: a
ABORT: (unbound-variable)
You can see where my astonishment comes from...Ruby doesn't draw as clear a line as compiled languages do between "compile time" and "run time," but the interpreter does parse your code before running it, and certain decisions are made during that process. An important one is the recognition and allocation of local variables.
When the Ruby parser sees the sequence identifier, equal-sign, value, as in this expression
x = 1
it allocates space for a local variable called x. The creation of the variable - not the assignment to it, but the internal creation of a variable - always takes place as a result of this kind of expression, even if the code isn't executed!Consider this example:
if false
x = 1
end
p x # Output: nil
p y # Fatal error: y is unknown
The assignment to x isn't executed, because it's wrapped in a failing conditional test. But the Ruby parser sees the sequence x = 1, from which it deduces that the program involves a local variable x. The parser doesn't care whether x is ever assigned a value. It's job is just to scour the code for local variables for which space needs to be allocated.The result is that x inhabits a strange kind of variable limbo. It has been brought into being and initialized to nil.
In many cases, a bug by design is a question of trade-offs. In this case, stricter rules for the parser might mean more syntax for the programmer.
if condition
x = true
end
puts "hello" if x
without initializing x to false before the conditional. >> a = 1
=> 1
>> b = 2
=> 2
>> a = 3 unless b==2
=> nil
>> a
=> 1
>> a = unless b==2
>> 2
>> end
=> nil
>> a
=> nil
The difference is in the variable definition semantics, not in the assignment semantics. Please do not spread this misinformation. >> if false
>> a = 1
>> end
>> defined? a
=> "local-variable"
>> a
=> nil def ex1
a = 5
if a
puts "a: #{a}"
end
end
def ex2
if a = 5
puts "a: #{a}"
end
end
def ex3
puts "a: #{a}" if a = 5
end
If we start with the code in ex1 and decide to shorten it, ex2 is okay (although some people don't care for assignment in a conditional because it could be confused for a typo). But, ex3 will raise an exception that 'a' has not been defined.I've just internalized using the longer form when using assignment in a conditional.
$ irb
>> b
NameError: undefined local variable or method `b' for main:Object
from (irb):1
>> if defined? b
>> b = 5
>> end
=> nil
>> b
=> nil
>> defined? b
=> "local-variable"Take this for example:
ra:~$ irb
a = irb(main):001:0> a = b
NameError: undefined local variable or method `b' for main:Object
from (irb):1
irb(main):002:0> defined? a
=> "local-variable"
irb(main):003:0>
It just so happens that using statement modifiers in the way I tried using them brings this behavior to light.They are expression modifiers and conditional expressions. Everything in Ruby is an expression, not a statement.