2013-08-24 4 views
4

Я работаю над Ruby Gem for creating presentations, и я хотел бы создать синтаксис для определения слайдов, который прост и интуитивно понятен. Я использую instance_eval, поэтому я могу вызывать методы самостоятельно. Вот что я изначально планировал сделать:Почему я не могу переопределить метод * в Ruby?

slide { 
    title 'What is Ruby?' 
    * 'a programming language' 
    * 'with lots of interpreters' 
    * 'lots of fun!' 
} 

Даже если я определил метод *, я получаю ошибку:

in `instance_eval': ... syntax error, unexpected '\n', expecting :: or '[' or '.' (SyntaxError)

Я скомпрометирован, создав короткий метод, называемый b для создания пуль , но это не так хорошо:

slide { 
    title 'What is Ruby?' 
    b 'a programming language' 
    b 'with lots of interpreters' 
    b 'lots of fun!' 
} 

Это просто ограничение переводчика? Или есть способ обойти это?

Update: Если вы хотите, вы можете выкопать в full source code, но вот небольшой пример того, как это реализовано:

class Slide 
    attr_accessor :title, :bullets 
end 

class SlidesDSL 
    attr_accessor :slides 
    def slide 
    @slides ||= [] 
    s = SlideDSL.new 
    s.instance_eval(&block) 
    @slides << s.slide 
    end 
    class SlideDSL 
    def slide 
     @slide ||= Slide.new 
    end 

    def title(text) 
     slide.title 
    end 

    def *(text) 
     bullet(text) 
    end 

    def b(text) 
     slide.bullets ||= [] 
     slide.bullets << text 
    end 
    end 
end 

# load_slides_from_file 
source = File.read(filename) 
dsl = SlidesDSL.new 
dsl.instance_eval(source, filename, 0) 
@slides = dsl.slides 
+0

Предоставленная вами информация не достаточно, чтобы сказать причину ошибки. – sawa

+1

* Как вы это определили? Вы можете * легко определить *, но это оператор, так что это зависит от того, как вы его определили и как работает ваша DSL. –

+0

Я просто добавил обновление с образцом исходного кода – Andrew

ответ

4

It seems that you are relying on syntactic sugar that is given to the * method for many things.

Это не так. Вы можете сделать это:

class Dog 

    private 
    def do_stuff(arg) 
    puts 2 + arg 
    end 

end 

d = Dog.new 

d.instance_eval do 
    do_stuff(3) 
end 

--output:-- 
5 
  1. instance_eval() изменяет себя свой приемник, d в этом случае.
  2. В Ruby private означает, что вы не можете вызвать метод с явным приемником.
  3. Вызов метода без явного приемника неявно использует self в качестве приемника.

Теперь, если вы измените имя методы от do_stuff к *:

class Dog 

    private 
    def *(arg) 
    puts 2 + arg 
    end 

end 

d = Dog.new 

d.instance_eval do 
    *(3) 
end 

--output:-- 
1.rb:13: syntax error, unexpected '\n', expecting tCOLON2 or '[' or '.' 

Так оп полагаются на нормальном режиме работы метода, а не какой-либо синтаксический сахар приписан *. Внутри instance_eval блока, можно было бы ожидать Руби беспрекословно выполнять:

self.*(3) 

что эквивалентно:

d.*(3) 
+1

Я не говорил ни о каком синтаксическом сахаре, связанном с eval или instance_eval, только в том смысле, что их нет. персонажей. И ОП, по моему наблюдению, может бороться с механизмами, указанными в + и *, например, при обычных вызовах метода. В том, что другие методы, если они находятся в конце строки, не предполагают других будущих событий, но могут быть терминатором линии. – vgoff

+1

@Confusion, я думаю, что ваш комментарий очень информативен. Я попытался переместить ваш комментарий под ваш пост, а затем удалить свой пост, но система не позволит мне удалить мой пост. – 7stud

+0

Вы правы, этот комментарий лучше служит расширением моего ответа; переместил его туда. – Confusion

3

Да, это ограничение грамматики Ruby. В частности, вы не можете, как указывает пила, использовать его с неявным приемником (и не превращаться в унарный оператор): он ожидает что-то с левой стороны.

Все операторы - это просто методы, называемые объектом, указанным перед ним, но некоторые операторы более равны, чем другие. Большинство методов принимают неявный приемник, но тот, который называется *, не поддерживает.

Я выбрал o в подобной ситуации.

- Добавлено позже (как я первоначально прокомментировал пост 7stud в):

Проблема заключается в том, что рубин анализатор (Yacc грамматика + куча методов) просто не позволяет линию, начиная с * быть анализируется так, что * обозначает вызов метода. Если строка начинается с символа *, единственный возможный синтаксический разбор - это тот, где * - оператор «splat». Это ограничение уникально для символа *, используемого в качестве имени метода.

+0

Код в блоке не проходит мимо анализатора. Нет приемника, потому что вызов метода отсутствует. – Confusion

+0

@sawa Вы уверены? '*' является особенным из-за splat. Вы могли бы определить '+ @' или '- @', и он будет работать нормально, но звездочка отличается и AFAIK вы не можете определить ее как унарный оператор. – spike

+0

@spike Кроме того, что '*' будет искать 'to_ary', то' to_a' (IIRC), так что, вероятно, есть некоторые играемые игры по этим линиям. –

0

Похоже, что метод * должен быть «анти-частным». С предопределенным способом * на цифрах вы можете это наблюдать.

3.instance_eval{*}  # => syntax error, unexpected '}', expecting '=' 
3.instance_eval{*()} # => syntax error, unexpected '}', expecting :: or '[' or '.' 
3.instance_eval{self.*} # => wrong number of arguments (0 for 1) 

Похоже * является зашитым требовать явного приемника.

+0

'ruby -e 'класс A; def * (x); 2 + x; конец; конец; помещает A.new * 5'' выходы '7'. Совершенно возможно переопределить метод * как общедоступный метод в любом классе. – Confusion

+0

@Confusion Я не вижу, как ваш пример связан с тем, является ли '*' общедоступным методом. – sawa

+0

В моем примере '*' является общедоступным методом? – Confusion

0

Кажется, что вы полагаетесь на синтаксический сахар, который дается методу * для многих вещей.

Если вы пишете свой метод * и используете его с неявным ., чтобы указать, что это сообщение получателю, вам может быть повезло больше.

Например, когда я делаю это с Fixnum

class Fixnum 
    def * 
    "This is the modified star" 
    end 
end 

Когда я пытаюсь это в IRB

>> 1 * 1 
ArgumentError: wrong number of arguments (1 for 0) 

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

Однако, если я это сделаю:

>> 1.* 
=> ""This is the modified star" 

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

Рассмотрите частный метод, который вы можете сделать, но у вас возникнут трудности с использованием семейства вызовов eval. Вы могли бы вместо того, чтобы сделать это:

some_instance.send(:*) 

Если, конечно, есть аргумент нужен, мы можем сделать

+0

Я отправил ответ на ваш пост. – 7stud

+0

Спасибо @ 7stud ответил в комментарии к вашему ответу. – vgoff

2

Если вместо того, чтобы использовать *, вы готовы использовать - (или +), вам может получить очень похожий результат, перегружая унарные минус (или плюс) операторы на String. Следующий код автоматически определяет и undefines унарный - на строку, поэтому он работает во время блока, но не изменяет никакого определения, когда блок закончен. Обратите внимание: если метод вызывается внутри блока, то - по-прежнему будет использовать настраиваемое поведение. Это, однако, не очень проблема, поскольку нет определения по умолчанию для унарных - для строк. Он также может быть полезен для извлечения общего кода.

class Slide 
    attr_accessor :title, :bullets 
end 

class SlidesDSL 
    attr_accessor :slides 
    def slide(&block) 
    @slides ||= [] 
    s = SlideDSL.new 
    old_unary_minus = String.instance_method(:[email protected]) rescue nil 
    begin 
     String.send(:define_method, :[email protected]) do 
     s.b(self) 
     end 
     s.instance_eval(&block) 
    ensure 
     String.send(:remove_method, :[email protected]) 
     if old_unary_minus 
     String.send(:define_method, :[email protected]) do 
      old_unary_minus.bind(self).call 
     end 
     end 
    end 
    @slides << s.slide 
    end 
    class SlideDSL 
    def slide 
     @slide ||= Slide.new 
    end 

    def title(text) 
     slide.title = text 
    end 

    def *(text) 
     bullet(text) 
    end 

    def b(text) 
     slide.bullets ||= [] 
     slide.bullets << text 
    end 
    end 
end 

Пример использования:

SlidesDSL.new.slide do 
    title "Some title" 
    - "one value" 
    - "another value" 
    4.times do |i| 
    - "repeated value #{i}" 
    end 
end 

Возвращает:

[#<Slide:0x007f99a194d0f8 @bullets=["one value", "another value", "repeated value 0", "repeated value 1", "repeated value 2", "repeated value 3"], @title="Some title">] 
+0

Интересный подход. Вопрос: зачем определять метод следующим образом: ': - @', а не как ': -'? – Andrew

+1

@Andrew: Потому что ': -' является двоичным оператором минус. Т.е. выражение '' this "-" that "' равнозначно '" этому ".send (: -," that ")', в то время как '-" string "' равнозначно '" string ".send (: - @) ' –

Смежные вопросы