Ruby的隐藏功能


160

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

尝试将讨论限制在核心Ruby之上,而没有任何Ruby on Rails的内容。

也可以看看:

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

谢谢


应该是社区Wiki
SilentGhost

Answers:


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
我实际上曾经写过一个gem来做到这一点,但是我的代码是(a)一团糟,并且(b)速度很慢。我很高兴该功能已成为核心。
詹姆斯·A·罗森

76

彼得·库珀(Peter Cooper)列出了很多不错的Ruby技巧。也许我最喜欢他的是允许枚举单个项目和集合。(也就是说,将非集合对象视为仅包含该对象的集合。)如下所示:

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

38
Array(items).each的一种更明确(也因此更好)的形式
mislav

如果items是字符串,则不必用[*…]括起来。String.each不会像某些人期望的那样遍历字符。它只是将自身返回到块中。
mxcl 2010年

这有什么用?只是好奇。
Ed S.

1
@Ed:如果您正在编写一个方法并希望允许该方法的用户传递一个变量列表或一个数组,那是很好的。
詹姆斯·罗森

64

不知道这有多隐藏,但是当需要用一维数组制作哈希时,我发现它很有用:

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

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

注意,Hash[ [["apple","red"], ["banana","yellow"] ]产生相同的结果。
马克-安德烈·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
顺便说一下,出于好奇,这可以通过在splat目标上隐式调用to_a来实现。
鲍勃·阿曼

1
如果您对这场比赛不感兴趣,可以选择text, number = *"text 555".match(/regexp/)[1..-1]
安德鲁·格林

text, number = "Something 981".scan(/([A-z]*) ([0-9]*)/).flatten.map{|m| Integer(m) rescue m}
乔纳斯·艾夫斯特伦(JonasElfström),2010年

7
两者都是好把戏,但必须要指出的是,它太神奇了,对吗?
tomafro

1
@Andrew,您是否认为比赛可以返回nil?无方法[]
Alexey

52

哇,没人提到过触发器运算符:

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

11
对...某人必须向我解释这一点。它有效,但是我不知道为什么。
Bob Aman 2010年

12
触发器运算符if处于有状态。它的状态一经切换为true,i == 3 i != 3之后又切换为false i == 15。类似于触发器:en.wikipedia.org/wiki/Flip-flop_%28electronics%29
Konstantin Haase

1
我不会完全称呼它为隐藏功能,这很烦人。我记得几年前第一次在Freenode上的#Ruby中被介绍给我。除了这一点,我基本上已经使用了Ruby的每个功能。
ELLIOTTCABLE 2011年

1
我不会觉得烦恼,这只是您从未使用过的东西。我使用它,它可以很好地减少代码,尤其是当我根据某些条件从文件中抓取几行代码时。
锡人

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


2
注意:Array#rand由ActiveSupport提供,您可以在Rails之外轻松地使用它require 'activesupport'
rfunduk 2009年

Array#choice在1.8.7中
Josh Lee 2009年

24
阵列#选择1.8.7 !不要使用它,它已经在1.9中消失,并且将在1.8.8中消失。使用#sample
马克-安德烈·Lafortune

python:类DictList([dict,list] [random.randint(0,1)]):通过
Anurag Uniyal 2010年

def do_something_at(something,at = lambda {Time.now})at.call #now动态分配时间结束
Jack Kinsella 2010年

47

另一个小功能-将a转换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
休·沃尔特斯,

40

具有默认值的哈希!在这种情况下为数组。

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

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

在元编程中非常有用。


1
是啊,没错。如果已经使用'='分配了默认值,则Ruby哈希可以接受'<<'运算符(即使它是空赋值也不必考虑),否则哈希将不接受'<<'。CMIIW
mhd 2010年

39

下载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获得隐藏的更整洁的东西。


38

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

35

非布尔值的布尔运算符。

&&||

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

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

然而,人们&&=对此并不十分了解。

string &&= string + "suffix"

相当于

if string
  string = string + "suffix"
end

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


2
更确切地说, string &&= string + "suffix" 等效于 string = string && string + "suffix"。这&&||返回他们的第二个参数是在镐讨论页。154(第一部分-Ruby的方面,表达式,条件执行)。
理查德·迈克尔

29

Rails提供的Symbol#to_proc函数确实很棒。

代替

Employee.collect { |emp| emp.name }

你可以写:

Employee.collect(&:name)

显然,这比使用块“数量级慢”。igvita.com/2008/07/08/6-optimization-tips-for-ruby-mri
Charles Roper

我只是尝试了一下,发现两者之间没有显着差异。我不确定这种“数量级”的东西是从哪里来的。(使用Ruby 1.8.7)
马特·格兰德,

1
在Rails之外执行此操作也很方便,并且可以完成操作,require 'activesupport'因为实际上这是大多数帮助程序的来源。
rfunduk

8
由于active_support的实现,此操作过去很慢,即它接受了多个参数,因此您可以像(1..10).inject&:*这样很酷,但主要的用例通常是仅在a的每个成员上调用一个方法集合,例如%w(棕色狐狸).map&:upcase。从1.8.7版开始,它的核心红宝石和性能是合理的。
史蒂夫·格雷厄姆

4
@thenduks:在ruby 1.8.7和1.9中,无需activesupport的帮助即可完成。
安德鲁·格林

28

最后一个-在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'
}

19
没有任何字符,但仍然很酷。它也可以与其他文字一起使用:%()/%{} /%[] /%<> /%|| %r()/%r {} /%r [] /%r <> /%r || %w()/%w {} /%w [] /%w <> /%w ||
Bo Jeanes 2010年

