七月 30, 2006

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

Work for fun

不知道该称为忙里偷闲还是闲里偷忙,总之,做了一个星期自己想做的事。

很少有像这一周这么累过,每天下班后感觉大脑就像一块砖头,没法像以前一样看看书,学点啥;中午看blog也都匆匆忙忙,有时甚至就不看了,更别提写blog了。做自己想做的事很有劲头,一头扎下去,抬起头来的时候就到了下班的时间。这种感觉很久都没有了,甚至可能从来都没有过。

很踏实,也很累。这样下去估计撑不了多久,所以,打算下周还是做些日常工作,偷懒也好,尽责也好,总之,劳逸结合嘛!说来好笑,最有成效的一周,可能会被认为不务正业,平时尽管闲着,却也算尽职尽责,有时甚至觉得,自己将随着这个项目一起,被丢进历史的垃圾堆,所以,当听说这个项目前途未卜时,反到有一丝兴奋。更讽刺的是,越是这种时候,一些应该做的事,才有可能做成,毕竟,一个没有明天的项目,人们也懒得阻挠来反对去的,随你怎么去“糟蹋”。

这一周网上最有趣的事,是有人对几位著名的程序员做了一次采访被采访者之一也谈了自己的感想,值得一读。

七月 15, 2006

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功能就留给大家自己玩吧。

七月 13, 2006

Open-End Funds Tracking Tool

在同事的帮助下开始尝试投资基金,然而每天统计收益是件头疼的事,于是写了一个Perl脚本来做这件事。参见中国开放式基金投资跟踪工具

七月 10, 2006

Rename: jpg2jpeg

Fedora Core 5不喜欢jpg这个后缀,认为存在安全隐患,所以Gnome程序都不把jpg文件当成是图片,没有关联任何应用程序,而且Nautilus不会显示预览,gthumb里面也不会看到,除非把后缀名改为jpeg。然而,我的柯达导入的照片全部是以jpg作为后缀名的,只好写了一个Perl脚本,完成从jpg到jpeg的重命名工作。

#!/usr/bin/perl
use strict;
use warnings;
use File::Find;

my @dir;

push @dir, '.';
for my $additional_directory (@ARGV) {
if (-e $additional_directory and -d _) {
push @dir, $additional_directory;
}
else {
warn "$additional_directory not exist or not a directory, skip.\n";
}
}

find(\&jpg2jpeg, @dir);

sub jpg2jpeg {
if (/(.+)\.jpg$/i) {
my $filename = "$File::Find::dir/$1.jpeg";
print "rename $File::Find::name to $filename ...\n";
rename $File::Find::name, $filename;
}
}

七月 06, 2006

Claim my feed at Bloglines

Bloglines提供了认领feed的功能,尝试一下。

成功了,这下连以前的问题也解决了,在Bloglines里面我的blog网站地址也纠正过来了,不再是从前那个,而且就算以后再搬的话也可以自己手工去改了。唯一的遗憾是原来Bloglines里面显示有14个订阅者,现在都没有了。

七月 03, 2006

Ruby Practice - Getting Started

读了两部关于Ruby的著作——Programming Ruby(第一版)和Why's (poignant) guide to Ruby (PDF),感觉像狗熊掰苞米,读的时候全懂,读完了全忘了。另外,关于Ruby的资料比较少,更新也不是太及时,参考手册还是关于1.4.6版本的,而Ruby已经是1.8.4了。Ruby的书就更少了,国内似乎还没有卖的?!不知道大家都是怎么学的?当然,对于“不求甚解”的人来说,现有的资料也够用了,只是读起来有点烦,冗余的东西太多,毕竟咱不是刚开始学编程,有些初级的东西还要翻来覆去地讲,就显得罗嗦了。还有就是实践讲得少,我很想知道有没有类似CPAN的东西,如果有的话,它在哪里?如果没有,我该到哪里去找库,然后如何安装、使用等等。

Ruby是面向对象的,而我学过C++;Ruby是脚本语言,我也用过Perl。我相信,这两方面的背景对于学习Ruby肯定是有帮助的,所以,我很希望能够看到这样的学习资料,它能利用我所拥有的这些知识,一方面去除冗余信息,另一方面通过适当比较,让我迅速上手Ruby。

遗憾的是,目前还没有找到既讲述实践过程,又能利用我的背景知识的学习资料,没办法,只好自己站出来,为自己写一份文档,顺便填补一下国内(国际?)空白吧。:-)

由于是初学Ruby,而且第一次写这样的文档,希望大家多多批评,多提宝贵意见。

言归正传。

Ruby的官方网站地址是http://www.ruby-lang.org/en/,它还有很多附属网站,如ruby-docrubygardenrubyforge,和Ruby Central等等,在官方网站上都有链接,可惜其中几个我这里无法访问,说什么“服务器响应时间过长”,不知道为什么。Ruby中国尚在建设中,还没有内容,不知道是谁在做?

Ruby的语言简介列出了它的重要特性,这里不再赘述。我也不打算推销Ruby,是否要学它由每个人自己决定,我个人的目的是想看看传说中的Ruby on Rails到底是什么样子,以及如何利用它做开发。当然,这份文档将尽力为那些已经决定开始学习Ruby的朋友提供帮助。

