2010年7月12日 星期一

Perl學習之旅的回顧(之四)

從大學時代初次學習Pascal程式語言開始,以及後來陸續學習的Fortran、C、Basic、FORTH以及Perl都是屬於指令式程式設計 (Imperative programming),也就是一步一步告訴電腦如何執行我們所設計的程序。

到今天我還記得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 所引起的。

沒有留言:

張貼留言