Scilabでのオブジェクト指向プログラミングの方法

Scilab

Scilab は現時点(バージョン 6.0.2)ではまだ、オブジェクト指向プログラミング(OOP)をサポートする機能を提供していませんが、既存の機能をうまく使って OOP 風のプログラミングをすることは可能です。Scilab での OOP の方法については、参考文献[1][2][3]で紹介されていますが、私は少し改良した方法を用いています。この記事では、Scilab での OOP の方法について、私の方法も含めて解説します。モジュールや GUI ツールなど、比較的規模の大きいプログラムを書くときには、OOP が役立ちます。良かったら試してみてください。

アウトライン

この記事は、次のような流れで話を進めます。まず、基礎となるデータ構造 “tlist” について解説します。次に、参考文献から2つの方法を紹介し、それぞれの欠点について述べます。最後に、その2つの方法を基に私が編み出した代替方法を紹介します。

基礎となるデータ構造 “tlist”

Scilab でオブジェクト指向プログラミングをする上で、基礎となるデータ構造は “tlist” です。tlist は型付のリスト(typed list)で、任意のデータ型を要素(フィールド)に格納でき、また各要素は名前(フィールド名)で参照することができます。さらに、tlist は新しいデータ型を定義し、組み込み関数や演算子を、その新しいデータ型に対して多重定義(オーバーロード)することができます。

以下のセッションでは、“Person”というデータ型で、“name” と “age” という名前の2つのフィールドを持つ tlist を作成しています。各要素にフィールド名でアクセスし、読み書きができることが分かります。

--> p = tlist(['Person', 'name', 'age'], 'Mike', 25); 

--> typeof(p)
 ans  =
 Person

--> p.name
 ans  =
 Mike

--> p.age
 ans  =
   25.

--> p.age = p.age + 1;

--> p.age
 ans  =
   26.

