Ruby的语法和语言特性总结

2019-09-25 09:36:14王冬梅

a = [5, 3, 4, 1]
a.sort => [1, 3, 4, 5] # 整数已通过Fixnum类实现太空船操作符,因此可比较可排序
a.any? {|i| i > 4} => true
a.all? {|i| i > 0} => true
a.collect {|i| i * 2} => [10, 6, 8, 2]
a.select {|i| i % 2 == 0} => [4]
a.member?(2) => false
a.inject {|product, i| product * i} => 60 # 第一个参数是代码块上一次执行的结果,若不设初始值,则使用列表第一个值作为初始值

4. 元编程(metaprogramming)
所谓元编程,说白了就是“写能写程序的程序”,这说起来有点拗口,下面会通过实例来讲解。

4.1 开放类

可以重定义Ruby中的任何类,并给它们扩充任何你想要的方法,甚至能让Ruby完全瘫痪,比如重定义Class.new方法。对于开发类来说,这种权衡主要考虑了自由,有这种重定义任何类或对象的自由,就能写出即为通俗易懂的代码,但也要明白,自由越大、能力越强,担负的责任也越重。

class Numeric
 def inches
  self
 end
 def feet
  self * 12.inches
 end
 def miles
  self * 5280.feet
 end
 def back
  self * -1
 end
 def forward
  self
 end
end

上面的代码通过开放Numeric类,就可以像这样采用最简单的语法实现用英寸表示距离:puts 10.miles.back,puts 2.feet.forward。

4.2 使用method_missing

Ruby找不到某个方法时,会调用一个特殊的回调方法method_missing显示诊断信息。通过覆盖这个特殊方法,可以实现一些非常有趣且强大的功能。下面这个示例展示了如何用简洁的语法来实现罗马数字。

class Roman
 # 覆盖self.method_missing方法
 def self.method_missing name, *args
  roman = name.to_s
  roman.gsub!("IV", "IIII")
  roman.gsub!("IX", "VIIII")
  roman.gsub!("XL", "XXXX")
  roman.gsub!("XC", "LXXXX")

  (roman.count("I") +
   roman.count("V") * 5 +
   roman.count("X") * 10 +
   roman.count("L") * 50 +
   roman.count("C") * 100)
 end
end

puts Roman.III # => 3
puts Roman.XII # => 12

我们没有给Roman类定义什么实际的方法,但已经可以Roman类来表示任何罗马数字!其原理就是在没有找到定义方法时,把方法名称和参数传给method_missing执行。首先调用to_s把方法名转为字符串,然后将罗马数字“左减”特殊形式转换为“右加”形式(更容易计数),最后统计各个符号的个数和加权。

当然,如此强有力的工具也有其代价:类调试起来会更加困难,因为Ruby再也不会告诉你找不到某个方法。因此method_missing是一把双刃剑,它确实可以让语法大大简化,但是要以人为地加强程序的健壮性为前提。

4.3 使用模块

Ruby最流行的元编程方式,非模块莫属。下面的代码讲述如何用模块的方式扩展一个可以读取csv文件的类。

module ActsAsCsv

 # 只要某个模块被另一模块include,就会调用被include模块的included方法
 def self.included(base)
  base.extend ClassMethods
 end

 module ClassMethods
  def acts_as_csv
   include InstanceMethods
  end
 end

 module InstanceMethods
  attr_accessor :headers, :csv_contents

  def initialize
   read
  end

  def read
   @csv_contents = []
   filename = self.class.to_s.downcase + '.txt'
   file = File.new(filename)
   @headers = file.gets.chomp.split(', ') # String的chomp方法去除字符串末尾的回车换行符
   file.each do |row|
    @csv_contents << row.chomp.split(', ')
   end
  end
 end

end # end of module ActsAsCsv

class RubyCsv  # 没有继承,可以自由添加
 include ActsAsCsv
 acts_as_csv
end

m = RubyCsv.new
puts m.headers.inspect
puts m.csv_contents.inspect