到今天我還記得Pascal教科書上讓我印象深刻的那句話:「Algorithms + Data Structures = Programs」。非常古典的看法,它宣告著,有了演算法加上適當的資料結構,等於擁有了通往電腦魔法世界的鑰匙;在當時,這似乎是永恆的真理,畢竟,美國太空總署透過Fortran,神奇地把太空人送上了月球,不是嗎?
但一般程式語言所提供的資料結構是平凡、單調而且僵固的。Pascal的資料結構尤其讓我倒胃口,不但要事先宣告型態與大小,而且又有許多的語法限制,在產生神奇的魔法效果之前,得先經過編譯器(Compiler)黑森林的重重刁難之後,才能勉強執行,更困難的是,想要把現實世界的東西,塞進電腦世界的資料結構裡面,沒有添加一點想像力,耐心的迂迴前進是不容易辦到的。
Perl 的資料結構,基本上說來是蠻單純的,但神奇的是,透過Reference的使用,可以建構成相當複雜有用的資料結構,除了之前所提到的聖經內容,也可以是一個網頁內容的樹狀結構,或者,把一間公司所有網路設備的資料放入資料結構裡。使用過這樣強大又具彈性的資料處理能力,再參考以下範例,終於可以體會這句話的意思:Algorithms + Data Structures = Programs。
use strict;
use LWP::Simple;
use HTML::TreeBuilder;
my $html=get("http://www.apress.com");
my $tree=HTML::TreeBuilder->new;
$tree->parse($html);
$tree->eof;
my @pnodes=$tree->find_by_tag_name('meta');
foreach my $node (@pnodes) {
print $node->as_HTML;
print "\n";
}
$tree->delete;
變化是無止盡的,接下來「物件導向程式設計」概念,衝擊著習慣第三代程式語言的程式設計師們,然而Perl並沒有拋棄我們,程式設計的原理原則是不會變的,它只是不斷地融合再生,就像上面的範例,就是不折不扣的物件導向程式呢。
為什麼需要「物件導向程式設計」,我認為最重要的原因之一是,藉由抽象所帶來的單純化,讓程式碼容易撰寫(模組化)、容易維護及reuse。我們不需要知道,如何去剖析組成網頁的HTML內容,也不用知道剖析後的資料結構,只要知道如何透過物件的介面去使用就可以了。
Moose 是Perl的一個模組,它讓我們可以撰寫 Perl物件導向程式。我試著把之前所建立DBM::Deep裡的聖經資料,加上物件的介面,我們便可以提供一個間單的讀經介面。
# Bible.pm
package Bible;
use Moose;
use DBM::Deep;
use List::Util qw(max min);
# 透過變數$bible連結DBM::Deep檔
my $bible = DBM::Deep->new( "bible_ary.dmd" )->{bible};
#properties
has readbook => (is => 'ro', isa => 'DBM::Deep', writer => 'set_readbook');
has opened => (is => 'ro', isa => 'Str', writer => 'set_opened');
has pointer => (is => 'ro', isa => 'Int', writer => 'set_pointer');
has nBooks => (is => 'ro', isa => 'Int', default => $#{$bible});
has booklines => (is => 'ro', isa => 'Int', default => 0, writer => 'set_booklines');
#methods
sub open {
my $self = shift;
my $book = shift || '創世紀';
if ($self->findbook($book)) {
print $self->opened, " opened\n";
}
else {
print "$book not found!!\n";
}
}
sub findbook {
# 由篇名尋找
my $self = shift;
my $tofind = shift;
for my $index ( 0 .. $self->nBooks ) {
if ( $bible->[$index]{bookname}{chn} eq $tofind
or $bible->[$index]{bookname}{eng} eq $tofind ) {
$self->set_readbook ( $bible->[$index]{words} );
$self->set_opened ( $tofind );
$self->set_booklines( $#{$self->readbook} );
$self->set_pointer ( 0 );
return 1;
}
}
# mark no book opened
$self->set_booklines( 0 );
return;
}
sub read {
my $self = shift;
my $nline = shift || 10; # 一目十行
my $from;
my $to;
return unless $self->booklines;
$nline = 10 if $nline < 0;
for (0 .. $nline - 1) {
$self->readline;
$self->incre_pointer;
last if $self->pointer == $self->booklines;
}
}
sub incre_pointer {
my $self = shift;
$self->set_pointer( min( $self->pointer+1, $self->booklines ) );
}
sub readline {
# 顯示現在pointer所指向的那一行
my $self = shift;
printf "%-5s %s\n",
$self->readbook->[$self->pointer]{index},
$self->readbook->[$self->pointer]{word},;
}
sub lookup {
# 翻閱至特定章節。 例.chapter(3,2) 第三章第二節
my ($self, $chap, $chap_sub) = @_;
for my $line (0 .. $self->booklines) {
my ($r_chap, $r_chap_sub) = split /:/, $self->readbook->[$line]{index};
if ($r_chap >= $chap && $r_chap_sub >= $chap_sub) {
$self->set_pointer($line);
$self->readline;
last;
}
}
return;
}
1;
有了Bible這個物件之後,我們的程式就不需要知道DBM::Deep或者YAML裡聖經章節的資料結構,只要透過物件去使用就可以了(就算以後我把聖經放到雲端資料庫裡面也是一樣!)。
use Bible;
my $bible = new Bible;
$bible->open('馬太福音');
$bible->read();
$bible->lookup(2,5);
$bible->read();
物件導向不是萬靈丹,它是程序導向方式的演化,良好的抽象介面,讓內部的細節隱藏,以便因應日趨複雜的資訊系統。"Easy things should be easy and hard things should be possible".
這幾年來,我從學習Perl中得到許多知識與樂趣,也由於資訊領域快速發展的特性,讓我可以經常的藉由Perl去探索新的事物。學習是無止盡的,如果學習過程能夠充滿了樂趣,我們會高興知道這個事實,並且樂在其中。
後記. 之所以會寫這幾篇Perl學習之旅的網誌,是因為前兩個星期,無意間知道這個模組Number::RecordLocator 所引起的。


