Lisp/始めの一歩/チュートリアルを始めよう

それではあなたの Lisp 実行環境を起動させてください。 おそらく画面にはあなたの入力を待つプロンプトが表示されているでしょう。このプロンプトは REPL と呼ばれるもので、Read-Evaluate-Print Loop の略語です。この時点で Lisp は”読む(read)”ための入力を待ち、それから入力内容を”評価(evaluate)”します。これらのシンプルな単語が意味するのは、計算結果を演算するということです。
では、プロンプトに "2" を打ち込んで、Return キー(あるいは Enter キー)を押してください。
これは Lispインタープリタ が 2 を調べ、評価したということを表します。 2 を数字として認識し、数字を評価するためのルールを適用し、それで答えが 2 となるのです。
"(+ 2 2)" とタイプして Return を押してください。
コンピューターは左括弧を見ると「 list を与えられたんだ」と認識します( list については後述)。 コンピューターが右括弧にたどり着くと、 list の内容を分析することが出来るようになり、 list の中を見てみると3つの要素があることが分かります。最初は + 記号です。 + 記号でコンピューターは、 list にある残りの項目の値を一緒に足す、ということがわかります。そして最後にコンピューターはそれらを評価します。以前見たように数字の 2 は、 2 という数値を持っているので、 2 と 2 で答えは 4 となります。
Lisp で足し算を行うには、 1+20+300+4000+50000 と書くのではなく、 list の項目の最初にプラス記号を書きます。 list はあなたが望む限りの長いものでかまいません。
list では ショッピングリスト (potatoes carrots onions bread milk) のように項目が配置されているように見えます。このルールに従うなら、 + は数式の一部で、ちょっと特殊なもの、ということになるでしょうか。しかし、注意が必要なのは、list の最初に来る項目は特別なもので、実際 + 記号が list の最初に配置されているということです。
掛け算のためには‘*’関数を使用します。
かけ算でも足し算のように list をいくらでも続けて長くしてもいいのでしょうか。もちろんいいのですよ。
実は list は長くするばかりでなく、可能な限り短くすることも出来ます。
どうしてこのような答えになるのか?それには後々に説明をゆずらなければならない難しさがあるのでまた後ほど。
ひき算は Lisp においては他の言語にはないような不恰好な感じになっています。
言い換えれば、
は、下の list と同じ意味を持ちます。
Lisp での割り算では少し驚きがあります。
Lisp は分数計算が出来るのです!(だから?とか言わないでくださいね)
これにはもしかすると困惑することになるかもしれません。
Lisp で (/ 2 3) を試した場合、期待していた答えが 0.6666667 だと、 2/3 が返ってきて混乱するでしょう。また、 (/ 8 12) を試した場合にも、 2/3 が返ってきて意外さに驚くかもしれません。もし、分数表記になることを欲しないのなら以下のように書くことが出来ます。
あるいは以下のように
割り算も引き算と同じように動きます。
上の式は、下の式と同じ意味を持ちます。
これは練習としてちょうどよいでしょう。計算式 6×5×4/(3×2×1) は Lisp で表現すると以下のような式になります。
バインド( Binding )は仮の場所( place holder )にある値を明確化するものです。このコンセプトは C言語 や Java のローカル変数と似ています。 Lisp でコードを書いていると、同じような長ったらしい記述を何度も書くのは面倒なので、しばしばバインドが必要になるはずです。あるいは、実行時に何度も値の更新が要求されるような、小さな部品パーツでの計算にバインドが必要でしょう。バインドを作成する主な方法は“特殊フォーム”の LET を使用することです。
ここでは、(* 5 5) と (* 10 10) の計算のためにそれぞれ 5-SQUARED と 10-SQUARED をプレースホルダ(ローカル変数)としています。とりあえず今のところは、プレースホルダを利用するためのいくつかのルールがあるということを留意しておいてください。これらのプレースホルダは シンボル と呼ばれ、名前を付けることが出来ます。(上の "5-SQUARED" とか "10-SQUARED" が名前です。)名前をつけるに際には使用できない文字があり、シングルクオート ' 、ダブルクオート " 、左括弧 ( 、右括弧 ) 、コロン : 、バックスラッシュ \ 、そして垂直線記号 | 、は Common Lisp においてシンボルの命名の際に文法上の理由があり使用できません。それ以外の文字は使用できます。シンボルに名前を付ける際は全ての文字を使用できるが、使用する際に特殊文字が必要なものもある、と言う風に覚えておいてもいいでしょう。
バインドは局所スコープを持ちます。 LET フォームが括弧で閉じられると、バインドが無効になるのです。これはつまりは下のような例では error を返すということです。閉じられた LET フォームの外から a が参照されているために error が生じます。
すでにバインドされているシンボルをさらにバインドしてみたときの挙動は興味深いものです。下の例が分かりやすいですが、内側のバインドが解除されると、外側のバインドは再び元の効果を保持します。
話は若干複雑になりますが、 Common Lisp ではバインドを作るのに二つのやり方があります。まずは レキシカル(静的スコープとも言う)、これは我々がちょうど上の例で見たものを言います。そしてダイナミック(動的スコープとも言う)です。この時点の方針として、ダイナミックバインドはレキシカルバインドとそれほど顕著に違うものだ、とはしませんが、しかし、この二つは違う方法で作られたもので、ダイナミックバインドは LET フォームと同じような有限範囲を持つわけではありません。ダイナミックバインドを作るために、 DEFVAR と DEFPARAMETER を使用することができます。これらはどちらの入力でも値をとることが出来ます。
Lisp では変数はいくつか特別な特徴を持っています。それはシンボルと呼ばれるものです。変数は値が中に入っている箱のようなものです。シンボルは外側に自分の名前を書いた、変数の箱よりもいくぶんか大きい箱となるでしょうか。シンボルは二つの値を持ちます。普通の目的のための値と、特定の状況で代わりに使われる関数値です。シンボルは値を考慮することなしに、そのもの自体として使うことができます。
ではシンボルに一般的な値を代入するところから始めましょう。シンボルに値を代入するにはいくつも方法があります。 set, setq, setf, psetq ... まずは setf だけでも相当役に立つので、最初は setf から始めましょう。
これはシンボル MY-FIRST-SYMBOL の一般的な値として 57 を代入しています。そして 57 が返ってきています。代入された今はシンボル名を打ち込むと 57 が返ってきます。
以下のように計算も出来ます。
あきらかに上のコードは計算を実行し、その答えを返したものですが、しかし second-symbol の一般的な値として代入されたものは何なのでしょうか?代入されているものは私たちが要求した計算式でしょうか?それとも、コンピューターが計算した答えが代入されているのでしょうか?とりあえずはシンボル名をタイプしてみましょう。
もし、計算式を記録したいのなら後で参照するために "quote" しなければなりません。これはコンピューターを馬、 "quote" を手綱だと考えてみてください。あなたが評価をしてほしいと思う前にコンピューターが性急に評価するのを手綱を引いて押しとどめるのです。
今、 third は以下のような内容を保持しています。
third シンボルには、一般的な値としてコンピューターが実行したくてうずうずしている計算式が入っています。
"quote" が手綱を引いて馬を制止するものだとしたら、再び動き出すためにはどうしたらいいのでしょうか? "eval" がその答えとなります。
"quote" を最初のレッスンで使用するのは議論の余地のあるところでしょう。なぜなら quote がはっきりと書かれることはめったにないからです。例えばこのようなことです、
これがすごく特別な省略形だと分かると思います。一文字の ' が5文字の quote の短縮形と言うだけでなく、括弧までもが省略されています。 Lisp インタープリタを使用しているとき、基本的には READ-EVAL-PRINT の無限ループにいるということに気をつけなければなりません。要するに、 eval を常に使っているということです。
ここまで3つのシンボルに代入を行ってきましたが、もしかしたらこれらの中に何が代入されたか忘れてしまうおそれがあります。そこで list の登場です。 list 関数は list を組み立てます。例えば以下のようにです。
それでは先ほど作った3つのシンボルの値の list を作成してみましょう。
これには2つの困惑する可能性があります。一つは、どの値がどのシンボルに対応するものか分かりにくいということです。もしかしたら quote を使用して以下のように評価を一旦止める必要があるかもしれません。
二つ目のもっとむずかしい混乱は比較することでわかります。
と以下の list を比べてみて下さい。
これはまるで list がどうにかして引数を評価するかしないか決めているみたいではないですか。最初の例では控えめに評価を行い、二つ目の例では節操もなく行っているように見えます。
quote と eval を行うことでこれを調べることが出来ます。 1 を0回、1回、2回、3回と評価してみましょう。(プログラマは 0 から数え始めるように習慣付けましょう。)
比較のために third を0回、1回、2回、3回と評価してみます。
数値はシンボルではありません。二つの値を含んでいる箱もありません。ただそこにあって、自分自身を評価するだけです。繰り返しますが数値はシンボルではないので、以下の例はエラーを返します。
とりあえずは打ち込んでみることでどういうことか感じがつかめると思います。
ここでは my-symbol-1 は my-symbol-1 として評価されます。つまりは数値のように実行が無視されるのです。
この点について長々と論じるのは理由があります。変数の比喩として、箱の中に何かが入っているというのはうまい比喩です。たいていの場合、比喩はうまくいくのですが、例えば、あなたはしばらく何かを箱の中に入れているとします。それからその箱の中身を取り出して、同じ箱に何か他のものを入れます。ここでこれから説明しようとしていることに対してはこの比喩は根本的に欠点があります。この二つの箱と中身は実体のないものなのです。以下の例を見てください。
この記述は 4th という箱には単純な計算式が入っているというものでしょうか。見てみましょう。
どうやらそのようですね。
これは先ほどの箱の比喩のように third の中身を移し変えたものでしょうか?
違いますね。
ではこれはコピーされたものなのでしょうか?これも違います。コピーを行うときは以下のように記述します。
ここでは 4th の内容が 5th に移動したでしょうか?いいえ、箱の中身の移動という比喩が意味を成すという限りにおいては、移動させるためには以下のようにしなければなりません。
(shiftf 6th 5th nil) というコマンドでは 5th の内容を 6th に移動した後で nil を 5th に書き込みます。しかしこのコマンドでは 6th の古い内容が返るので、内容がまだない新品の変数には使用できません。
しかし、コピーされるのでもなく、移動するのでもないのだとしたら何が起きているというのでしょう。まるで私たちが今でも探検を行っていない存在しない世界にある、ねじれた箱の何か特別なことのようです。
顕著な例を示します。
少々トリッキーですが以下の様になります。じっくりと考えながら実行してみてください。
では理解できているかの試金石として、次の例を考えてみましょう。 (+ 1 (* 2 3)) と (+ 1 '(* 2 3)) を評価すると結果はどうなるでしょうか。 2つ目の例は簡単ですね。評価を停止するために quote をしたので (* 2 3) は三つの項目を含んだ list となります。いつかあとで計算が実行される命令を与えることになります。 '(* 2 3) はこのままでは計算結果を返さないので、これが数値ではないとエラー文で警告されるでしょう。エラー文は以下のようなものでしょう。
これとは対照的に
上の list は見た目よりもずっと賢いことをしています。まるでインタープリターがそれぞれの引数を見てどれを実行すべきかを決めているみたいではないですか。例えば以下の例がもっとわかりやすいです。
上の list では第1引数と、第4引数をのけものにして、第2引数と第3引数を最初に評価する、ということをしています。
実際、上の例ではあなたが一見してすぐにわかるような単純なことをしているように見えるだけかもしれませんが、ここで行っていることは、 2 と 3 を評価する際、 2 の評価で 2 を、 3 の評価で 3 を結果としてそれぞれ受け取り、そして二つの結果を掛け算して、 6 という結果を受け取るのです。
インタープリタが (+ 1 (* 2 3)) を評価する際も、インタープリタは 1 と (* 2 3) を評価します。 1 は 1 として評価され、 (* 2 3) は 6 として評価されます。それから二つを足して結果は 7 となるのです。
ここで少し熟考の必要があります。引き出しからでも、安くて古い電卓を取り出して、 1 + 2 x 3 を試してみてください。典型的には、あなたが x を押したとき、保留の結果を保持する外部記憶のない電卓は、 1 足す 2 を実行してしまいます。最後には 3 x 3 の計算を行い、 9 を得ます。 7 ではありませんね。最新の電卓では計算の優先順位の標準的な規則にしたがっているはずです。つまりは掛け算の 2 x 3 のあとまで足し算の実行が保留され、ついには望むとおりに 7 という答えにたどり着くのです。
コンピューター言語には足し算と掛け算のほかにもたくさんの命令があり、ほとんどの言語では、どの命令を最初に実行するかをコントロールする優先順位の精巧なシステムを持っています。しかし Lisp にはそのような繊細さはありません。
例えば、 (1+2)x3 のような計算は
と書けますし、あるいは
1+(2x3) のような計算は
と書くことが出来ます。 Lisp のこの書き方にはあいまいさを保ったままでいる方法がないのです。
結局はこのやり方が最も良いものだということがわかるでしょう。
あいまいなメモ: (+ 1 * 2 3) を試すことも可能です。トップレベルでは * は直前のコマンドの結果を呼び出すために使われるものですが、もしそれが数値なら間違った答えを返すでしょう。もし数値ではないならインタープリタはエラーを返します。プログラムの中で (+ 1 * 2 3) は * は値を持っていない、というエラーメッセージを返すでしょう。これについての詳細は後述します。
一旦 third に戻りましょう。 third シンボルには三つの項目が入った list を代入したことを思い出してください。 third をタイプすれば list の内容を見ることが出来ます。
Lisp には list から項目を抜き出す関数があります。 first 関数は最初の項目を抜き出します。
second 関数は list の二番目の項目を抜き出します。
もし掛け算の方が良いのなら list の最初のシンボルを変更することが出来ます。
二番目の項目を変更することも出来ます。
そして、この例では不可解に見えますが、同じことを三番目の項目にも適用できます。
どのようにしてこれは動いているのでしょうか?以前に説明したことを思い出してください。"シンボルは二つの値を持ちます。普通の目的のための値と、特定の状況で代わりに使われる関数値です。" という説明を変数のところでしたはずです。
特定の状況、とは eval が list の最初のシンボルを評価するときのことです。 list のその他の項目を評価する結果のために、 eval が利用する関数はシンボルの関数値であって、シンボルの一般的な値ではありません。
これをはっきりさせるために、 symbol-function と symbol-value を使用します。
直前の二つの例は非常に紛らわしいですね。インタープリタはシンボル + の一般的な値として、すぐ前のコマンドが実行されたものを保持します。そのため (symbol-value '+) はあなたが直前にしたこと次第になるのです。 (symbol-value '+) の中で行われているのはおよそ以下のようなことです。
上の例は下の例と内容的にはほぼ同じことをしています。
上の例のようなエラーメッセージには非常に苛立たせられます。エラーメッセージを防ぐ方法はないのでしょうか?もちろんあります。 boundp は一般的な値かどうかを、 fboundp は関数値かどうかをチェックします。
T は true を表します。
NIL は false を表すのに使います。 F ではないところに注意してください。
Lisp の入門編では普通、 symbol-funcion については黙っておくものです。なぜかということは分かっています。今やこれを言うからには、大混乱と、あらゆる種類の面倒ごとを引き受けることに備えてください.
たとえば、
上の例と、下の例は
add 関数と multiply 関数へのアクセスを提供しています。
後々のために保存しておきましょう。
setf 一つで好きなだけたくさんのシンボルを代入できることが分かります。
これらの関数をシンボルの一般的な値に入れ込んだことも分かります。
同様に
上の2例がうまく行くことも分かると思います。ここで symbol-value を使っているのは、 symbol-function との類似をよりはっきりさせるためです。
一般的な値のシンボルに、関数を格納することが出来ます。これはまさに、一般的な値のシンボルなのであって、データ値を持つシンボルではないことに注意してください。
上の例はエラーメッセージが出ていることからも分かるように、うまく動きません。 eval がシンボルで始まっている list を評価しようとするとき、 eval は関数値のシンボルを探します。そして見つからなければエラーで知らせるのです。
上の例はちゃんと動くようですね。同じように下の3例もちゃんと動きます。
apply は funcall を組み込んでいることは覚えておく価値があります。つまりは下の例は全て機能します。
再び混乱するようなことですが
おわかりになったでしょうか。ここでは + と * を入れ替えています。
こんな紛らわしいことは元に戻した方が良いでしょうね。
やはりこの方がしっくりきますね。
symbol-function は頻繁に使用されます。そのため、より簡単な書き方が用意されています。 (symbol-function (quote +)) の代わりの短縮形として (function +) がありますが、単純化したものをさらに短縮して #'+ とも書けます。すこし筋道から外れることになりますが、これはまったくもって正しいというわけではありません。が、これは今は先の説明にゆずることとします。