Perlでクラスを使う
2022/07/15
構造化データはアクセスが難しい(忘れてしまう)
  • 前置き長いです...。オブジェクト指向に慣れている方はこちらへスキップして下さい。

  • 以前書いたPerlプログラミングに関する"レポート"にて「Perlは構造化データを作りやすい」ことを説明しました。例えば下記モデルがあるとします。"top"モジュールの下に"A"というインスタンス名で"sub_a"モジュールを置いています、それぞれのポートをワイヤで結んでいるイメージです。
    +-top-------------------------------------+
    |             +-sub_a A---+               |
    |in_0--in_a0--|in_0  out_0|--out_a0--out_0|
    |in_1--in_a1--|in_1       |               |
    |             +-----------+               |
    +-----------------------------------------+
    

  • このデータ構造を表現すると、例として下記のようになるでしょう。
    $obj->{name}='top';
        ->{ports}->['in_0', 'in_1', 'out_0'];
        ->{wires}->{in_a0}->['in_0', 'A.in_0'];
                 ->{in_a1}->['in_1', 'A.in_1'];
                 ->{out_a0}->['out_0', 'A.out_0'];
                 
        ->{instances}->{A}->{name}='mod_a';
                          ->{ports}=['in_0', 'in_1', 'out_0'];
                          ...
  • このデータをたどれば接続関係はわかります。例えばワイヤ"in_a1"の接続先は下記のように出力できます。
    for (my $i=0; $i<@{$obj->{wires}->{in_a1}}; $i++) {
        print $obj->{wires}->{in_a1}->[$i],"\n";
    }
  • ですが、スクリプトを作っている最中や作ったばかりなら良いですが、しばらく時間が経ってから何かをしようとした場合、データ構造を忘れてしまっています。対応として、処理がシンプルになる関数を作ることでしょう  my @connections = getConnections($obj, 'in_a1');

  • 上記からわかるように、構造化データとアクセス/処理関数は密接に関わっているので、セットで扱うべきものです。セット...そうですね...「同じ仲間」に入れる...「クラス」とすれば便利そうです。

  • Perlで「同じ仲間/クラス」として関数をまとめる仕組みは package です。一般的に名前空間(name space)と呼ばれるものです。'use パッケージ名;'って見たことありますよね。

  • packageの関数は処理対象データと密接に関わっているので、データ(オブジェクト)をどのpackageで扱う想定かわかれば適用関数も明確になります。このときデータ(オブジェクト)を中心にしてプログラミングするので、まさに「オブジェクト指向プログラミング(OOP:Object Oriented Programming)」になります。

  • 最初からOOPを念頭に置いた言語では、データ(オブジェクト)と関数を紐づける仕組みとして'Class'を持っています。PerlはClassを持ちませんがオブジェクトにpackage名情報を付ければ「クラス」として機能します。(*1)

  • PerlでOOPするために、オブジェクトにpackage名情報を追加する仕掛け...それが'bless'です。これによって'package'を「クラス」として扱うことができるようになります。

Perlのクラスは処理対象オブジェクトにpackage情報を与えたもの
  • 少し強引かもしれませんが、オブジェクト指向プログラミング(OOP)におけるクラスとは関数と処理対象データを仲間としてまとめたものと考えることができます。

  • Perlではpackageを使用すれば関数群をまとめることができます。そしてpackageの仲間としてデータのオブジェクトを加えるにはblessを使用します。これによってpackageをクラスとして扱うことが可能になります。下記が記述例(BuildVerilogクラス)です。
    #!perl -w
    package BuildVerilog; # パッケージ名/クラス名
    use strict;
    
    # ==============================================================================
    # コンストラクタ
    sub new {
        my $obj = {};            # 無名ハッシュのリファレンス
        
        bless $obj, __PACKAGE__; # __PACKAGE__は所属package名を持つ特殊変数
        
        return $obj;             # blessされた$objを戻す
    }
    
    1; # package最終行に付けるPerlの慣習。true値を返す。無いとコンパイルエラーになる。

  • 上記のBuildVerilogクラスを使用するには、下記のようにコンストラクタを実行します。コンストラクタもpackage関数の一つなのでnewである必要性はありませんが、一般的にはnewと名付けるのが普通です。
    #!perl -w
    use lib './';
    use BuildVerilog;
    use strict;
    
    {
        my $obj = BuildVerilog->new(); # BuildVerilogに属するデータのオブジェクト
    }

