当一个方法调用在原则上可以是常量时,为什么需要消除歧义?

6

方法调用通常可以省略接收器和参数的括号:

def foo; "foo" end
foo # => "foo"

在上述情况下,foo 在方法调用和引用潜在局部变量之间存在歧义。如果不存在后者,则它将被解释为方法调用。
但是,当方法名原则上可以是常量名时(即以大写字母开头,且仅由字母组成),似乎需要消除歧义。
def Foo; "Foo" end
Foo # => NameError: uninitialized constant Foo
Foo() # => "Foo"
self.Foo # => "Foo"

为什么会这样呢?即使没有同名的常量,为什么方法调用还需要明确地与常量引用区分开来?
3个回答

5
在程序的任何给定点,作用域内的局部变量集是按词法定义的,因此可以在静态情况下确定,甚至可以在解析时就确定。所以,Ruby 即使在运行之前也知道哪些局部变量在作用域内,因此可以区分消息发送和局部变量引用。
常量首先按词法顺序查找,然后通过继承动态查找。在运行时之前不知道哪些常量在作用域内。因此,为了消除歧义,Ruby 总是假设它是一个常量,除非显然不是,即它带有参数或具有接收器或两者都有。

感谢回答。 - sawa
1
局部变量在解析时定义是“如果 false then foo = 43 end;foo #=> nil(而不是 NameError)”这种有点令人费解的行为的原因。 - Jörg W Mittag
@JörgWMittag 那很有道理。 - Wand Maker
所以这是为了效率?换句话说,如果我们随意使用以大写字母开头的方法,那么所有这些方法都必须经过消歧过程。而且考虑到如果出于某种原因需要这样做,您可以覆盖行为(如我的答案所示),这是一个很好的答案。 - Mitch VanDuyn
@MitchVanDuyn:首先,你可以使用freeze来冻结类。但实际上,区别在于:通过猴子补丁修改方法而导致的变化是可以预期的。而引入常量所导致的变化则不是。请注意,这不仅仅涉及到定义Foo::Bar。还包括在Foo的任何超类中定义名为Bar的常量,或者在任何被Foo包含的模块中定义名为Bar的常量,或者在任何被Foo或其超类之一包含的模块中定义名为Bar的常量,或者在任何被Foo或其超类之一包含的模块所包含的任何模块中定义名为Bar的常量等等。常量是通过继承进行查找的,它们可能会出现在很远的地方! - Jörg W Mittag
显示剩余2条评论

2

这种差异没有太大的原因。我只是希望,如果在作用域中没有局部变量foo,那么foo就像foo()一样工作。我认为这对于创建DSL等非常有用。但我看不出让Foo表现得像Foo()的理由。


4
惊喜!这是真正的松本先生吗? - sawa

0

你提出了一个很好的问题。正如你所指出的,Ruby希望将其视为常量并因此进行常量查找。

然而,以下代码片段显示了当前行为,通过修改const_missing,您似乎可以获得所需的行为。说实话,我似乎无法破坏任何东西。

我的结论是,正如有人已经建议的那样,这只是一个设计决策,但奇怪的是,通常情况下,Ruby更倾向于惯例而不是强制执行。

或者我可能错过了某些情况,导致混淆和错误发生。

<script type="text/ruby">
def puts(s); Element['#output'].html = Element['#output'].html + s.to_s.gsub("\n", "<br/>").gsub(" ", "&nbsp;") + "<br/>"; end

class ImAClass
  def self.to_s
    "I am ImAClass Class"
  end
end

def ImAMethod
  "hello"
end

class DontKnowWhatIAm
  def self.to_s
    "a Class"
  end
end

def DontKnowWhatIAm
  "a method"
end

puts "ImAClass: #{ImAClass}" 

begin 
  puts "ImAMethod: #{ImAMethod}" 
rescue Exception => e
  puts "confusion! #{e.message}"
end

puts "ImAMethod(): #{ImAMethod()}"

puts "DontKnowWhatIAm: #{DontKnowWhatIAm}"

puts "DontKnowWhatIAm(): #{DontKnowWhatIAm()}"

class Module
  alias_method :old_const_missing, :const_missing
  def const_missing(c)
    if self.respond_to? c
      self.send c
    else
      old_const_missing(c)
    end
  end
end

class Foo
  def self.Bar
    "im at the bar"
  end
end

puts "now we can just say: Foo::Bar and it works! #{Foo::Bar}"
 
</script>


<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rawgit.com/reactive-ruby/inline-reactive-ruby/master/inline-reactive-ruby.js"></script>
<div id="output" style="font-family: courier"></div>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接