为什么我不能覆盖Ruby中的*方法?

时间:2013-08-24 22:25:19

标签: ruby

我正在处理Ruby Gem for creating presentations,我想创建一种用于定义简单直观的幻灯片的语法。我正在使用instance_eval,因此我可以在自己上调用方法。这是我原本打算做的事情:

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

即使我已经定义了*方法,我也会收到错误:

  

在`instance_eval':...语法错误,意外' \ n',期待::或' ['或'。' (的SyntaxError)

我已经通过创建一个名为b的简短方法来创建项目符号,但它并没有那么好:

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

这仅仅是口译员的限制吗?或者有办法解决它吗?

更新:如果您愿意,可以深入了解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

5 个答案:

答案 0 :(得分:4)

  

似乎你依赖于给予的语法糖   许多事情的方法。

事实并非如此。你可以这样做:

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作为接收器。
  4. 现在,如果您将方法的名称从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 '.'
    

    因此,op依赖于正常的方法操作,而不是归因于*的任何语法糖。在instance_eval块内,您可能希望Ruby隐式执行:

    self.*(3)
    

    相当于:

    d.*(3)
    

答案 1 :(得分:3)

是的,这是Ruby语法的限制。具体来说,你不能像sawa指出的那样,将它与隐式接收器一起使用(也不能变成一元运算符):它需要左侧的东西。

所有运算符都是在它之前引用的对象上调用的方法,但是某些运算符比其他运算符更平等。大多数方法接受隐式接收器,但名为*的接收器不接受。

我在类似情况下选择o

- 稍后添加(正如我最初评论7stud的帖子):

问题是Ruby解析器(Yacc语法+一堆方法)根本不允许解析以*开头的行,以便*表示方法调用。如果一行以*开头,则唯一可能的解析是*是'splat'运算符。此限制对于用作方法名称的*字符是唯一的。

答案 2 :(得分:2)

如果不是使用*,而是愿意使用-(或+),则可以通过重载一元减号(或加号)运算符来获得非常相似的结果String。以下代码自动定义并取消定义字符串上的一元-,因此它在块期间有效,但在块完成时不会更改任何定义。请注意,如果在块内调用方法,-仍将使用自定义行为。这不是一个问题,因为字符串的一元-没有默认定义。它对于提取公共代码也很有用。

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(:-@) rescue nil
    begin
      String.send(:define_method, :-@) do
        s.b(self)
      end
      s.instance_eval(&block)
    ensure
      String.send(:remove_method, :-@)
      if old_unary_minus
        String.send(:define_method, :-@) 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">]

答案 3 :(得分: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)

看起来*很难连接到需要明确的接收器。

答案 4 :(得分:0)

似乎你依赖于为许多方法赋予*方法的语法糖。

如果您编写*方法并将其与隐式.一起使用,以表明它是给接收方的消息,那么您可能会有更好的运气。

例如,当我使用Fixnum

执行此操作时
class Fixnum
  def *
    "This is the modified star"
  end
end

当我在IRB中尝试这个时

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

如果我只是按下回车键,我也会遇到正常的问题,即让它识别* ...它会在下一行中获得更多输入......

但是,如果我这样做:

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

因此,这不是不可能做到的,你可能正在与围绕这个特定符号演变的语法糖作斗争。

考虑一下你可以做的私有方法,但是你在使用'eval'系列调用时会遇到困难。你可以这样做:

some_instance.send(:*)

当然,如果需要论证,我们可以some_instance.send(:*, argument)