見出し画像

関数型プログラミング事始め (20) 継承の事 - Lisp超入門9

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

オブジェクト指向における差分プログラミングの代表的なものに継承があります。継承はスーパークラスのスロットやメソッドを暗黙的に(自動的に)使えるようになる。このため、スーパークラスとの差分だけをプログラミングすればよく、結果的にプログラム規模が小さくなり、生産性が向上するという考えです。実際はプログラム規模が小さくなることは事実ですが、自動的に実行されるプログラムコードが隠されていることと同じであり、デバッグのときはこの隠されて見えないコードを含めて考慮する必要があり、生産性は向上するかどうかはプログラマに依存することに注意してください。

(12) 継承

継承(inheritance)はスーパークラス群のスロットやメソッドを自動的に(暗黙的に)呼び出すことで、スーパークラス群との差分のみをプログラミングすることができます。このため、プログラム規模が小さくなり、生産性が向上します。

しかしプログラム規模が小さくなるところまでは事実ですが、生産性が向上するかどうかは難しい問題です。これはこの記事の最後に書くことにします。

まずはislispを起動してください。なおISLisp処理系はLisp処理系の導入で紹介していますので参照してください。

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

(a) 準備

以前のオブジェクト指向で紹介したクラスpersonをここでも定義していきます。

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

次にジェネリック関数とメソッド関数を定義します。

ISLisp>(defgeneric add-title (x))
ADD-TITLE
ISLisp>(defmethod add-title ((p person))
                 (string-append "Mr." (get-name p)))

ADD-TITLE

(defgeneric add-title (x))でジェネリック関数と、(defmethod add-title ((p person)) (string-append "Mr." (get-name p)))でそのメソッド関数を定義します。これらの関数はジェネリック関数で紹介していますので参照してください。

(b) サブクラスの定義

次にクラスpersonのサブクラスstudentを定義し、そのクラスに対応するメソッド関数も定義します。

ISLisp>(defclass student (person) ())
STUDENT
ISLisp>(defmethod add-title ((s student))
                 (string-append "Student:" (call-next-method)))

ADD-TITLE

(defclass student (person) ())でクラスstudentの定義していて、そのスーパークラスにクラスpersonにしています。

このときにクラス優先順位リスト (Class Precedence List, CPL)が、クラス studentに動的に作られます。今回はクラスpersonだけが入っているリストになります。なお単一継承のときは単純にスーパークラスのリストなので、わざわざCPLを作る必要はなく、単純にスーパークラスを呼び出すだけになります。

(defmethod add-title ((s student)) (string-append "Student:" (call-next-method)))でクラスstudentのメソッド関数を定義します。call-next-methodは多重継承されたのクラス優先順位リストの先頭にある次のメソッドを呼び出すものです。

(c) サブクラスのインスタンス生成

次にクラスstudentのインスタンスオブジェクトを生成します。

ISLisp>(defglobal a (create (class student) 'name "GOMI"))
A

(create (class student) 'name "GOMI")でクラスstudentのインスタンスオブジェクトを生成します。このときに暗黙的に(自動的に)スーパークラスであるpersonが呼び出され、引数の 'name "GOMI"が受け渡されます。この結果、クラスpersonのインスタンスオブジェクトのスロットを含むstudentのインスタンスオブジェクトが生成されます。

(d) 継承による実行

次に総称関数add-titleを実行します。

ISLisp>(add-title a)
"Student:Mr.GOMI"

(add-title a)でまずクラスstudentのメソッド関数add-titleが呼び出され、その中のcall-next-methodが実行されると、クラス優先順位リストの先頭にあるクラスpersonのメソッド関数add-titleが呼び出されます。

call-next-methodによるpersonのメソッド関数add-titleでは"Mr.GOMI"が結果として返され、その結果に"Student:"が文字列連結れ、"Student:Mr.GOMI"が値として返されます。

(e) 継承の評価

継承の実装を考えると、継承はスーパークラス群をアクセスするためのプログラムコードを自動的に(暗黙的に)生成して(またはそのような仕掛けを持っていて)、自動的に実行することです。

このため、確かにプログラマが作るプログラムコードは少なくなります。しかし、デバッグするときには、この自動的に生成されたコード(またはその仕掛け)も含んでプログラムを見なければなりません。

継承のような自動コードがなければ、デバッグ時にプログラムを追いかけて犯人を追い詰めるときは、プログラマが自ら作ったプログラムでの関数の呼び出し関係を見ていくだけでいいのですが、継承のような自動生成コードがあるときには、関数の呼び出し関係だけでなく、継承関係も追い詰めなくてはいけません。複雑怪奇、魑魅魍魎になります。大変です。

このため、バグをあまり出さない優秀なプログラマにとっては生産性が向上しますが、継承の実装を知らずに継承を使う、にわかプログラマは生産性は却って低下するでしょう。これは事実です。

ということで、継承は「敵を知って己を知れば百戦危うからず」です。イミフ?

(次回予告)Lisp超入門10

次回はLisp超入門の最終回として、ISLisp言語仕様のサーベイになる「ISLisp検証システム」を紹介します。お楽しみに。

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


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