メソッドの追加と使用
  • それでは、BuildVerilogクラスにメソッド(setModuleName, getModuleName)を追加してみましょう。とは言ってもpackageに関数を追加するだけですが、引数の定義に注目して下さい。先頭が処理対象のデータオブジェクトになっています。
    #!perl -w
    package BuildVerilog; # パッケージ名/クラス名
    use strict;
    
    # ==============================================================================
    # コンストラクタ
    sub new {
        my $obj = {};            # 無名ハッシュのリファレンス
        
        bless $obj, __PACKAGE__; # __PACKAGE__は所属package名を持つ特殊変数
        
        return $obj;             # blessされた$objを戻す
    }
    
    # ==============================================================================
    # モジュール名設定
    sub setModuleName {
        my ($obj, $top_module_name) = @_; # 先頭が$objであることに注目
        
        $obj->{name} = $top_module_name;
    }
    
    # ==============================================================================
    # モジュール名取得
    sub getModuleName {
        my ($obj) = @_;
        
        return $obj->{name};
    }
    
    1; # package最終行に付けるPerlの慣習。true値を返す。無いとコンパイルエラーになる。

  • メソッド呼び出しはオブジェクトにアロー演算子を使用します。オブジェクトがクラス名/package名情報を持っていることがわかりますね。そして呼び出し元オブジェクト自身が「1番目の引数」としてメソッドに渡されることがポイントです。
    #!perl -w
    use lib './';
    use BuildVerilog;
    use strict;
    
    {
        my $obj = BuildVerilog->new(); # BuildVerilogに属するデータのオブジェクト
        
        $obj->setModuleName('top');    # setModuleName()呼び出し
        
        print $obj->getModuleName();   # getModuleName()呼び出し
    }

インスタンス
  • コンストラクタで戻るオブジェクトは処理対象データのリファレンスなので、もう一度コンストラクタを実行すれば別のリファレンス、つまり別のインスタンスとして振る舞います。

  • 例として下記コードを見て下さい。モジュール名が別々にセットされていることがわかります。
    #!perl -w
    use lib './';
    use BuildVerilog;
    use strict;
    
    {
        my $obj_0 = BuildVerilog->new(); # インスタンス0
        my $obj_1 = BuildVerilog->new(); # インスタンス1
        
        $obj_0->setModuleName('top_0');  # インスタンス0 モジュール名設定
        $obj_1->setModuleName('top_1');  # インスタンス1 モジュール名設定
        
        print 'インスタンス0: ',$obj_0->getModuleName(),"\n"; # インスタンス0 モジュール名表示
        print 'インスタンス1: ',$obj_1->getModuleName(),"\n"; # インスタンス1 モジュール名設定
    }
    >runme.pl インスタンス0: top_0 インスタンス1: top_1

クラスを積極的に使いましょう
  • Perlのクラスは、処理対象データオブジェクトにblessでpackageの名前情報を与え、関数とセットにして使えるようにしているだけです。かなりシンプルです。

  • クラス一般の利点ですが、複雑なデータ構造を隠蔽して使いやすく作ることができます。また、途中でデータ構造自身をリファクタリングすることも可能です。

  • また今回紹介していませんが演算子オーバーロードも可能です。複雑な構造のデータを直感的に操作できるようになります。

  • 似たようなツールやアプリケーションを複数個作成する場合など、ベース部分をクラスにしておくと便利に使えます。積極的な利用をお勧めします。
Notes
  • VBScriptですらClassを持っていました。Perlは古い...いや、長い間親しまれている言語ですから。
Copyright(C) 2022 Altmo
本HPについて