また、次のような関数を定義すれば、上で作成した “Person” データ型に対し、表示と「+」演算子をオーバーロードします。後者では、「Person + 数値」の演算が呼び出されたとき、Person の age フィールドの値に数値を加算した上で、その Person を返すようにしています。オーバーロードをするための関数の名前は、“%” で始まる規定のパターンになっています。(Scilabヘルプの「overloading」の項を参照

// 表示のオーバーロード
function %Person_p(person)
    mprintf("Person:\n");
    mprintf("==============\n");
    mprintf("name: %s\n", person.name);
    mprintf("age: %d\n", person.age);
endfunction

// 演算子のオーバーロード: Person + constant(数値)
function person = %Person_a_s(person, n)
    person.age = person.age + n;
endfunction

以下は、オーバーロードの確認です。

--> p
 p  = 
Person:
==============
name: Mike
age: 26

--> p = p + 10
 p  = 
Person:
==============
name: Mike
age: 36

tlist は非常に柔軟で多用途なデータ構造で、Scilab におけるオブジェクト指向プログラミングは tlist を基礎として行います。

参考文献の方法①

参考文献[1][2]では、クラス名が付いた関数群をコンストラクタおよびメソッドとして用いる方法が提案されています。次に示すプログラムは Person クラスを定義します。Person_new() は Person 型のオブジェクト(tlist)を生成するコンストラクタ、Person_say() と Person_grow() は Person 型のオブジェクトを操作するメソッドです。

// 参考文献[1][2]の方法によるPersonクラスの定義

function this = Person_new(name, age)
    this = tlist(['Person', 'name', 'age'], name, age);
endfunction

function Person_say(this)
    mprintf("Hello! My name is %s. I am %d years old.\n", this.name, this.age);
endfunction

function this = Person_grow(this)
    this.age = this.age + 1;
endfunction

上で定義した Person クラスは次のように使用します。

--> p = Person_new('Mike', 25);

--> Person_say(p);
Hello! My name is Mike. I am 25 years old.

--> p = Person_grow(p);

--> Person_say(p);
Hello! My name is Mike. I am 26 years old.

Person_grow() のように、オブジェクトのデータを変更する場合には、自己代入が必要となります。Scilabでは関数への参照渡しができないため、入力引数に与えられたオブジェクトのデータを関数の中から書き換えることができないのです。

さて、この「クラス名がついた関数群」を用いる方法はシンプルで良いのですが、欠点が2つあります。1つ目は、コマンド “genlib” で作成するライブラリにおいてクラスを定義したいとき、1つの .sci ファイルに1つの公開関数しか定義できないので[4]、クラスの各メソッドを別々のファイルに定義しなくてはならないことです。これではメソッドが多い場合に管理が大変です。2つ目の欠点は、あるクラスを拡張した派生クラスを定義したいときに、メソッドを継承する単純な方法がないことです。

参考文献の方法②

参考文献[3]では、Scilabの関数ポインタの機能を利用して、メソッド関数の関数ポインタをオブジェクトのフィールドに格納する方法を取っています。次に示すのは、この方法による Person クラスの定義です。コンストラクタ Person_new() で生成されるオブジェクトのフィールド “say” と “grow” に、それぞれ関数ポインタ Person_say と Person_grow が格納されています。これにより、メソッドをオブジェクト経由で呼び出すことができます。

// 参考文献[3]の方法によるPersonクラスの定義

function this = Person_new(name, age)
    this = tlist(['Person', 'name', 'age', 'say', 'grow'],..
        name, age, Person_say, Person_grow);
endfunction

function Person_say(this)
    mprintf("Hello! My name is %s. I am %d years old.\n", this.name, this.age);
endfunction

function this = Person_grow(this)
    this.age = this.age + 1;
endfunction

以下は、この Person クラスの使用例です。

--> p = Person_new('Mike', 25);

--> p.say(p);
Hello! My name is Mike. I am 25 years old.

--> p = p.grow(p);

--> p.say(p);
Hello! My name is Mike. I am 26 years old.

この方法を用いると、コンストラクタだけを公開関数とすれば良いので、genlib で作成するライブラリにおいてクラスを定義したい場合にも、1つの .sci ファイルにすべてのメソッドを定義することができます。またメソッドの継承も、tlist のフィールドを継承するだけで良いので簡単にできます(継承の仕方については次項を参照)。つまり、前項の関数群を直接用いる方法が抱えていた2つの欠点を、この方法は持ちません。

ただ、この方法にも欠点があります。それは、メソッドを呼び出すときの文が不自然であることです。p.grow(p) や p.say(p) など、オブジェクトを指定してメソッドを呼び出しているのに、引数としてまたそのオブジェクト自身を与えなければいけないところが非常に不自然で、読みにくさを感じます。

代替方法の紹介

上で紹介した2つの方法を基に私が編み出した代替方法は、次に示すようなクラス定義です。ここでは2種類の tlist を用います。一つはクラスを表す tlist、もう一つはオブジェクトを表す tlist です。オブジェクトのコンストラクタおよびメソッドは、クラスを表す tlist のフィールドに関数ポインタを格納します。

// 代替方法によるPersonクラスの定義

function Person = PersonClass()
    Person = tlist(['PersonClass', 'new', 'say', 'grow'],..
        Person_new, Person_say, Person_grow);
endfunction

function this = Person_new(name, age)
    this = tlist(['Person', 'name', 'age'],..
        name, age);
endfunction

function Person_say(this)
    mprintf("Hello! My name is %s. I am %d years old.\n", this.name, this.age);
endfunction

function this = Person_grow(this)
    this.age = this.age + 1;
endfunction

この Person クラスは次のように使用します。まず、公開関数 PersonClass() によって、「Person クラスオブジェクト」を作成します。次に Person クラスオブジェクトに含まれるコンストラクタ Person.new() で「Person オブジェクト」を作成します。メソッドはすべて Person クラスオブジェクトのフィールドに含まれているので、メソッドの呼び出しは Person.method(p, …) という形になります。

--> Person = PersonClass(); // Personクラスオブジェクトを作成

--> p = Person.new('Mike', 25); // Personオブジェクトを作成

--> Person.say(p);
Hello! My name is Mike. I am 25 years old.

--> p = Person.grow(p);

--> Person.say(p)
Hello! My name is Mike. I am 26 years old.

この代替方法は、先に示した2つの方法のいいとこどりをしたようなものです。公開関数は PersonClass() だけで良いので、genlib で作成するライブラリにおいても、クラスの定義を一つの .sci ファイルにまとめられます。また、メソッドの呼び出し文も、先の方法②より分かりやすいものになっています。

クラスの定義をより簡便にするために、次のような関数を定義します。この関数は新しい tlist を生成するもので、第1引数に型名、第2引数にフィールドの名前と値を指定する struct、オプションの第3引数には基底となる tlist を与えます。

function derivedTList = createTList(typeName, fieldsStruct, varargin)
    if argn(2) >= 3 then
        baseTList = varargin(1);
        derivedTList = baseTList;
        derivedTList(1)(1) = typeName; // データ型名を変更
    else 
        derivedTList = tlist(typeName);
    end
    fieldNames = fieldnames(fieldsStruct);
    for i = 1:size(fieldNames, 1)
        fieldName = fieldNames(i);
        if ~isfield(derivedTList, fieldName) then
            derivedTList(1)($+1) = fieldName; // フィールドを追加
        end
        derivedTList(fieldName) = fieldsStruct(fieldName);
    end
endfunction

この createTList() 関数を用いれば、Person クラスの定義を次のように書き換えることができます。フィールド名とフィールド値を隣り合わせに記述できるので、tlist 関数を直接使うよりも分かりやすいクラス定義ができます。

// createTList関数を用いた、代替方法によるPersonクラスの定義

function Person = PersonClass()
    Person = createTList('PersonClass', struct(..
        'new', Person_new,..
        'say', Person_say,..
        'grow', Person_grow..
    ));
endfunction

function this = Person_new(name, age)
    this = createTList('Person', struct(..
        'name', name,..
        'age', age..
    ));
endfunction

function Person_say(this)
    mprintf("Hello! My name is %s. I am %d years old.", this.name, this.age);
endfunction

function this = Person_grow(this)
    this.age = this.age + 1;
endfunction

また、createTList() 関数を使えば、派生クラスの定義も明快に行えます。以下は、 Person クラスを拡張(継承)した Resident クラスの定義です。

// 代替方法による、Personクラスを拡張したResidentクラスの定義

function Resident = ResidentClass()
    Person = PersonClass();
    Resident = createTList('ResidentClass', struct(..
        'new', Resident_new,..  // コンストラクタ
        'say', Resident_say,..  // sayメソッドを再定義
        'move', Resident_move.. // moveメソッドを追加
    ), Person); // Personクラスオブジェクトを拡張
endfunction

function this = Resident_new(name, age, address)
    Person = PersonClass();
    person = Person.new(name, age);
    this = createTList('Resident', struct(..
        'address', address..    // addressプロパティを追加
    ), person); // Personオブジェクトを拡張
endfunction

function Resident_say(this)
    mprintf("Hello! My name is %s. I am %d years old. I live at %s.\n",..
        this.name, this.age, this.address);
endfunction

function this = Resident_move(this, newAddress)
    this.address = newAddress;
endfunction

以下は、Resident クラスの使用例です。

--> Resident = ResidentClass();

--> r = Resident.new('Mike', 25, '101 ABC street');

--> Resident.say(r);
Hello! My name is Mike. I am 25 years old. I live at 101 ABC street.

--> r = Resident.grow(r);

--> Resident.say(r);
Hello! My name is Mike. I am 26 years old. I live at 101 ABC street.

--> r = Resident.move(r, '777 XYZ avenue');

--> Resident.say(r);
Hello! My name is Mike. I am 26 years old. I live at 777 XYZ avenue.

Resident クラスは、Person クラスの “name” および “age” プロパティと “grow()” メソッドを継承し、“say()”メソッドを再定義、“address” プロパティと “move()” メソッドを追加しています。上のセッションの出力を見ると、それぞれ意図通りに機能していることが確認できます。

まとめ

以上、Scilab でオブジェクト指向プログラミングをする方法について解説しました。ご参考にしていただければ幸いです。

参考文献

[1] “Emulate Object Oriented in Scilab”. Scilab Wiki. 2011-12-08. https://wiki.scilab.org/Emulate%20Object%20Oriented%20in%20Scilab, (参照 2019-03-01).

[2] Baudin, M. “Programming in Scilab”. Scilab. 2011-09. https://www.scilab.org/programming-scilab, (参照 2019-03-01).

[3] “Scilabを使ったオブジェクト指向プログラミング”. ysuga.net. 2009. http://motor.geocities.jp/ysuga0731/robot/rtm/rtc_scilab/manual060/oop_on_scilab.htm, (参照 2019-03-01).

[4] Baudin, M. “Introduction to Scilab”. The Scilab Consortium – Digiteo. 2010-01. https://mars.uta.edu/mae3183/simulation/introscilab_baudin.pdf, (参照 2019-03-04)

コメント