Variable scope and order of parsing vs. operations: Assignment in an “if”

后端 未结 3 955
既然无缘
既然无缘 2020-12-06 14:15

My understanding is that the if statements at the end of the line are evaluated before the code at the front of the line:

\'never shown\' if (fa         


        
相关标签:
3条回答
  • 2020-12-06 14:43

    My guess is that the order of parsing is different from the (logical) order of execution. In particular, given

    array << error if (error = some_function)
    

    Then logically, execution should go something like

    1. call some_function
    2. if error not defined, define error
    3. assign return value of some_function to error
    4. evaluate if
    5. if if evaluates to true, append value of error to array

    However, parsing wise (assuming typical LR parser), it goes

    1. Got token array (identifier). Is this defined? Yes. Is it a variable? Yes.
    2. Got token << (operator). Does array respond to <<? Yes (otherwise, output "undefined method" error).
    3. Got token error (identifier). Is this defined? No. Output "undefined local variable or method".
    0 讨论(0)
  • 2020-12-06 14:48

    It only happens when you try to assign a literal value, if you call a function it works.

    def foo(a)
      a
    end
    
    p 'not shown' if(value = foo(false))
    p 'shown' if(value = foo(true))
    
    # This outputs a Warning in IRB
    p 'shown' if(value = false)
    (irb):2: warning: found = in conditional, should be ==
    

    If you turn on debugging (-d) you will see a warning about an used variable value

    warning: assigned but unused variable - value
    

    This "works" because the statement does evaluate to true as far as if is concerned, allowing the code preceeding it to run.

    What is happening here is that if() when used as a modifier has it's own binding scope, or context. So the assignment is never seen outside of the if, and therefore makes no sense to perform. This is different than if the control structure because the block that the if statement takes is also within the same scope as the assignment, whereas the line that preceeded the if modifier is not within the scope of the if.

    In other words, these are not equivelant.

    if a = some(value)
      puts a
    end
    
    puts a if(a = some(value))
    

    The former having puts a within the scope of the if, the latter having puts a outside the scope, and therefore having different bindings(what ruby calls context).

    Ruby Order of Operations

    0 讨论(0)
  • 2020-12-06 14:50

    In Ruby, local variables are defined by the parser when it first encounters an assignment, and are then in scope from that point on. Since Ruby is parsed like English, left-to-right, top-to-bottom, the local variable doesn't exist yet when you are using it, because the usage is further left from the assignment.

    Here's a little demonstration:

    foo # NameError: undefined local variable or method `foo' for main:Object
    
    if false
      foo = 42
    end
    
    foo # => nil
    

    As you can see, the local variable does exist on line 7 even though the assignment on line 4 was never executed. It was, however, parsed and that's why the local variable foo exists. But because the assignment was never executed, the variable is uninitialized and thus evaluates to nil and not 42.

    Now let's get to simplest version of your case:

    bar if bar = true
    # warning: found = in conditional, should be ==
    # NameError: undefined local variable or method `bar' for main:Object
    
    bar # => true
    

    The variable is created when the assignment is parsed, which is here:

    bar if bar = true
           ^^^^^^^^^^
    

    But it is used here:

    bar if bar = true
    ^^^
    

    Which is before the assignment. The fact that the assignment is executed before the usage is irrelevant because the parsing is relevant here, and the assignment gets parsed after the usage which means that at the point of usage the parser still thinks it's a method call with no argument list and an implicit receiver (i.e. equivalent to self.bar()) and not a local variable.

    0 讨论(0)
提交回复
热议问题