見出し画像

関数型プログラミング事始め (18) オブジェクト指向 - Lisp超入門7

関数型プログラミングがはじめての方へ贈る入門の書
前節:代入 次節:ジェネリック関数
参考書:
・五味 弘「はじめてのLisp関数型プログラミング」技術評論社(2016)
・大山口 通夫、五味 弘「プログラミング言語論」コロナ社(2008)
・五味 弘「関数型プログラミングと数学(ITと数学)」技術評論社(2021)

オブジェクト指向はプログラミングパラダイムとして、その有用性が認められ、1980年から現在まで流行しています。このオブジェクト指向の考えは関数型プログラミングの考えとは独立していて、まさに直交しています。このため、オブジェクト指向関数型プログラミングということも矛盾していません。これは一般の配列や代入を認めている関数型プログラミングと同様な考えです。つまり、オブジェクト指向で副作用が生じるのであれば、それに注意して関数型プログラミングをしていくことになります。今回はLispにおけるオブジェクト指向機能を紹介します。

(10) オブジェクトシステム - オブジェクト指向機能

Lispの多くには1980年代からオブジェクトシステムを持っています。Common LispのオブジェクトシステムであるCLOS(Common Lisp Object System)があり、ISLispにはILOC(ISLisp Obect System)が用意されています。

LispのオブジェクトシステムはJavaやSmall Talk-80などとは異なり、多重継承をサポートしているなど豊富なオブジェクト指向機能を持っています。

ここではISLispのILOSを紹介します。ILOSは複雑なCLOSをシンプルにしたオブジェクトシステムです。まずはislispを起動してください。なおISLisp処理系はLisp処理系の導入で紹介していますので参照してください。

> ISLisp Version 0.80 (1999/02/25)
>
ISLisp>

(a) クラス定義

ISLispはクラスベースのオブジェクト指向とも捉えることができます。インスタンスオブジェクトを生成するためには、そのテンプレートであるクラスを定義することになります。ISLispではdefclass(define class)で以下のようにクラス定義します。

ISLisp>(defclass person () ((name :reader get-name :initarg name)))
PERSON

クラス定義は(defclass クラス名 (スーパークラス …) ((スロット名 :reader リーダ関数名 :initarg 初期値指示パラメータ名 …) …))のようにします。スロットのオプションには:reader以外には、:writerや:accessorなど、初期値指定として:initformなどが用意されています。スーパークラスはここで指定していませんが、スーパークラスについては後述します。なお、この構文はCommon Lispとほぼ同様です。

上記のdefclassによって、personクラスが定義され、そのスロット変数(インスタンス変数、メンバ変数)としてnameを持っていて、インスタンスオブジェクトを生成するときにnameの初期値を指定する指示パラメータとしてnameを与えています。

このpersonクラスのインスタンスオブジェクトは以下のようにcreateで生成します。

ISLisp>(defglobal a (create (class person) 'name "GOMI"))
A
ISLisp>a
#<INSTANCE PERSON 0026EC5E> ; 0026EC5E はインスタンスのアドレス

'name "GOMI"でスロット変数nameに初期値として"GOMI"を与えています。これは:initagr nameとしていることでパラメータが有効になっています。

(b) スロット変数のアクセス

スロット変数に対するアクセスは、defclassで定義したリーダ関数やライター関数(セッター関数)、アクセッサ関数によって行います。ここではpersonクラスではリーダ関数のみ定義していて、以下のように使います。

ISLisp>(get-name a)
"GOMI"

アクセッサ関数はスロットの読み出し、また汎用代入setfを使って書き込める両刀使いの関数です。

(c) (予告)多重継承と総称関数

ISLispのILOSはCommon Lispと同様に包括関数(総称関数 generic function)を基本的な考えを採用しています。包括関数は複数のクラスを包括した(総省した)関数で、具体的には関数の引数に複数のクラスを定義でき、これらのクラスは同等に扱います。

これは単一継承のクラスベースのオブジェクト指向と大きく異なることです。Javaなどのスーパークラスがただ1個しか指定できない単一継承のオブジェクト指向では、クラスにすべてのメンバ関数が属することになり、まさにクラス中心のオブジェクト指向です。

このため、メンバ関数の引数に複数のクラスを指定しても、その扱いは不平等です。例えば、Javaではobject1.method(object2, object3)のように、明らかにobject1を特別扱いにしています。object2やobject3のクラスは無視されてしまい、不平等な扱いをされています。悲劇です。

逆に包括関数の考えでは、第1引数のオブジェクトのクラスを特別扱いすることで、Javaのように振る舞うことができます。多重継承に関しても、スーパークラスを1個のみ指定することで単一継承と同じ動作をするようになります。つまり多重継承は単一継承を含んでいます。包括関数もまたメソッド関数の関数を含んでいます。つまり強いです。

この包括関数とそもそものベースにある多重継承については後述します。

(d) オブジェクト指向の罪と罰

クラスベースのオブジェクト指向では、インスタンスオブジェクトにスロットのようなデータを格納する構造になっています。そしてオブジェクト専用のメソッド関数もあり、これは抽象データ型(abstract data type)の考えになります。

オブジェクトにスロット変数を含んでいるということは、配列などと同様に副作用の危険性があります。誰かがどこかでスロット変数の値を変えてしますと、不変であると信じているプログラムコードにとっては、バグの原因になります。もちろん不変であると信じていたコードが注意不足なのか、それとも勝手に値を変えた人が悪いのかは両者の話し合いの結果で判定されるでしょう。これは有名なインタフェースミスマッチのバグかもしれません。

この意味ではオブジェクト指向では配列と同様に罪があります。それを正しく認識して使わないと罰が下ります。オブジェクト指向、危険!です。

しかし、オブジェクト指向はプログラムをカプセル化し、情報隠蔽の制御が行え、さらに継承システムで差分プログラミングができ、実に有用なプログラミング手法のひとつです。これを使わないのはもったいないです。配列を使わないのと同じくらいもったいないです。オブジェクト指向との共存は重要ですので、後述することにしたいと思います。

(次回予告)Lisp超入門8

次回もOK! ISLisp処理系を使って、Lispの超入門の第8回を紹介する予定です。お楽しみに。

参考:プログラミング言語はどれがお得?(前編)|五味弘 (note.com)
参考:プログラミング言語はどれがお得?(後編)|五味弘 (note.com)

いいなと思ったら応援しよう!

五味弘
よろしければサポートをお願いします!