ソフトウェア工学実験/計算機工学実験’94 課題 2 (Lisp 言語) ＃ 1
久野靖(筑波大学大学院経営システム科学専攻)
1994.5.9,1994.6.8

1

Lisp について皆様はこれまでに、 Pascal、C、アセンブラ、機械語など比較的「計算機ハードウェアのモデルに忠実な」言語 (手続き型言語) を学習して来られた。ここで少し毛色の変わった言語にも親しんで頂きたいと思い、課題 2 では Lisp 言語を取り上げる。 Lisp とはどんな言語か? という問いにはいろいろな答が可能である:• 関数型 (functional) ないし適用型 (appricative) 言語である。
• 記号処理 (symbolic computational) 言語である。
• 対話型 (interactive) 言語である。
どれもある面では正しいし、それだけではないともいえる。まあごたくは置いておく (というか、別の講義で習うでしょう) ことにして、ここでは Lisp を体験して頂くのに必要な最低限の説明にとどめる。とにかく、実習 3 回しかないのだから、時間中からたっぷり体験して頂かないと何も身につかない。そのため毎回練習問題の実行結果を 1 ページ程度プリントアウトして頂き出席に替えるとともに、皆様の理解度をチェックさせて頂きます。よろしく…

2

Lisp の動かし方

以下では実習に Unix 上の CommonLisp 処理系である KCL(Kyoto Common Lisp) を用いる。その使い方は:% lisp
KCl (Kyoto Common Lisp) May 15, 1989>
この「>」というのが Lisp のプロンプト。ここで任意の Lisp の式を打ち込むことができる。終りにしたい
時は Ctrl-D を打てばよい。それだけである。

1

3

Lisp の形式の基本

上で Lisp は関数型とか適用型、というのがあったが、Lisp ではすべてのプログラムは関数 (厳密には関数でないのもあるがまあそれと類似したもの) の適用から成る。関数呼び出しは常に次の形式から成っている。

