Ruby的隐藏特征

Hidden features of Ruby

继续讨论"meme的隐藏特性",让我们分享Ruby编程语言中不太知名但有用的特性。

尝试用核心Ruby来限制讨论,不要使用Ruby on Rails之类的东西。

参见:

  • C的隐藏特征#
  • Java的隐藏特性
  • javascript的隐藏特性
  • Ruby on Rails的隐藏特性
  • Python的隐藏特征

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

谢谢你


From Ruby 1.9 proc==是proc call的别名,这意味着proc对象可用于以下case语句:

1
2
3
4
5
6
7
8
9
10
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
2
3
[*items].each do |item|
  # ...
end


不知道这有多隐蔽,但我发现它在需要对一维数组进行哈希运算时很有用:

1
2
3
4
5
fruit = ["apple","red","banana","yellow"]
=> ["apple","red","banana","yellow"]

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


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

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

其他示例包括:

1
2
3
4
5
a, b, c = *('A'..'Z')

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


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

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


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

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

1
2
3
4
5
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方法),并且这个例子是精心设计的,但是您可以看到这里的力量。

另一个很酷的例子是,可以将非固定的默认参数值(就像其他语言通常要求的那样):

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

当然,第一个示例的问题在于它是在定义时进行评估的,而不是调用时。所以,一旦选择了一个超类,它将在程序的其余部分保留该超类。

但是,在第二个示例中,每次调用do_something_at时,at变量将是调用该方法的时间(好吧,非常接近它)。


另一个很小的特点是将Fixnum转换成36以下的任意基:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>> 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所评论的,转换另一种方式也同样简单:

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


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

1
2
3
4
5
6
parties = Hash.new {|hash, key| hash[key] = [] }
parties["Summer party"]
# => []

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

在元编程中非常有用。


下载Ruby1.9源代码,发布make golf,然后您可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
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了解隐藏起来的更整洁的东西。


1.9 proc功能中另一个有趣的添加是proc curry,它允许您将接受n个参数的proc转换为接受n-1的proc。这里,它与上面提到的proc==tip i结合在一起:

1
2
3
4
5
6
7
8
9
10
11
12
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


非布尔值上的布尔运算符。

&&||

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

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

然而,&&=并不是那么广为人知。

1
string &&= string +"suffix"

等于

1
2
3
if string
  string = string +"suffix"
end

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


Rails提供的符号to_proc函数非常酷。

而不是

1
Employee.collect { |emp| emp.name }

你可以写:

1
Employee.collect(&:name)


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

1
2
message ="My message"
contrived_example ="#{message}"

如果不想转义字符串中的双引号,只需使用不同的分隔符:

1
2
contrived_example = %{#{message}}
contrived_example = %[#{message}]

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

1
2
3
4
5
sql = %{
    SELECT strings
    FROM complicated_table
    WHERE complicated_condition = '1'
}


我发现使用define_method命令动态生成方法是非常有趣的,并不是众所周知的。例如:

1
2
3
4
5
((0..9).each do |n|
    define_method"press_#{n}" do
      @number = @number.to_i * 10 + n
    end
  end

上面的代码使用"defineu method"命令动态创建方法"press1"到"press9"。而不是键入所有10个基本上包含相同代码的方法,define method命令用于根据需要动态生成这些方法。


使用范围对象作为无限的惰性列表:

1
2
3
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/


模函数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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_函数,那么module_函数语句之后的任何module方法都将自动成为module_函数本身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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!'


短时间注射,如:

范围和:

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


警告:该项目被评为2008年最骇人听闻的黑客,请小心使用。事实上,避免它像瘟疫一样,但它肯定是隐藏的红宝石。

超级运算符向Ruby添加新的运算符

有没有想要一个超级秘密的握手接线员在你的代码中进行一些独特的操作?喜欢打代码高尔夫吗?尝试像-~+~-或<最后一个例子用于颠倒一个项目的顺序。

我与超人计划没有任何关系,除了欣赏它。


在Ruby中自动激活哈希

1
2
3
4
5
6
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 } } }

这真是太方便了。


我参加晚会迟到了,但是:

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

