Lisp 一夜漬け -- 1.Lisp の書き方


対象としている読者は、 何らかのプログラミングを経験していて(関数・変数という概念がわかる)、 Lisp インタプリタに触れる機会を持つ人である。 すでに Lisp でプログラムを書ける人は対象外だ。


■ Lisp の世界

Java、C、C++、BASIC、COBOL、FORTRAN、Lisp、Pascal、Perl。 現在、代表的なプログラミング言語というと、こんなものか?(*1) これらの中で、Lisp は他の言語とはまったく異なった書き方、考え方を必要とする。 あまりに特殊なため、Lisp 専用のハードウェアも作られたほどである。

FORTRAN が生まれた1956年のわずか3年後、1959年に Lisp は生まれた。 C 言語が1972年であるから、わりと歴史の長い言語なのである。 (ちなみに、COBOL:1959年、BASIC:1964年、Pascal:1968年、MS-BASIC:1975年、C++:1980年、Java:1995年 である)

Lisp は、LISt Processor という語源が表すように、 「リスト」というデータ型を扱うことに長けている。 Lisp のプログラム自身もリストでできているので、 プログラム中でプログラムを生成して実行するという芸当が簡単に行なえる。 人工知能の分野でよく用いられるのは、この芸のおかげである。

Lisp は処理系ごとの方言が強い。 一応、Common Lisp という基準があるにはあるが、基準としては高すぎた。 Common Lisp には、例えば分数や複素数まで含まれているのである。 これは実装者にとっては酷だ。 他のメジャーな言語に比べるとフリーソフトの処理系が多い Lisp であるから、 実装が面倒な機能は実装されない。


*1:筆者の偏見が大いに含まれている。

■ 関数という概念

プログラミング言語は「手続き型言語」「関数型言語」「論理型言語」「オブジェクト指向言語」などに分類することができる。

Lispは関数型言語に分類される。 関数を組み合わせることによりプログラミングする言語なのである。

関数とは何なのかを考えてみよう。 数学の世界の関数を簡単に言うと「いくつかの値を与えると、ある1つの値が定まるという関係」である。 プログラミング言語の世界の関数は一般的に 「0個以上の値を与えると、何かをして、1つの値を返してくれるもの」である。 与える値を「引数」「parameter」「argument」、返ってくる値を「返り値」などと呼ぶ。

「いくつかの値を持って関数の頭に飛んで行って、中身を実行して、値を1つ持って帰ってくる」 という認識は、手続き型言語の考え方である。 Lispではこういう認識は捨てて欲しい。数学と同じ考え方で構わない。


・GNU Emacs / Mule を Lisp インタプリタとして使う

Emacs / Mule を引数無しで起動すると、最初に `*scratch*' という名前のバッファがあるはずです。

モードラインの右側を見て、`(Lisp Interaction)' の表示がない場合、 M-x lisp-interaction-mode と入力します。

そのバッファ内で Lisp のプログラムを入力し、カーソルが最後の `)' の右にあるときに C-j を押すと実行されます。


■ 書き方

Lispインタプリタに

(+ 345 654)

と入力し、最後に改行キー(Emacs の場合は C-j)を押してみて欲しい。 おそらく画面には999と表示されて、再び入力待ちになるだろう。

今、あなたは Lisp インタプリタに「S 式 (Symbolic-expression)」を入力した。 インタプリタは S 式を「評価 (evaluate)」し、評価結果の999を画面に表示したのである。

Lisp のプログラムは S 式(単に「式」とも言う)の集りで、 「プログラムを実行する」というのは入力の S 式を順に評価することである。

上記の式は「数値の345と654を加算する」という意味である。

再び Lisp インタプリタに向かって

345

と入力する。 結果、345と表示されるはずである。 これは、「345を評価すると345になる」ということを表す。 単に数値だけでも S 式であり、評価結果はそれ自身となる。

ともかく、

(関数名 引数1 引数2 ...)

  1. 全体を括弧で囲む
  2. 関数名、引数どうしは空白で区切る

と書けば関数を呼び出すことができるのである。引数の数は関数による。 加算を行なう関数が `+' であり、引数は可変長である。

(+ 345 654 1 100 11)

というように3個以上の引数を与えることもできる。

関数の引数も関数呼び出しでよい。 関数を呼び出すときはまず引数が評価され、その結果が関数に渡される。 関数の引数はすべて評価される。 つまり、「12×80+20/5」を計算したいときには

