識別子 'Blog' が定義されていません

気が向いたらプログラムとかそのほかとかについてつらつらと書いていくブログです。

Lisp日記 1にちめ

f:id:nanasium:20170313181434p:plain
Common Lispの彼、描いてみました。上手に描けてる?

Lispを勉強するとは言ったものの、いまいちLispってどういうものなのかわかりません。

ただ漠然と、古い、かっこがいっぱい、かっこいいということぐらいしか知りません。これはだめです。事案です。

なのでLispについて学んだことやLispの構文だとか知識だとかを、備忘録的に書いていこうと思います。

まず初めにLispの名前の意味から行きましょう。
LISt Processingの略です。
Lots of Insane Stupid Parantheses(たくさんの基地外じみた愚かな括弧)の略ではありません。

名前からもわかる通り、Listと呼ばれるものが大事になってきます。
どう大事なのかはもう少し後で……

このLispという言語には方言と呼ばれるものがたくさんあり、実装者の数だけ方言があるとすら言われていた時期があったそうです。
このままじゃいかんと遺憾の意を示した方が、この方言を統一するためにCommon Lisp(要するに標準語のようなもの)を発表しました。
しかしその水準はとても高過ぎるものだそうです。

また、Lisp神の言語と言われるだけあり、その力強さはまさにバベルの塔の建設により神の怒りを買う前、人々が話していた共通言語のように素晴らしいです。
その証拠に、Lispに見られる数々の機能は数多のプログラム達に影響を及ぼしています。

閑話休題

僕はCommon Lispを高度な次元で再現している、xyzzy Lispをベースに勉強していきます。
ダウンロードリンク


早速LispでHallo Worldを書いてみました。

(format t "Hello World\n")

これを入力した後にCtrl + Jを入力すると

(format t "Hello World\n")
Hello World
nil

と出力されます。

この時左右の括弧()の数、ダブルクォーテーションマーク""の数があっていないとエラーが出ます。

他のプログラムの使用経験があればそこまで迷うような仕様ではないと思います。

さて上のほうに書いたリストについてです。

(format t "Hello World\n")

この括弧で囲われた部分をリストと呼びます。
括弧のセットで一つのリストです。

そしてformat t "Hello World\n"それぞれを要素と呼びます。

さてこのLispになくてはならないリストですが、このリストには二つの役割があります。

  1. データをためること
  2. 関数(function)を呼び出す

この二つがリストの役割です
C++をメインに使っている自分としては1つ目は変数(Variable)で、二つ目はそのまま関数かな? なんて当てはめてみたくもなりますが、無理に対応させようとすると肝心なところを見落としそうなので、それぞれ今までになかった概念というとらえ方で行こうと思います。

データの保管

さて一つ目のデータをためることですが、リストの中に格納されたデータを要素と言い(上で述べているformat, t, "Hello World\n" などのこと)リストでないこれら要素のことをアトム(atom)と言います。

またリストはコンスセル(cons cell)と呼ばれるものに分解することができます。言い換えますと、リストは複数のコンスセルの集合体です。

一つのコンスセルはcar、cdrと呼ばれるそれぞれの部位に分解することができます。
car、cdrにそれぞれ一つずつアトムが入り、二つの要素を格納することができます。

(format t "Hello World\n")

先ほども利用したこのHallo Worldを用いて説明します。
このリストにはformat、t、"Hello World\n"の三つのアトムが含まれており、それぞれが一つのコンスセルに格納されています。
要するにコンスセルが合計で3つあるということになります。

中にはコンスセルは二つの要素を格納できるのだから、3つではなく2つもあれば十分なのではないか? そう思う方もいると思います。なかなか鋭い推察です。しかし端的に答えを言ってしまえばやはり3つ必要です。

先ほども書いた通り、コンスセルにはcarとcdr、合わせて二つの格納庫があります。
3つあるコンスセルのcarにそれぞれformat, t, "Hello World\n"が格納されます。
そしてcdrにはそれぞれのコンスセルを連結させるつなぎの役割を果たしてもらうことになります。
また"Hello World\n"とセットになっているcdrには連結先がないので、終止符が打たれます。
図1
f:id:nanasium:20170315080107j:plain

関数

そしてもう一つの役割が関数(function)です。
Lispにではリストが入力されると、一番初めの要素を関数名、残りの要素を引数として判断します。

(format t "Hello World\n")
Hello World
nil

何度も使いまわしているHello Worldを例に説明すると、formatが関数名で、tと"Hello World\n"が引数になります。

関数には実行結果が返されます。Hello Worldの場合はnil(0の意)を返り値になります。
同じく画面に表示されるHello Worldは返り値ではなく、関数formatの働きによって画面へ出力されたデータです。
言い換えると、formatはC系言語でのprintfやcoutのようなものと考えられます。

他の例を見てみましょう。

(+ 1 2 3)
6

この関数は単純な足し算を実行したものです。

  1. が足し算を行う関数になります。+を関数として実行する場合、引数には数値が与えられます。また数値データもアトムです。
(+ a b)

このような数字ではない引数を用いても結果は出力されません。

xyzzy Lispでは浮動小数点(float doubleなど)、多倍長整数(いわゆるlong型)、分数などを計算することができます。

ではもう少し複雑な計算をしてみましょう。

(+ (* (* 1 2 3) (/ 3 2)) (- 5 1))
13

これを普通の数式に直すと、
1 * 2 * 3 * 3 / 2 + 5 - 1 = 13
となります。

