Ruby Practice – Dynamicity

Ruby是一种动态的语言,下面的几个例子将展示Ruby的动态特性。

先看第一个例子:

$ irb
irb(main):001:0> class Hello
irb(main):002:1> end
=> nil
irb(main):003:0> hello = Hello.new
=> #<Hello:0xb7f36cb4>
irb(main):004:0> hello.hi
NoMethodError: undefined method `hi' for #<Hello:0xb7f36cb4>
        from (irb):4
        from :0
irb(main):005:0> class Hello
irb(main):006:1>   def hi
irb(main):007:2>     puts "hello, world"
irb(main):008:2>   end
irb(main):009:1> end
=> nil
irb(main):010:0> hello.hi
hello, world
=> nil

在这个例子中,我们先定义一个“空”的Hello类,所以,当我们对它的实例hello调用hi方法时会报错。接着,我们把一个hi方法的定义塞进Hello类里面,然后再调用hi。这个例子说明,Ruby的类很像C++中的名字空间,我们可以随时打开它的作用域,向里面加点东西,然后再关闭它。不过,在Ruby里,我们可以做更多的事,比如:

irb(main):011:0> class Hello
irb(main):012:1>   private :hi
irb(main):013:1> end
=> Hello
irb(main):014:0> hello.hi
NoMethodError: private method `hi' called for #<Hello:0xb7f36cb4>
        from (irb):14
        from :0

虽然hi方法还在,但是已经不能通过hello来调用它了,因为它已经变成一个私有的方法。不过,我们仍然可以在类里面调用它。

irb(main):015:0> class Hello
irb(main):016:1>   def bye
irb(main):017:2>     hi
irb(main):018:2>   end
irb(main):019:1> end
=> nil
irb(main):020:0> hello.bye
hello, world
=> nil

接下来的事情更令人惊讶:

