関数型プログラミング事始め (14) 変数と名前 - Lisp超入門3
関数型プログラミングがはじめての方へ贈る入門の書
前節:リストは全能 次節:配列
参考書:
・五味 弘「はじめてのLisp関数型プログラミング」技術評論社(2016)
・大山口 通夫、五味 弘「プログラミング言語論」コロナ社(2008)
・五味 弘「関数型プログラミングと数学(ITと数学)」技術評論社(2021)
(6) 変数と名前
(a) 変数の種類
Lispには3種類の変数が用意されています。関数内の有効範囲(スコープ)と関数実行中の有効期間(エクステント)を持つローカル変数(局所変数)とすべての範囲とすべての時間で有効なグローバル変数(広域変数)があります。
この他に、ダイナミック変数(動的変数)があります。ダイナミック変数は呼び出された関数内の有効範囲と呼び出された関数実行中の有効時間を持ちます。つまり動的(一時的)に範囲と時間が限定された広域変数と捉えることができます。
なおこの変数はCommon Lispではスペシャル変数と呼ばれているものです。
しかしこのダイナミック変数を無理に使う必要はありません。他の言語と同様にローカル変数とグローバル変数のみでもプログラミングできます。
(b) ISLispの変数
それではISLispで変数を見ていきましょう。まずはislispを起動してください。なおISLisp処理系はLisp処理系の導入で紹介していますので参照してください。
> ISLisp Version 0.80 (1999/02/25)
>
ISLisp>
グローバル変数の宣言はdefglobal (define global variable)で行います。
なおCommon Lispではdefvarで宣言します。
ISLisp>(defglobal *age* 19)
*AGE*
ISLisp>*age*
19
ISLisp>(defun foo () *age*)
FOO
ISLisp>(foo)
19
(defglobal *age* 19)でグローバル変数*age*を宣言し、その初期値を19にします。値の*AGE*となるのは、Lispは小文字で入力しても、大文字インターン(デフォルトの名前は大文字で記録)されます。
グローバル変数はどこでもいつでもその変数は有効です。トップレベルでももちろん有効で19を返します。どの関数でも有効で19の値になります。
これは他の言語でも同様なものです。
次にダイナミック変数(動的変数)を紹介します。上記にもあるようにダイナミック変数は呼び出された関数内の有効範囲と呼び出された関数実行中の有効時間を持ちます。以下の例にあるようにdynamic-letによって、動的(一時的)に値が変更できる広域変数と捉えることができます。
ISLisp>(defdynamic *dynamic* 10) ; ダイナミック変数 *dynamic* の宣言
*DYNAMIC*
ISLisp>(dynamic *dynamic*) ; ダイナミック変数 *dynamic* の値を返す
10
ISLisp>(defun foo () (dynamic *dynamic*)) ; 関数内で*dynamic*の値を返す
FOO
ISLisp>(foo)
10
ISLisp>(dynamic-let ((*dynamic* 20)) (foo))) ; 一時的に束縛(値を変更)
20 ; 一時的な値を返す
ISLisp>(foo) ; 一時的束縛は解除
10
ダイナミック変数は(dynamic 変数)でアクセスします。dynamic-letで一時的に束縛(変数の値を変更)し、その中でのみ、その値は有効です。dynamic-letの範囲外では元の値を保持しています。
このダイナミック変数はデフォルトの値を変えて使い、使い終わったらデフォルトの値に自動的に戻したいというときに使えます。逆に言えば、このときのみに使ってください。例えば、標準入出力を一時的にファイルに書き出したいときにこのダイナミック変数を使うと便利です。
なおCommon Lispではダイナミック変数(スペシャル変数)をグローバル変数と同じ枠組みで使用しています。これはプログラムの了解性を悪くしていますので、ISLispではダイナミック変数とグローバル変数を明確に分離しています。
ローカル変数は関数引数やletなどの特殊形式で宣言して使います。これは他の言語とほぼ同様なものになります。
(注意)グローバル変数とダイナミック変数は使うな!
この関数型プログラミングの変数の苦難でも紹介しましたが、変数は使うなと言明しました。さすがに変数は使うなは極端ですが、少なくともグローバル変数とダイナミック変数の使用には注意が必要です。はっきり言って、使わない方がいいでしょう。バグの原因になります。
グローバル変数については関数型プログラミング以外のパラダイムでも、同様な言及があります。つまりプログラマにとって、グローバル変数は麻薬です、必要悪です、身を滅ぼします。
ローカル変数も一時的な値の保持に止め、破壊的代入はなるべく避けた方がいいでしょう。すべては関数引数の受け渡しで行いましょう。これが正統派のバグなし関数型プログラミングです。・・・という過激な話は置いておいて、変数の苦難で救いの道を示していますので、参照してください。
(参考)変態な名前
Lispは伝統的に大文字インターンです。つまり小文字で入力しても大文字でインターン(シンボル表に登録)されます。これは昔のコンピュータが大文字のみで使われていた歴史的な背景があります。逆に小文字でインターンしたいときは、||で囲うとその中の文字はそのままでインターンされます。また\(バックスラッシュ)で1文字だけそのままインターンされます。
そしてここが大事なところですが、Lispでは名前は自由です。記号もそのまま使えます。例えば、グローバル変数やダイナミック変数では慣習として、*age*のように*(アスタリスク)で囲う慣習があります。また名前の連結は、my-name のように -(マイナス、ハイフン)で行うケバブ方式が慣習になっています。
もちろん慣習に従わずにスネーク方式も可能です(が止めた方がいいでしょう。村八分になります)。
-1.0e0は浮動小数点数ですが、-1.0eは変数名として有効です。(defglobal -1.0e -2.0e0)のようにしない方がいいでしょう。
ISLisp>(defglobal -1.0e -2.0e0)
|-1.0E|
ISLisp>-1.0e
-2.0 ; -1.0eが2.0となって一瞬びっくり!
(次回予告)Lisp超入門4
次回もOK! ISLisp処理系を使って、Lispの超入門の第4回を紹介する予定です。お楽しみに。
参考:プログラミング言語はどれがお得?(前編)|五味弘 (note.com)
参考:プログラミング言語はどれがお得?(後編)|五味弘 (note.com)