リスト()で括ることでリストの中にリストをアトムとして入れることができるのもLispの大きな特徴の一つです。

ちなみに+や*では引数を設定せずに計算を行うこともできます。

(+)
0
(*)
1
  • や/の場合は引数が少なすぎます。というエラーがでます。

Lispでは与えられてデータ(引数)に対して、定められた規則(関数)を実行していくことで動作します。これを評価すると言います。
上でも述べた通り、Lispでは第一要素を関数として扱い、関数を呼び出す(表示したりさらにほかの関数の引数として使用したり)する前にその引数を評価するという規則なのです。

数値アトムの場合にも規則があります。それは評価される自分自身(そのままの数値)を返すというものです。

シンボル
(format t "Hello World\n")

上で話したように、formatが評価される前にまず引数が評価されます。第一引数のtはシンボル(symbol)と言います。またformat、+、-などもシンボルです。シンボルにはそれぞれ固有の名前がついているので、区別することができます。

シンボルはデータを格納する変数、および関数を格納する役割を持っています。さらに属性リストというデータも格納することができますが、その話はまた後で

Lispはシンボルを評価するとそこに入っているデータを返します。しかし、リストの第一要素である場合は関数の定義を実行します。
formatは第一要素なので関数の定義を実行します。

シンボルにどのようなデータが入っているかを確認するには、Lispにそのシンボル名を打ち込めばわかります。

format
=>エラー
a
=>エラー

エラーが出るということはそのデータに何も入っていない、または関数に引数がセットされていないという状況になります。
つまりformatの場合引数がないから、aの場合データが入っていないからエラーが出ています。
またformatは関数以外にも、変数として使うこともできます。

(setf format "Hello World\n");Ctrl + J
"Hello World\n"
(format t format);Ctrl + J
Hello World
nil
setf

さて真上の例でsetfというものが出てきました。
これはデータを入力するのに使います。
データをセットするにはsetqもしくはsetfを用います。
Common Lispではsetfを使うことが推奨されています。setfはsetqに比べると融通が利くようです。

a;;エラー
(setf a 25) ;;aに25を代入する
25
a
25

このようにデータの入っていないシンボルではエラーが出ますが、setfなどでデータを設定してあげると、それ以降(データが変更されるまで)入力したデータを格納してくれます。
普通データが設定されていない引数(ここではシンボルa)が評価されるときエラーが出るはずですが、setfは引数を評価しないで受けとる関数のなので何事もなく実行できるのです。
Common Lispでは引数を評価しないで受け取る関数が二つ、特殊形式(Special Form)とマクロ(Macro)のがあります。これらの二つはプログラムの制御を行う場合などに使われます。

上でも書いた通りsetfでは第一引数(シンボル)は評価せず、第二引数を評価した結果をシンボルに格納します。例えば第二因数にリストを書けば、それを評価した結果をシンボルへと代入するのです。

(setf a (+ 25 26 27)) ;;aに25+26+27が代入される
78
a
78

幾つかのシンボルにはあらかじめデータがセットされています。例えばtとかnilのことです。
どちらもHello Worldで出てきましたね。

(format t "Hello World\n")
Hello World
nil

tをタイプしてctrl + jを入力すると、以下のような結果になります。

t
t

言葉で説明すると、tが出力されるということです。
tというシンボルは条件を判別するときのという値が入っています。Trueのことですね。
反対に(False)の値を持っているシンボルがnilになります。
実をいうとtが真の代表的な値というだけであって、本当はnilを除くすべての値が真の値を持っています。
nilなら偽、nil以外なら真ということです。

これらのシンボルは定数なので、setfなどの関数で値を変更することはできません。

nilには偽以外の意味もあります。

();;リストの中身が無い
nil

()は中身が無いリスト、空リストです。nilにはこのような空リストの意味もあります。
ちなみにnilnil、'nil、()、'()の四つの方法で記すことができます。

何度も話がそれてしまいましたが、話を再び"Hello World"に戻しましょう。

(format t "Hello World\n")
Hello World
nil

" "(ダブルクォート)で括られたデータ(この式では"Hello World\n")を文字列(string)といいます。文字列もアトムの一種で、評価されると数字のようにそれ自身の値を返します。

Lisp上で"Hello World\n"と入力すると、二つ目のクオテーションマークの前に開業されます。

"Hello World\n"
"Hello World
"  ;; 改行される

これは\nによる影響です。\nは改行を意味します。
\をエスケープシーケンス(escape sequence)と言います。\nの他には\t(tab)や\"("を出力)などいろいろなものがあります。

これでようやくformatに渡される引数の評価方法の説明が終わりました。
formatは第一引数がtの場合、データを出力します。第二引数には文字列を与えて、それに従ってデータを変換します。format関数の場合は文字列の内容を評価するだけです。


長くなってきたのでこの辺で一度区切ります。
個人用の忘備録なので間違っていることもあると思いますのでご注意ください。
掲載内容に間違いなどありましたら、コメントにて指摘していただけると幸いです。

今回のまとめ

  • リストにはデータを保持すること、関数を呼び出すことの二つの役割がある。
  • シンボルにはデータを保存できる。
  • setfまたはsetqでシンボルにデータを入力できる。


参照
M.Hiroi's Home Page / xyzzy Lisp Programming
マンガで分かるLisp(Manga Guide to Lisp)

追記
2017/3/25