Ruby 的隐藏特征

继续“ ... 的隐藏特性”文化基因,让我们分享一些不太为人所知但却很有用的 Ruby 编程语言特性。

尽量限制与核心 Ruby 的讨论,不要使用任何 Ruby on Rails 内容。

参见:

(每个答案只有 隐藏特性。)

谢谢你

43021 次浏览

我发现使用 Definition _ method 定义 _ 方法命令动态生成方法非常有趣,而且不那么为人熟知。例如:

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

上面的代码使用‘ Definition _ method’命令通过“ press9”动态创建方法“ press1”不需要输入所有10个基本上包含相同代码的方法,而是使用 Definition method 命令根据需要动态生成这些方法。

发送()方法是一种通用方法,可以用于 Ruby 中的任何 Class 或 Object。如果没有被重写,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

我将在 BloggShoes: Simple-Calc 应用程序中进一步讨论这个特性

下载 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了解更多隐藏起来的整洁的东西。

使用任何对 ===(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和许多其他类定义了一个实例方法: = = = (other) ,并且都可以使用。

感谢 法雷尔提醒我们在 Ruby 1.9中 Proc#call的别名是 Proc#===

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

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

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

readfile.rb:

$<.each_line{|l| puts l}


ruby readfile.rb testfile.txt

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

你在 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创建一个新方法,其任务是发送消息,包括所有参数和块。

元编程问题的一个很好的资源是 乔纳森·吉列的“看清元编程”

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

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

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

$" << "something"

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

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

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

Rails 提供的符号 # to _ proc 函数非常酷。

而不是

Employee.collect { |emp| emp.name }

你可以写:

Employee.collect(&:name)

警告: 此项目被投票为 # 1 2008年最可怕的黑客,所以使用时要小心。事实上,像躲瘟疫一样躲开它但它绝对是隐藏红宝石。

Superators 向 Ruby 添加新的运算符

有没有想过在代码中使用一个超级秘密的握手操作符来执行一些独特的操作?比如打密码高尔夫?试试像这样的操作员 -~+~- 或者 < —— - 最后一个是用于反转一个项目的顺序的例子。

除了欣赏它之外,我和 超级工程师计划没有任何关系。

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

不知道它有多隐藏,但我发现当需要从一维数组中创建散列时,它很有用:

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


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

我喜欢的一个技巧是对 Ararray 以外的对象使用 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

另一个微小的特性——将 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-in 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'
}

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 _ function 本身。

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

Class.new()

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

不是你每天都需要的东西,但是你需要的时候很方便。

Ruby 有一个 呼叫/抄送机制,允许在堆栈上自由地跳上跳下。

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

#!/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

要获得一个更复杂的示例,其中包含在堆栈上向另一个方向移动的延续,请将源代码读取到 发电机

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

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'

短期注射,就像这样:

范围总和:

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

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

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


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

在元编程中非常有用。

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

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

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

x = [*0..5]

集合 x 到[0,1,2,3,4,5]

我迟到了,但是:

您可以很容易地将两个等长数组转换为散列,其中一个数组提供键,另一个数组提供值:

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


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

(这是因为 Array # zip“ zip”来自两个数组的值:

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

Hash []可以使用这样的数组,我见过人们也这样做:

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

结果是一样的,但是板子和扁平是完全没有必要的——也许它们过去不是这样的?)

使用 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/

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

&&||

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

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

然而,&&=并没有如此广为人知。

string &&= string + "suffix"

相当于

if string
string = string + "suffix"
end

对于未定义变量时不应该进行的破坏性操作,它非常方便。

Rosen 的技巧很酷([ * item ] . each) ,但我发现它会破坏散列:

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

当我接受一个需要处理的事情的清单,但是我比较宽容,允许来电者提供一个清单时,我更喜欢这种处理方式:

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

这可以很好地与方法签名结合起来:

def process(*entries)
[entries].flatten.each do |e|
# do something with e
end
end

“ ruby”二进制(至少是 MRI)支持许多使 perl 一行程序非常流行的开关。

重大问题:

  • - n 设置一个只有“ gets”的外部循环-它神奇地对给定的文件名或 STDIN 起作用,在 $_ 中设置每个读行
  • - p 类似于-n,但在每次循环迭代结束时使用自动 put
  • 在每个输入线上自动调用。分割,存储在 $F 中
  • - i 就地编辑输入文件
  • - l 自动呼叫。咀嚼输入
  • 执行一段代码
  • 检查源代码
  • 有警告

一些例子:

# 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

随时谷歌“红宝石一行程序”和“ perl 一行程序”吨更有用和实际的例子。它实际上允许您使用 ruby 作为 awk 和 sed 的一个相当强大的替代品。

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

这真是太方便了。

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"

我只是 的行内关键字 营救像这样:
编辑实例:

@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 。尝试()发布的功能要好得多

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

我喜欢:

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

有意思的是,这种方法经常有用。

哇,没人提到人字拖操作员:

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

Each _ with _ index 方法用于任何可枚举对象(数组、散列等) ?

myarray = ["la", "li", "lu"]
myarray.each_with_index{|v,idx| puts "#{idx} -> #{v}"}


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

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

解构数组

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

地点:

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

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

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

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

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

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

ActiveSupport 的对象有时伪装成内置对象。

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

当然,上面所说的依赖于 active _ support 没有重新定义 Object # instance _ method 这个事实,如果是这样的话,我们就真的遇到麻烦了。不过,我们总是可以在加载任何第三方库之前保存 Object.instance _ method (: class)的返回值。

Instance _ method (...)返回一个 Unbound Method,然后可以将其绑定到该类的实例。在这种情况下,您可以将它绑定到 Object 的任何实例(包括子类)。

如果对象的类包含模块,还可以使用这些模块中的 Unbound 方法。

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"

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

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'

多个返回值

def getCostAndMpg
cost = 30000  # some fancy db calls go here
mpg = 30
return cost,mpg
end
AltimaCost, AltimaMpg = getCostAndMpg
puts "AltimaCost = #{AltimaCost}, AltimaMpg = #{AltimaMpg}"

平行赋值

i = 0
j = 1
puts "i = #{i}, j=#{j}"
i,j = j,i
puts "i = #{i}, j=#{j}"

虚拟属性

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

Method _ miss-一个很棒的想法

(在大多数语言中,当找不到方法并抛出错误并且程序停止时。在 Ruby 中,实际上可以捕捉到这些错误,或许还可以对这种情况进行一些智能处理)

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

我不知道减法

没有

符号文字有一些方面是人们应该知道的。使用特殊符号字面值解决的一种情况是,当您需要创建一个符号时,由于某种原因,该符号的名称会导致语法错误,使用标准符号字面值语法:

:'class'

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

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

若要将多个正则表达式与 |组合,可以使用

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

创建类似于:

/(Ruby\d|[tT][eE][sS][tT]|cheat)/
private unless Rails.env == 'test'
# e.g. a bundle of methods you want to test directly

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

我刚刚读了所有的答案... ... 一个值得注意的疏忽是解构任务:

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

它也适用于块参数。当您有嵌套数组时,这非常有用,每个元素代表一些不同的东西。不需要编写像“ array [0][1]”这样的代码,您可以将嵌套数组分解,并在一行代码中为每个元素赋予一个描述性名称。

短跑的捷径

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

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

对于数组(format_string % array_of_arguments)也同样适用

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