【问题标题】:Declaring variables in conditional在条件中声明变量
【发布时间】:2015-03-19 02:34:06
【问题描述】:

我知道整行都被解析了,变量的值是在puts解析之前设置的:

def get_value
  42
end

if value = get_value
  puts value
end
# => 42

我得到以下结果,这是我所期望的:

#p = "Im totally a string" # <--Commented.
puts "Am i a string? #{p}" if p = "Im a confused string"
# => "Am i a string? "

然而,这很奇怪。在单行之前声明 p 会以一种意想不到的方式改变输出:

p = "foo" # <--Un-commented.
puts "Am i a string? #{p}" if p = "Im a confused string"
# => "Am i a string? Im a confused string"

pFixNum,而不是String

p = 1
puts "Am i a string? #{p}" if p = "Im a confused string"
# => "Am i a string? Im a confused string"

发生了什么事?如果一开始并不明显,那么第二个代码块说明了"Im a confused string" 是如何被插值失败的。但是,在第三个示例中,简单地声明 p(与类型无关)会导致 "Im a confused string" 被插值

我认为这个问题与这些问题不同但相似:

【问题讨论】:

  • 在使用p = 而不是p == 时,您将分配p 而不是检查相等性,这会将分配的值返回给if,并且该值对于字符串(或false, nil以外的任何内容)始终为真。
  • 我刚开始打字。 LOL 是的,Ruby 允许您使用方法的成功作为条件。在这种情况下,方法是=
  • 你总是可以在一个变量上调用 .class 来检查它是什么类。 @MichaelBerkowski 称它为...您使用的是 = 而不是 ==
  • 你的问题是什么?
  • 好吧——无论是谁编辑了这个问题,都完全改变了我所问的内容,让我看起来像个白痴。很抱歉不清楚:我了解 = 与 == 的用法。编辑帖子的人在我提供的第二个代码块中取消注释#p="Im totally a string"。问题在于,当预先声明 p 时,相同行 puts "Am i a string? #{p}" if p = "Im a confused string" 的结果会发生变化。简明扼要:完全声明变量 p(FixNum/String 等)会更改 puts "Am i a string? #{p}" if p = "Im a confused string" 的输出

标签: ruby syntax


【解决方案1】:

TLDR:不幸的是,您的示例存在缺陷,因为您选择的变量名称与核心 ruby​​ 中的现有方法冲突。


正如@SteveTurczyn 几分钟前提到的,如果在条件行之前不知道变量,则将其解释为方法调用。

让我们探索一些机器代码,好吗?重要的行被注释了。

puts "Am i a string? #{myvar}" if myvar = "Im a confused string"

== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1)
[ 2] myvar      
0000 trace            1                                               (   2)
0002 putstring        "Im a confused string"
0004 dup              
0005 setlocal_OP__WC__0 2
0007 branchunless     22
0009 putself          
0010 putobject        "Am i a string? "
0012 putself          
0013 opt_send_simple  <callinfo!mid:myvar, argc:0, FCALL|VCALL|ARGS_SKIP> # call method myvar
0015 tostring         
0016 concatstrings    2
0018 opt_send_simple  <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>
0020 leave            
0021 pop              
0022 putnil           
0023 leave            

当变量被预先声明时

myvar = "Im totally a string"
puts "Am i a string? #{myvar}" if myvar = "Im a confused string"

== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1)
[ 2] myvar      
0000 trace            1                                               (   1)
0002 putstring        "Im totally a string"
0004 setlocal_OP__WC__0 2
0006 trace            1                                               (   2)
0008 putstring        "Im a confused string"
0010 dup              
0011 setlocal_OP__WC__0 2
0013 branchunless     27
0015 putself          
0016 putobject        "Am i a string? "
0018 getlocal_OP__WC__0 2 # Read variable value
0020 tostring         
0021 concatstrings    2
0023 opt_send_simple  <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>
0025 leave            
0026 pop              
0027 putnil           
0028 leave            

现在,您的代码的问题在于 p 是一个存在的方法。如果您不知道,p foo 等同于puts foo.inspect。与puts 类似,它接受灵活数量的参数(甚至是零个参数)并返回nil

puts "Am i a string? #{p}" if p = "Im a confused string"
                       ^ call method `p` here

但是

p = "foo" # Shadow existing method `p`
puts "Am i a string? #{p}" if p = "Im a confused string"
                       ^ get local var
                         if you wanted to also call the method `p`, you'd have to just through some extra hoops
                         or just rename the variable. 

【讨论】:

    【解决方案2】:

    我没有得到与你相同的结果......(但是,旧的 ruby​​)

    irb(main):001 > puts "#{variable_heaven}" if variable_heaven = "pizza"
    NameError: undefined local variable or method `variable_heaven' for main:Object
    
    irb(main):002 > p variable_heaven
    => "pizza"
    

    根据您引用的第一个堆栈溢出答案,在这个尾随条件示例中,解析器假定 variable_heaven ... 到目前为止还没有遇到 ... 是一个方法调用。

    推导的经验法则是……

    条件中的赋值总是在条件代码执行之前执行(对于前导条件(当然)和尾随条件都是如此)

    但是...在尾随条件中引用未定义的变量是有问题的,即使在条件中使用赋值来建立变量。

    您“预期”的代码是异常的:在正常行为中,尾随条件中的赋值和/或方法在条件代码之前执行,因此如果条件代码引用了一个内容被条件修改的变量,条件代码将使用修改后的变量。

    【讨论】:

      【解决方案3】:

      pKernel 中定义的方法。这是一个带有“未使用”名称和显式方法定义的示例:

      def x
        123
      end
      
      puts "x is a #{defined?(x)} with value #{x}" if x = 'foo'
      #=> x is a method with value 123
      

      这是由于 Ruby 的解析顺序造成的。 Ruby 首先解析puts 表达式并将x 识别为方法。然后它执行if 表达式并分配一个局部变量x。之后评估puts 部分时,x 仍然指的是方法。 (详见Modifier if and unless

      另一方面:

      x = 123
      
      puts "x is a #{defined?(x)} with value #{x}" if x = 'foo'
      #=> x is a local-variable with value foo
      

      这里,x 从一开始就是一个局部变量。

      注意局部变量x在这两种情况下都存在:

      def x
        123
      end
      
      puts "#{send(:x)} #{binding.local_variable_get(:x)}" if x = 'foo'
      #=> 123 foo
      

      【讨论】:

        【解决方案4】:

        p 更改变量停止了异常。

        Sergio Tulentsev 的回答是“正确的”!

        【讨论】:

        • “正确”是什么意思。这是 100% 正确的 :)
        • 啊。谢谢你提醒我。感谢您展示指令序列,非常棒。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多