見出し画像

関数型プログラミング事始め (23) 属性リストは万能 - Lisp超入門12

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

Lispの有史以前から存在する属性リストはシンボルの属性とその値を暗黙的に持つリストです。属性リストは原始的ですが便利な機能です。シンボル(記号)にはただ1個の値しか格納できないLisp-1の実装があり、この実装では変数名と関数名などに同一の名前は使えませんが、心配する必要はありません。この属性リストを使えば、変数名と関数名を属性とすることで、同名の変数名と関数名を実装することができます。なおこの実装を効率的にしたのがLisp-2の実装で、属性リストでの実装だけではなく、効率を考慮して配列などで実装されることが多くなっています。

(15) 属性リスト

属性リスト (property list, plist) は初期のLisp処理系から存在する言語仕様のひとつです。属性リストはシンボル(記号)の複数の属性と各々の値を格納しているリストですが、シンボルにリンクされている暗黙的なリストです。

ここで暗黙的と言っているのは、属性リストは明示的に取り出すリストではなく、属性をキーとしてシンボルにアクセスし、その値を取り出すことを機能としているからです。

属性リストは属性の値を取り出すという原始的な機能だけを提供していますが、原始的な機能なので逆に制限が少なく、機能的には万能です。

ここではこの属性リストについて紹介していきます。まずはislispを起動してください。なおISLisp処理系はLisp処理系の導入で紹介していますので参照してください。

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

(a) 属性リストの設定

Lispでは属性リストの設定は以下のようにpropertysetfで行います。

ISLisp>(setf (property 'person1 'age) 30)
30
ISLisp>(setf (property 'person1 'name) "gomi")
"gomi"

(setf (property 'person1 'age) 30)でシンボル person1 の属性 ageに値 30を設定しています。
同様に(setf (property 'person1 'name) "gomi")でシンボル person1 の属性 nameに値 "gomi"を設定しています。

(b) 属性リストのアクセス

Lispで属性リストのアクセスはpropertyで行います。

ISLisp>(property 'person1 'age)
30
ISLisp>(property 'person1 'name)
"gomi"

(property 'person1 'age)でシンボルperson1の属性ageの値30を取り出します。同様に(property 'person1 'name)でシンボルperson1の属性nameの値"gomi"を取り出します。

(c) 属性リストは万能

このように属性リストは暗黙的なリストの設定と参照アクセスのみだけの原始的な機能だけです。原始的な機能だけなので、逆に使い道は多くあります。

例えば、以下のように変数の値と関数の値を属性リストに設定することができます。

ISLisp>(setf (property 'car 'value) "自動車")
"自動車"
ISLisp>(setf (property 'car 'function) #'car)
#<SFUNCTION 00000932: CAR>

シンボルcarの属性valueに"自動車"の値を設定し、属性functionに関数carを設定しています。
これによりシンボルcarは同名の変数名と関数名を持つように振る舞うLisp処理系が作れます。
実際に使用するときには以下のようになります。

ISLisp>(property 'car 'value)
"自動車"
ISLisp>(funcall (property 'car 'function) '(1 2 3 4))
1

変数carの値を取り出すときは(property 'car 'value)を使い、関数carを呼び出すときは(funcall (property 'car 'function) '(1 2 3 4))のようにします。

このように属性には任意のシンボルを設定できますので、それを利用することで、属性リストはなんでもできる万能なものです。

なお同様の機能を明示的なリストで実装する連想リスト (assciation list, alist)もあります。これは基本的にリスト処理を属性アクセスに特化した関数を用意しているものです。

(d) (参考)Lisp-1でも大丈夫

Lisp処理系の実装ではシンボルの格納領域をただ1個で実装するLisp-1と2個以上の領域を用意するLisp-2の実装があります。

初期のLispではLisp-1の実装が多く、変数名と関数名を同一にすることはできませんでした。ただ1個の格納領域にはラムダ式や関数を入れるか、それ以外の値を入れるかの選択をしなければなりませんでした。

一方、Lisp-2の実装では、シンボルの格納領域として、関数の格納場所、関数以外の値の格納場所、さらに高機能になり、パッケージの格納場所、属性リストの格納場所、名前そのものの格納場所などを別々に用意することになります。

Lisp-2の実装では目的の属性にアクセスのは固定的な場所なので高速にアクセスできます。しかし一方で未使用の属性の領域が無駄になります。つまりメモリは多く消費します。

一方、Lisp-1の実装で(c)で紹介したように属性リストを用いることでLisp-2のように別々の格納場所を提供することができます。未使用のときも無駄なメモリも消費しません。でもアクセスはO(n)になりますので遅くなります。

このため属性リストのように暗黙的なリストでシンボルの格納領域を実装するLisp処理系もあります。

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

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


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

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