继续“...的隐藏特性”模因,让我们分享 Ruby 编程语言鲜为人知但有用的特性。
试着用核心 Ruby 来限制这个讨论,而不用任何 Ruby on Rails 的东西。
也可以看看:
(请,每个答案只有一个隐藏功能。)
谢谢
继续“...的隐藏特性”模因,让我们分享 Ruby 编程语言鲜为人知但有用的特性。
试着用核心 Ruby 来限制这个讨论,而不用任何 Ruby on Rails 的东西。
也可以看看:
(请,每个答案只有一个隐藏功能。)
谢谢
从 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
Peter Cooper 有一个很好的 Ruby 技巧列表。也许我最喜欢他的是允许列举单个项目和集合。(也就是说,将非集合对象视为仅包含该对象的集合。)它看起来像这样:
[*items].each do |item|
# ...
end
不知道这是多么隐藏,但我发现它在需要从一维数组中生成哈希时很有用:
fruit = ["apple","red","banana","yellow"]
=> ["apple", "red", "banana", "yellow"]
Hash[*fruit]
=> {"apple"=>"red", "banana"=>"yellow"}
我喜欢的一个技巧是在*
数组以外的对象上使用 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
哇,没有人提到触发器运算符:
1.upto(100) do |i|
puts i if (i == 3)..(i == 15)
end
ruby 的一个很酷的地方是您可以在其他语言不喜欢的地方调用方法和运行代码,例如在方法或类定义中。
例如,要创建一个直到运行时才具有未知超类的类,即随机类,您可以执行以下操作:
class RandomSubclass < [Array, Hash, String, Fixnum, Float, TrueClass].sample
end
RandomSubclass.superclass # could output one of 6 different classes.
这使用了 1.9Array#sample
方法(仅在 1.8.7 中,请参阅 参考资料Array#choice
),该示例非常做作,但您可以在此处看到强大的功能。
另一个很酷的例子是能够放置不固定的默认参数值(就像其他语言经常需要的那样):
def do_something_at(something, at = Time.now)
# ...
end
当然,第一个示例的问题在于它是在定义时评估的,而不是调用时。因此,一旦选择了一个超类,它就会在程序的其余部分保持该超类。
但是,在第二个示例中,每次调用do_something_at
时,at
变量都会是调用方法的时间(嗯,非常接近)
另一个小功能 - 将 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
具有默认值的哈希!在这种情况下是一个数组。
parties = Hash.new {|hash, key| hash[key] = [] }
parties["Summer party"]
# => []
parties["Summer party"] << "Joe"
parties["Other party"] << "Jane"
在元编程中非常有用。
下载 Ruby 1.9 源代码,然后 issue 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
更多隐藏的东西。
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
非布尔值的布尔运算符。
&&
和||
两者都返回最后评估的表达式的值。
这就是为什么||=
如果变量未定义,将使用右侧的值返回表达式更新变量。这没有明确记录,而是常识。
然而,&&=
它并没有那么广为人知。
string &&= string + "suffix"
相当于
if string
string = string + "suffix"
end
如果变量未定义,这对于不应进行的破坏性操作非常方便。
Rails 提供的 Symbol#to_proc 函数真的很酷。
代替
Employee.collect { |emp| emp.name }
你可以写:
Employee.collect(&:name)
最后一个 - 在 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'
}
使用 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/
我发现使用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 命令根据需要动态生成这些方法。
声明为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!'
警告:此项目被评为2008 年最可怕的黑客攻击第一名,因此请小心使用。实际上,像瘟疫一样避免它,但它肯定是隐藏的红宝石。
曾经想要一个超级秘密的握手操作符来执行代码中的一些独特操作吗?喜欢打代码高尔夫?尝试像 -~+~- 或 <--- 这样的运算符。最后一个在示例中用于反转项目的顺序。
除了钦佩它之外,我与Superators Project没有任何关系。
在 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 } } }
这简直太方便了。
我参加聚会迟到了,但是:
您可以轻松地获取两个等长数组并将它们转换为散列,其中一个数组提供键,另一个提供值:
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 完全没有必要——也许它们不是过去的?)
解构数组
(a, b), c, d = [ [:a, :b ], :c, [:d1, :d2] ]
在哪里:
a #=> :a
b #=> :b
c #=> :c
d #=> [:d1, :d2]
使用这种技术,我们可以使用简单的赋值从任何深度的嵌套数组中获取我们想要的确切值。
Class.new()
在运行时创建一个新类。参数可以是派生自的类,块是类体。您可能还想查看以const_set/const_get/const_defined?
正确注册您的新课程,以便inspect
打印出名称而不是数字。
不是您每天都需要的东西,但是当您这样做时非常方便。
您在 Rubyland 中看到的许多魔力都与元编程有关,元编程就是编写代码,为您编写代码。Ruby 的attr_accessor
、attr_reader
和attr_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
创建一个新方法,其工作是发送消息,包括所有参数和块。
元编程问题的一个很好的资源是Why the Lucky Stiff's "Seeing Metaprogramming Clearly"。
创建一个连续数字数组:
x = [*0..5]
将 x 设置为 [0, 1, 2, 3, 4, 5]
使用任何响应===(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
)、Regexp
、Date
和许多其他类定义了一个实例方法 :===(other),并且都可以使用。
感谢Farrel提醒您在 Ruby 1.9Proc#call
中使用别名。Proc#===
“ruby”二进制文件(至少是 MRI)支持许多使 perl 单行代码非常流行的开关。
重要的:
put
-p 与 -n 类似,但在每次循环迭代结束时有一个自动s一些例子:
# 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 one-liners”和“perl one-liners”以获得更多有用和实用的例子。它本质上允许您使用 ruby 作为 awk 和 sed 的相当强大的替代品。
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 应用程序中详细讨论了这个功能
愚弄一些类或模块,告诉它需要一些它确实不需要的东西:
$" << "something"
例如,当需要 A 而需要 B 但我们在代码中不需要 B(并且 A 也不会通过我们的代码使用它)时,这很有用:
例如 Backgroundrb's bdrb_test_helper requires
'test/spec'
,但您根本不使用它,所以在您的代码中:
$" << "test/spec"
require File.join(File.dirname(__FILE__) + "/../bdrb_test_helper")
private unless Rails.env == 'test'
# e.g. a bundle of methods you want to test directly
看起来像 Ruby 的一个很酷且(在某些情况下)不错/有用的 hack/功能。
定义一个接受任意数量参数并丢弃它们的方法
def hello(*)
super
puts "hello!"
end
上面的hello
方法只需要puts
"hello"
在屏幕上调用super
——但由于超类hello
定义了参数,它也必须这样做——但是因为它实际上并不需要使用参数本身——它不必给它们一个名字。
如何打开基于 ARGV[0] 的文件?
readfile.rb:
$<.each_line{|l| puts l}
ruby readfile.rb testfile.txt
这是编写一次性脚本的绝佳捷径。有一大堆大多数人不知道的预定义变量。明智地使用它们(阅读:不要乱扔你打算用它们维护的代码库,它会变得混乱)。
要将多个正则表达式与 结合|
,您可以使用
Regexp.union /Ruby\d/, /test/i, "cheat"
创建类似于以下内容的正则表达式:
/(Ruby\d|[tT][eE][sS][tT]|cheat)/
我发现这在某些脚本中很有用。它使直接使用环境变量成为可能,例如在 shell 脚本和 Makefile 中。环境变量用作未定义的 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
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"
我是:
%w{An Array of strings} #=> ["An", "Array", "of", "Strings"]
有用的频率有点有趣。
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
(在大多数语言中,当找不到方法并且抛出错误并且您的程序停止时。在 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
我不知道减法
零
James A. Rosen 的提示很酷([*items].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
调用在继承链中任意位置定义的方法,即使被覆盖
ActiveSupport 的对象有时会伪装成内置对象。
需要'active_support' 天 = 5.天 days.class #=> Fixnum days.is_a?(Fixnum) #=> true Fixnum === days #=> false(嗯?你到底是什么?) Object.instance_method(:class).bind(days).call #=> ActiveSupport::Duration(啊哈!) ActiveSupport::Duration === days #=> true
当然,上面的内容依赖于 active_support 没有重新定义 Object#instance_method 的事实,在这种情况下,我们真的会陷入困境。再说一次,我们总是可以在加载任何 3rd 方库之前保存 Object.instance_method(:class) 的返回值。
Object.instance_method(...) 返回一个 UnboundMethod ,然后您可以将其绑定到该类的实例。在这种情况下,您可以将其绑定到 Object 的任何实例(包括子类)。
如果对象的类包含模块,您还可以使用这些模块中的 UnboundMethod。
模组模组 def var_add(更多); @var+更多;结尾 结尾 类 包括模组 定义初始化(变量);@var=var; 结尾 # 覆盖 def var_add(更多); @var+更多+更多;结尾 结尾 cla = Cla.new('abcdef') cla.var_add('ghi') #=> "abcdefghighi" Mod.instance_method(:var_add).bind(cla).call('ghi') #=> "abcdefghi"
这甚至适用于覆盖对象所属类的实例方法的单例方法。
Foo 类 定义我的方法;'原来的'; 结尾 结尾 foo = Foo.new foo.mymethod #=> '原始' def foo.mymethod; '单身'; 结尾 foo.mymethod #=> '单例' Foo.instance_method(:mymethod).bind(foo).call #=> 'original' # 你也可以在单例类上调用#instance 方法: 类 << foo; 自己; end.instance_method(:mymethod).bind(foo).call #=> 'singleton'
我只是喜欢这样的内联关键字救援:
编辑示例:
@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 .try()发布的功能要好得多
任何可枚举对象(数组、哈希等)的each_with_index方法?
myarray = ["la", "li", "lu"]
myarray.each_with_index{|v,idx| puts "#{idx} -> #{v}"}
#result:
#0 -> la
#1 -> li
#2 -> lu
也许它比其他答案更广为人知,但对所有 ruby 程序员来说并不那么出名:)
人们应该知道符号文字的某些方面。特殊符号文字解决的一种情况是,当您需要使用正常的符号文字语法创建一个名称由于某种原因导致语法错误的符号时:
:'class'
您还可以进行符号插值。在访问器的上下文中,例如:
define_method :"#{name}=" do |value|
instance_variable_set :"@#{name}", value
end
我刚刚阅读了所有答案......一个值得注意的遗漏是解构分配:
> (a,b),c = [[1,2],3]
=> [[1,2],3]
> a
=> 1
它也适用于块参数。当您有嵌套数组时,这很有用,其中每个元素都代表不同的东西。无需编写像“array[0][1]”这样的代码,您可以将嵌套数组分解并在一行代码中为每个元素指定一个描述性名称。
Ruby 有一种call/cc机制,允许人们自由地在堆栈中上下跳跃。
简单的例子如下。这当然不是在 ruby 中乘以一个序列的方式,但它演示了如何使用 call/cc 到达堆栈以使算法短路。在这种情况下,我们递归地乘以一个数字列表,直到我们看到每个数字或者我们看到零(我们知道答案的两种情况)。在零的情况下,我们可以在列表中任意深并终止。
#!/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)}"
你可以在这里看到输出:
有关在堆栈上向另一个方向移动延续的更复杂示例,请阅读Generator的源代码。
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'
@user #=> nil (but I did't know)
@user.name rescue "Unknown"
我最喜欢的红宝石功能。语法是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"