1
2
3
4
5
a = [:x, :y, :z]
b = [123, 456, 789]

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

(这是因为数组"压缩"两个数组中的值:

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

hash[]可以接受这样的数组。我也见过有人这样做:

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

哪一个结果相同,但展开和展平是完全不必要的——也许它们不是在过去吗?)


破坏阵列

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

哪里:

1
2
3
4
a #=> :a
b #=> :b
c #=> :c
d #=> [:d1, :d2]

使用这种技术,我们可以使用简单的赋值从任何深度的嵌套数组中获得所需的精确值。


Class.new()

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

不是你每天都需要的东西,而是你做的时候非常方便。


创建一个连续数字数组:

1
x = [*0..5]

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


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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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创建一个新方法,它的任务是发送消息,包括所有参数和块。

对于元编程问题来说,一个很好的资源就是为什么Lucky Stick"清晰地看到元编程"的原因。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
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(因此ClassRegexpDate和许多其他类定义了一个实例方法:==(其他),并且都可以使用。

感谢法雷尔提醒我们,在Ruby1.9中,Proc#call被化名为Proc#===


"ruby"二进制文件(至少是mri的)支持许多使PerlOne衬板非常流行的交换机。

重要事项:

  • -n设置一个只使用"get"的外部循环,它可以神奇地使用给定的文件名或stdin,设置每个读取行$_
  • -p与-n相似,但在每个循环迭代结束时有一个自动的puts
  • -在每个输入行上自动调用.split,存储在$f中
  • -就地编辑输入文件
  • -l输入时自动调用.chomp
  • -执行一段代码
  • -C检查源代码
  • -带警告的W

一些例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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

谷歌的"ruby一行程序"和"perl一行程序"提供了大量实用的例子。它本质上允许您使用Ruby作为awk和sed的一个相当强大的替代品。


send()方法是一种通用方法,可以用于Ruby中的任何类或对象。如果不被重写,send()接受一个字符串并调用其传递字符串的方法的名称。例如,如果用户单击"clr"按钮,"press_clear"字符串将发送到send()方法,并调用"press_clear"方法。send()方法允许以一种有趣的动态方式调用Ruby中的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 %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中详细讨论了这个特性:简单的calc应用程序


1
2
private unless Rails.env == 'test'
# e.g. a bundle of methods you want to test directly

看起来像是Ruby的一个酷的(在某些情况下)好的/有用的黑客/特性。


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

1
$" <<"something"

例如,当需要a时,这很有用,而a反过来需要b,但我们的代码中不需要b(a也不会通过我们的代码使用b):

例如,backgroundRB的bdrb_test_helper requires'test/spec',但您根本不使用它,因此在代码中:

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


定义一个方法,该方法接受任意数量的参数并将它们全部丢弃

1
2
3
4
def hello(*)
    super
    puts"hello!"
end

上面的hello方法只需要屏幕上的puts"hello"并调用super,但是由于超类hello定义了参数,所以它也必须定义参数,但是由于它实际上不需要使用参数本身,所以不必给它们起名字。


在某些情况下,Fixnum#to_s(base)确实有用。其中一种情况是通过使用36的基数将随机数转换为字符串来生成随机(伪)唯一标记。

长度为8的标记:

1
2
3
rand(36**8).to_s(36) =>"fmhpjfao"
rand(36**8).to_s(36) =>"gcer9ecu"
rand(36**8).to_s(36) =>"krpm0h9r"

长度为6的标记:

1
2
3
rand(36**6).to_s(36) =>"bvhl8d"
rand(36**6).to_s(36) =>"lb7tis"
rand(36**6).to_s(36) =>"ibwgeh"

要将多个regex与|组合,可以使用

1
Regexp.union /Ruby\d/, /test/i,"cheat"

要创建类似以下内容的regexp:

1
/(Ruby\d|[tT][eE][sS][tT]|cheat)/

我发现这在一些脚本中很有用。它使得直接使用环境变量成为可能,比如在shell脚本和makefiles中。环境变量用作未定义Ruby常量的回退。

1
2
3
4
5
6
7
8
9
10
11
12
13
>> class <<Object
>>  alias :old_const_missing :const_missing
>>  def const_missing(sym)
>>   ENV[sym.to_s] || old_const_missing(sym)
>>  end
>> end
=> nil

>> puts SHELL
/bin/zsh
=> nil
>> TERM == 'xterm'
=> true

打开一个基于argv[0]的文件怎么样?

readfile.rb:

1
2
3
$<.each_line{|l| puts l}

ruby readfile.rb testfile.txt

这是编写一次性脚本的一个很好的捷径。有一大堆的预先定义的变量,大多数人都不知道。明智地使用它们(请阅读:不要乱扔计划维护的代码库,这样会变得混乱)。


我是一个粉丝:

1
%w{An Array of strings} #=> ["An","Array","of","Strings"]

有点可笑的是,它的用处有多大。


多个返回值

1
2
3
4
5
6
7
def getCostAndMpg
    cost = 30000  # some fancy db calls go here
    mpg = 30
    return cost,mpg
end
AltimaCost, AltimaMpg = getCostAndMpg
puts"AltimaCost = #{AltimaCost}, AltimaMpg = #{AltimaMpg}"

并行分配

1
2
3
4
5
i = 0
j = 1
puts"i = #{i}, j=#{j}"
i,j = j,i
puts"i = #{i}, j=#{j}"

虚拟属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Employee < Person
  def initialize(fname, lname, position)
    super(fname,lname)
    @position = position
  end
  def to_s
     super +", #@position"
  end
  attr_writer :position
  def etype
     if @position =="CEO" || @position =="CFO"
        "executive"
     else
        "staff"
     end
  end
end
employee = Employee.new("Augustus","Bondi","CFO")
employee.position ="CEO"
puts employee.etype    =>  executive
employee.position ="Engineer"
puts employee.etype    =>  staff

缺少方法-一个好主意

(在大多数语言中,当找不到方法并抛出错误并且程序停止时。在Ruby中,您实际上可以捕捉到这些错误,并可能在这种情况下做一些明智的事情)

1
2
3
4
5
6
7
8
9
10
11
class MathWiz
  def add(a,b)
    return a+b
  end
  def method_missing(name, *args)
    puts"I don't know the method #{name}"
  end
end
mathwiz = MathWiz.new
puts mathwiz.add(1,4)
puts mathwiz.subtract(4,2)

5

I don't know the method subtract

nil


调用继承链中任意位置定义的方法,即使重写

ActiveSupport的对象有时会伪装为内置对象。

1
2
3
4
5
6
7
require 'active_support'
days = 5.days
days.class  #=> Fixnum
days.is_a?(Fixnum)  #=> true
Fixnum === days  #=> false (huh? what are you really?)
Object.instance_method(:class).bind(days).call  #=> ActiveSupport::Duration (aha!)
ActiveSupport::Duration === days  #=> true

当然,上面的内容依赖于这样一个事实:主动支持并没有重新定义对象实例方法,在这种情况下,我们真的会遇到麻烦。同样,我们可以在加载任何第三方库之前保存object.instance_方法(:class)的返回值。

object.instance_method(…)返回一个未绑定方法,然后可以将该方法绑定到该类的实例。在这种情况下,您可以将它绑定到对象的任何实例(包括子类)。

如果对象的类包含模块,也可以使用这些模块中的unboundmethod。

1
2
3
4
5
6
7
8
9
10
11
12
module Mod
  def var_add(more); @var+more; end
end
class Cla
  include Mod
  def initialize(var); @var=var; end
  # override
  def var_add(more); @var+more+more; end
end
cla = Cla.new('abcdef')
cla.var_add('ghi')  #=>"abcdefghighi"
Mod.instance_method(:var_add).bind(cla).call('ghi')  #=>"abcdefghi"

这甚至适用于重写对象所属类的实例方法的单例方法。

1
2
3
4
5
6
7
8
9
10
11
class Foo
  def mymethod; 'original'; end
end
foo = Foo.new
foo.mymethod  #=> 'original'
def foo.mymethod; 'singleton'; end
foo.mymethod  #=> 'singleton'
Foo.instance_method(:mymethod).bind(foo).call  #=> 'original'

# You can also call #instance method on singleton classes:
class << foo; self; end.instance_method(:mymethod).bind(foo).call  #=> 'singleton'

James A.Rosen的提示很酷([*项]。每个),但我发现它破坏了哈希:

1
2
3
4
irb(main):001:0> h = {:name =>"Bob"}
=> {:name=>"Bob"}
irb(main):002:0> [*h]
=> [[:name,"Bob"]]

当我接受要处理的事情列表时,我倾向于采用这种方式处理案件,但我很宽容,允许来电者提供一个:

1
2
3
4
irb(main):003:0> h = {:name =>"Bob"}
=> {:name=>"Bob"}
irb(main):004:0> [h].flatten
=> [{:name=>"Bob"}]

这可以与方法签名结合在一起,就像这样:

1
2
3
4
5
def process(*entries)
  [entries].flatten.each do |e|
    # do something with e
  end
end

我喜欢这样的关键词救援:编辑实例:

1
2
3
@user #=> nil (but I did't know)
@user.name rescue"Unknown"
link_to( d.user.name, url_user( d.user.id, d.user.name)) rescue 'Account removed'

这可以避免破坏我的应用程序,而且比Rails上发布的功能要好得多。请尝试()。


人们应该知道符号文字的一些方面。特殊符号文本解决的一个问题是,当您需要创建一个名称由于某些原因导致语法错误的符号时,使用普通符号文本语法:

1
:'class'

也可以进行符号插值。在访问器的上下文中,例如:

1
2
3
define_method :"#{name}=" do |value|
  instance_variable_set :"@#{name}", value
end

对于任何可枚举对象(数组、散列等),每个u with u index方法可能是什么?

1
2
3
4
5
6
7
myarray = ["la","li","lu"]
myarray.each_with_index{|v,idx| puts"#{idx} -> #{v}"}

#result:
#0 -> la
#1 -> li
#2 -> lu

也许它比其他答案更广为人知,但并不是所有Ruby程序员都知道的。


我刚刚读了所有的答案…一个显著的遗漏是破坏了任务:

1
2
3
4
> (a,b),c = [[1,2],3]
=> [[1,2],3]
> a
=> 1

它也适用于块参数。当您有嵌套的数组时,这很有用,其中的每个元素表示不同的内容。您可以在一行代码中分解嵌套数组并为每个元素提供描述性名称,而不是编写类似"array[0][1]"的代码。


1
2
@user #=> nil (but I did't know)
@user.name rescue"Unknown"


Ruby有一个call/cc机制,允许您在堆栈中自由地上下跳跃。

下面是简单的例子。这当然不是一个如何在Ruby中乘法序列的方法,但它演示了如何使用call/cc到达堆栈以短路一个算法。在本例中,我们递归地将一个数字列表相乘,直到我们看到每个数字或看到零(我们知道答案的两个情况)。在零情况下,我们可以任意深入列表并终止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env ruby

def rprod(k, rv, current, *nums)
  puts"#{rv} * #{current}"
  k.call(0) if current == 0 || rv == 0
  nums.empty? ? (rv * current) : rprod(k, rv * current, *nums)
end

def prod(first, *rest)
  callcc { |k| rprod(k, first, *rest) }
end

puts"Seq 1:  #{prod(1, 2, 3, 4, 5, 6)}"
puts""
puts"Seq 2:  #{prod(1, 2, 0, 3, 4, 5, 6)}"

您可以在这里看到输出:

http://codepad.org/oh8ddh9e

对于一个更复杂的例子,其特征是连续移动堆栈上的另一个方向,请将源代码读取到生成器。


1
2
3
4
5
6
7
8
9
10
11
12
class A

  private

  def my_private_method
    puts 'private method called'
  end
end

a = A.new
a.my_private_method # Raises exception saying private method was called
a.send :my_private_method # Calls my_private_method and prints private method called'


sprintf快捷方式

我最喜欢的红宝石特性。语法为format_string % argument

1
2
"%04d"  % 1         # =>"0001"
"%0.2f" % Math::PI  # =>"3.14"

也适用于阵列(format_string % array_of_arguments)

1
2
"%.2f %.3f %.4f" % ([Math::PI]*3)
# =>"3.14 3.142 3.1416"