Ruby 的隐藏特性

Posted

技术标签:

【中文标题】Ruby 的隐藏特性【英文标题】:Hidden features of Ruby 【发布时间】:2008-09-15 15:34:28 【问题描述】:

继续“...的隐藏特性”模因,让我们分享 Ruby 编程语言鲜为人知但有用的特性。

尽量用核心 Ruby 来限制这个讨论,不要使用任何 Ruby on Rails 的东西。

另见:

Hidden features of C# Hidden features of Java Hidden features of javascript Hidden features of Ruby on Rails Hidden features of Python

(请每个答案一个隐藏功能。)

谢谢

【问题讨论】:

应该是社区维基 【参考方案1】:

从 Ruby 1.9 开始,Proc#=== 是 Proc#call 的别名,这意味着 Proc 对象可以在 case 语句中使用,如下所示:

def multiple_of(factor)
  Proc.new|product| product.modulo(factor).zero?
end

case number
  when multiple_of(3)
    puts "Multiple of 3"
  when multiple_of(7)
    puts "Multiple of 7"
end

【讨论】:

我实际上曾经写过一个 gem 来做到这一点,但我的代码 (a) 一团糟,而且 (b) 很慢。我很高兴该功能已成为核心。【参考方案2】:

Peter Cooper 有一个good list 的 Ruby 技巧。也许我最喜欢他的是允许列举单个项目和集合。 (也就是说,将非集合对象视为仅包含该对象的集合。)它看起来像这样:

[*items].each do |item|
  # ...
end

【讨论】:

一个更明确(因此更好)的形式是 Array(items).each 如果items 是一个字符串,则不必用 [*…] 括起来。 String.each 不会像某些人预期的那样遍历字符。它只是将自身返回到块中。 这有什么用?只是好奇。 @Ed:如果您正在编写一个方法并希望允许该方法的用户传递可变参数列表或数组,那就太好了。【参考方案3】:

不知道这有多隐蔽,但我发现它在需要从一维数组中生成哈希时很有用:

fruit = ["apple","red","banana","yellow"]
=> ["apple", "red", "banana", "yellow"]

Hash[*fruit]    
=> "apple"=>"red", "banana"=>"yellow"

【讨论】:

