Land of Lisp勉強ノート#3

定番のLISP本 オレイリー LISP
定番のLISP本 オレイリー

第3章のノート。意訳っぽっくなっているの注意。

3章 Lispの構文の世界を探検する

Lispの構文は括弧で括られた極めてシンプルなものだ。どうなっているのか探検してみよう。

3.1 シンタックスとセマンティックス

シンタックスは文法、セマンティックスは意味。

プログラムを書く際には、選んだプログラミング言語のシンタックス(文法)に従ってを仕様書の内容をコーディングしていくことになる。(仕様書無しでいきなりもあるけどね)

仕様書の内容はコンピュータの動作について定義したものであり、セマンティック(意味)と考える。

どんな言語でもこの考え方は共通するものと思う。

ではLispはどうなっているのか。そのシンタックスについて考える。

3.2 Lispシンタックスの構成要素

Lispのコードは単純なリストシンタックスを使う。リストは構成要素が括弧で括られたものだ。

(defun square (n)
    (* n n))

これはsquareと言う関数を定義している。
その機能は与えられた数を二乗するだけだ。
括弧で括られたリストの中には他のリスト、シンボル、数値、文字列などの要素で構成されている。

下記は個々の基本的構成要素について。

シンボル

これは基本的なデータ型1だ。独立した単語である。この単語を構成するのは、アルファベット、数字、記号(+ -/* = < > ? | _のような)である。例を示す。

foo, ice9, my-killer-app27, –<<==>>–

シンボルの扱いは難しい。何にでも化けそうだ。

Common Lisp2のシンボルは大文字と小文字を区別しない。
多くのLispプログラマー3は大文字を使わないようだと言っている。
例えば下記のようになるのだ。

> (eq 'fooo 'FoOo)
T

シンボルの左に’文字(データモードの指定)が付いていることに注意しておくこと。

数値

整数と浮動小数点数を扱える。
小数点があれば浮動小数点数扱い。
整数と浮動小数点数が混ざれば浮動小数点で計算される。
これを整数が汚染されると言うらしい。
整数同士の演算で有理数が返るときがあるので注意。と言うか面白い。

> (/ 4 6)
2/3

整数同士の演算に限るが、誤差の蓄積を心配しなくて良い。

文字列

ダブルクオートで括れば良い。小文字が大文字に化けることはない。

> (princ "Tutti Fitti")
Tutti Futti
"Tutti Fitti"

シンボルのときとは違って、ダブルクオートの間のデータがそのまま出力される。
エスケープ文字を含めることもできる。例を示す。

> (princ "He yelled \"Stop that thief!\" from the busy street.")
He yelled "Stop that thief!" from the busy street.

3.3 Lispはコードとデータをどう区別するか

Lispにどうやってこれらのモードを示すのか、そのシンタックスを勉強する。
Lispはコードを読み込む際に2つのモードを持っている。
コードモードデータモードだ。このモードの間を行き来しながらプログラミングするのだ。

コードモード

Lispのデフォルトはコードモードだ。だからREPL4へ入力し始める際はコードモードになっている。Lispは読んでいるのはコードだと考えているわけ。

コードモードのLispに入力できるのはコード。それはフォームの構造になっていないといけない。

(foo bla bla bla bla bla)

フォームは最初の要素が特別のコマンド(通常は関数名)であるリストのことだ。2番目以降の要素は全て関数の引数として関数に渡される。例を示す。

> (exp 2 3)
8
> (exp 2 (+ 3 4))
128

2個目の例はリストがネストしている。まずコードモードで全体が見られ、expコマンドを認識し、次に引数をコードモードで見られ、(+ 3 4)のフォームが見つかり、実行される。その後2と7が引数としてexp関数に引き渡されるのだ。その後関数の演算結果の出力が表示されている。

データモード

データモードで書かれたものは全てデータとして扱われる。Lispはそれを実行しようとはしない。ではこのように入力するとどうなるだろうか。

> '(exp 2 3)
(exp 2 3)

そのまま出力される。これはシングルコートが付いているせい。コードモードでもシングルコートをつける5とその範囲内でデータモードになって、リストはコマンドではなくてデータの一塊なるんだ。シンボルのところで’fooという表現があったけど、このシンボル文字列の範囲(この語だけ)でデータモードになるんだ。

3.4 Lispとリスト

Lispのプログラムはリスト構造で記述されている。家で言えば壁がリストで出来ているだろう。壁のレンガはリストを構成するシンボル、数値、文字列で、これらをつなぎ合わせるものがコンスセルだ。レンガならモルタルで、リストならコンスセルという構造なのだ。

これはLispの内部的記述になる。リストを支えているコンスセルという存在は普段は見えないが、何かあると見え隠れするだろう。

コンスセル

コンスセルの概念は並んでおかれた2個の箱だ。夫々の箱の機能は別のものを指すことだ。相手は他のコンスセルでも良いし、それ以外のLispデータでも良い。2種類のものを指せるという性質の応用で、コンスセルを繋いでリストを表現できる。(副作用でデータ移動の必要性が少なくなる)

実のところ、Lispにリストという特別な構造があるわけではなく、コンスセルの連なりがリストに見えているだけだ。しかし、人の見た目を考慮してリストとしている。

簡単な例で実際を考える。(メンドクサイので図は載せない)

‘(1 2 3)を例題とすると、コンスセルは下記のように作られている。

  1. 最初のコンスセルは1というデータと次のコンスセルを指す
  2. 次のコンスセルは2というデータと次のコンスセルを指す
  3. 最後のコンスセルは3というデータとnil6を指す

プログラミングの経験がある方なら何となくどこかで眺めた光景ではなかろうか。
コンスセルのチェインをたどれば(最初のコンスセルがわかればね)リストの論理的構造を辿れる。

リストを扱う関数

ここではコンスセルを扱う基本的関数を紹介する。

cons関数

2つのデータを結びつけたいときに使う。
通常2つ目の要素は他のリストだ。

> (cons 'chiken 'cat)
(CHIKEN . CAT)

シンボルchikenをシンボルcatとくっつけた。
cons関数はリストを返しているが、普通と違って、ドット(.)が記述されている。これはその両方をつなげているコンスセルが出来ていることを示している。つまりコンスセルが顔を出しているんだね。

>(cons 'chiken ())
(CHIKEN)

> (cons 'pork '(beef chiken))
(PORK BEEF CHIKEN)

(cons 'beef (cons 'chiken ()))
(BEEF CHIKEN)

> (cons 'pork (cons 'beef (cons 'chiken ())))
(PORK BEEF CHIKEN)

コンスセルの存在を隠すように動作するのだ。

carとcdr

リストとは2つの要素をつなげるセルがただただ長く連なったものである。

car関数とcdr関数はリストの要素を探し出すのに使用する。

car関数

car関数はセルの最初のスロットにあるデータ7を取り出す。
簡単に言うとリストの最初の要素を取り出す。

> (car '(pork beef chiken))
PORK

取り出した要素はリストじゃないところに注意。

cdr関数

一方cdr関数は2番めの要素以降を取り出すのに使う。

> (cdr '(pork beef chiken))
(BEEF CHIKEN)

今度はリストなので括弧で括られている。
carとcdrをつなげた関数も定義されているが、4階層まで。
Lisp的にはこれ以上深いリストを使うなということだろうか。

Lispのコメントは行内なら;文字、複数行まとめるなら#|と|#で囲えばよい。
ただし、REPLでは複数行のコメントはうまく行かなかった。

> (cdr '(pork beef chiken))    ;listから先頭要素以外を取り出す
(BEEF CHIKEN)
> (car '(beef chiken))         ;listから先頭要素を取り出す
BEEF

> (car (cdr '(pork beef chiken)))  ;car,cdrがネストしている
BEEF

> (cadr '(pork beef chiken))       ;標準定義の便利関数 2番めの要素を求めよ
BEEF
list関数

cons、car、cdrという関数を使って定義された、便利な関数がlist関数。

> (cons 'pork (cons 'beef (cons 'chiken ())))  ;1個ずつ
> (list 'pork 'beef 'chiken)                     ;見た目がわかりやすい
> '(pork beef chiken)                            ;コーディング簡単

これらは全て同じと考えられる。

ネストしたリスト

リストはネストできる。しかし、コンスセルから出来ていることは同じ。
リストをネストすることで、コンスセルを使った複雑な構造が出来上がる。

サンプルコードで練習してみて欲しい。

3.5 本章で学んだこと

  • Lispの括弧は、シンタックスを最小限に保つためにある。
  • リストはコンスセルから作られる。
  • consコマンドでコンスセルを作ってゆくことでリストが構成される。
  • carとcdrを使ってリストの中身を調べることができる。
  1. シンボル型と言うらしい。 ↩︎
  2. 今後はLispとだけ呼ぶ ↩︎
  3. 今後はLisperと呼ばせてもらおう ↩︎
  4. read-eval-print-loopでREPLだね ↩︎
  5. 「クオートする」と呼ぶんだ ↩︎
  6. ニルと読む 煮ると間違えやすい^^ ↩︎
  7. リストの最初のコンスセルが指すデータかな? ↩︎

コメント

  1. SOHOつかさ読者 より:

    いつか直すことが前提だねえ。

  2. SOHOつかさ読者 より:

    comment test by reader user.

タイトルとURLをコピーしました