irb(main):021:0> class Hello
irb(main):022:1>   undef bye
irb(main):023:1> end
=> nil
irb(main):024:0> hello.bye
NoMethodError: undefined method `bye' for #<Hello:0xb7f36cb4>
        from (irb):24
        from :0

我们甚至删除了一个方法!更要命的是,我们不仅可以对自己写的类做这样的事,还可以对Ruby的标准类做同样的事。说实话,我还是第一次接触拥有这种特性的语言,想象不出它有怎样的用处(正面的),尤其是删除一个方法。虽然我愿意抱着开放的心态接受这种特性,但是我相信,它会让很多经理们疯狂,他们绝不愿意把这样一种语言用在他们的项目里。那么,究竟是怎样的一群人在使用Ruby呢?

Ruby Practice – Reflection

Reflection对我来说是个新概念,其含义很简单,我们可以使用支持Reflection的语言写一个能够查看自身信息的程序。第一次接触Reflection,感觉它对于学习语言非常有帮助,我们可以写一些程序来了解语言和运行时系统,不用再去构造“shape, circle, rectangle, draw”之类书卷气十足的例子。

Ruby的Reflection支持从对象查看类,查看类的基类,程序中有那些全局变量、局部变量等等。查看对象的类型很简单,如:

$ irb
irb(main):001:0> 5.class
=> Fixnum

第一次发现程序可以这样写时觉得很惊奇,其实并不难理解,Ruby的立即数的类型是一个类,而不像其它语言那样是一种内建类型,所以,5实际上是一个对象,因此我们可以调用该对象的类型支持的方法,class就是其中之一,它返回该对象的类型。可以看出,5的类型是Fixnum。让我们看看文档里是怎么介绍Fixnum

$ ri -T Fixnum
------------------------------------------------ Class: Fixnum < Integer
     A +Fixnum+ holds +Integer+ values that can be represented in a
     native machine word (minus 1 bit). If any operation on a +Fixnum+
     exceeds this range, the value is automatically converted to a
     +Bignum+.

看来还有一种类型叫Bignum

$ ri -T Bignum
------------------------------------------------ Class: Bignum < Integer
     Bignum objects hold integers outside the range of Fixnum. Bignum
     objects are created automatically when integer calculations would
     otherwise overflow a Fixnum. When a calculation involving Bignum
     objects returns a result that will fit in a Fixnum, the result is
     automatically converted.

似乎没有什么特别值得关注的内容了,让我们回到前面的例子。需要特别指出的是,class的返回值并不是字符串,而是一个类对象,比如我们可以调用superclass查看Fixnum的基类是什么。

irb(main):002:0> 5.class.superclass
=> Integer

如果我们留意前面的文档,会发现第一行有Fixnum < IntegerBignum < Integer这样的表述,看来<是用来描述子类与基类关系的符号。我们的好奇心也被激起了,到底这是怎样一幅类的层次图呢?

$ cat class.rb
#!/usr/bin/ruby

c = 5.class

while c
  s = c.superclass
  if s then
    print c, " < "
  else
    print c
  end
  c = s
end

puts
$ ruby class.rb
Fixnum < Integer < Numeric < Object

噢,我们找到了Object

$ ri -T Object
---------------------------------------------------------- Class: Object
     +Object+ is the parent class of all classes in Ruby. Its methods
     are therefore available to all objects unless explicitly
     overridden.

单根?!这让我想起了使用Delphi的日子……

也许把c作为print的参数让人感觉它是个字符串,其实不然,只不过Ruby在这里做了一次自动的类型转换。下面的程序统计出当前共有352个类,如果想知道都有哪些,可以用puts o代替1。

irb(main):003:0> ObjectSpace.each_object(Class) {|o| 1 }
=> 352

使用ri可以详细了解ObjectSpaceObjectSpace#each_objectClass的详细解释。简单的说,它提供了一种方式遍历所有的类,并对每个类调用花括号之内的程序。两条竖线之间的o代表当前正被遍历到的类,这里的o有点像函数的参数,我们也可以使用其它变量名。下面的程序显示了一个更加完全的类层次:

$ cat objectspace.rb
#!/usr/bin/ruby

c = 5.class
level = 1
while c
  print "#{level}: #{c}"
  ObjectSpace.each_object(Class) do |o|
    print " #{o}" if c.superclass == o.superclass and c != o
  end
  puts
  c = c.superclass
  level += 1
end
$ ruby objectspace.rb
1: Fixnum Bignum
2: Integer Float
3: Numeric Binding UnboundMethod Method Proc Process::Status Time Dir File::Stat
 IO Range MatchData Regexp Struct Hash Array ThreadGroup Continuation Thread
 Exception String FalseClass TrueClass Data Symbol NilClass Module
4: Object

这里的类并不多,把每个类的文档看一遍,不算是过分的要求。

前面的例子都是从5开始的,我们还可以从另一个方向——字符串——做同样的事情,还是从”hello, world”开始吧

irb(main):004:0> "hello, world".class
=> String

接下来的部分就由大家自己完成吧。

好,我们再回到5。

irb(main):005:0> 5.methods

大家可以尝试一下,看看我们都可以对5做什么?不过返回的结果有点乱,不太好看,让我们整理一下。比如看有哪些操作符:

irb(main):006:0> 5.methods.grep(/^W*$/)

如果觉得结果太多也可以选择一部分:

irb(main):007:0> 5.methods.grep(/^w*$/)[0..5]
=> ["upto", "div", "object_id", "times", "singleton_methods", "taint"]

还可以先排序再筛选

irb(main):008:0> 5.methods.sort.grep(/^w*$/)[10..15]
=> ["divmod", "downto", "dup", "extend", "floor", "freeze"]

sortgrep都是基于数组的操作,与Perl中的类似。

其它的如respond_to?kind_of?instance_of?,以及Kernel#global_variablesKernel#local_variables等Reflection功能就留给大家自己玩吧。