本を読む

読書やコンピュータなどに関するメモ

Bash on Railsを作る(10) bashOOをbashOOで作る

 bashの内蔵コマンドだけでいかにRuby on Railsっぽいことをやるかというパロディ企画です。実用性は求められても私が困るので、期待は勘弁してください。でもいちおう、すでに動いてます。

 今回は、「Bash on Railsを作る(8) bashでオブジェクト指向」で予告したように、bashのコマンド実行をオブジェクト指向ふうにするしくみ「bashOO」(ばしょー)の実装を解説します。「bashOOも、bashOO自身で実装しています」と予告したとおりの内容です。

 bashOOは、Bash on RailsのMVCそれぞれの根幹で使われています。

基本的なタネあかし

 賢明な方は気づいていると思いますが、「ore.hello」というメソッド呼び出しは、本当に「ore.hello」という(ドットを含む)bashのコマンド(関数)名の呼び出しです。newとかextendとかは、そういうコマンド(関数)を動的に生成するメソッドです。

ObjectクラスをbashOOで作る:クラスメソッドnew

 最も基本的なクラスであるObjectクラスを、bashOOで作ります。

 まず、インスタンスを作るクラスメソッドnewを定義します。第8回で解説したように、bashOOのクラスメソッドは「(クラス名)::class.(メソッド名)」という関数として定義します。

 ざっくりと、こんな感じの定義になります。

function Object::class.new() {
    local class=$1
    local self=$2
    shift 2

    # 「(クラス名)::instance.(メソッド名)」から
    # 「(インスタンス名).(メソッド名)」を定義
}

 コメントになってる部分の仕様をコードにすると、こんな感じでしょうか。

    local m
    for m in $(search_functions "${class}::instance.*"); do
        m=${m#${class}::instance.}
        eval "function ${self}.${m}() {
                  ${class}::instance.${m} \"$self\" \"\$@\"
              }"
    done

 仕様のとおり、「(クラス名)::instance.*」に該当する関数を探して、「(インスタンス名).(メソッド名)」の関数から呼ぶようにするわけです。

 ここで、search_functionは、引数に文字列パターン(ワイルドカード形式)を受け取り、該当する関数を標準出力にリストアップするコマンド(関数)です。定義は以下のとおり。

function search_functions() {
    local ptn=$1
    local func line

    typeset -F | while read line; do
        func=${line##* }
        [[ "$func" == ${ptn} ]] && echo $func
    done
}

 ようは、「typeset -F | grep パターン」みたいなののpure bash版です。

 ここでついでに、「(インスタンス名).class」というインスタンスメソッドの関数を特例で定義してみます。

    eval "function ${self}.class() {
              echo ${class}
          }"

 最初は「(インスタンス名)_class」という変数に入れようとしたのですが、インスタンス名に「:」などが含まれると変数名にできないので、特例としました。

 あと、コンストラクタが定義されていれば、ここで呼び出します。

    if type -t ${self}.initialize > /dev/null; then
	"${self}.initialize" "$@"
    fi

ObjectクラスをbashOOで作る:インスタンスメソッドdelete

 クラスメソッドの次はインスタンスメソッドです。なんでもいいのですが、ここでは自分自身を消滅させるdeleteメソッドを定義してみます。

 上にも書いたように、インスタンスメソッドは「(クラス名)::instance.(メソッド名)」という関数として定義します。

function Object::instance.delete() {
    local self=$1
    local fun

    unset "$self"
    eval "unset \${!${self}_*}"
    for fun in $(search_functions "${self}.*"); do
        unset -f "$fun"
    done
}

 search_functionsは上で説明したとおりです。

 なお、${!文字列パターン}は、変数名をワイルドカード展開するbashの記法です。たとえば、hoge_a、hoge_b、hoge_cという変数があるとき、「${!hoge_*}」は「hoge_a hoge_b hoge_c」という文字列に展開されます。変数の間接参照とまぎらわしいので、詳しくはman bashを。

親クラスからObjectクラスを作る…どこから?

 第8回で解説したように、クラスメソッドとインスタンスメソッドを定義したら、「(親クラス名).extend」というクラスメソッドを実行すれば、新しいクラスが定義されます。

 では、最も基本的なクラスであるObjectクラスは、どこからextendするのでしょうか。

 bashOOでは、存在しない空のクラスからextendするものと考えます。ただし、まだbashOOが存在しないので、「(クラス名)::class.extend」という、クラスメソッド定義を直接呼び出します。

::class.extend '' Object

 bashOOをブートストラップする仕組み

 というわけで、「::class.extend」というコマンド(関数)を定義しましょう。構造としては、以下のとおりです。

function ::class.extend() {
    local super=$1
    local class=$2

    # 親クラスのクラスメソッドとインスタンスメソッドを継承

    # 「(クラス名)::class.(メソッド名)」から
    # 「(クラス名).(メソッド名)」を定義

 まずクラスメソッドの定義。これは、newでインスタンスメソッドを定義するときと同じように書けます。

    for m in $(search_functions "${class}::class.*"); do
        m=${m#${class}::class.}
        eval "function ${class}.${m}() {
                  ${class}::class.${m} \"$class\" \"\$@\"
              }"
    done

 その前に、親クラスからの継承を処理します。前もって定義しておくことによって、子クラス(定義するクラス)で定義したときに、親クラスの定義を後から上書きしてくれます。

    local t m
    for t in class instance; do
	for m in $(search_functions "${super}::${t}.*"); do
            m=${m#${super}::${t}.}
	    if ! type -t "${class}::${t}.${m}" > /dev/null; then
		eval "function ${class}::${t}.${m}() {
                          ${super}::${t}.${m} \"\$@\"
                      }"
	    fi
	done
    done

 ここで注目してほしいのは、Objectクラスを作ったときには、第1引数superが''として渡されていたことです。これにより、この関数自身である「::class.extend」が、親クラスのクラスメソッドとしてObjectクラスに継承され、以後のクラスで「extend」メソッドが使えるというわけです。

まとめ

 bashの解釈系(シンタックス)の中で、オブジェクト指向っぽい書き方(セマンティックス)を実現するために、実装方法を解説しました。

 できてみると単純なしくみですが、作っているときは論理のつじつまをあわせるのに丸1日ぐらい悩んでました。いや、文系なんで。

 次回は、また軽めに、pure bashでの文字列処理について書こうかと思います。

コメント

コメントの投稿

管理者にだけ表示を許可する

トラックバック

http://emasaka.blog65.fc2.com/tb.php/362-a38c1b98

 | HOME | 

Categories

Recent Entries

Recent Comments

Recent Trackbacks

Appendix

emasaka

emasaka

フリーター。
連絡先はこのへん

Monthly


FC2Ad