请注意Hash[ [["apple","red"], ["banana","yellow"] ] 产生相同的结果。【参考方案4】:

我喜欢的一个技巧是在数组以外的对象上使用 splat (*) 扩展器。下面是一个正则表达式匹配的例子:

match, text, number = *"Something 981".match(/([A-z]*) ([0-9]*)/)

其他示例包括:

a, b, c = *('A'..'Z')

Job = Struct.new(:name, :occupation)
tom = Job.new("Tom", "Developer")
name, occupation = *tom

【讨论】:

顺便说一句,对于好奇的人,这是通过在 splat 的目标上隐式调用 to_a 来实现的。 如果对比赛不感兴趣,可以text, number = *"text 555".match(/regexp/)[1..-1] text, number = "Something 981".scan(/([A-z]*) ([0-9]*)/).flatten.map|m| Integer(m) rescue m 这两个技巧都不错,但总有一个地方太神奇了,对吧?! @Andrew,你考虑过,匹配可以返回 nil 吗? nil 没有方法 []【参考方案5】:

哇,没有人提到触发器操作符:

1.upto(100) do |i|
  puts i if (i == 3)..(i == 15)
end

【讨论】:

对...有人必须向我解释这一点。它有效,但我不知道为什么。 触发器操作符是一个有状态的 if。它的状态在i == 3 时立即切换为真,并在之后 i != 3i == 15 切换为假。类似于人字拖:en.wikipedia.org/wiki/Flip-flop_%28electronics%29 我不会完全称其为隐藏功能,这太烦人了。我记得几年前我第一次在 Freenode 上的#Ruby 中被介绍到它;我基本上在某个时候使用过 Ruby 的每一个特性除了这个。 我不会称之为烦恼,它只是你没有使用过的东西。我使用它,它可以很好地减少代码,尤其是当我根据某些标准从文件中抓取行块时。【参考方案6】:

关于 ruby​​ 的一个很酷的事情是,您可以在其他语言不喜欢的地方调用方法和运行代码,例如在方法或类定义中。

例如,要创建一个在运行时之前具有未知超类的类,即是随机的,您可以执行以下操作:

class RandomSubclass < [Array, Hash, String, Fixnum, Float, TrueClass].sample

end

RandomSubclass.superclass # could output one of 6 different classes.

这使用了 1.9 Array#sample 方法(仅在 1.8.7 中,请参阅 Array#choice),该示例非常做作,但您可以在此处看到强大的功能。

另一个很酷的例子是能够放置不固定的默认参数值(就像其他语言经常需要的那样):

def do_something_at(something, at = Time.now)
   # ...
end

当然,第一个示例的问题在于它是在定义时评估的,而不是调用时。因此,一旦选择了一个超类,它就会在程序的其余部分中保持该超类。

但是,在第二个示例中,每次调用do_something_atat 变量将是调用该方法的时间(嗯,非常非常接近)

【讨论】:

注意:Array#rand 由 ActiveSupport 提供,您可以像 require 'activesupport' 一样轻松地在 Rails 之外使用它 Array#choice 是 1.8.7 !不要使用它,它在 1.9 中消失了,并且将在 1.8.8 中消失。使用#sample python:类 DictList([dict,list][random.randint(0,1)]):通过 def do_something_at(something, at = lambdaTime.now) at.call #now 动态分配时间 end 您还可以在默认参数列表中调用方法的对象上调用方法。【参考方案7】:

另一个小功能 - 将 Fixnum 转换为最多 36 个基数:

>> 1234567890.to_s(2)
=> "1001001100101100000001011010010"

>> 1234567890.to_s(8)
=> "11145401322"

>> 1234567890.to_s(16)
=> "499602d2"

>> 1234567890.to_s(24)
=> "6b1230i"

>> 1234567890.to_s(36)
=> "kf12oi"

正如 Huw Walters 所评论的,转换另一种方式同样简单:

>> "kf12oi".to_i(36)
=> 1234567890

【讨论】:

为了完整起见,String#to_s(base) 可用于转换回整数; "1001001100101100000001011010010".to_i(2)"499602d2".to_i(16)等都返回原来的Fixnum【参考方案8】:

具有默认值的哈希!本例中为数组。

parties = Hash.new |hash, key| hash[key] = [] 
parties["Summer party"]
# => []

parties["Summer party"] << "Joe"
parties["Other party"] << "Jane"

在元编程中非常有用。

【讨论】:

是的。 Ruby 哈希可以接受 ' 【参考方案9】:

下载 Ruby 1.9 源码,并发出make golf,然后你可以这样做:

make golf

./goruby -e 'h'
# => Hello, world!

./goruby -e 'p St'
# => StandardError

./goruby -e 'p 1.tf'
# => 1.0

./goruby19 -e 'p Fil.exp(".")'
"/home/manveru/pkgbuilds/ruby-svn/src/trunk"

阅读golf_prelude.c 了解更多隐藏的东西。

【讨论】:

【参考方案10】:

1.9 Proc 功能中另一个有趣的附加功能是 Proc#curry,它允许您将接受 n 个参数的 Proc 变成一个接受 n-1 的 Proc。这里结合了我上面提到的 Proc#=== 技巧:

it_is_day_of_week = lambda |day_of_week, date| date.wday == day_of_week 
it_is_saturday = it_is_day_of_week.curry[6]
it_is_sunday = it_is_day_of_week.curry[0]

case Time.now
when it_is_saturday
  puts "Saturday!"
when it_is_sunday
  puts "Sunday!"
else
  puts "Not the weekend"
end

【讨论】:

【参考方案11】:

非布尔值的布尔运算符。

&amp;&amp;||

两者都返回最后一个表达式的值。

这就是为什么如果变量未定义,||= 将使用右侧的值返回表达式更新变量。这没有明确记录,而是常识

不过,&amp;&amp;= 并没有那么广为人知。

string &&= string + "suffix"

等价于

if string
  string = string + "suffix"
end

如果变量未定义,则对于不应进行的破坏性操作非常方便。

【讨论】:

更准确地说,string &amp;&amp;= string + "suffix" 等价于 string = string &amp;&amp; string + "suffix"&amp;&amp;|| 返回它们的第二个参数在 PickAx, p. 中讨论。 154(第一部分 - Ruby 的各个方面、表达式、条件执行)。【参考方案12】:

Rails 提供的 Symbol#to_proc 函数真的很酷。

代替

Employee.collect  |emp| emp.name 

你可以写:

Employee.collect(&:name)

【讨论】:

这显然比使用块“慢一个数量级”。 igvita.com/2008/07/08/6-optimization-tips-for-ruby-mri 我刚试了一下,发现两者并没有明显的区别。我不确定这个“数量级”的东西来自哪里。 (使用 Ruby 1.8.7) 在 Rails 之外执行此操作也很方便,可以使用 require 'activesupport' 完成,因为实际上大多数帮助程序都来自那里。 这曾经很慢,因为 active_support 的实现,即它接受多个参数,所以你可以做一些很酷的事情,比如 (1..10).inject &:*,但主要用例通常只是在集合的每个成员上调用一个方法,例如%w(快速的棕色狐狸).map &:upcase。从 1.8.7 开始,它的核心 ruby​​ 和性能是合理的。 @thenduks:在 ruby​​ 1.8.7 和 1.9 中,它可以在没有 activesupport 帮助的情况下完成。【参考方案13】:

最后一个 - 在 ruby​​ 中,您可以使用任何要分隔字符串的字符。取以下代码:

message = "My message"
contrived_example = "<div id=\"contrived\">#message</div>"

如果您不想转义字符串中的双引号,您可以简单地使用不同的分隔符:

contrived_example = %<div id="contrived-example">#message</div>
contrived_example = %[<div id="contrived-example">#message</div>]

除了避免转义分隔符之外,您还可以将这些分隔符用于更好的多行字符串:

sql = %
    SELECT strings 
    FROM complicated_table
    WHERE complicated_condition = '1'

【讨论】:

不是 any 字符,但它仍然很酷。它也适用于其他文字:%() / % / %[] / % / %|| %r() / %r / %r[] / %r / %r|| %w() / %w / %w[] / %w / %w|| 还有 herenow doc 语法: 【参考方案14】:

使用 Range 对象作为无限惰性列表:

Inf = 1.0 / 0

(1..Inf).take(5) #=> [1, 2, 3, 4, 5]

更多信息在这里:http://banisterfiend.wordpress.com/2009/10/02/wtf-infinite-ranges-in-ruby/

【讨论】:

链接文章中的lazy_select非常整洁。 这真是太棒了。我喜欢 Infinity 是一个浮点数的方式,当我尝试这个时: (-Inf..Inf).take(4) 它引发了一个(逻辑上一致的)无法从浮点数迭代的错误。 :D【参考方案15】:

我发现使用 define_method 命令动态生成方法非常有趣,但并不为人所知。例如:

((0..9).each do |n|
    define_method "press_#n" do
      @number = @number.to_i * 10 + n
    end
  end

以上代码使用“define_method”命令动态创建方法“press1”到“press9”。无需输入所有 10 个方法,它们本质上都包含相同的代码,而是使用 define method 命令根据需要动态生成这些方法。

【讨论】:

define_method 的唯一问题是它不允许在 ruby​​ 1.8 中将块作为参数传递。请参阅this blog post 了解解决方法。【参考方案16】:

module_function

声明为 module_function 的模块方法将在包含该模块的类中创建自己的副本作为 私有 实例方法:

module M
  def not!
    'not!'
  end
  module_function :not!
end

class C
  include M

  def fun
    not!
  end
end

M.not!     # => 'not!
C.new.fun  # => 'not!'
C.new.not! # => NoMethodError: private method `not!' called for #<C:0x1261a00>

如果你使用 module_function 没有任何参数,那么在 module_function 语句之后的任何模块方法都将自动成为 module_functions。

module M
  module_function

  def not!
    'not!'
  end

  def yea!
    'yea!'
  end
end


class C
  include M

  def fun
    not! + ' ' + yea!
  end
end
M.not!     # => 'not!'
M.yea!     # => 'yea!'
C.new.fun  # => 'not! yea!'

【讨论】:

如果你只想在模块中声明私有方法,使用private关键字即可。除了在包含模块的类中将方法设为私有之外,module_function 还将该方法复制到模块实例中。在大多数情况下,这不是您想要的。 我知道你可以只使用私人。但这是一个关于 Ruby 隐藏特性的问题。而且,我认为大多数人从未听说过 module_function(包括我自己),直到他们在文档中看到它并开始使用它。 使用module_function(第二种方式)的替代方法是使用extend self(看起来不错:D)【参考方案17】:

短注入,像这样:

Sum of range:

(1..10).inject(:+)
=> 55

【讨论】:

值得注意的是,您需要 Ruby 1.9 或带有 Ruby 1.8 的 Rails 才能工作。 @Max Howell: 或require 'backports' :-) 这不是hoyhoy的答案的重复吗?【参考方案18】:

警告:此项目被评为 #1 2008 年最可怕的黑客攻击,因此请小心使用。实际上,像瘟疫一样避免它,但它肯定是隐藏的红宝石。

Superators 为 Ruby 添加新的运算符

曾经想要一个超级秘密的握手操作符在你的代码中进行一些独特的操作吗?喜欢打代码高尔夫?尝试像这样的运营商 -~+~- 要么

除了欣赏Superators Project,我与它无关。

【讨论】:

【参考方案19】:

我迟到了,但是:

您可以轻松地获取两个等长数组并将它们转换为散列,其中一个数组提供键,另一个提供值:

a = [:x, :y, :z]
b = [123, 456, 789]

Hash[a.zip(b)]
# =>  :x => 123, :y => 456, :z => 789 

(这是因为 Array#zip “压缩”了两个数组中的值:

a.zip(b)  # => [[:x, 123], [:y, 456], [:z, 789]]

而 Hash[] 可以采用这样的数组。我也看到有人这样做:

Hash[*a.zip(b).flatten]  # unnecessary!

这会产生相同的结果,但 splat 和 flatten 完全没有必要——也许它们不是过去的?)

【讨论】:

这确实在很长一段时间内都没有记录(参见redmine.ruby-lang.org/issues/show/1385)。请注意,这种新形式是 Ruby 1.8.7 的新形式【参考方案20】:

在 Ruby 中自动激活哈希

def cnh # silly name "create nested hash"
  Hash.new |h,k| h[k] = Hash.new(&h.default_proc)
end
my_hash = cnh
my_hash[1][2][3] = 4
my_hash # =>  1 =>  2 =>  3 =>4   

这真是太方便了。

【讨论】:

我会将它包装在一个模块中,以获得与本机哈希初始化相同的感觉:module InfHash; def self.new; Hash.new |h,k| h[k] = Hash.new(&amp;h.default_proc); end; end【参考方案21】:

解构数组

(a, b), c, d = [ [:a, :b ], :c, [:d1, :d2] ]

地点:

a #=> :a
b #=> :b
c #=> :c
d #=> [:d1, :d2]

使用这种技术,我们可以使用简单的赋值从任何深度的嵌套数组中获取我们想要的确切值。

【讨论】:

【参考方案22】:

Class.new()

在运行时创建一个新类。参数可以是派生自的类,块是类体。您可能还想查看const_set/const_get/const_defined? 以正确注册您的新课程,以便inspect 打印出名称而不是数字。

不是您每天都需要的东西,但当您需要时非常方便。

【讨论】:

MyClass = Class.new Array do; def hi; 'hi'; end; end 似乎等同于class MyClass &lt; Array; def hi; 'hi'; end; end 可能比我想象的更真实。甚至看起来您可以从变量继承,而不仅仅是一个常量。但是,如果您需要在运行时构造类名,糖化版本(第二个)似乎不起作用。 (当然是霸菱评估。) 这本书在Metaprogramming Ruby中有很好的描述。【参考方案23】:

创建一个连续数字的数组:

x = [*0..5]

将 x 设置为 [0, 1, 2, 3, 4, 5]

【讨论】:

是的,但它没有那么短而甜美;) 简洁是客观的,可读性是品味和经验的问题 splat (*) 运算符基本上调用to_a【参考方案24】:

您在 Rubyland 中看到的许多魔力都与元编程有关,元编程就是编写为您编写代码的代码。 Ruby 的attr_accessorattr_readerattr_writer 都是简单的元编程,因为它们按照标准模式在一行中创建了两个方法。 Rails 使用它们的关系管理方法(如 has_onebelongs_to)进行了大量元编程。

但是使用class_eval 创建自己的元编程技巧来执行动态编写的代码非常简单。

以下示例允许包装对象将某些方法转发给内部对象:

class Wrapper
  attr_accessor :internal

  def self.forwards(*methods)
    methods.each do |method|
      define_method method do |*arguments, &block|
        internal.send method, *arguments, &block
      end
    end
  end

  forwards :to_i, :length, :split
end

w = Wrapper.new
w.internal = "12 13 14"
w.to_i        # => 12
w.length      # => 8
w.split('1')  # => ["", "2 ", "3 ", "4"]

方法Wrapper.forwards 采用方法名称的符号并将它们存储在methods 数组中。然后,对于给定的每一个,我们使用define_method 创建一个新方法,其工作是发送消息,包括所有参数和块。

元编程问题的一个很好的资源是Why the Lucky Stiff's "Seeing Metaprogramming Clearly"。

【讨论】:

我希望首先深入研究 ruby​​ 中的元编程。您能否提供一些参考以开始使用它(除了给定的链接)?书籍也可以。谢谢。 PragProg 的视频广播系列“Ruby 对象模型和元编程”很好地介绍了使用 ruby​​ 进行元编程:pragprog.com/screencasts/v-dtrubyom/… @Chirantan,看看Metaprogramming Ruby。【参考方案25】:

使用任何响应 ===(obj) 的内容进行案例比较:

case foo
when /baz/
  do_something_with_the_string_matching_baz
when 12..15
  do_something_with_the_integer_between_12_and_15
when lambda  |x| x % 5 == 0 
  # only works in Ruby 1.9 or if you alias Proc#call as Proc#===
  do_something_with_the_integer_that_is_a_multiple_of_5
when Bar
  do_something_with_the_instance_of_Bar
when some_object
  do_something_with_the_thing_that_matches_some_object
end

Module(因此Class)、RegexpDate 和许多其他类定义了一个实例方法:===(其他),并且都可以使用。

感谢Farrel 提醒Proc#call 在Ruby 1.9 中被别名为Proc#===

【讨论】:

【参考方案26】:

“ruby”二进制文件(至少是 MRI)支持许多使 perl 单行代码非常流行的开关。

重要的:

-n 只用“gets”设置一个外循环——它神奇地适用于给定的文件名或 STDIN,在 $_ 中设置每个读取行 -p 与 -n 类似,但在每次循环迭代结束时自动添加 puts -在每个输入行上自动调用 .split,存储在 $F 中 -i 就地编辑输入文件 -l 输入时自动调用 .chomp -e 执行一段代码 -c 查看源代码 -w 带有警告

一些例子:

# Print each line with its number:
ruby -ne 'print($., ": ", $_)' < /etc/irbrc

# Print each line reversed:
ruby -lne 'puts $_.reverse' < /etc/irbrc

# Print the second column from an input CSV (dumb - no balanced quote support etc):
ruby -F, -ane 'puts $F[1]' < /etc/irbrc

# Print lines that contain "eat"
ruby -ne 'puts $_ if /eat/i' < /etc/irbrc

# Same as above:
ruby -pe 'next unless /eat/i' < /etc/irbrc

# Pass-through (like cat, but with possible line-end munging):
ruby -p -e '' < /etc/irbrc

# Uppercase all input:
ruby -p -e '$_.upcase!' < /etc/irbrc

# Same as above, but actually write to the input file, and make a backup first with extension .bak - Notice that inplace edit REQUIRES input files, not an input STDIN:
ruby -i.bak -p -e '$_.upcase!' /etc/irbrc

请随意在 Google 上搜索“ruby one-liners”和“perl one-liners”以获取更多有用和实用的示例。它本质上允许您使用 ruby​​ 作为 awk 和 sed 的相当强大的替代品。

【讨论】:

【参考方案27】:

send() 方法是一种通用方法,可用于 Ruby 中的任何类或对象。如果没有被覆盖,send() 接受一个字符串并调用传递它的字符串的方法的名称。例如,如果用户单击“Clr”按钮,则“press_clear”字符串将被发送到 send() 方法并调用“press_clear”方法。 send() 方法允许以有趣且动态的方式调用 Ruby 中的函数。

 %w(7 8 9 / 4 5 6 * 1 2 3 - 0 Clr = +).each do |btn|
    button btn, :width => 46, :height => 46 do
      method = case btn
        when /[0-9]/: 'press_'+btn
        when 'Clr': 'press_clear'
        when '=': 'press_equals'
        when '+': 'press_add'
        when '-': 'press_sub'
        when '*': 'press_times'
        when '/': 'press_div'
      end

      number.send(method)
      number_field.replace strong(number)
    end
  end

我在Blogging Shoes: The Simple-Calc Application 中更多地谈论这个功能

【讨论】:

听起来是打开安全漏洞的好方法。 我会尽可能使用符号。【参考方案28】:

愚弄一些类或模块,告诉它需要一些它确实不需要的东西:

$" << "something"

这很有用,例如当需要 A 而需要 B 但我们在代码中不需要 B(A 也不会通过我们的代码使用它):

比如Backgroundrb的bdrb_test_helper requires'test/spec',但是你根本不用,所以在你的代码中:

$" << "test/spec"
require File.join(File.dirname(__FILE__) + "/../bdrb_test_helper")

【讨论】:

这是否解决了 gem A 需要 foo-1.0.0 而 gem B 需要 foo-1.0.1 的问题? 否,因为“某事”的代码将不可用:这仅模拟“某事”是必需的,但实际上并不需要它。 $" 是一个包含由 require 加载的模块名称的数组(它被 require 用来防止两次加载模块)。所以,如果你用它来欺骗 gems,当 gems 尝试使用实际的“东西”时会产生崩溃代码,因为它不存在。你可能想强制加载一个 gem 的具体版本(例如 foo-1.0.0),而不是最新版本:docs.rubygems.org/read/chapter/4#page71【参考方案29】:

定义一个接受任意数量参数并丢弃所有参数的方法

def hello(*)
    super
    puts "hello!"
end

上面的hello 方法只需要在屏幕上puts "hello" 并调用super - 但由于超类hello 定义了它必须的参数 - 但是因为它实际上并不需要使用参数本身 - 它不必给它们一个名字。

【讨论】:

【参考方案30】:
private unless Rails.env == 'test'
# e.g. a bundle of methods you want to test directly

看起来像 Ruby 的一个很酷且(在某些情况下)不错/有用的 hack/功能。

【讨论】:

以上是关于Ruby 的隐藏特性的主要内容,如果未能解决你的问题,请参考以下文章

ruby入门知识:了解ruby历史及特性

Erlang 的隐藏特性

Ruby 2.6.0 首个预览版本发布,引入重要新特性 JIT

Ruby 2.5 的十个新特性

Ex1. CocoaPods 中的 Ruby 特性之 Mix-in

Ruby元编程