Необязательные скобки в Ruby для метода с заглавной начальной буквой?

Я только начал использовать IronRuby (но поведение кажется последовательным, когда я тестировал его на простом Ruby) для DSL в моем приложении .NET, и как часть этого я определяю методы, которые будут вызываться из DSL через define_method.

Однако я столкнулся с проблемой, касающейся необязательных скобок при вызове методов, начинающихся с заглавной буквы.

Учитывая следующую программу:

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.

Есть ли способ обойти эту проблему?


person RasmusKL    schedule 01.06.2010    source источник


Ответы (1)


Это всего лишь часть разрешения неоднозначности 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 будет оцениваться как nil, как это делают все неинициализированные переменные в Ruby.

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 запускается, self является DemoClass, что означает, что он пытается вызвать DemoClass.test, когда он должен вызывать DemoClass#test через demo.test.

Я не знаю, как легко это решить.

person Jörg W Mittag    schedule 01.06.2010
comment
Кстати: использование test в качестве имени метода - действительно плохая идея. В базовой библиотеке уже определен метод с именем Kernel#test, и если вы попытаетесь отладить метапрограммирование, это может действительно испортить ваш мыслительный процесс. Это определенно смутило меня, когда я получил исключение ArgumentError, когда я ожидал NoMethodError в приведенном выше примере кода! - person Jörg W Mittag; 01.06.2010