来週( 2002/11/20 )は、学術講演会があるために、この日の講義は、休講です。もちろん、補習もありません。
ぜひ、皆さんで、日ごろ大学では、どのようなことが研究されているかを知る(1)良い機会ですので、聴きに行ってみましょう。
今週はWindows XPの、security updaterの更新があるようです。
これは、別にupdateしなくても問題ない( Security Holeではないから.. )のですが、次の機会に結局やることになるので、まあ、今回の機会に行っておきましょう。
とりあえず、windows updateをお勧めしておきます(2)。
くどいようですが、講義時間にwindows updateを行うのは止めてください。Networkが混む原因になりますから。
講義終了後(演習の時間)などに、空いている教室などを利用して実行してください。
僕は、必ず行っています。環境の統一という観点からも、受講者全員がwindows updateを行っていることが望ましいと思います。
先週にも述べたようにここの演習目的は、必ずしも、Lisp言語を学ぶことにはないのですが、新しい言語を学ぶにしても、Programmingの考え方を学ぶにしろ、共通な点は、いずれにせよ習うより慣れろが基本なので、具体的な例題を考えてみたいと思います。
そこで、昔から良くあるのですが、「数当てゲーム」をLispで作ることを考えてみましょう。
「数当てゲーム」というのは、次のような簡単な(子供が行うような.. )ゲームです。
ここでは、このGameの出題者をLisp Programとし、回答者は人間と考えることにしましょう。
上記のProgramを考える上で、まず、基本的な仕組みを色々しらなければなりません。
たとえば、次のような項目がそれぞれ必要となるでしょう。
そこで、順番に説明して行くことにします。
Lispで出力を行う場合は、組み込み関数(3)を利用します。xlispでは、次の関数が出力用の関数が利用できます。
いずれにせよ、関数の値は、引数があれば、その値をそのまま返し、出力は、副作用(4)として、実現されます。
> 1 1 > (princ 1) 1 1 > (princ "abc") abc "abc" > (setq x 12) 12 > x 12 > (princ x) 12 12 >
> NIL NIL > (terpri) NIL > (princ NIL) NIL NIL >
副作用というのは、「薬の?」のような使い方をし、あんまり良い意味では使いませんが、「本来の目的ではないが、生じてしまうこと」という意味です。
Lispでは、関数に引数を与えてその評価結果(関数値)を得ることが、本来の関数の意味なので、その意味で、出力関数は、何ら意味を持ちません(まあ、要するに恒等関数なのですから..)し、「出力を行う」という機能は、(関数値を求めることではないという意味で..)副作用と解釈されるわけです。
Lispの入力もまた、組み込み関数を利用します。入力というのは、人間がキーボードからいくつかの文字を入れた後に、Enter Keyを押すと、その文字列が何らかの形で、Lispに取りこまれるということです。
今回の場合は、数値ですが、以下のread関数を利用すると、キーボードから、数値アトムを意味するような文字列(5)を入れ、Enter Keyを押すだけで、read関数の値は、その数値アトムに評価した値となります。
> (read) 1 1 >
これは、基本的な機能ではないのですが、数当てゲームでは必要なので、おまけとして説明します。
問題を作成するには、計算機が、適当(6)に数を生成する必要があります。
今回は、組み込み関数randomを利用します。
> (random 100) 92 > (random 100) 24 > (random 100) 61 > (random 100) 8 > (random 1) 0 > (random 1) 0 >
「適当」というのは、人間には、それほど難しい話ではないのですが、計算機には大変難しいことのひとつです。幸い、今回の場合は、「意味がなくてもよい」ので、乱数が利用できます。
実を言えば、「乱数」自身も、大変難しい仕組みなのですが、一応、Lispには、お仕着せのものがあるので、(その良し悪しはともかく..)それをそのまま、利用してしまいましょう。
このように、「本来は、それを使って良いのか?」という判断は大変重要かつ、コストの掛かる作業なのですが、「一旦、そこで思考を止めて、まあ、今回はこれにしよう。問題があるなら後でなんとかしよう」という、「棚上げ思考」は、Programmingを行う上で、有効かつ良く利用される思考法であることも、覚えておきましょう(ただ、「棚上げしていたことを忘れてしまう」という問題が、しょっちゅう生じてしまうという困った性質もあるのですが.. )。
何かをした後に、別のことをする。これは、Programmingの中でも、最も基本的な操作の一つです。
Lispでは、これを関数合成で実現するのが普通です。たとえば、「3に1を足して2倍する」というのは、「1を加える」という作業と「2倍する」という作業を続けて行うわけですが、これをLispでは、関数*と+の合成、すなわち、(* (+ 3 1) 2)で実現します。
> (+ 3 1) 4 > (* 4 2) 8 > (* (+ 3 1) 2) 8 >
しかし、出力のような副作用を持つ関数の場合は、どうでしょうか? abcだして、次にefgを出す(つまり、結果的にabcefgを出力する)には.. ?
lispでは、値ならば関数を合成することによって、実現できますが、副作用は、値ではないので、このような単純な関数合成では実現できません。
組み込み関数or, andには、次のような約束があるので、その約束と副作用を組み合わせることにより、この問題に答えることができます。
したがって、たとえば、次のような形で、andを利用することができます。
> (princ "abc") abc "abc" > (princ "efg") efg "efg" > (and (princ "abc") (princ "efg")) abcefg "efg" > (and (princ "abc") (not (terpri)) (princ "efg") (princ "xyz")) abc efgxyz "xyz" >
入出力と順接を学べば、次のような典型的な応答プログラムを作成することができます。
(defun ans-two-time() (and (and (princ "times 2" ) (not (terpri))) (princ "Please Input Number: " ) (ans-two-time-print (read)) )) (defun ans-two-time-print ( n ) (and (princ "Answer is " ) (princ (* n 2)) (princ "." ) (not (terpri)) ))
> (ans-two-time) times 2 Please Input Number: 2 Answer is 4. T > (ans-two-time) times 2 Please Input Number: 15 Answer is 30. T >
「判断」をするには、いわゆる、述語関数が利用できます。これは、別の言い方をすれば、その関数の結果がTかNILになるような関数一般をさします。
たとえば、等しいかどうかを判定したり、大小関係を比較するなどが典型的です。
整数値の比較に関しては、すでに、= (等しい) > (大きい) < (小さい)などがあるので、これを利用するだけです。
問題は、「その判断結果をどう利用するか?」ですが、そのために利用される組み込み関数がifです。このif関数を利用すれば、条件の成立不成立の違いによって、異なる振る舞いをプログラムにさせることができます。
引数は、2つか3つだが、2つの場合は、単に3つ目にNILが指定された場合と同じ振る舞いをする。
ifは最初に、1つ目の引数を調べ、それがNILかどうかを判断する。NILでなければ、2つ目の引数を評価し、その値が全体の値となる。そうでなければ、3つ目を評価し、その値が、全体の値となる。
注意すべきことは、「副作用は、評価された時点で有効になる」ということで、たとえば、一つ目の引数の値がNILでない場合、3つ目の引数が副作用があるようなものでも、それが評価されないので、結果的に副作用自身も生じない。
(defun ans-sign() (and (and (princ "sign" ) (not (terpri))) (princ "Please Input Number: " ) (ans-sign-print (read)) )) (defun ans-sign-print (n) (and (princ "Answer is " ) (if (<= 0 n) (princ "positive") (princ "negative") ) (princ "." ) (not (terpri)) ))
> (ans-sign) sign Please Input Number: 2 Answer is positive. T > (ans-sign) sign Please Input Number: -3 Answer is negative. T >
Lispにおいて、同じことを繰り返すのは、「再帰呼び出し」を利用します。
これも、説明するより、実際にやった方が簡単なので、例で示すことにします。
(defun echo-loop () (and (echo-print (read)) (echo-loop) )) (defun echo-print ( n ) (and (princ n) (not (terpri))) )
> (echo-loop) 1 1 2 2 123 123 [ back to top level ] >
つまり、「定義する関数の中で、自分自身を利用することによって、繰り返しが実現できる」わけです。
もちろん、このように、無限に繰り返すような形では、色々と不便なので、通常は、if (繰り返しを続けるか、それとも止めるかの判定をさせる)と組み合わせることによって、「必要回数の繰り返し」が可能になります。
(defun echo-non-zero-loop () ( echo-non-zero-check (read) )) (defun echo-non-zero-check ( n ) (and (if (= n 0) nil (and (princ n) (not (terpri))) ) (echo-non-zero-loop) ))
> (echo-non-zero-loop) 1 1 2 2 0 NIL >
基本的な内容は、上記で説明したので、いよいよ、数当てゲームです。この関数も、いくつかの関数の組み合わせで作成することを考えます。
まず、Gameの繰り返し部分から着目しましょう。問題は引数で与えられているものとし、入力によって、振る舞いを変えるというものです。
基本は、一つ前の「条件付き繰り返しと同じ」なので、次のような感じになります。
(defun number-game-loop ( q ) (number-game-check q (read)) ) (defun number-game-check (q a) ( if (= q a) (and (princ "Hit") (not (terpri))) (and (if (< q a) (princ "Big" ) (princ "Small" ) ) (not (terpri)) (number-game-loop q) )))
後は、問題を生成して、上記の関数を呼ぶだけですから、次のような感じになります。
(defun number-game-main () (number-game-loop (+ 1 (random 100))) )
更に、「何回目の試行か?」、「もう一度ゲームをするか?」などの機能を追加した版をここ置きましたので参考にしてください。
lispを参照して、先週に引き続き、2章の演習問題( [演習問題2.x.y]とある問題)を全て解いてみてください。
「lispの演習問題の結果」をreportとして、送ってください。
提出内容は「lispの演習問題の結果」です。具体的には、xlispに対して、行った入力と、その結果をcopyしたものでOkeyです。例えば、演習1.1.1であれば、次のような形でOkeyです。
> (+ 123 456) 579 >
ただし、適当に空行を入れて、課題と課題の区別がつくようにしてください。
以前、紹介したsample.xmlを参考(7)に、XML文章を書きます。
なお、reportとして、提出するe-mailのSubjectには、必ず、[R]から始まる、わかり易いSubjectを付けてください。