【问题标题】:Common Ruby Idioms常见的 Ruby 习语
【发布时间】:2010-10-11 11:15:31
【问题描述】:

我喜欢 ruby​​ 的一点是,它主要是一种可读性很强的语言(非常适合自记录代码)

但是,受这个问题的启发:Ruby Code explained 以及||= 如何在 ruby​​ 中工作的描述,我在考虑我不使用的 ruby​​ 习语,坦率地说,我并没有完全理解它们。

所以我的问题是,类似于引用问题中的示例,我需要了解哪些常见但不明显的 ruby​​ 习语才能成为真正精通 ruby​​ 的程序员?

顺便说一下,从引用的问题

a ||= b 

等价于

if a == nil || a == false
  a = b
end

(感谢 Ian Terrell 的更正)

编辑:事实证明,这一点并非完全没有争议。正确的展开其实是

(a || (a = (b))) 

查看这些链接了解原因:

感谢 Jörg W Mittag 指出这一点。

【问题讨论】:

  • 通常更简洁地表述为:foo ||= bar 等价于foo || foo = bar
  • 我认为说它相当于 foo = foo || 更清楚。 bar,就像 foo *= bar 等价于 foo = foo * bar。
  • Chuck - 但事实并非如此。区别在于obj.foo ||= bar 这样的情况,如果是obj.foo = obj.foo || bar,那么即使 obj.foo 是错误的(并且 obj.foo= 可能有副作用),也会调用 obj.foo=。但这不是它的工作原理
  • 可读性强?相比什么?我发现 Ruby 非常适合愚蠢的代码技巧(编写循环的 35 种方法!块和 lambdas 和其他东西),但是成语使它变得非常不可读。但是,既然做事的方法有很多,总要找到一种(或几种)方法来做某事……
  • "grok" 也是 Python Web 框架的名称。为了消除混淆,我从您的问题中删除了标签

标签: ruby idioms


【解决方案1】:

神奇的 if 子句可以让同一个文件充当库或脚本:

if __FILE__ == $0
  # this library may be run as a standalone script
end

打包和解包数组:

# put the first two words in a and b and the rest in arr
a,b,*arr = *%w{a dog was following me, but then he decided to chase bob}
# this holds for method definitions to
def catall(first, *rest)
  rest.map { |word| first + word }
end
catall( 'franken', 'stein', 'berry', 'sense' ) #=> [ 'frankenstein', 'frankenberry', 'frankensense' ]

哈希作为方法参数的语法糖

this(:is => :the, :same => :as)
this({:is => :the, :same => :as})

哈希初始化器:

# this
animals = Hash.new { [] }
animals[:dogs] << :Scooby
animals[:dogs] << :Scrappy
animals[:dogs] << :DynoMutt
animals[:squirrels] << :Rocket
animals[:squirrels] << :Secret
animals #=> {}
# is not the same as this
animals = Hash.new { |_animals, type| _animals[type] = [] }
animals[:dogs] << :Scooby
animals[:dogs] << :Scrappy
animals[:dogs] << :DynoMutt
animals[:squirrels] << :Rocket
animals[:squirrels] << :Secret
animals #=> {:squirrels=>[:Rocket, :Secret], :dogs=>[:Scooby, :Scrappy, :DynoMutt]}

元类语法

x = Array.new
y = Array.new
class << x
  # this acts like a class definition, but only applies to x
  def custom_method
     :pow
  end
end
x.custom_method #=> :pow
y.custom_method # raises NoMethodError

类实例变量

class Ticket
  @remaining = 3
  def self.new
    if @remaining > 0
      @remaining -= 1
      super
    else
      "IOU"
    end
  end
end
Ticket.new #=> Ticket
Ticket.new #=> Ticket
Ticket.new #=> Ticket
Ticket.new #=> "IOU"

块、过程和 lambda。生活和呼吸它们。

 # know how to pack them into an object
 block = lambda { |e| puts e }
 # unpack them for a method
 %w{ and then what? }.each(&block)
 # create them as needed
 %w{ I saw a ghost! }.each { |w| puts w.upcase }
 # and from the method side, how to call them
 def ok
   yield :ok
 end
 # or pack them into a block to give to someone else
 def ok_dokey_ok(&block)
    ok(&block)
    block[:dokey] # same as block.call(:dokey)
    ok(&block)
 end
 # know where the parentheses go when a method takes arguments and a block.
 %w{ a bunch of words }.inject(0) { |size,w| size + 1 } #=> 4
 pusher = lambda { |array, word| array.unshift(word) }
 %w{ eat more fish }.inject([], &pusher) #=> ['fish', 'more', 'eat' ]

【讨论】:

  • +1 - 谢谢,这篇文章很棒。你能详细解释一下 '&' 在 '&block' 中代表什么吗?
  • &表示block是proc参数。
  • 视情况而定。 & 作为方法 CALL 的最后一个参数的前缀意味着获取该变量中的 Proc 对象并将其解压缩到一个块中以传递给该方法。
  • [续] & 作为方法的最后一个参数的前缀定义意味着将传递给该方法的块打包到一个对象中,并将其分配给该变量
  • Ticket 示例中的@remaining 不是必须是@@remaining 所以它是一个类变量吗?
