Ruby的隐藏特性

159

应该是社区维基 - SilentGhost
46个回答

80

从 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

1
我曾经写过一个宝石来完成这个功能,但我的代码(a)很混乱,(b)运行缓慢。我非常高兴这个功能已经被整合到核心中了。 - James A. Rosen

76

Peter Cooper在他的Ruby技巧清单中有一些不错的技巧。其中我最喜欢的是允许枚举单个元素和集合。

也就是说,将一个非集合对象视为只包含该对象的集合。

它的代码如下所示:

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

38
更明确(也更友好)的形式是使用 Array(items).each。 - mislav
如果 items 是一个字符串,你不必用 [*…] 把它括起来。String.each 不会像一些人期望的那样迭代字符,它只是将自身返回给块。 - mxcl
这有什么用处吗?只是好奇。 - Ed S.
1
@Ed:如果你正在编写一个方法,并希望允许该方法的用户传递可变参数列表或数组,那就很好。 - James A. Rosen

63

我不确定这个方法有多少人知道,但在需要将一维数组转换为哈希表时,我发现它非常有用:

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

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

请注意,Hash[ [["apple","red"], ["banana","yellow"] ] 产生相同的结果。 - Marc-André Lafortune

54

我喜欢的一个技巧是在非数组对象上使用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

13
顺便提一句,对于好奇的人来说,这是通过在星号目标上隐式调用 to_a 来实现的。 - Bob Aman
1
如果你对这个匹配不感兴趣,你可以使用 text, number = *"text 555".match(/regexp/)[1..-1] - Andrew Grimm
text, number = "Something 981".scan(/([A-z]*) ([0-9]*)/).flatten.map{|m| Integer(m) rescue m} - Jonas Elfström
7
两种都是不错的戏法,但总有一个点会让人觉得用太多魔术了,对吧?! - tomafro
1
@Andrew,你是否考虑到了match可能返回nil?nil没有[]方法。 - Alexey

51

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

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

11
好的,有人得向我解释一下这个。它是有效的,但我不明白为什么。 - Bob Aman
12
Flip-flop 操作符是一种有状态的 if 语句。当 i == 3 时,其状态会转换为 true,而在 i != 3 并且 i == 15 后则会转换为 false。类似于电子器件中的 flip-flop:http://en.wikipedia.org/wiki/Flip-flop_%28electronics%29。 - Konstantin Haase
1
我不会完全把这称为一个隐藏的功能,而更像是一个烦恼。我还记得几年前在 Freenode 上介绍 #Ruby 时第一次接触到它;实际上我已经使用了 Ruby 的每一个功能,除了这个。 - ELLIOTTCABLE
1
我不会称它为烦恼,它只是你还没有使用过的东西。我使用它可以很好地减少代码量,特别是当我根据某些条件从文件中获取一些行块时。 - the Tin Man

49

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_at时,at变量将是方法被调用的时间(非常接近)。


2
注意:Array#rand由ActiveSupport提供,您可以像require 'activesupport'一样轻松地在Rails之外使用它。 - rfunduk
24
Array#choice 只存在于 1.8.7 版本!不要使用它,因为在 1.9 版本中已经被移除,并且在 1.8.8 版本中也将被移除。请使用 #sample 方法。 - Marc-André Lafortune
Python:class DictList([dict,list][random.randint(0,1)]): pass - Anurag Uniyal
def do_something_at(something, at = lambda{Time.now}) at.call #现在动态分配时间 end - Jack Kinsella
在默认参数列表中,您还可以对调用方法的对象调用方法。 - Martin T.

47

另一个微小的特性 - 可以将一个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

1
为了完整性,可以使用String#to_s(base)将字符串转换回整数;例如"1001001100101100000001011010010".to_i(2)"499602d2".to_i(16)等都会返回原始的Fixnum - Huw Walters

40

带有默认值的散列!在这种情况下是一个数组。

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

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

在元编程中非常有用。


1
是的,没错。如果Ruby哈希表已经使用'='分配了默认值(不管它是否为空赋值),那么它可以接受'<<'运算符,否则哈希表将不会接受'<<'。请纠正我如果我有误。 - mhd

38

在1.9版中,另一个有趣的补充是Proc#curry功能,它允许您将接受n个参数的Proc转换为接受n-1个参数的函数。这里将其与我上面提到的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

37

下载 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 可以找到更多隐藏的有趣内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接