(関数名 引数 1 引数 2 ... )この、「(」がまず来る、というのを Pascal から来た人は絶対間違えるので注意。また、区切りは空白だということも。つまり、Pascal では「f(x, y)」だったものが Lisp では「(f x y)」になる。そして、「(」の次に必ず「演算」が来る (前置記法という)。これは例えば「かけ算」とか「足し算」でもそうである --慣れるまではこれも異様に思えると思うけど。さっそくやってみよう。

>(+ 1 2)
; 前置記法 --- 演算が最初3>(* 3 4 5) ; 引数はいくつあってもいい60>(- 10 3.5) ; 小数点があると実数6.5>(/ 10 3); どうなると思いますか?
もちろん、「引数」のところはまた「式」でもよい。つまり式を入れ子にしてもいい。

>(/ (+ 2.71 3.14) 3.0)
1.95
練習 1 次の計算を行なえ。

• 半径 5cm の円の面積• 半径 5cm の球の体積• 底の半径 5cm、高さ 15cm の円柱の体積• 底の半径 5cm、高さ 15cm の円錐の体積

4

関数の定義

Lisp では「プログラムを組む」というのは要するに「関数を作る」ことに他ならない。複雑なプログラ
ムは関数が一杯ある、というだけである。関数定義は次の形をしている。
(defun 関数名 (引数 ...) 式)
引数というのは Pascal などの値引数と同じやつである。そして、関数の本体としてその関数が返す値を計算する式を書く。次のような具合である。

2

>(defun circle (r) (* r r 3.14))
CIRCLE>(circle 5.0)
78.5>(circle 10.0)
314.0
練習 2 次の関数を定義して動かしてみよ• 半径を与えて球の体積を計算する関数 sphere• 底面の半径と高さを与えて円柱の体積を計算する関数 cylinder• 底面の半径と高さを与えて円錐の体積を計算する関数 corn
蛇足であるが、定義した関数はすぐ使用可能になるから、他の関数から呼び出してよい。だから円柱の体積を計算するのに球の面積を求める関数が利用できるし、円錐の体積を計算するのに円柱の体積を利用することもできる。このように「もうできてる」部分を利用するのはスマートでよいことである。

5Lisp の動かし方 (つづき)ときに、関数名を間違えたり引数の数が合わないと当然エラーになるが、KCL の場合は次のような感じである。

> (crcle 5.0)
Error: The function CRCLE is unbound.
Error signalled by EVAL.
Broken at EVAL. Type :H for Help.
>>「>>」というプロンプトが出ているけど、これはエラーの起きたところで停止して、そこで色々な指令を使って詳細を調べられることを意味する。それはについてはおいおい後で述べるとして、とりあえず通常の「>」の状態に戻るには>> :qTop level.
>
このように「:q」と打てばよい。
さて、関数を定義するようになると、「虫」がいて修正するのに毎回打ち込み直したくはないですよね?
そこで、Pascal などと同様、emacs でファイルを作り、そこに関数定義を保存しておくようにする。その時、ファイル名の最後は「.lsp」で終るようにしておくのがよい。例えば「circle.lsp」という関数に上記の defun (1 行だけだけれど!) を書いておいたとして、

3

>(load "circle")
Loading circle.lsp
Finished loading circle.lsp
T>
このようにして読み込める。なお 1 つのファイルに複数の関数の defun を書いても構わない。

6

場合分けと条件

プログラムを書くにあたって、「もし... なら... そうでなければ...」という「場合わけ」は重要な概念であり、これなしにはほとんど意味のあるプログラムは書けない。 Lisp ではこれを(if 条件 式 1 式 2)で行なわせることができる。なお、条件 (=、<、>、<=、>=、などなど) も前置記法になることに注意。

>(defun absolute (x); 絶対値(if (< x 0) (- x) x)); このように 2 行に分けてもよい。
>(absolute 1.0)
1.0>(absolute -5.0)
5.0>(defun inverse (x)(if (= x 0.0) 0.0 (/ 1.0 x)))>(inverse 3.0)
0.3333333333333333>(inverse 0.0)
0.0
練習 3 次のような計算を行なう関数を定義せよ。

• 1 つの引数 x を取り、その正、負、ゼロに対応して 1、-1、0 を返す関数 sign
• 2 つの引数 x、y を取り、その大きい方を返す関数 maximum• 3 つの引数 x、y、z を取り、その最大のもの方を返す関数 max3

7

and、or、not および t と nil

さて、Pascal などと同じように条件式の and、or、not による結合は自由にできる。ただし、例によってすべて前置記法である。

(and 条件 ...) ; 条件はいくつでも書ける(or 条件 ...) ; これも(not 条件); これは 1 つだけ
4ところで、比較演算やこれらの論理演算が返す値について説明していなかった。Pascal ではこれらの式はtrue か false を返すが、Lisp では t と nil がこれらに相当する。例えば次の通り。

>(defun between (x a b)
(if (and (< a x) (< x b)) t nil))
BETWEEN>(between 1 3 5)
NIL>(between 1 -1 4)
T
もちろん、上のようにいちいち t と nil を返さなくても and の結果をそのまま返してもよい。

>(defun between (x a b) (and (< a x) (< x b)))BETWEEN

8

再帰的関数定義

ある関数を定義する時に別の関数を利用するとよいという話をしたが、実は自分自身を利用してもよい。
あたりまえだけれど。

>(defun factorial (x)(if (< x 1) 1 (* x (factorial (- x 1)))))
FACTORIAL>(factorial 5)
120>(factorial 50)
30414093201713378043612608166064768844377641568960512000000000000>(defun sqsum (x)(if (< x 1) 0 (+ (* x x) (sqsum (- x 1)))))
SQSUM>(sqsum 10)385>(sqsum 100)
338350
練習 4 次のような計算を行なう関数を定義せよ。

• 2 つの引数 x、n(n は非負整数) を取り、xn を返す関数 power1• 2 つの引数 x、n(n は任意の整数) を取り、xn を返す関数 power• 2 つの引数 x、n(n は自然数) を取り、1 + x + x2 + x3 + . . . + xn を返す関数 crsum

59関数引数QP

先の factorial、sqsum、power1、crsum などはすべて nx=1 f (x) または nx=1 f (x) の計算になっている。だから、いちいち個別の関数を書かなくても、積 (や和) の計算部分だけ書いて、関数 f を別に用意して引数として渡せばいいはずである。

>(defun ident (x) x); f(x) = x
IDENT>(defun nprod (x f)(if (< x 1) 1 (* (funcall f x) (nprod (- x 1) f))))
NPROD>(nprod 5 #’ident)120
なお、注意すべきことは次の通り。

• 関数名を渡す時は「#’ 関数名」のように書く。単に名前を書くと、変数名だと思われて「そんな変数はない」といわれてしまう。
• 関数を呼ぶ時も「(funcall 関数を表す式 引数 ...)」のように書く。単に「(f x)」と書くと「fなんて関数は定義されていない」といわれてしまう。
P

練習 5 上と同様に一般の f (x) を計算する関数を作り、それを用いて sqsum、power1、crsum を作り直してみよ。また、1 からの和でなく、指定された m からの和にしてみよ。
練習 6 引数 f、a、b(ただし f は a から b までの区間に f (x) = 0 の根を持ち、なおかつこの区間で単調増加) を受けとり、この根を求める関数 findroot を書け。なお、誤差 E は適当に決めてよい。またこれを用いて 2 の平方根を求めてみよ。 110lambda 式

ところで、前節の ident のようにわざわざ「つまらない」関数に名前をつけて定義するのはなんとなく面白くない。実は Lisp では「名前のついていない関数本体」を書くことができる。これをラムダ式という。
ラムダ式の形は(lambda (引数 ...) 式)
つまり defun の関数名を取ってしまったようなものである。これを使えば先の階乗の例は次のようにすればよい。(関数を渡すときは#’ が要るという点は変わらないので注意。 )>(nprod 5 #’(lambda (x) x))120
練習 7 練習 5 と練習 6 で渡す関数をラムダ式で直接書くようにして実行してみよ。
1
ヒント: f (a) < 0、f (b) > 0 のはずである。従って、f ( a+b) が正なら根はこの区間の前半、負なら区間の後半にあるので、区2間を半分にして findroot を呼び出せばよい。

611

実習

実習では本資料の最初から順に実行例と同じものを打ち込み、結果を確認して下さい。練習問題も順にやってください。皆様の理解度を見るため、次のように「本日の出席」をとります。

1. 練習問題をやっている状況の記録をファイルに取ってください。 2
2. 採取したファイルを emacs で編集し、どうでもいい所は削除してプリントアウト 2〜3 ページ程度にしてください。やった練習問題の量が多くて収まらない時には最初の方の問題を省略してください。
3. そのファイルをプリントアウトして、その裏側に (複数ページにわたる場合には見やすさのため最後のページの裏側に) 次のものを記入してください。
• 学籍番号、氏名、所属、本日の日付。
• 以下のアンケートに対する答え (簡単でいいですよ)。
Q1. Lisp を使ったことがありますか。 Lisp についてどう思っていましたか。
Q2. 本日やったことのうちどれくらい既知でしたか。特に難しいと思った部分はどこですか。面
白かった部分はありますか。
Q3. 本日の感想、今後の要望などお書きください。
2 ページ以上にわたる場合にはホチキスでとめて下さい。ホチキスは自分で持って来るか友人に借りること。
(事務室で借りると苦情が来るので…) 出席として認定されるためには、本日 5 時までに事務室のレポートボックスに提出のこと。時間切れで練習問題が全部できなかった場合には途中まででも結構です。

X でやる場合には、xterm の窓の上にマウスカーソルがある状態で Ctrl キーを押したまま中央のマウスボタンを押してメニューを出す。その中で「logging」という項目を選ぶと以後のやりとりの記録が取られる。もう一度同じ操作をやると止めることができる。記録は各自のホームディレクトリの「XtermLog.xxxx」というファイルに取られる。何回でも on/off できるので、色々試して OK だったら記録する、というふうにすれば効率的である。
27