(+ (* 12 80) (/ 20 5))

と書ける、ということである。 乗算をする関数 `*'、 除算をする関数 `/' の評価結果をそのまま加算の引数にできる。 C 言語や BASIC などに比べるとずいぶん冗長な書き方になるが、これは我慢してもらいたい。 これが Lisp のポリシーである。 演算の優先順位を考慮する必要がないという利点もある。


■ 変数

(setq hoge 345)

を評価する(setq は「せっとく」などと読む)。 これ以降 `hoge' を評価すると345が結果となる。 Lisp インタプリタに対して

hoge

と入力すると、345が表示される。

(+ hoge 654)

は、もちろん999である。

変数の名前は、すでに変数として用いられていない名前ならばなんでもよい。 関数名と同じ変数名にしても構わない(が、ややこしいのでそんなことをしてはいけない)。 多くの処理系ではアルファベットの大文字小文字は区別しないので `hoge' `HOGE' `Hoge' は同じ名前だとみなされるが、 まれに大文字小文字を区別する処理系もある。

変数名として使えない文字も、処理系によって様々である。 普通、括弧は使えない。 C 言語などと違い、`-' を含めることができる。 日本産の処理系だとマルチバイト文字(いわゆる全角文字)も使えることが多い。

では、まだ setq されていない名前を使ったらどうなるのか。 `fuga' がまだ一度も使われていないときに

(+ fuga 654)

とすると、まともな処理系なら「fuga には値がない」というエラーになる。

先に「関数の引数はすべて評価される」と書いた。 では、初めて「(setq hoge 345)」を評価するときに `hoge' が評価されて「hoge には値がない」というエラーにはならないのか。 ---ならないのである。 setq は1番目の引数を評価しない。 setq は関数ではなく、「特殊形式 (special from)」というものである。 すべての引数を必ず評価するわけではない関数(のようなもの)は、関数ではなく特殊形式である。 ifletlet*quotesetq などが特殊形式である。

もちろん、setq の2番目の引数は評価されるので、 いくらでも複雑な式を書くことができる。

(setq hoge 345)
(setq foo 654)
(setq bar (+ hoge foo))

Lisp では変数に型というものはなく、データに型がある。 変数に型宣言は必要ないし、数値を代入した変数に文字列を代入したっていい。 文字列は2重引用符 " で囲む。

(setq hoge 345)
(setq hoge "strings")