还有的此时此地文档语法:<< BLOCK ...块,这是我喜欢用的东西一样多的SQL语句等等
马丁T.

26

我发现使用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命令根据需要动态生成这些方法。


4
define_method的唯一问题是,它不允许在ruby 1.8中将块作为参数传递。有关变通方法,请参见此博客文章
安德鲁·格林

26

将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非常简洁。
Joseph Weissman

真的很棒 我喜欢Infinity是一个浮点数,当我尝试这样做时,它就是这样:(-Inf..Inf).take(4)它引发了一个(逻辑上一致的)不能从浮点数错误中进行迭代的情况。:D
zachaysan 2011年

23

module_function

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

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!'

4
如果只想在模块中声明私有方法,请使用private关键字。除了使方法在包含模块的类中私有之外,module_function将方法复制到模块实例。在大多数情况下,这不是您想要的。
tomafro

我知道您可以只使用私有。但这是关于Ruby隐藏功能的问题。而且,我认为大多数人直到在文档中看到并开始使用它之前,都从未听说过module_function(包括我自己)。
newtonapple

使用module_function(第二种方法)的另一种方法是只使用extend self(看起来很不错:D)
J -_- L


21

警告:该项目被评为2008年“最恐怖的黑客工具”第一名,请谨慎使用。实际上,请像瘟疫一样避免使用它,但是最肯定的是隐藏的Ruby。

超级运算符将新运算符添加到Ruby

是否曾经想过使用超级秘密的握手运算符来执行代码中的某些唯一操作?喜欢打代码高尔夫吗?尝试使用-〜+〜-或<---等运算符。在示例中,最后一个用于反转项目的顺序。

除了欣赏它之外,我与Superators项目无关。


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完全不必要-也许它们不是过去了吗?)


3
确实,很长一段时间都没有对此进行记录(请参阅redmine.ruby-lang.org/issues/show/1385)。需要注意的是这种新形式是新的红宝石1.8.7
马克-安德烈·Lafortune

19

在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 } } }

这简直太方便了。


1
我会将其包装在一个模块中,以具有与本机哈希init相同的感觉:module InfHash; def self.new; Hash.new {|h,k| h[k] = Hash.new(&h.default_proc)}; end; end
asaaki 2011年

16

分解数组

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

哪里:

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

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


15

Class.new()

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

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


1
MyClass = Class.new Array do; def hi; 'hi'; end; end似乎等于class MyClass < Array; def hi; 'hi'; end; end
yfeldblum

1
可能比我想象的要真实。甚至看起来您可以从变量继承而不仅仅是从常量继承。但是,如果需要在运行时构造类名,则加糖版本(第二个)似乎不起作用。(当然是Baring eval。)
Justin Love

该技术在《Metaprogramming Ruby》一书中有很好的描述。
Paul Pladijs 2011年

13

创建一个连续数字数组:

x = [*0..5]

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


是的,但是它并不那么简短和甜蜜;)
horseyguy 2010年

2
简洁是客观的,可读性是口味和经验的问题
Alexey

splat(*)运算符基本上to_a还是会调用。
Matheus Moreira

13

您在Rubyland中看到的许多魔术都与元编程有关,元编程只是编写可为您编写代码的代码。Ruby的attr_accessorattr_readerattr_writer都是简单的元编程,因为它们遵循标准模式在一行中创建了两个方法。Rails使用诸如has_one和的关系管理方法进行了大量的元编程belongs_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一个新方法来创建新方法,该方法的任务是一起发送消息,包括所有参数和块。

关于元编程问题的一个重要资源是为什么Lucky Stiff的“清晰地看到元编程”


我希望首先深入研究Ruby的元编程。您能否提供一些参考来开始使用它(除了给定的链接)?书也可以。谢谢。
Chirantan

PragProg的电视广播系列“ Ruby对象模型和元编程”很好地介绍了使用ruby进行元编程:pragprog.com/screencasts/v-dtrubyom/…–
caffo

@Chirantan,看看元编程Ruby
Paul Pladijs 2011年

12

使用任何可响应的内容===(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#callProc#===Ruby 1.9 一样被别名。


11

“红宝石”二进制文件(至少是MRI的)支持许多使perl一线式文件非常流行的开关。

重要的:

  • -n仅使用“ gets”设置一个外部循环-可以神奇地使用给定的文件名或STDIN,在$ _中设置每个读取行
  • -p与-n相似,但put在每次循环迭代结束时使用自动s
  • -a在每个输入行上自动调用.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的强大替代品。


10

所述的send()方法是,可以在任何类或对象在红宝石中使用的通用方法。如果未重写,则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:Simple-Calc应用程序中讨论了有关此功能的更多信息


听起来是打开安全漏洞的好方法。
mP。

4
我会尽可能使用符号。
雷托

9

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

$" << "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用来阻止两次加载模块)。因此,如果使用此方法来欺骗gem,当gem尝试使用实际的“东西”时,将导致崩溃。代码,因为它不存在,您可能想要强制加载gem的具体版本(例如foo-1.0.0),而不是最新版本:docs.rubygems.org/read/chapter/4#page71
olegueret 2010年

9

Fixnum#to_s(base)在某些情况下非常有用。一种这样的情况是通过使用36的底数将随机数转换为字符串来生成随机(伪)唯一令牌。

令牌长度为8:

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

令牌长度为6:

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

9

定义一个可以接受任意数量参数的方法,然后将其全部丢弃

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.