Part3EmacsとSLIMEの設定のおさらい。Lispにおける定数、スペシャル変数(グローバル変数)、レキシカル変数(局所変数)についての説明。Part2ではプログラミングそのものの説明と、簡単な計算式の書き方について説明した。Part3以降では、計算式と計算式を組み合わせたり、値を保持する方法など、より複雑なプログラムを記述する方法について説明していく。Part1で設定した開発環境のおさらいをする。今後、REPLに直接入力する方法とあわせて、ファイルにプログラムコードを記述し、その内容をREPLに転送することでプログラムの実行を確認していくためだ。以下の画像は、Emacsを起動し、左側にSLIME、右側にsample.lispという何も書かれていないテキストファイルを開いた状態になっている。この状態にするための手順を説明する。Emacsの操作の詳細に関しては、Emacsマニュアルの翻訳がある。Emacsの操作説明で出てくるC-xはCtrlとxキーを押すことを示し、M-xはAltキーとxキーを押すことを示す。プログラミングを行うためのディレクトリを作成しよう。Terminalを起動し以下を入力することで、Projectsディレクトリとその下のcommon-lispというディレクトリを作成し、その下に移動する。~/Projects/common-lisp/次に、Emacsを起動する。&Emacsをアクティブにした状態で、M-xを入力すると、画面左下にM-xという表示が出てくる。その後ろに、カーソルが点滅状態になっていることが確認できるはずだ。ここで、slimeと続けて入力してENTERキーを押すと、SLIMEが起動する。無事SLIMEが起動した場合は、以下のような表示になる。CLのREPL環境のバッファが下にある状態だ。Emacsのウインドウ分割操作で、ひとまずREPLのバッファのみ表示させる。oで、今いるバッファをREPLのほうにする。oは、入力するたびにアクティブなバッファを切り替えるためのコマンドだ。1を入力すると、REPLバッファのみになる。ccl*というバッファを選択してENTERキーを押せば、REPLのバッファがアクティブになる。2で上下に分割しても良い。3で左右に分割すると、左右のバッファがともにREPLの表示になる。oで左右どちらかのバッファに移動する。~/Projects/common-lisp/という表示がEmacsウインドウ下部に表示される。ここで、sample.lispというテキストを入力してENTERキーを押すと、sample.lispというファイル名で新しいバッファが作成される。file)と表示されれば成功だ。このままではファイルはまだ保存されていない。C-sと入力することで、sample.lisp(今はまだ何も書かれていないが)を保存できる。/home/username/Projects/common-lisp/sample.lispと表示されれば、書き込まれたことになる。SLIMEとLispプログラムコード用のファイルが用意できたところで、ファイルに記述したコードをREPL側でどのように認識させるかについて説明する。SLIMEのドキュメントには詳しい使い方が書いてあるが、差し当たって覚えておくコマンドは2つだ。slime-restart-inferior-lispを覚えておく。これを入力することで、SLIMEは起動直後の状態に戻る。次のコマンドは、sample.lisp側のバッファを選択した状態で使用する。e)を入力すると、その式をREPL上で評価する。値に名前を付けるプログラムコードの中では、値に対して名前を付けることができる。他と区別し、人間にわかりやすくし、その名前を使って何度も利用するためだ。今までの例では、計算やリストの操作を取り扱ったが、全て1回の実行で結果がわかるものだった。2)3この3という得られた値を、別の処理、別の場所で使いたい場合はどうすれば良いだろうか。実際に使いそうな数値を考えてみる。円周率3.14や重力加速度9.8、うまい棒は10円というように、世の中には大切な不変の数値というものがある。このように変更されることのない数値に対しては、定数の定義という仕組みを使う。Lispでは、piをREPLに入力すると円周率の値が返ってくる。Lispの処理系によって予めpiという名前に数値が紐付けられているためだ。重力加速度(g)は紐付けられていない。gという名前に何も値が紐付けられていないため、エラーになるpiと同じように、gで9.8が返ってくるようにしよう。sample.lisp側のバッファで、以下のプログラムコードを入力する。C-eと入力すると、このS式は評価され、REPL上でgという名前が利用できるようになる。g9.8これは、gという名前に対して、9.8という値が紐付けられたことを意味する。defconstantは、定数を定義するオペレーターであり、定数とは、一度定義すれば変更のできない値になる。定義された名前は、アトムのうちシンボルに属する。値)重力加速度を定義したので、10秒間で物体が落下する距離を求める計算を行ってみよう。Lispの式にそのまましてしまう。C-eでdefconstantの式を評価したため、gはREPL上でそのまま使用できる状態だ。exptは、乗数を求めるオペレーターだ。分数の2分の1という値は、1/2という形でそのまま記述することができる。これらを踏まえると、以下のような式になる。2))490.0無事490mという数値が出た。合っているかどうか不安であれば、手で計算して確認しよう。このように値を意味のある名前にしておくと、名前を指定することで様々な計算で使いまわすことができる。defconstantは定数の定義であり、一度定義した名前に紐づく値が変化しないことが保証される。プログラムコード中で誤って値が上書きされることがないため、変化しない使いまわす値は、defconstantで定数として定義しておくと良い。スペシャル変数(グローバル変数)変数には、スペシャル変数(グローバル変数)と呼ばれるものと、レキシカル変数(局所変数)と呼ばれるものの2種類が存在する。スペシャル変数は、プログラムのどこからでも参照可能な変数で、レキシカル変数は、プログラム内の一定の箇所でのみ参照可能な変数だ。この2つの使い分けはとりあえず脇に置いておいて、まずはスペシャル変数から説明する。スペシャル変数の定義スペシャル変数を定義する方法として、defvarとdefparameterという2つのオペレーターが存在する。値)というような記述で定義を行う。先程の10秒という落下時間を変数名で保持しておくことを考えると、以下のように定義できる。10)もしくは、以下のようにも書ける。fall-time-1と区別するため、fall-time-2という変数名にする。C-eで評価し、REPL上で値を確認する。fall-time-210defvarとdefparameterはまったく同じものではない。defvarは、何度変数定義を実行したとしても最初に設定した値を保持し続けるのに対し、defparameterは、最後に実行した際の値を保持する。この挙動を理解するためのコードを確認してみよう。10という値と紐付けられているfall-time-1に対して、REPL上で以下の処理を実行する。fall-time-110念のため2回defvarを実行しているが、fall-time-1の値は10のままだ。最初に設定した10という値が保持され続けていることになる。defparameterを使った場合の挙動を見てみよう。10が紐付けられているfall-time-2に対して、20、30という値を設定する。fall-time-230実行の度に値が再設定されていることがわかる。defvarとdefparameterのもうひとつの違いは、定義時に値を必須とするかどうかという点だ。a)上記の式は、エラーにはならない。値が定まっていないが、変数用にaというシンボルが定義される。b)しかし、上記のようにdefparameterを使った場合はエラーとなる。defparameterの場合は、第一引数である変数名に加えて、第二引数の値が必須となる。この2つの方法の使い分けは、式が再評価された際、値がどのようになってほしいかという部分が深く関わってくるものの、一旦はこの2種類の定義方法があるということを覚えておく。値の指定方法としては、以下のような書き方もできる。3)という式を指定している。計算の結果が、値として変数名と紐付く。lisp"上記のコードは、文字列を値として指定している。messageには指定した文字列が紐付く。スペシャル変数の値を変更する変数は、値を変更できる。defparameterでは定義を行う際に値が変化したが、defvarでは値がこのままでは変更できない。どちらの方式で変数を定義したとしても、値を変更するためには通常、setq、setfというオペレーターを使用する。10という値が設定されているfall-time-1の値を変更するためには、以下のようにする。20)20setfも同様に使用できる。30)30この2つの違いについては、別のパートで詳しく説明する。現段階では、どちらも変数に値を代入する同じような挙動を持つということで理解しておく。レキシカル変数(局所変数)どこからでも参照できるスペシャル変数に対して、レキシカル変数はある一定の範囲内で使用できる変数であり、letというオペレーターを使用する。world”という文字の出力が行われる。world"NILここで少し脇道にそれるが、formatオペレーターの第一引数がtであることに着目する。tはシンボルで、真偽値のtrue(真)であることを意味する。以前設定したnilというシンボルはfalse(偽)であることを意味する。formatオペレーターは、この第一引数の値を見て、文字列の出力先を決める。引数にnilを指定していた場合は、式の結果として値(文字)を返す。3)の結果の6という値が返ってきていた状態と同じことを意味する。引数にtを指定した場合は、文字を標準出力に表示する。標準出力というと聞き慣れないかもしれないが、ここではREPLやTerminalへの出力と捉えれば良い。今までnilを指定していても文字が表示されていたのは、REPLが返ってきた値=結果を出力するという挙動をしているためだ。REPL上ではなく、今後のパートで説明する単独のプログラムとして実行している場合には、明示的に出力先を指定しなければ文字は表示されない。ここからは、tを指定することで明示的に標準出力に出力するようにする。先程のformatの式の実行後、以下の2行が出力された。world"NILこの1行目は、tを指定したことによる標準出力への表示が行われた結果表示されたものだ。2行目は、formatの式の実行結果になる。t〜と指定したことで、式の実行結果として偽、false、空のリストを示すNILが返ってくるようになったことがわかる。letオペレーターは2つの引数を取り、以下のような書式を持つ。message2)の部分になる。letを詳しく見ていく前に、このような長いコードは適切な位置で改行すると読みやすい。message2))変数名と値の定義は、以下の第一引数のリストで行っている。"world"))この記述は、message1という変数名に”hello”という値を、message2という変数名に”world”という値を設定しているという意味になる。複数の変数名と値の組をここに記述し定義することができる。letオペレーターで定義した変数は、この外のS式からは参照できない局所的に使われる変数であり、この中でのみ有効な変数だ。formatオペレーターでは、この定義したmessage1、message2という変数名をカンマ区切りで出力するようにしている。world”が表示される。message2)defparameterやdefvarを使った際は、定義した変数名から紐付く値を取得できたが、試しにmessage1やmessage2をREPLに入力してみるとエラーになるはずだ。message1はlet内でのみ利用できるため、エラーになるどこからでも参照できるスペシャル変数と、このようなletの式内でしか参照できないレキシカル変数では、後者は不便なだけだと考えるかもしれない。変数の種別によるメリット、デメリットは実際にプログラムコードを記述していくことで理解が深まる部分も多いとは思うが、プログラムの不具合の原因として多くあるもののひとつに、値がプログラマーの想定したものではないという状態が挙げられる。その原因究明の際、どこからでも参照できるスペシャル変数を利用していた場合は、全ての処理がその変数を書き換える可能性があるためプログラム全体を確認していく必要がある。逆にレキシカル変数によって管理されている値であれば、let式単位で値を確認できるため問題箇所を特定しやすい、管理しやすいという利点が生まれる。名前を付ける際も、局所変数であればletの中でのみ有効であるため、重複した名前を使っていないかどうかなど、気にしなければならない事柄が減る。数の引数を取ることができる。3)のように、いくつもの数値を与えられる+オペレーターと似たような形だ。worldNILレキシカル変数の値を変更するレキシカル変数も、スペシャル変数と同様、setq、setfによる値の変更が行える。一度定義した値のうち、message2に”lisp”という値をsetfで設定する処理は以下のようになる。lispNILまとめこのPartでは、定数、変数(スペシャル変数、レキシカル変数)の説明を行った。