■ 制御と述語

  • if
  • まず条件分岐の雄、if である。if の書式は

    (if P THEN ELSE)

    である。式 P の評価結果が真のときには式 THEN を評価し、 偽のときは式 ELSE を評価する。返り値は評価した式の評価結果である。

    Lisp において、「偽」の値は `nil' で表わす。 nil以外の値はすべて「真」である。

    (if nil 11 24)

    では11は評価されず、24が評価される。

    (if 3 11 24)

    は11である。

    「〜かどうか」の判定をして「真」または「偽」を返す関数を 「述語 (predicate)」と呼ぶ。 述語の返り値は偽 `nil' もしくは真 `t' である。 nil 以外はすべて真とみなされるが、 値 `t' を真の値の代表としている。 BASIC、C 言語との比較を表1に掲載する。 Lisp では整数の0は真と見なされるので注意すること。

    表1: 真偽の値
    真の代表
    BASIC00以外-1
    C/C++00以外1
    Javafalsetruetrue
    lispnilnil以外t

    nilt を変数に代入することも、もちろんできる。

    (setq hoge nil)
    (if hoge 11 24)

    述語関数は色々なものがある。 数値どうしの関係を比較する < <= > >=、 あらゆる型において同一判定をする equal、 型の検査をする integerp numberp stringp symbolp null などなど。 詳細は各処理系のマニュアルを見て欲しい。 述語関数の名前には最後に p が付くものが多いが、この p は predicate の p である。

    真偽の値に対する演算を行なう not and or ももちろんある。 and or は C 言語と同じく、必要最小限の評価しかしない。 and は引数を左から順に評価し、nil になるものがあった時点で nil を返して終了する。 すべての評価結果が nil 以外のときは t を返す。 or は引数を左から順に評価し、 nil 以外のものがあった時点で t を返して終了する。 すべての評価結果が nil だったら nil を返す。

  • progn

  • (progn E0 E1 E2 ...)

    progn は、ただ引数を左から順に評価する。 progn の返り値は、最後に評価した式の返り値である。 if の引数のように、 1つの式しか書けないところに複数の式を書くために用いられる。 C 言語の {〜}、Pascal の begin〜end と同じである。

    (if hoge (progn
              (setq hoge 1)
              (setq fuga 2))
             (progn
               (setq hoge 0)
               (setq fuga 1)))
    
    もし hogenil でないなら、 hoge=1,fuga=2を代入。hogenil なら、hoge=0,fuga=1 を代入する。
  • cond

  • (cond (P0 E00 E01 E02 ...)
          (P1 E10 E11 E12 ...)
          (P2 E20 E21 E22 ...)
          ...
          (Pn En0 En1 En2 ...))
    

    P0 を評価し、もし真ならば E00 E01 E02 ... を評価して終了する。 P0 が偽ならば P1 を評価し、P1 が真なら E10 E11 E12 ... を評価して終了する。つまり、

    1. P0〜Pn は順に評価される
    2. 評価結果が真になるものがあると、 それに対応する Ex0 Ex1 Ex2 ... を評価して終了する

    ということである。cond の返り値は、最後に評価した値である。 P0〜Pn のどれも真にならない場合は、何も行なわない。

    cond は C 言語などの if〜else if〜 に対応する。 上記の書式例は、C 言語では以下のようになる。

    if ( P0 ) {
        E00; E01; E02; ...;
    } else if ( P1 ) {
        E10; E11; E12; ...;
    } else if ( P2 ) {
        E20; E21; E22; ...;
    ..
    } else if ( Pn ) {
        En0; En1; En2; ...;
    }
    

    前掲の if progn の例を cond で書き直すと以下のようになる。 if での ELSE 部は、cond では「常に真」を表す `t' を条件とする。

    (cond (hoge (setq hoge 1)
                (setq fuga 2))
          (t (setq hoge 0)
             (setq fuga 1)))
    
  • let let*

  • (let (V0 V1 V2 ...)
      E0 E1 ...)
    

    let let* は局所変数を定義する。 V0 V1 V2 ...に書いた変数名は、その let 内でのみ有効な局所変数となる。 E0 E1 ...progn と同じである。

    (setq foo 1)   ; fooに1を代入
    foo            ; 1である
    (let (foo bar)
      (setq foo 2) ; 局所変数のfoo
      foo          ; 2である
      (setq bar 3))
    foo            ; これは1
    

    変数名を書くところに `(変数名 式)' と書くと式の評価結果で初期値を与えることができる。

        (let ((foo 2) bar)
          foo)         ; 2である
    

    この初期値の式を評価する段階では、 let で生成される局所変数は使えない。

    (setq foo 1)
    (let ((foo 2) (bar foo))
      ...)
    

    この場合の bar の値は、1である。 局所変数 foo ではない。これが

    (setq foo 1)
    (let* ((foo 2) (bar foo))
      ...)
    

    let* だと、bar の値は2である。 let* では、初期値の式の中でも直前で定義した局所変数を使うことができる。 letlet* の違いはこれだけである。

C 言語などでは文が実行されるかどうかは「通るか通らないか」という捉えかたをするが、 Lisp では「評価されるかどうか」と考えて欲しい。


■ 関数定義

関数を定義するのが、defunde などである(処理系による)。 以降、defun として説明する。

(defun FNAME (V0 V1 ...)
   E0 E1 ...)

関数名が FNAME の関数を定義する。 V0 V1 ... は引数を受け取る変数名である。 E0 E1 ...progn と同じで、 最後に評価した結果がこの関数の返り値となる。

(defun foo (a b c) ...)

で定義した関数 foo を、

(foo 3 4 89)

で呼び出すと、aに3、bに4、cに89が代入されて E0 E1 ...が評価されるわけである。

絶対値を返す関数 abs を定義してみよう。 数学的な定義は

         {  x (x≧0のとき)
  |x| := {
         { -x (上記以外)

である。これを Lisp で書くと以下のようになる。

  (defun abs (x)
    (cond ((>= x 0) x)
          (t (- 0 x))))

[ 次へ | 目次へ ]
(c) 1995-2002 TAMURA Kent
$Id: 1.html,v 1.5 2007/02/06 01:26:15 kent Exp $