プログラミングを頑張る日記

プログラミングを勉強して、ハッカーになろう

Common Lispを頑張る(2)

さてさて、今日もやっていきます。
「Land of Lisp」の2章を進めていきます。

2章では簡単な数あてゲームを作りながら、
変数と関数の定義の仕方について紹介してくれるようです。
そこらへんの定義についてまとめていこうと思います。

グローバル変数の定義

グローバル変数と言いながら、Lispではグローバルに定義される変数を
トップレベル定義と呼ぶらしいです。
ただ文中でも基本的にグローバル変数と呼ばれていてどっちが正しいのやら。
とりあえず自分もグローバル変数と呼ぶことにします。

さて、定義にはdefparameterを使うようです。

[1]> (defparameter *my-name* "neko")
*MY-NAME*
[2]> *my-name*
"neko"
[3]>

こんな感じでしょうかね。
この*は「耳あて」と呼ばれるもので、グローバル変数につける慣習があるようです。
これをつけないグローバル変数を含んだコードをLisper達に公開してしまうと、
その人の身の安全は保証されないらしいです。怖いのでつけることにします。

また、すでに定義されている変数名をdefparameterを使って再定義すると元の値は上書きされるそうですが、
上書きをしないdefvarというのもあるそうです。ふむふむ。

[3]> (defvar *my-name* "hito")
*MY-NAME*
[4]> *my-name*
"neko"
[5]> (defvar *study* "fun")
*STUDY*
[6]> *study*
"fun"
[7]> (defparameter *study* "boring")
*STUDY*
[8]> *study*
"boring"
[9]>

・defparameterで定義された変数はdefvarで上書きできない。
・defvarで定義されててもdefparameterで上書きされてしまう。

まあ、説明通りでしたね。
楽しさが退屈で上書きされてしまいましたが、挫けず進めます。

Lispのエチケット

うすうす感づいていましたが、Lispの基本的な文法は
(コマンド 引数)と、コマンドとその引数をいちいち括弧に入れてやらねばならないそうです。
Lispがコードを読むときに空白や改行は無視するということで、
コードをキレイに書き甲斐がありそうです。

グローバル関数の定義

さあいよいよ関数の定義の仕方です。それにはdefunを使うとのこと。
define functionかなあ。デファンと呼べばよいのでしょうかね。
早速使ってみます。

[9]> (defun call-my-name()
            (princ *my-name*))
CALL-MY-NAME
[10]> (call-my-name)
neko
"neko"
[11]>

名前を呼んでくれます。楽しいです。
どうやら、

(defun <関数名> (<引数>) (実際の処理))

という書き方のようです。

ローカル変数の定義

グローバル変数があるのだから当然ローカル変数もありますな。
下記のような書式らしいです。

(let (変数定義) ...本体...)

はて、本体とは。実際に書くとこんな感じになるらしいです。

[13]>
(let ((a 3)
      (b 5))
     (* a b))
15
[14]>

だんだんと括弧の数が増えてきました。楽しいです。
ローカル変数は定義したらそのまま使わなきゃいけないということですね。
そりゃそうですがなんか忙しない印象です。使いこなせるかなあ。
変数宣言部全体を括弧でくくり、各変数の定義部を括弧でくくり、
さらに本体(処理部分)も括弧で括ると。わけわからなくならないようにしたいです。

ローカル関数の定義

長くなっていますがこれで2章は終わりなので一気に行きます。
ローカル変数の定義は下記のようにするそうです。

(flet ((<関数名> (<引数>)
                ...関数本体...))
          ...本体...)

実際に書いてみましょう。

[17]>
(flet ((say (str)
     (princ str)))
  (say "meow"))
meow
"meow"
[18]> (say "meow")

*** - EVAL: undefined function SAY
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead of (FDEFINITION 'SAY).
RETRY          :R2      Retry
STORE-VALUE    :R3      Input a new value for (FDEFINITION 'SAY).
ABORT          :R4      Abort main loop
Break 1 [19]>

できました、と定義した括弧の外では使えないのでちゃんとローカル関数ですね。

また、ローカル関数の中で同じスコープ内で定義されるローカル変数を使う、
ということもlabels関数でできるようです。
なんかややこしいことを言っていますが実際にやってみます。

[23]>
(labels ((return-5 (n) (+ n 3))
         (return-10 (n) (* n (return-5 n))))
  (return-10 2))
10
[24]>

まったく意味のない関数ではありますが、目を瞑ってくださいな。
ここでlabelsではなくfletを使うと、return-10はreturn-5を知らないので使えないと言うことらしいです。ふむふむ。

[24]>
(flet ((return-5 (n) (+ n 3))
         (return-10 (n) (* n (return-5 n))))
  (return-10 2))

*** - EVAL: undefined function RETURN-5
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead of (FDEFINITION 'RETURN-5).
RETRY          :R2      Retry
STORE-VALUE    :R3      Input a new value for (FDEFINITION 'RETURN-5).
ABORT          :R4      Abort main loop
Break 1 [25]>

なるほど「return-5なんて知らないぞ」と怒られました。

また、labelsはローカル変数が自分自身を呼び出すようなときにも使われるそうです。
そういった処理を再帰と言うとか。これからいっぱい出てくるらしく楽しみです。

そんな期待に胸を膨らませたところですが、今日はここまでとします。

こんな単調で長い記事を呼んでくれた方に、(いるとすれば)心から感謝です。