【解决方案2】:

slideshow 在主要的 Ruby 习语上相当完整,如:

  • 交换两个值:

    x, y = y, x

  • 如果未指定,则采用默认值的参数

    def somemethod(x, y=nil)

  • 将无关的参数批量化到一个数组中

    def substitute(re, str, *rest)

等等……

【讨论】:

    【解决方案3】:

    更多成语:

    使用%w%r%( 分隔符

    %w{ An array of strings %}
    %r{ ^http:// }
    %{ I don't care if the string has 'single' or "double" strings }
    

    case 语句中的类型比较

    def something(x)
      case x
        when Array
          # Do something with array
        when String
          # Do something with string
        else
          # You should really teach your objects how to 'quack', don't you?
      end
    end
    

    ...以及在case语句中对===方法的整体滥用

    case x
      when 'something concrete' then ...
      when SomeClass then ...
      when /matches this/ then ...
      when (10...20) then ...
      when some_condition >= some_value then ...
      else ...
    end
    

    对于 Ruby 主义者来说应该看起来很自然,但对于来自其他语言的人来说可能不是这样:使用 each 来支持 for .. in

    some_iterable_object.each{|item| ... }
    

    在 Ruby 1.9+、Rails 中,或者通过修补 Symbol#to_proc 方法,this 正在成为一个越来越流行的习惯用法:

    strings.map(&:upcase)
    

    条件方法/常量定义

    SOME_CONSTANT = "value" unless defined?(SOME_CONSTANT)
    

    查询方法和破坏性(bang)方法

    def is_awesome?
      # Return some state of the object, usually a boolean
    end
    
    def make_awesome!
      # Modify the state of the object
    end
    

    隐式 splat 参数

    [[1, 2], [3, 4], [5, 6]].each{ |first, second| puts "(#{first}, #{second})" }
    

    【讨论】:

    【解决方案4】:

    我喜欢这个:

    str = "Something evil this way comes!"
    regexp = /(\w[aeiou])/
    
    str[regexp, 1] # <- This
    

    这(大致)相当于:

    str_match = str.match(regexp)
    str_match[1] unless str_match.nil?
    

    或者至少这是我用来替换这些块的方法。

    【讨论】:

      【解决方案5】:

      我建议通读您欣赏和尊重的人的流行且设计良好的插件或宝石的代码。

      我遇到的一些例子:

      if params[:controller] == 'discussions' or params[:controller] == 'account'
        # do something here
      end
      

      对应

      if ['account', 'discussions'].include? params[:controller]
        # do something here
      end
      

      稍后将被重构为

      if ALLOWED_CONTROLLERS.include? params[:controller]
        # do something here
      end
      

      【讨论】:

      • 不是真正地道的 ruby​​ IMO。这已在 PHP 中广泛使用(CakePHP 示例): in_array(array('controller1', 'controller2'), $this->params['controller']))
      • 喜欢使用 '||'而不是 'or' 在 ruby​​ 中,因为在 ruby​​ 中这些东西会以不同的方式工作 - 如果你使用 '||'如果第一种情况为“真”,ruby 解释器不会转到第二种情况,但是你使用“或”解释器将始终检查第二种情况
      【解决方案6】:

      这里有一些,从各种来源中挑选出来的:

      使用“除非”和“直到”而不是“如果不是”和“而不是”。但是,当存在“else”条件时,尽量不要使用“unless”。

      请记住,您可以一次分配多个变量:

      a,b,c = 1,2,3
      

      甚至交换没有临时变量的变量:

      a,b = b,a
      

      在适当的地方使用尾随条件,例如

      do_something_interesting unless want_to_be_bored?
      

      注意定义类方法的一种常用但不是立即显而易见(至少对我而言)的方式:

      class Animal
        class<<self
          def class_method
            puts "call me using Animal.class_method"
          end
        end
      end
      

      一些参考资料:

      【讨论】:

        【解决方案7】:

        顺便说一下,从参考 问题

        a ||= b 
        

        等价于

        if a == nil   
          a = b 
        end
        

        这是不正确的,并且是新手 Ruby 应用程序中错误的来源。

        由于nilfalse 都(且仅)评估为布尔值false,因此a ||= b 实际上(几乎*)等效于:

        if a == nil || a == false
          a = b
        end
        

        或者,用另一个 Ruby 习语重写它:

        a = b unless a
        

        (*由于每个语句都有一个值,因此它们在技术上并不等同于 a ||= b。但如果您不依赖语句的值,您将看不到差异。)

        【讨论】:

        • 对不起,你的例子是错误的。几乎所有可以想象的邮件列表、新闻组、论坛和博客都对此进行了多次讨论,“最佳”答案(至少到目前为止,直到有人找到另一个反例)是:a ||= b 等价于 (a | | (a = (b)))
        • 无需抱歉。我真正的观点是人们忘记了,如果您使用布尔值,您的错误值可能会被覆盖。我的例子(在精神上)不是关于改写成语。他们的目的是使其尽可能具有表现力,以便人们不仅记住 nil 的情况,而且记住错误的情况。
        • 我永远无法理解的是为什么 Ruby 程序员似乎如此热衷于使用这样的成语。只需明确地编写代码,就可以省去其他人解读所有愚蠢边缘情况的麻烦。
        【解决方案8】:

        我维护了一个 wiki 页面,其中涵盖了一些 Ruby 习语和格式:

        https://github.com/tokland/tokland/wiki/RubyIdioms

        【讨论】:

        • 此页面返回404
        【解决方案9】:

        我总是忘记这个简写 if else 语句的确切语法(以及运算符的名称。cmets 任何人?)我认为它在 ruby​​ 之外被广泛使用,但如果其他人想要这里的语法,它是:

        refactor < 3 ? puts("No need to refactor YET") : puts("You need to refactor this into a  method")
        

        扩展到

        if refactor < 3
          puts("No need to refactor YET")
        else
          puts("You need to refactor this into a  method")
        end
        

        更新

        称为三元运算符:

        返回 myvar ? myvar.size : 0

        【讨论】:

          【解决方案10】:

          您可以轻松地使用 Marshaling 对象进行深度复制。 - 取自 The Ruby Programming Language

          def deepcopy(o)
            Marshal.load(Marshal.dump(o))
          end
          

          请注意,文件和 I/O 流,如 以及 Method 和 Binding 对象, 过于动态而无法编组;那里 将没有可靠的方法来恢复 他们的状态。

          【讨论】:

            【解决方案11】:
            a = (b && b.attribute) || "default"
            

            大概是:

            if ( ! b.nil? && ! b == false) && ( ! b.attribute.nil? && ! b.attribute.false) a = b
            else a = "default"
            

            当 b 是一条可能已找到或未找到的记录时,我会使用它,并且我需要获取它的一个属性。

            【讨论】:

              【解决方案12】:

              我喜欢 If-then-elses 或 case-when 可以缩短,因为它们返回一个值:

              if test>0
                result = "positive"
              elsif test==0
                result = "zero"
              else
                result = "negative"
              end
              

              可以改写

              result = if test>0
                "positive"
              elsif test==0
                "zero"
              else
                "negative"
              end
              

              同样适用于 case-when:

              result = case test
              when test>0 ; "positive"
              when test==0 ; "zero"
              else "negative"
              end
              

              【讨论】:

              • result = (test&gt;0)? "positive" : (test==0 ? "zero" : "negative")
              • 为了避免分号,你也可以写when test &gt; 0 then "positive"。 (@Salil 我发现嵌套的三元运算符真的很难阅读。)
              【解决方案13】:

              好问题!

              我认为代码越直观、越快,我们正在构建的软件就越好。我将向你展示我是如何使用 Ruby 在小代码 sn-ps 中表达我的想法的。 Read more here

              地图

              我们可以用不同的方式使用map方法:

              user_ids = users.map { |user| user.id }
              

              或者:

              user_ids = users.map(&:id)
              

              示例

              我们可以使用rand方法:

              [1, 2, 3][rand(3)]
              

              随机播放:

              [1, 2, 3].shuffle.first
              

              还有惯用、简单和最简单的方法……示例!

              [1, 2, 3].sample
              

              双管等于/记忆

              正如你在描述中所说,我们可以使用记忆:

              some_variable ||= 10
              puts some_variable # => 10
              
              some_variable ||= 99
              puts some_variable # => 10
              

              静态方法/类方法

              我喜欢使用类方法,我觉得这是一种非常惯用的创建和使用类的方式:

              GetSearchResult.call(params)
              

              简单。美丽的。直觉的。后台发生了什么?

              class GetSearchResult
                def self.call(params)
                  new(params).call
                end
              
                def initialize(params)
                  @params = params
                end
              
                def call
                  # ... your code here ...
                end
              end
              

              有关编写惯用 Ruby 代码的更多信息,请阅读here

              【讨论】:

                【解决方案14】:

                Array.pack 和 String.unpack 用于处理二进制文件:

                # extracts four binary sint32s to four Integers in an Array
                data.unpack("iiii") 
                

                【讨论】:

                  【解决方案15】:

                  方法缺少魔法

                  class Dummy  
                    def method_missing(m, *args, &block)  
                      "You just called method with name #{m} and arguments- #{args}"  
                    end  
                  end
                  
                  Dummy.new.anything(10, 20)
                  => "You just called method with name anything and arguments- [10, 20]"
                  

                  如果您调用 ruby​​ 对象中不存在的方法,ruby 解释器将调用名为“method_missing”的方法(如果已定义),您可以将其用于一些技巧,例如编写 api 包装器或 dsl,您不知道所有方法和参数名称

                  【讨论】:

                  • 请提供一些解释。
                  猜你喜欢
                  • 2011-01-20
                  • 1970-01-01
                  • 2016-10-16
                  • 2010-09-17
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-06-25
                  • 2010-10-14
                  • 2020-02-09
                  相关资源
                  最近更新 更多