Ruby 中带有大写字母的方法的可选括号?

发布于 2024-09-03 17:34:22 字数 967 浏览 5 评论 0原文

我刚刚开始在 .NET 应用程序中使用 IronRuby(但当我在普通 Ruby 中测试它时,其行为似乎是一致的)作为 DSL - 作为其中的一部分,我正在定义通过 Define_method 从 DSL 调用的方法。

但是,在调用以大写字母开头的方法时,我遇到了有关可选括号的问题。

给出以下程序:

class DemoClass
    define_method :test do puts "output from test" end
    define_method :Test do puts "output from Test" end

    def run
        puts "Calling 'test'"
        test()
        puts "Calling 'test'"
        test
        puts "Calling 'Test()'"
        Test()
        puts "Calling 'Test'"
        Test
    end
end

demo = DemoClass.new
demo.run

在控制台中运行此代码(使用普通 ruby​​)会产生以下输出:

ruby .\test.rb
Calling 'test'
output from test
Calling 'test'
output from test
Calling 'Test()'
output from Test
Calling 'Test'
./test.rb:13:in `run': uninitialized constant DemoClass::Test (NameError)
    from ./test.rb:19:in `<main>'

我意识到 Ruby 约定是常量以大写字母开头,并且 Ruby 中方法的一般命名约定是小写字母。但括号目前确实扼杀了我的 DSL 语法。

有什么办法可以解决这个问题吗?

I just started out using IronRuby (but the behaviour seems consistent when I tested it in plain Ruby) for a DSL in my .NET application - and as part of this I'm defining methods to be called from the DSL via define_method.

However, I've run into an issue regarding optional parens when calling methods starting with an uppercase letter.

Given the following program:

class DemoClass
    define_method :test do puts "output from test" end
    define_method :Test do puts "output from Test" end

    def run
        puts "Calling 'test'"
        test()
        puts "Calling 'test'"
        test
        puts "Calling 'Test()'"
        Test()
        puts "Calling 'Test'"
        Test
    end
end

demo = DemoClass.new
demo.run

Running this code in a console (using plain ruby) yields the following output:

ruby .\test.rb
Calling 'test'
output from test
Calling 'test'
output from test
Calling 'Test()'
output from Test
Calling 'Test'
./test.rb:13:in `run': uninitialized constant DemoClass::Test (NameError)
    from ./test.rb:19:in `<main>'

I realize that the Ruby convention is that constants start with an uppercase letter and that the general naming convention for methods in Ruby is lowercase. But the parens are really killing my DSL syntax at the moment.

Is there any way around this issue?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

英雄似剑 2024-09-10 17:34:22

这只是 Ruby 歧义解决的一部分。

在 Ruby 中,方法和变量位于不同的命名空间中,因此可以存在同名的方法和变量(或常量)。这意味着,在使用它们时,需要有某种方法来区分它们。一般来说,这不是问题:消息有接收者,而变量没有。消息有参数,而变量没有。变量被分配,消息则没有。

唯一的问题是当你没有接收者、没有参数、也没有分配时。然后,Ruby 无法区分不带参数的无接收者消息发送和变量之间的区别。因此,它必须制定一些任意规则,这些规则基本上是:

  • 对于以小写字母开头的不明确标记,更愿意将其解释为消息发送,除非您明确知道它是一个变量(即解析器(不是(!)解释器)之前已经看到过赋值)
  • 对于以大写字母开头的不明确标记,更喜欢将其解释为常量

请注意,对于使用以下命令发送的消息参数(即使参数列表为空),没有歧义,这就是您的第三个示例有效的原因。

  • test():显然是一个消息发送,这里没有歧义
  • test:可能是一个消息发送或一个变量;解析规则说这是一个消息发送
  • Test():显然是一个消息发送,这里没有歧义
  • self.Test显然是一个消息发送,这里没有歧义
  • Test:可能是发送的消息或常量;解析规则说它是一个常量

请注意,这些规则有点微妙,例如:

if false
  foo = 'This will never get executed'
end

foo # still this will get interpreted as a variable

这些规则说,是否将不明确的标记解释为变量或消息发送由解析器确定而不是口译员。因此,因为解析器已经看到 foo =whatever,所以它将 foo 标记为变量,即使代码永远不会被执行并且 foo 将与 Ruby 中所有未初始化的变量一样,计算结果为 nil

TL;DR 摘要:你是 SOL。

可以做的是覆盖const_missing以转换为消息发送。像这样的东西:

class DemoClass
  def test; puts "output from test" end
  def Test; puts "output from Test" end

  def run
    puts "Calling 'test'"
    test()
    puts "Calling 'test'"
    test
    puts "Calling 'Test()'"
    Test()
    puts "Calling 'Test'"
    Test
  end

  def self.const_missing(const)
    send const.downcase
  end
end

demo = DemoClass.new
demo.run

除了这显然行不通,因为 const_missing 是在 DemoClass 上定义的,因此,当 const_missing 运行时,selfDemoClass,这意味着它在应该通过 调用 DemoClass#test 时尝试调用 DemoClass.test演示.测试

我不知道如何轻松解决这个问题。

This is just part of Ruby's ambiguity resolution.

In Ruby, methods and variables live in different namespaces, therefore there can be methods and variables (or constants) with the same name. This means that, when using them, there needs to be some way to distinguish them. In general, that's not a problem: messages have receivers, variables don't. Messages have arguments, variables don't. Variables are assigned to, messages aren't.

The only problem is when you have no receiver, no argument and no assignment. Then, Ruby cannot tell the difference between a receiverless message send without arguments and a variable. So, it has to make up some arbitrary rules, and those rules are basically:

  • for an ambiguous token starting with a lowercase letter, prefer to interpret it as a message send, unless you positively know it is a variable (i.e. the parser (not(!) the interpreter) has seen an assignment before)
  • for an ambiguous token starting with an uppercase letter, prefer to interpret it as a constant

Note that for a message send with arguments (even if the argument list is empty), there is no ambiguity, which is why your third example works.

  • test(): obviously a message send, no ambiguity here
  • test: might be a message send or a variable; resolution rules say it is a message send
  • Test(): obviously a message send, no ambiguity here
  • self.Test: also obviously a message send, no ambiguity here
  • Test: might be a message send or a constant; resolution rules say it is a constant

Note that those rules are a little bit subtle, for example here:

if false
  foo = 'This will never get executed'
end

foo # still this will get interpreted as a variable

The rules say that whether an ambiguous token gets interpreted as a variable or a message send is determined by the parser and not the interpreter. So, because the parser has seen foo = whatever, it tags foo as a variable, even though the code will never get executed and foo will evaluate to nil as all uninitialized variables in Ruby do.

TL;DR summary: you're SOL.

What you could do is override const_missing to translate into a message send. Something like this:

class DemoClass
  def test; puts "output from test" end
  def Test; puts "output from Test" end

  def run
    puts "Calling 'test'"
    test()
    puts "Calling 'test'"
    test
    puts "Calling 'Test()'"
    Test()
    puts "Calling 'Test'"
    Test
  end

  def self.const_missing(const)
    send const.downcase
  end
end

demo = DemoClass.new
demo.run

Except this obviously won't work, since const_missing is defined on DemoClass and thus, when const_missing is run, self is DemoClass which means that it tries to call DemoClass.test when it should be calling DemoClass#test via demo.test.

I don't know how to easily solve this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文