代码之家  ›  专栏  ›  技术社区  ›  brad

Ruby运算符是智能的吗?

  •  17
  • brad  · 技术社区  · 15 年前

    我有一个关于ruby中的语句的问题,当我使用它来写入memcache时,我特别感兴趣。我想知道的是,=在调用setter之前先检查接收器是否已设置,或者它是 x = x || y

    对于一个正态变量来说,这并不重要,但是使用如下的方法:

    CACHE[:some_key] ||= "Some String"
    

    可能会执行比简单变量集更昂贵的memcache写入。奇怪的是,我在RubyAPI中找不到任何关于的内容,所以我自己也无法回答这个问题。

    我当然知道:

    CACHE[:some_key] = "Some String" if CACHE[:some_key].nil?
    

    要做到这一点,我只是在寻找最简洁的语法。

    5 回复  |  直到 15 年前
        1
  •  15
  •   molf    15 年前

    这非常容易测试:

    class MyCache
      def initialize
        @hash = {}
      end
    
      def []=(key, value)
        puts "Cache key '#{key}' written"
        @hash[key] = value
      end
    
      def [](key)
        puts "Cache key '#{key}' read"
        @hash[key]
      end
    end
    

    现在只需尝试 ||= 语法:

    cache = MyCache.new
    cache["my key"] ||= "my value"  # cache value was nil (unset)
    # Cache key 'my key' read
    # Cache key 'my key' written
    
    cache["my key"] ||= "my value"  # cache value is already set
    # Cache key 'my key' read
    

    因此,我们可以得出这样的结论:如果缓存键已经存在,则不会发生分配。

    以下是红宝石的提取物 shows 这就是 按设计 并且不应依赖于Ruby实现:

    describe "Conditional operator assignment 'obj.meth op= expr'" do
      # ...
      it "may not assign at all, depending on the truthiness of lhs" do
        m = mock("object")
        m.should_receive(:foo).and_return(:truthy)
        m.should_not_receive(:foo=)
        m.foo ||= 42
    
        m.should_receive(:bar).and_return(false)
        m.should_not_receive(:bar=)
        m.bar &&= 42
      end
      # ...
    end
    

    在同一个文件中,有一个类似的规范 [] []= 这要求相同的行为。

    尽管Rubyspec仍在进行中,但很明显,主要的Ruby实现项目打算遵守它。

        2
  •  7
  •   Jörg W Mittag    15 年前

    根据 Draft ISO Specification ,

    CACHE[:some_key] ||= "Some String"
    

    扩展到

    o = CACHE
    *l = :some_key
    v = o.[](*l)
    w = "Some String"
    x = v || w
    l << x
    o.[]=(*l)
    x
    

    或者,在一般情况下

    primary_expression[indexing_argument_list] ω= expression
    

    (我正在使用) ω 这里表示 任何 接线员,所以可能是 ||= , += , *= , >>= , %= ,…)

    扩展到:

    o = primary_expression
    *l = indexing_argument_list
    v = o.[](*l)
    w = expression
    x = v ω w
    l << x
    o.[]=(*l)
    x
    

    所以,根据规范, []= 总是 打电话来。但在当前的实现中事实并非如此(我测试了mri、yarv、rubinius、jruby和ironruby):

    def (h = {}).[]=(k, v) p "Setting #{k} to #{v}"; super end
    h[:key] ||= :value # => :value
    # "Setting key to value"
    h[:key] ||= :value # => :value
    

    所以,很明显,要么规范是错误的,要么当前发布的五个实现都是错误的。由于规范的目的是描述现有实现的行为,很明显规范一定是错误的。

    一般来说,作为第一近似值

    a ||= b
    

    扩展到

    a || a = b
    

    但是,有各种各样的转租,例如,无论是否 a 未定义,是否 是简单变量还是更复杂的表达式 foo[bar] foo.bar 等等。

    另请参阅此问题的其他一些实例,这些实例在StackOverflow(例如, this one )而且,这个问题在Ruby Talk邮件列表中讨论了很多次,现在有 discussion threads 其唯一目的是总结其他讨论主题。(尽管请注意,该列表还远远不够完整。)

        3
  •  1
  •   Daniel Vandersluis    15 年前

    下面是另一个与其他答案稍有不同的演示,它明确地显示了散列何时被写入:

    class MyHash < Hash
      def []=(key, value)
        puts "Setting #{key} = #{value}"
        super(key, value)
      end
    end
    
    >> h = MyHash.new
    => {}
    >> h[:foo] = :bar
    Setting foo = bar
    => :bar
    >> h[:bar] ||= :baz
    Setting bar = baz
    => :baz
    >> h[:bar] ||= :quux
    => :baz
    

    通过比较:

    // continued from above
    >> h[:bar] = h[:bar] || :quuux
    Setting bar = baz
    => :baz
    
        4
  •  0
  •   sepp2k    15 年前
    CACHE[:some_key] ||= "Some String"
    

    等于

    CACHE[:some_key] = "Some String" unless CACHE[:some_key]
    

    (相当于 if + nil? 除非 CACHE[:some_key] 是一个布尔值)。

    换句话说:是的, ||= 只有当lhs为零或错误时才会写入。

        5
  •  0
  •   marcgg    15 年前

    [我删除了比其他人更不准确的例子。我把答案留给了一些人可能感兴趣的基准点。我的观点是:

    所以基本上

    CACHE[:some_key] ||= "Some String"
    

    是一样的

    CACHE[:some_key] = "Some String" unless CACHE[:some_key]
    

    我更赞成第一种语法,但接下来由您决定,因为在这种情况下,可读性有点降低。


    我很好奇,所以这里有一些基准:

    require "benchmark"
    CACHE = {}
    Benchmark.bm do |x|
      x.report { 
        for i in 0..100000
          CACHE[:some_key] ||= "Some String" 
        end
      }
      x.report { 
        for i in 0..100000
          CACHE[:some_key] = "Some String" unless CACHE[:some_key] 
        end
      }
    end
    
    
          user     system      total        real
      0.030000   0.000000   0.030000 (  0.025167)
      0.020000   0.000000   0.020000 (  0.026670)