関数型プログラミング事始め (17) 代入 - Lisp超入門6
関数型プログラミングがはじめての方へ贈る入門の書
前節:繰り返し 次節:オブジェクト指向
参考書:
・五味 弘「はじめてのLisp関数型プログラミング」技術評論社(2016)
・大山口 通夫、五味 弘「プログラミング言語論」コロナ社(2008)
・五味 弘「関数型プログラミングと数学(ITと数学)」技術評論社(2021)
(9) 代入
Lispでは他の一般的な言語と同様に、代入が可能です。Lispでは任意の形式(フォーム)の場所(これを左辺値(left value)と言い、具体的には格納場所を表すアドレス)に、代入される値(これを右辺値(right value)と言います)を格納できます。Lispは自由です。
左辺値に既に前の値があった場合、代入では前値は上書きされ、前値はなくなります。この代入を破壊的代入(destructive assignment)と言います。
またLispはタイプレス(typeless)の言語なので、値にはタイプ(型)を気にせずに、任意の左辺値に任意の値を代入できます。Lispは自由です。
まずはislispを起動してください。なおISLisp処理系はLisp処理系の導入で紹介していますので参照してください。
> ISLisp Version 0.80 (1999/02/25)
>
ISLisp>
(a) 変数代入 setq
変数に代入する形式に、setq(set quote)があります。
ISLisp>(defglobal x 1) ; グローバル変数xを初期値1で生成
X
ISLisp>x
1
ISLisp>(setq x 2) ; xに2を破壊的代入する
2
ISLisp>x
2
setqは(setq 変数 値)の形式で実行します。
defglobalでグローバル変数xを生成し、その初期値を1に設定します。次にsetqで変数xに2を代入します。xの前の値の1は上書きされ、xの値は2になります。
なんと悲しい前値の1なのでしょうか。供養が必要になるくらい悲しいです。ああ、1君の雄姿は決して忘れません。3秒だけ覚えておきます。
(b) 汎用代入 setf
Lispには、正確にはCommon Lisp以降には、汎用代入setf(set form)があります。
ISLisp>(defglobal list '(1 2 3 4))
LIST
ISLisp>list
(1 2 3 4)
ISLisp>(defglobal vector #(1 2 3 4))
VECTOR
ISLisp>vector
#(1 2 3 4)
ISLisp>(defglobal array #2a((11 12 13) (21 22 23) (31 32 33)))
ARRAY
ISLisp>array
#2A((11 12 13) (21 22 23) (31 32 33))
ISLisp>(setf x 3) ; 変数xに代入
3
ISLisp>x
3
ISLisp>(setf (car list) 10) ; (car list)の左辺値に代入
10
ISLisp>list
(10 2 3 4)
ISLisp>(setf (aref vector 1) 20) ; (aref vector 1)の左辺値に代入
10
ISLisp>vector
#(1 20 3 4)
ISLisp>(setf (aref array 1 2) 230) ; (aref array 1 2)の左辺値に代入
10
ISLisp>array
#2A((11 12 13) (21 22 230) (31 32 33))
setfは(setf フォーム 値)の形式をしていて、フォームには任意の形式が左辺値として表記できます。
setfは変数に対しても、(setf x 3) のように代入できます。setfはsetqの拡張となっていて、この意味ではsetqはもはや使う必要がありません。
リストの任意の場所に対しても代入できます。上記のように(car list)でcarの場所(=リストの先頭)に代入するときは、(setf (car リスト) 値)のようにします。同様にベクターや多次元配列に対しても代入できます。
この他にも任意のフォームが左辺値にできます。どれが左辺値になるかどうかを気にする必要がなく、自由に代入できます。
この汎用代入は有用なものです。しかし一方、CやJavaなどのALGOL系言語では、配列には代入できますが、その他のデータ型に対しては代入できません。これらの言語では左辺値に強い制限があり、その仕様も中途半端な表現になっていて、左辺値として、何が書けるか書けないかが一見だけでは謎です。
これらの言語では配列以外では、代入文が使えずに、仕方なしに専用のセッター関数をわざわざ用意して、それをプログラマに使わせています。残念です。
なおこれらの言語をディスっていますが、他意はありませんので、ご了承ください。たぶん。
(c) 破壊的代入は悪魔の所業
破壊的代入は、代入する前の値を破壊します。永遠にこの世から消滅させます。まるで悪魔の所業です。前値が悲しんでいます。setqのところでも書きましたが、これは供養が必要です。
もし前値が変更されることを想定していないプログラムがあったとすれば、破壊的代入によって、それはバグになります。これがバグの誕生の瞬間です。
もちろん、これは前値の変更を想定していないプログラムがバグの正犯です。でも破壊的代入は従犯までは言いませんが、犯罪のきっかけになったのは事実です。はっきり言って、バグの原因のひとつです。
なお、一番の原因はグローバル変数という諸説もあります。
現代においては、破壊的代入の悪さは知れ渡っており、汎用代入がない言語においては、セッター関数も同様に知れ渡っています。破壊的代入やセッター関数を使うときには、周りに注意して、安全運転を心がけてください。
歩行者妨害(代入文妨害)をする必要はありません。破壊的代入はあまりにも便利な技なので、使用禁止にはできません。純粋関数型プログラマ(原理主義的関数型プログラマ)以外は。
Lispは自由なので(というよりも、一般の言語と同様に何も対処していないので)、破壊的代入では最大限の注意をする必要があります。
(次回予告)Lisp超入門7
次回もOK! ISLisp処理系を使って、Lispの超入門の第7回を紹介する予定です。お楽しみに。
参考:プログラミング言語はどれがお得?(前編)|五味弘 (note.com)
参考:プログラミング言語はどれがお得?(後編)|五味弘 (note.com)