Ruby的隐藏功能

时间:2020-03-05 18:53:45  来源:igfitidea点击:

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

尝试将讨论限制在核心Ruby之上,而不要使用任何Ruby on Rails的东西。

也可以看看:

  • C#的隐藏功能
  • Java的隐藏功能
  • JavaScript的隐藏功能
  • Ruby on Rails的隐藏功能
  • Python的隐藏功能

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

谢谢

解决方案

回答

我发现使用define_method命令动态生成方法非常有趣,而且并不为人所知。例如:

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

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

回答

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

模块(以及类),正则表达式,日期和许多其他类定义了一个实例方法:===(其他),并且都可以使用。

感谢Farrel提醒我们在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_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"使用符号作为方法名称,并将其存储在"方法"数组中。然后,对于每个给定的参数,我们使用define_method创建一个新方法,该方法的任务是一起发送消息,包括所有参数和块。

关于元编程问题的重要资源是为什么Lucky Stiff的"清晰地看到元编程"。

回答

从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

回答

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

$" << "something"

例如,这在需要A而又需要B但我们的代码中不需要B的情况下很有用(并且A也不会通过我们的代码使用它):

例如,Backgroundrb的bdrb_test_helper需要'test / spec'`,但是我们根本不使用它,因此在代码中:

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

回答

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

代替

Employee.collect { |emp| emp.name }

你可以写:

Employee.collect(&:name)

回答

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

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

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

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

回答

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

回答

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

回答

红宝石的最后一个,我们可以使用要分隔字符串的任何字符。采取以下代码:

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的类中创建自己的副本作为私有实例方法:

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

回答

Class.new()

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

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

回答

Ruby有一个call / cc机制,允许人们自由地在堆栈中上下移动。

下面是一个简单的示例。当然,这不是将红宝石上的序列相乘的方式,但是它演示了如何使用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)}"

我们可以在此处查看输出:

http://codepad.org/Oh8ddh9e

有关具有沿堆栈的另一个方向连续移动的连续性的更复杂示例,请阅读Generator的源代码。

回答

关于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脚本和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

回答

创建一个连续数字数组:

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"压缩"了两个数组中的值:

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

Hash []可以采用这样的数组。我也看到人们也这样做:

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

产生相同的结果,但是splat和flatten完全不必要-也许它们不是过去了吗?)

回答

将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

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

回答

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

回答

"红宝石"二进制文件(至少是MRI的)支持许多开关,这些开关使perl一线式很受欢迎。

重要的是:

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

回答

在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 .try()发布的功能更好。

回答

@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

也许它比其他答案更广为人知,但并不是所有红宝石程序员都那么了解:)

回答

解构数组

(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方法只需要在屏幕上放入" 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)的返回值。

Object.instance_method(...)返回一个UnboundMethod,我们可以将其绑定到该类的实例。在这种情况下,我们可以将其绑定到Object的任何实例(包括子类)。

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

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_missing-一个好主意

(在大多数语言中,当找不到方法并且抛出错误并且程序停止时。在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
  
  I don't know the method subtract
  
  nil