Scheme で、関数を作成するために、lambda 式を使用する。
lambda 式は、特殊形式の式である。その形式は以下のとおりとなる。
(lambda (引数1 引数2 … 引数n) 式1 式2 … 式m) |
最左部分式に「lambda」と記載する。次に括弧を書いてその中に仮引数となる変数名を列挙する。その次に、関数での計算手順を列挙する。計算が複雑な場合などに対して、複数の式を並べて良い。関数の返り値は、最後の式(式m)の評価結果である。
与えられた値 x を3倍して返す関数を作ろう。
(lambda (x) (* x 3)) |
※ 定義エリアで上記を記述し、実行(Run)すると、結果が対話エリアに出力される。以下の例も同様。
例1を実行してみると、#<procedure>が返される。 lambda 式を評価した結果として、作成された関数が返り値となっていることが分かる。
返り値が「procedure」となるものは、コンビネーションの最左部分式に置くことで、使用することができる。
例1の関数を使ってみよう。
((lambda (x) (* x 3)) 6) |
この例は、全体を見るとコンビネーション ( 式1 式2 ) という形式であり、式1が例1の式、式2が定数6となっている。
例2では、先に作成したlambda 式の関数を、使う段階で再度書かなければならなかった。何度も使用される関数には名前を付けておきたい。そのためには、defineを使用する。
例1の関数に「sanbai」という名前を付けよう。
(define sanbai (lambda (x) (* x 3))) |
例3で付けた名前を、コンビネーションの最左部分式のところに記載することで、関数が使用できる。
例3の関数を用いて計算をしてみよう。
(sanbai 7) |
lambda 式では、複数の式を並べることができる。
与えられた値3つ x y z の分散を返す関数を作ろう。
(lambda (x y z) (define m1 (/ (+ x y z) 3)) (define m2 (/ (+ (* x x) (* y y) (* z z)) 3)) (- m2 (* m1 m1))) |
(1) 上記の関数を直接用いて、3つの値「3、4、5」の分散を求めよ(例2の方法で実行)。
(2) 上記の関数に「variance3」という名前を付けよう。
(3) variance3 を用いて、3つの値「6、7、8」の分散を求めよ(例3の方法で実行)。
上記の例では、「m1、m2」という名前を使ったが、C言語でいうところのローカル変数に似ている。
以下のプログラムを見てみよう。3つの値の平均を求める関数を「average3」としている。この名前をラムダ式の中だけで使うことができる。さらに、2乗の関数を「pow2」としている。これらは、ローカルな関数といえる。Smalltalk でのブロックに似ている。
(lambda (x y z) (define average3 ; 3値の平均値を求める関数を定義 (lambda (a b c) ; (/ (+ a b c) 3))) ; (define pow2 ; 2乗を求める関数を定義 (lambda (a) ; 変数 a は lambda 式内でローカルなので、 (* a a))) ; average3 の a と区別される。 (define m1 (average3 x y z)) (define m2 (average3 (pow2 x) (pow2 y) (pow2 z))) (- m2 (pow2 m1))) |
条件式のための特殊形式としてifがある。次の2つの書式がある。
(if 〈真偽を返す式〉 〈真の際に評価する式〉 〈偽の際に評価する式〉) (if 〈真偽を返す式〉 〈真の際に評価する式〉) |
ここで、2つ目の書式において、「真偽を返す式」が偽となった際に評価する式が無い。したがって、返り値は不明である。しかし、処理はエラー終了とならずに進む。
Lispにおける伝統的な条件式として、cond がある。
(cond (〈真偽を返す式1〉 〈真偽を返す式1真の際に評価する式の並び1〉) (〈真偽を返す式2〉 〈真偽を返す式2真の際に評価する式の並び2〉) - - - (〈真偽を返す式n〉 〈真偽を返す式n真の際に評価する式の並びn〉)) |
〈真偽を返す式n〉の位置に、else または #t を記載することで、「その他の場合」、たとえば、C言語での default に相当するものが記述できる。
引数の値が、正ならば 'plus を、負ならば 'minus を、それ以外ならば 'zero を返す関数を作成せよ。
(1) if を用いて作成したものを sign-if と名前を付けよ。
(2) cond を用いて作成したものを sign-cond と名前を付けよ。
ある名前で定義した関数の中で、その名前の関数を評価することを再帰呼出しという。
階乗を計算する関数 factorial を定義しよう。factorial では再帰を終了する条件は、0 の階乗を求めるときであり、その返り値は、1 である。
(define factorial (lambda (x) (if (= x 0) 1 (* x (factorial (- x 1)))))) |
以下の関数について scheme プログラムを作成せよ。プログラムを印刷し、各行に手書きで説明を書いて、提出せよ。そして、実行の様子をスクリーンショットで画像化し、様子を手書きで説明して、提出せよ。
(1) ある事象の生起する確率 p を入力し、その事象の自己情報量を返す関数 self-information を作成せよ。なお、自己情報量は - log2 p で計算する。DrScheme の log 関数は底が e であるので、底を 2 で計算するようにしておこう。
(2) ある事象 a が事象 b の生起という条件付きで生起する確率 pij および事象 a の生起する確率 pi を入力し、事象 a と b の相互情報量を返す関数 mutual-information-c を作成せよ。なお、条件付き確率を用いた相互情報量の計算式は、log2( pij / pi ) である。
成績を入力して、ABCラベルを出力する関数を作成しよう。もし、成績 s が 90以上ならば 'A、80以上90未満ならば 'B、70以上80未満ならば 'C、60以上70未満ならば 'D、60未満ならば 'E をそれぞれ返り値とせよ。
(1) if を使う場合を、label1 という名前を付けて、作成しよう。
(2) cond を使う場合を、label2 という名前を付けて、作成しよう。
n人の人物が集まったとき、そのうち少なくとも誰か2人が同じ誕生日である確率を計算する関数 p-same-day を作成しよう。ちなみに、(p-same-day 23)は、小数に変換すると、約 0.507 となる。ただし、プログラム内で再帰呼び出しを使うこととし、コメントによりその部分を説明せよ。
(実行例) (exact->inexact (p-same-day 23)) ; exact->inexact で、分数を小数に変換 0.5072972343239853 |
365 * (365 - 1) * (365 - 2) (通り)
が存在する。全ての誕生日の状況は、365 * 365 * 365 通りであるので、3 人が全て異なる誕生日である確率は、365 * (365 - 1) * (365 - 2) / 365 * 365 * 365
である。n人の場合の確率を一般式で表わせば次のようになる。
1 * (1 - 1/365) * (1 - 2/365) * … * (1 - (n - 1)/365)
そこで、この式に従った再帰呼び出しを利用して、全員が異なる誕生日である確率を求める関数を作成すればよい。