Ruby是一种脚本语言,所以,必须有一个解释器,才能运行Ruby程序,官方下载页面为http://www.ruby-lang.org/en/20020102.html。Ruby支持多种平台,对于Fedora用户来说,可以使用如下方式安装Ruby及其相应工具。

$ su -c 'yum install ruby ruby-libs ruby-devel ruby-irb ruby-ri ruby-docs'
ruby
这个rpm包含有Ruby语言的解释器:ruby。
ruby-irb
这个rpm包会安装一个名为irb的执行程序,它是交互式Ruby(Interactive Ruby)的简称,用来从标准输入读入并执行Ruby表达式的工具,像一个shell。这种工具太适合我这种懒人了,免去了创建文件夹、命名文件等一系列繁琐的工作。不知道Perl有没有相应的工具?
ruby-ri
和Perl一样,Ruby也设计了嵌入式文档。ri就是查看文档的工具,令我好奇的是,为什么它不叫rubydoc,而是起了这么一个古怪的名字?

让我们从“Hello, world”开始吧!

$ irb
irb(main):001:0> print "hello, world\n"
hello, world
=> nil
irb(main):002:0>

大于号之前是irb的提示符,如irb(main):001:0>其中irb指当前运行的程序名字,main指当前对象的名字,001指当前行数,0指缩进几层。在我们输入print "hello, world"后,irb打印出hello, world=> nil,其中hello, world是print打印出来的内容,=>后面是其返回值,nil相当于说print的返回值为void。我们也可以把这个程序存到文件(如hello.rb)里,Emacs有ruby-mode,方便写Ruby程序,不过还没有rubydb,所以调试程序可能还是要在命令行下。

#!/usr/bin/ruby

print "hello, world\n"

然后运行这个程序:

$ ruby hello.rb
hello, world

这时屏幕上只会打出hello, world,返回值信息就没有了。Ruby的命令行选项几乎和Perl一模一样,比如检查语法的-c选项。

$ ruby -c hello.rb
Syntax OK

值得注意的是,同一个程序用ruby执行和在irb里面运行可能会得到不同的结果,原因是irb读一行执行一行,而ruby读入整个程序之后才会执行,因此一些无法通过(ruby)编译的程序却可以在irb里面运行。

下面让我们看看print是怎么回事。

$ ri print
More than one method matched your request. You can refine
your search by asking for information on one of:

Kernel#sprintf, Kernel#printf, Kernel#print,
Zlib::GzipWriter#printf, Zlib::GzipWriter#print, StringIO#printf,
StringIO#print, IO#printf, IO#print, CGI#print
/tmp/ri_4875.0 (END)

Perl缺省的package是main,而Ruby里面是Kernel,准确的说应该叫module。先不管它,让我们看看Kernel#print,先按q退出当前ri,然后:

$ ri Kernel#print
----------------------------------------------------------- Kernel#print
print(obj, ...) => nil
------------------------------------------------------------------------
Prints each object in turn to +$stdout+. If the output field
separator (+$,+) is not +nil+, its contents will appear between
each field. If the output record separator (+$\+) is not +nil+, it
will be appended to the output. If no arguments are given, prints
+$_+. Objects that aren't strings will be converted by calling
their +to_s+ method.

print "cat", [1,2,3], 99, "\n"
$, = ", "
$\ = "\n"
print "cat", [1,2,3], 99

_produces:_

cat12399
cat, 1, 2, 3, 99

/tmp/ri_4896.0 (END)

让我们暂时忽略几个熟悉的面孔($,$\),先掌握好这几个命令行工具,再来学习语法细节吧。

七月 01, 2006

Encode

这两天在用Perl写一个程序,从一个网页上抽取出某个表格的内容,然后存起来供日后使用。

取网页用LWP::UserAgent,分析表格使用HTML::TableExtract,保存使用Config::General。然而,在用Config::General读取它自己存的文件时出了问题,根源在于编码。网页编码是GB2312,而Perl的内部表示使用Unicode,所以Perl把中文字符看成是一系列奇怪的字节,而Config::General偏偏使用chr(182)作为一个特殊的标志,用来处理Here-Document。当某些中文字符恰好含有这个字节的时候,Config::General就被骗了。

一度,我只好把数据存为CSV格式。

后来在Advanced Perl Programming第二版上看到6.5节Encode后,把网页内容读入后用decode处理一下,在print出去之前再用encode处理一下,就可以了。而Config::General保存的文件本身就是Unicode,所以无需使用Encode来处理。

举个例子

use LWP::UserAgent;
use Encode;

$browser = new LWP::UserAgent;
$response = $browser->get('http://some.where');
$content = decode('euc-cn', $response->content);

这样,一个汉字就会被当成一个字,而不是一系列字节。这里euc-cn就代表gb2312,对于Encode来说它们是同义词,或者说gb2312是euc-cn的alias。

直接打印经过decode的字符串会产生警告,去掉警告的方法是再encode一下,如:

$content = encode('utf8', $content);
print $content, "\n";