Top Banner
高知大学教育学部の情報数学のテキスト 文責 高知大学名誉教授 中村 Scheme のプログラミング 次に LISP の方言である Scheme のプログラミングを勉強します。 DrRacket を立ち上げる。 上の領域に #lang racket が表示されていることを確認する。 そうでなければ、 Language メニューで、 Choose Language を選択。一番上の「Use the language declare in the source...」のラジオボタンをクリックする。「OK」ボタンをクリックする。 1
30

Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

Jan 23, 2021

Download

Documents

dariahiddleston
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

高知大学教育学部の情報数学のテキスト   文責 : 高知大学名誉教授 中村 治

Scheme のプログラミング次に LISP の方言である Scheme のプログラミングを勉強します。

DrRacket を立ち上げる。

上の領域に #lang racket が表示されていることを確認する。

そうでなければ、Languageメニューで、Choose Languageを選択。一番上の「Use the language

declare in the source...」のラジオボタンをクリックする。「OK」ボタンをクリックする。

1

Page 2: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

上の領域に #lang racket が表示されていることを確認する。上の行の「Run」ボタンをクリッ

クする。

上の領域にはプログラムを書き込み、下の領域には実行したい関数を書き込む。

下の領域に次のように書き込む。

486

次のような返事が返る。

486

四則計算下の領域に次のように書き込む。

2

Page 3: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

(+ 137 349)

次のような返事が返る。

486

下の領域に次のように書き込む。

(- 1000 334)

次のような返事が返る。

666

下の領域に次のように書き込む。

(* 5 99)

次のような返事が返る。

495

下の領域に次のように書き込む。

(/ 10 5)

次のような返事が返る。

2

これらはネストしても良い。下の領域に次のように書き込む。

(+ (* 3 5) (- 10 6))

次のような返事が返る。

19

変数の定義と使用数値に名前を付けてみる。下の領域に次のように書き込む。

(define size 2)

今度は何も返事をせず、次のプロンプト(入力促進記号)を表示するだけ。下の領域に次のように

書き込む。

size

次のような返事が返る。

2

下の領域に次のように書き込む。

(* 5 size)

次のような返事が返る。

3

Page 4: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

10

上の行の「Run」ボタンをクリックする。環境が初期化される。

関数の定義と使用関数を定義してみる。下の領域に次のように書き込む。

(define (square x) (* x x))

今度も何も返事をせず、次のプロンプト(入力促進記号)を表示するだけ。下の領域に次のように

書き込む。

(square 21)

次のような返事が返る。

441

下の領域に次のように書き込む。

(square (+ 2 5))

次のような返事が返る。

49

下の領域に次のように書き込む。

(square (square 3))

次のような返事が返る。

81

(define (square x) (* x x))

は、引数 x を取る関数 (square x) を (* x x) と定義すると読みます。

関数は次のように定義する。

(define (<name> <formal parameters>) <body>)

< name> は関数の名前を表す symbol です。< formal parameters> は引数の並びです。この

引数は < body> の中だけで有効なです。< body>が関数の本体の定義です。引数と < body

>の中の出現を別のものに入れ替えても同じ定義になります。引数は < body>の中で局所的で

す。つまり、

(define (square x) (* x x))

(define (square abc) (* abc abc))

は同じ関数 square を定義していて、(square < e>)を評価することは (* < e> < e>)を計算

することになります。但し、< e> は square の引数として一度だけまず評価され、その値の平方

を計算します。< e> を形式的に置き換えてから、(* < e> < e>) を計算するわけではありま

せん。

さらに関数を定義してみる。下の領域に次のように書き込む。

4

Page 5: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

(define (sum-of-square x y)

(+ (square x) (square y)))

今度も何も返事をせず、次のプロンプト(入力促進記号)を表示するだけ。下の領域に次のように

書き込む。

(define (f a)

(sum-of-square (+ a 1) (* a 2)))

今度も何も返事をせず、次のプロンプト(入力促進記号)を表示するだけ。下の領域に次のように

書き込む。

(f 5)

次のような返事が返る。

136

(define (f a)

(sum-of-square (+ a 1) (* a 2)))

は、引数 a を取る関数 (f a) を すでに定義している関数

(define (sum-of-square x y)

(+ (square x) (square y)))

を使って、(sum-of-square (+ a 1) (* a 2)) で定義しています。

絶対値関数次に絶対値を与える関数 (abs x) を定義してみる。実際は絶対値を与える関数 (abs x) は元々シ

ステムで定義されているので、わざわざ定義しなくても使えるのですが。

(define (abs x)

(cond ((> x 0) x)

((= x 0) 0)

((< x 0) (- x))))

今度も何も返事をせず、次のプロンプト(入力促進記号)を表示するだけ。

cond の構文は

(cond (<p1> <e1>)

(<p2> <e2>)

.

.

.

(<pn> <en>))

または

5

Page 6: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

(cond (<p1> <e1>)

(<p2> <e2>)

.

.

.

(<pn> <en>)

(else <e>))

の型で、< pi> で条件(真か偽を与える関数または変数)を指定し、、< pi> が真であれば、そ

の値が < ei> となる。2番目の型の場合、< p1>,< p2>,...,< pn> がすべて偽であれば、

その値が < e> になる。

従って絶対値を与える関数 (abs x) を次のように定義しても良い。

(define (abs x)

(cond ((< x 0) (- x))

(else x)))

今度も何も返事をせず、次のプロンプト(入力促進記号)を表示するだけ。下の領域に次のように

書き込む。

(abs -8)

次のような返事が返る。

8

さらに絶対値を与える関数 (abs x) を次のように定義しても良い。

(define (abs x)

(if (< x 0)

(- x)

x))

今度も何も返事をせず、次のプロンプト(入力促進記号)を表示するだけ。ここでは if 構文を使っ

ている。

(if <predicate> <consequent> <alternative>)

の型で使い、< predicate> の値が真なら、< consequent> がその値になり、< predicate> の

値が偽なら、< alternative> がその値になる。

引数 a に b の絶対値を加える関数 (a-plus-abs-b a b) を次のように定義しても良い。

(define (a-plus-abs-b a b)

((if (> b 0) + -) a b))

下の領域に次のように書き込む。

(a-plus-abs-b 4 -6)

次のような返事が返る。

6

Page 7: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

10

論理積、論理和、否定論理積、論理和、否定はそれぞれ and, or, not で表される。

(and <e1> <e2> ... <en>)

は、左から右に順に評価し、< ei> が偽になれば、全体が偽で、それ以降の式は評価しない。す

べての < ei> が真なら全体が真となる。例えば、

5 < x < 10

(and (> x 5) (< x 10))

と表現できる。

(or <e1> <e2> ... <en>)

は、左から右に順に評価し、< ei> が真になれば、全体が真で、それ以降の式は評価しない。す

べての < ei> が偽なら全体が偽となる。

(not <e>)

は、< e> が真なら全体は偽で、< e> が偽なら全体は真である。

階乗を与える関数次に階乗を与える関数 (factorial n) を定義してみる。

(define (factorial n)

(if (= n 1)

1

(* n (factorial (- n 1)))))

下の領域に次のように書き込む。

(factorial 5)

次のような返事が返る。

120

(define (factorial n)

(if (= n 1)

1

(* n (factorial (- n 1)))))

は、階乗の再帰的な定義

N! = 1 if N = 1

N! = N * (N-1)! if N > 1

7

Page 8: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

を Racket で表現したものです。

問題:三個の数字を引数に取り、その内の大きい二個の平方の和を返す関数を定義しなさい。

平方根を与える関数次に平方根を与える関数 (sqrt x) を定義してみる。実際は平方根を与える関数 (sqrt x) は元々

システムで定義されているので、わざわざ定義しなくても使えるのですが、ニュートン法を使って

計算します。ニュートン法は後で説明します。

上の領域に次のように書き込む。

(define (sqrt x)

(sqrt-iter 1.0 x))

(define (sqrt-iter guess x)

(if (good-enough? guess x)

guess

(sqrt-iter (improve guess x)

x)))

(define (improve guess x)

(average guess (/ x guess)))

(define (average x y)

(/ (+ x y) 2))

(define (good-enough? guess x)

(< (abs (- (square guess) x)) 0.001))

(define (square x)

(* x x))

上の行の「Run」ボタンをクリックする。上の領域に書き込んだ関数の定義が有効になる。

8

Page 9: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

下の領域に次のように書き込む。

(sqrt 9)

次のような返事が返る。

3.00009155413138

square, good-enough?, average, improve, sqrt-iter の各関数は sqrt を定義するための

みに必要だから、次のように定義する方がもっと良い。上の定義を消して、上の領域に次のように

書き込む。

(define (sqrt x)

9

Page 10: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

(define (square x)

(* x x))

(define (good-enough? guess x)

(< (abs (- (square guess) x)) 0.001))

(define (average x y)

(/ (+ x y) 2))

(define (improve guess x)

(average guess (/ x guess)))

(define (sqrt-iter guess x)

(if (good-enough? guess x)

guess

(sqrt-iter (improve guess x) x)))

(sqrt-iter 1.0 x))

上の行の「Run」ボタンをクリックすると上の領域に書き込んだ関数の定義が有効になる。下の

領域に次のように書き込む。

(sqrt 9)

次のような返事が返る。

3.00009155413138

10

Page 11: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

このように上の領域に書き込むとこれらのプログラムを保存したり、保存したプログラムを読み

込んだり出来ます。

(define (sqrt x)

(define (square x)

(* x x))

(define (good-enough? guess x)

(< (abs (- (square guess) x)) 0.001))

(define (average x y)

(/ (+ x y) 2))

(define (improve guess x)

(average guess (/ x guess)))

(define (sqrt-iter guess x)

(if (good-enough? guess x)

guess

(sqrt-iter (improve guess x) x)))

(sqrt-iter 1.0 x))

(define (square x)

(* x x))

は、x の平方を与える関数 (square x) を定義していて、

(define (good-enough? guess x)

(< (abs (- (square guess) x)) 0.001))

は、近似値 guess が x の平方根に十分近いかを判定する関数 (good-enough guess x?) を guess の

平方と x の差の絶対値が 0.001 より小さいかどうかで判定することで定義し、

11

Page 12: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

(define (average x y)

(/ (+ x y) 2))

は、x と y の平均を与える関数 (average x y) を定義し、

(define (improve guess x)

(average guess (/ x guess)))

は、guessと xからより良い近似値に改良する関数 (improve guess x)を (average guess (/ x guess))

で定義し、

(define (sqrt-iter guess x)

(if (good-enough? guess x)

guess

(sqrt-iter (improve guess x) x)))

は、xの平方根の近似値を、近似値 guessからはじめて、再帰的に求める関数 (sqrt-iter guess x)

を、近似値 guess が良い近似値なら答えを guess にし、そうでなければ、近似値 guess をより良

い近似値に改良し、再び、(sqrt-iter guess x) を再帰的に呼ぶことで計算するように定義して

います。最後に、

(sqrt-iter 1.0 x))

で、最初の近似値を 1.0 として、(sqrt-iter 1.0 x) を呼んで、x の近似値を計算しています。最

初の近似値は 1.0 でなくても、x の値に応じて、2.0 でも 3.0 でも 9.0 でも良いです。

問題:上の平方根を返す関数を参考に立方根を返す関数を定義しなさい。

硬貨による両替の個数を計算する関数

How many different ways can we make change of $1.00, given half-dollars,quarters,

dimes,nickels, and pennies?

に答えるためには次のようなプログラムを作ればよい。

(define (count-change amount)

(cc amount 5))

(define (cc amount kinds-of-coins)

(cond

((= amount 0) 1)

((or (< amount 0) (= kinds-of-coins 0)) 0)

(else (+ (cc amount

(- kinds-of-coins 1))

(cc (- amount (first-denomination kinds-of-coins))

kinds-of-coins)))))

(define (first-denomination kinds-of-coins)

(cond

((= kinds-of-coins 1) 1)

((= kinds-of-coins 2) 5)

((= kinds-of-coins 3) 10)

12

Page 13: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

((= kinds-of-coins 4) 25)

((= kinds-of-coins 5) 50)))

次を実行する。

(count-change 100)

292

を得る。

(define (count-change amount)

(cc amount 5))

(define (cc amount kinds-of-coins)

(cond

((= amount 0) 1)

((or (< amount 0) (= kinds-of-coins 0)) 0)

(else (+ (cc amount

(- kinds-of-coins 1))

(cc (- amount (first-denomination kinds-of-coins))

kinds-of-coins)))))

(define (first-denomination kinds-of-coins)

(cond

((= kinds-of-coins 1) 1)

((= kinds-of-coins 2) 5)

((= kinds-of-coins 3) 10)

((= kinds-of-coins 4) 25)

((= kinds-of-coins 5) 50)))

は、まず、金額 amount をコインに両替する個数を数える関数

(count-change amount)

をコインの種類が五個あるので、五種類のコインを使って、金額 amount を両替する個数を数

える関数 (cc amount 5) を計算すればよいと問題を書き直します。次に、

(define (cc amount kinds-of-coins)

(cond

((= amount 0) 1)

((or (< amount 0) (= kinds-of-coins 0)) 0)

(else (+ (cc amount

(- kinds-of-coins 1))

(cc (- amount (first-denomination kinds-of-coins))

kinds-of-coins)))))

で、kinds-of-coins 種類のコインを使って、金額 amount を両替する個数を数える関数

(cc amount kinds-of-coins)

は、残りの金額 amount が 0 になれば、一と通りあったことになり、そうでなく、残りの金額

amount が負になるか両替すべきコインの種類がなくなれば、両替に失敗したことになり、従って、

13

Page 14: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

両替の個数は 0 とし、そうでなければ、再帰的に、両替の個数は残りの金額 amount はそのまま

で、使えるコインの種類を一つ減らした場合の両替の個数と使えるコインの最初のもの一枚でで

残りの金額 amount 両替したのち使えるコインの種類はそのままで両替を続けた場合の個数との

和が答えであると定義しています。文章で書くと複雑ですが、関数をじっと眺めていれば分かりま

す。最後に

(define (first-denomination kinds-of-coins)

(cond

((= kinds-of-coins 1) 1)

((= kinds-of-coins 2) 5)

((= kinds-of-coins 3) 10)

((= kinds-of-coins 4) 25)

((= kinds-of-coins 5) 50)))

で、kinds-of-coins 種類のコインの最初のものを使って両替するとは、使えるコインの種類が1種

類の場合は 1 セント、2種類の場合は 5 セント、3種類の場合は 10 セント、4種類の場合は 25

セント、そして、5種類の場合は 50 セントを両替すると定義しています。

問題:このプログラムを作り替えて、1000円札を両替する方法の個数を求めよ。

正弦関数を計算するsin(x)は xが十分小さければ xと等しいということとsin(x) = 3 sin(x/3) - 4 sin^3(x/3) と

いう公式を用いて次のように sin(x) の近似値を計算するプログラムを定義することが出来る。

(define (cube x) (* x x x))

(define (p x) (- (* 3 x) (* 4 (cube x))))

(define (sine angle)

(if (not (> (abs angle) 0.1))

angle

(p (sine (/ angle 3.0)))))

次を実行する。

(sine (/ pi 6))

0.5002561624296237

を得る。

(define (cube x) (* x x x))

(define (p x) (- (* 3 x) (* 4 (cube x))))

(define (sine angle)

(if (not (> (abs angle) 0.1))

angle

(p (sine (/ angle 3.0)))))

(define (cube x) (* x x x))

14

Page 15: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

は、x の三乗を計算する関数 (cube x) を定義していて、

(define (p x) (- (* 3 x) (* 4 (cube x))))

は、x の三次関数 3x - 4x^3 を計算する関数 (p x) を定義していて、最後に

(define (sine angle)

(if (not (> (abs angle) 0.1))

angle

(p (sine (/ angle 3.0)))))

で、angle の sin は、angle の絶対値が 0.1 以下であれば、angle が答えで、そうでなければ、再

帰的に、 3 sin(angle/3) - 4 sin^3(angle/3) を計算せよと定義しています。

問題:この関数を用いて、cos(x) の近似値を計算するプログラムを定義せよ。

べき乗を計算するべき乗 b^n を計算するプログラム expt はシステムですでに定義されているので改めて定義す

る必要はないが、次のようにすることが出来る。

(define (expt b n)

(if (= n 0)

1

(* b (expt b (- n 1)))))

これは、べき乗 b^n が帰納的に

b^n = 1 if n = 0

b^n = b * b^(n-1) if n > 1

と定義できることに基づいています。しかし、これは次のように定義した方が効率がよい。

(define (fast-expt b n)

(cond

((= n 0) 1)

((even? n) (sqr (fast-expt b (/ n 2))))

(else (* b (fast-expt b (- n 1))))))

これは帰納的定義

b^n = 1 if n = 0

b^n = (b^(n/2))^2 if n is even

b^n = b * b^(n-1) if n is odd

に基づいています。

さらに、これは次のようにも定義することができる。

(define (expt-fast b n)

(define (expt-fast-iter b n e p)

(cond

((zero? n) p)

15

Page 16: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

((even? n) (expt-fast-iter b (/ n 2) (sqr e) p))

(else (expt-fast-iter b (/ (- n 1) 2) (sqr e) (* e p)))))

(expt-fast-iter b n b 1))

次を実行する。

(expt-fast 2 1000)

10715086071862673209484250490600018105614048117055336074437503883703510511249361

22493198378815695858127594672917553146825187145285692314043598457757469857480393

45677748242309854210746050623711418779541821530464749835819412673987675591655439

46077062914571196477686542167660429831652624386837205668069376

を得る。これは文章で説明しようとすると複雑になりますが、1000 の 2進数表現が 1111101000

であることより 2 の 1000 乗は 2^3 * 2^5 * 2^6 * 2^7 * 2^8 * 2^9 を計算すれば良いという

事実を使って計算しています。(expt-fast-iter b n e p) の p に答えの途中経過の値、e に 2

のべき乗の途中経過の値を保存して、再帰的に計算しています。多分、じっと眺めていれば分かり

ます。

Fibonacci 数列を求めるFibonacci 数列は Fib(0)=0, Fib(1)=1, Fib(2)=1, Fib(3)=2, Fib(4)=3, ... , Fib(n)=Fib(n-

1)+Fib(n-2), ... で定義される。素朴なプログラムは次のようになる。

(define (fib n)

(cond

((= n 0) 0)

((= n 1) 1)

(else (+ (fib (- n 1)) (fib (- n 2))))))

次を実行する。

(time (fib 40))

cpu time: 40623 real time: 40819 gc time: 62

102334155

を得る。計算はしてくれましたが時間がかかります。

(define (fib n)

(cond

((= n 0) 0)

((= n 1) 1)

(else (+ (fib (- n 1)) (fib (- n 2))))))

は、Fibonacci 数列の帰納的な定義

Fib(n) = 0 if n = 0

Fib(n) = 1 if n = 1

Fib(n) = Fib(n-1) + Fib(n-2) if n > 1

16

Page 17: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

をそのままプログラミングしたものです。

次のように修正します。

(define (fib2 n)

(define (fib-iter a b count)

(if (= count 0)

b

(fib-iter (+ a b) a (- count 1))))

(fib-iter 1 0 n))

次を実行する。

(time (fib2 40))

cpu time: 0 real time: 0 gc time: 0

102334155

を得る。めちゃくちゃ早くなりました。

問題:アッカーマン関数 Ack(m.n) は、非負整数 m,n に対して

Ack(m,n) =

n+ 1 if m = 0

Ack(m− 1, 1) if m > 0 and n = 0

Ack(m− 1, Ack(m,n− 1)) otherwise

(1)

で定義されます。アッカーマン関数 Ack(m.n) を計算する関数を定義しなさい。

級数を使って円周率を計算する1/1・3+1/5・7+1/9・11+・・・ は pi/8 に非常にゆっくり収束する。

(define (pi-sum a b)

(if (> a b)

0

(+ (/ 1.0 (* a (+ a 2))) (pi-sum (+ a 4) b))))

次を実行する。

> (* 8 (pi-sum 1 1000))

3.139592655589783

を得る。

(define (pi-sum a b)

(if (> a b)

0

(+ (/ 1.0 (* a (+ a 2))) (pi-sum (+ a 4) b))))

は、a が b 以下の間は a を 4 だけ増やしながら、1/a(a+2) を加えている。

級数を使って円周率を計算の一般化これを一般化して、加える項を計算する関数 (term a) と次の a の値を与える関数 (next a) を

引数として

17

Page 18: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

(define (sum term a next b)

(if (> a b)

0

(+ (term a)

(sum term (next a) next b))))

という関数を定義することが出来る。この関数を使って

(define (sum term a next b)

(if (> a b)

0

(+ (term a)

(sum term (next a) next b))))

(define (pi-sum a b)

(define (pi-term x)

(/ 1.0 (* x (+ x 2))))

(define (pi-next x)

(+ x 4))

(sum pi-term a pi-next b))

で、

(define (pi-sum a b)

(if (> a b)

0

(+ (/ 1.0 (* a (+ a 2))) (pi-sum (+ a 4) b))))

を書き直すことが出来る。

次を実行する。

(* 8 (pi-sum2 1 1000))

3.139592655589783

を得る。

関数 f(x) の数値積分を計算をする同じく (sum term a next b) を使って、関数 f(x) の数値積分は次で与えられる。

(define (sum term a next b)

(if (> a b)

0

(+ (term a)

(sum term (next a) next b))))

(define (integral f a b dx)

(define (add-dx x) (+ x dx))

(* (sum f (+ a (/ dx 2.0)) add-dx b) dx))

18

Page 19: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

次を実行する。

(integral cube 0 1 0.01)

0.24998750000000042

を得る。ここで、cube は

(define (cube x)

(* x x x))

で定義される、3次関数である。次を実行する。

(integral cube 0 1 0.001)

0.249999875000001

を得る。

今度は、Simpson の公式

h

3(y0 + 4y1 + 2y2 + 4y3 + 2y4 + · · ·+ 2yn−2 + 4yn−1 + yn)

where h =(b− a)

n, for some even integer n, and yk = f(a+ kh).

を使う。

(define (simpson f a b n)

(define h (/ (- b a) n))

(define (sum term a next b)

(cond

((>= a b) 0.0)

(else (+ (term a) (sum term (next a) next b)))))

(define (next x)

(+ x (* 2 h)))

(define (term x)

(* (+ (* 2 (f x)) (* 4 (f (+ x h)))) (/ h 3)))

(+ (sum term a next b) (* (- (f b) (f a)) (/ h 3))))

次を実行する。

(simpson cube 0 1 100)

0.25

を得る。

関数 f(x) の固定点を求める関数 f(x) の固定点(fixed point)

f(x), f(f(x), f(f(f(x))), f(f(f(f(x)))), ...

19

Page 20: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

は、次で見つけることが出来る。

(define (fixed-point f first-guess)

(define tolerance 0.00001)

(define (close-enough? v1 v2)

(< (abs (- v1 v2)) tolerance))

(define (try guess)

(let ((next (f guess)))

(if (close-enough? guess next)

next

(try next))))

(try first-guess))

次を実行する。

(fixed-point cos 1.0)

0.7390822985224023

を得る。

(define (fixed-point f first-guess)

(define tolerance 0.00001)

(define (close-enough? v1 v2)

(< (abs (- v1 v2)) tolerance))

(define (try guess)

(let ((next (f guess)))

(if (close-enough? guess next)

next

(try next))))

(try first-guess))

(define (try guess)

(let ((next (f guess)))

(if (close-enough? guess next)

next

(try next))))

で使っている

(let ((next (f guess)))

(if (close-enough? guess next)

next

(try next)))

は、

20

Page 21: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

(let (変数の定義の並び)

body )

の形で、今の場合、変数 next に (f guess) を定義した後

(if (close-enough? guess next)

next

(try next))

を実行する事を指示している。これが関数の値になる。

次を実行する。

(fixed-point (lambda (y) (+ (sin y) (cos y))) 1.0)

1.2587315962971173

を得る。

(fixed-point (lambda (y) (+ (sin y) (cos y))) 1.0)

(lambda (y) (+ (sin y) (cos y)))

は、関数名のない1変数 y の関数 (+ (sin y) (cos y)) を定義する方法である。

方程式 g(x) = 0 の根を Mewton 法で求める方程式 g(x) = 0 の根を Mewton 法で求める。

(define (deriv g)

(define dx 0.00001)

(lambda (x)

(/ (- (g (+ x dx)) (g x)) dx)))

(define (newton-transform g)

(lambda (x)

(- x (/ (g x) ((deriv g) x)))))

(define (newtons-method g guess)

(fixed-point (newton-transform g) guess))

これを使って、x の平方根は次で得られる。

(define (square-root2 x)

(newtons-method (lambda (y) (- (sqr y) x)) 1.0))

次を実行する。

(square-root2 2)

1.4142135623822438

を得る。

21

Page 22: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

(define (deriv g)

(define dx 0.00001)

(lambda (x)

(/ (- (g (+ x dx)) (g x)) dx)))

(define (newton-transform g)

(lambda (x)

(- x (/ (g x) ((deriv g) x)))))

(define (newtons-method g guess)

(fixed-point (newton-transform g) guess))

(define (deriv g)

(define dx 0.00001)

(lambda (x)

(/ (- (g (+ x dx)) (g x)) dx)))

(define (newton-transform g)

(lambda (x)

(- x (/ (g x) ((deriv g) x)))))

は、関数を定義する関数である。

問題:x の立方根を計算するプログラムを定義しなさい。

ソートGNU Prolog で与えたソートのプログラムは Scheme では次のようになる。

インサートソートのプログラム

(define (sort alon)

(cond

[(empty? alon) empty]

[else (insert (first alon)(sort (rest alon)))]))

(define (insert n alon)

(cond

[(empty? alon)(cons n empty)]

[else (cond

[(>= n (first alon))(cons n alon)]

[else (cons (first alon)(insert n (rest alon)))])]))

クイックソートのプログラム

(define (qsort alon)

(cond

[(empty? alon) empty]

[(empty? (rest alon)) (list (first alon))]

[else (append

22

Page 23: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

(qsort (smaller-items alon (first alon)))

(list (first alon))

(qsort (larger-items alon (first alon))))]))

(define (larger-items alon threshold)

(cond

[(empty? alon) empty]

[else (if (> (first alon) threshold)

(cons (first alon) (larger-items (rest alon) threshold))

(larger-items (rest alon) threshold))]))

(define (smaller-items alon threshold)

(cond

[(empty? alon) empty]

[else (if (< (first alon) threshold)

(cons (first alon) (smaller-items (rest alon) threshold))

(smaller-items (rest alon) threshold))]))

絵を描くプログラム最後に絵を描くプログラムの例をあげておきます。

#lang racket/gui

(define size-x 400)

(define size-y 400)

(define-struct posn (x y))

(define picture (make-bitmap size-x size-y))

(define bm-dc (new bitmap-dc% [bitmap picture]))

(send bm-dc clear)

(define frame-gui (new frame%

[label "Picture Language"]

[width (+ size-x 10)]

[height (+ size-y 35)]))

(define canvas (new canvas%

[parent frame-gui]

[paint-callback

(lambda (canvas dc)

(send dc draw-bitmap picture 0 0))]))

(define (draw-solid-line a b color)

(send bm-dc set-pen color 1 ’solid)

(send bm-dc draw-line

(posn-x a) (posn-y a)

(posn-x b) (posn-y b)))

(define (sierpinski a b c)

23

Page 24: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

(cond

[(too-small? a b c) true]

[else

(local ((define a-b (mid-point a b))

(define b-c (mid-point b c))

(define c-a (mid-point a c)))

(and

(draw-triangle a b c)

(sierpinski a a-b c-a)

(sierpinski b a-b b-c)

(sierpinski c c-a b-c)))]))

(define (mid-point a-posn b-posn)

(make-posn

(mid (posn-x a-posn)(posn-x b-posn))

(mid (posn-y a-posn)(posn-y b-posn))))

(define (mid x y)

(/ (+ x y) 2))

(define (too-small? a b c)

(local ((define (distance u v)

(local ((define x (- (posn-x u)(posn-x v)))

(define y (- (posn-y u)(posn-y v))))

(sqrt (+ (* x x)(* y y)))))

(define a-b (distance a b))

(define b-c (distance b c))

(define c-a (distance c a)))

(or (< a-b 5)(< b-c 5)(< c-a 5))))

(define (draw-triangle a b c)

(and (draw-solid-line a b "blue")

(draw-solid-line b c "blue")

(draw-solid-line c a "blue")))

(define A (make-posn 200 0))

(define B (make-posn 27 300))

(define C (make-posn 373 300))

(send frame-gui show #t)

(sierpinski A B C)

と定義して、Run のボタンをクリックする。

24

Page 25: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

のような絵を描きます。Window を使った GUI のプログラミングに興味があれば、

http://docs.racket-lang.org/gui/index.html

から、Racket のドキュメントを辿ればいいです。絵を描くことに興味があれば

http://docs.racket-lang.org/draw/overview.html

から、Racket のドキュメントを辿ればいいです。

絵を描く基本は、例えば、

#lang racket/gui

(define size-x 400)

(define size-y 400)

(define picture (make-bitmap size-x size-y))

(define bm-dc (new bitmap-dc% [bitmap picture]))

(send bm-dc clear)

(define frame-gui (new frame%

[label "Picture Language"]

[width (+ size-x 10)]

[height (+ size-y 35)]))

(define canvas (new canvas%

[parent frame-gui]

[paint-callback

(lambda (canvas dc)

(send dc draw-bitmap picture 0 0))]))

25

Page 26: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

(send bm-dc set-brush "green" ’solid)

(send bm-dc set-pen "blue" 1 ’solid)

(send bm-dc draw-rectangle

50 150 ; Top-left at (50,150), 50 pixels right 150 pixels down from top-left

300 100) ; 300 pixels wide and 100 pixels high

(send bm-dc set-pen "red" 3 ’solid)

(send bm-dc draw-line

50 50 ; Start at (50,50), the top-left corner

350 350) ; and draw to (350,350), the bottom-right corner

(send bm-dc draw-line

50 350 ; Start at (50,350), the bottom-left corner

350 50) ; and draw to (350,50), the top-right corner

(send bm-dc set-pen "white" 1 ’transparent)

(send bm-dc set-smoothing ’aligned)

(send bm-dc set-brush "black" ’solid)

(send bm-dc draw-ellipse

100 100 ; Top-left at (100,100), 100 pixels 100 right pixels down from top-left

200 200); 200 pixels wide and 200 pixels high

(send bm-dc set-brush "yellow" ’solid)

(send bm-dc draw-rectangle 150 150 30 30)

(send bm-dc draw-rectangle 220 150 30 30)

(send bm-dc set-pen "red" 5 ’solid)

(send bm-dc set-brush "yellow" ’transparent)

(send bm-dc draw-arc

125 105 ; Top-left at (125,105), 125 pixels 105 right pixels down from top-left

155 155 ; 155 pixels wide and 155 pixels high

(* 5/4 pi) ; start angle

(* 7/4 pi)); end angle

(send frame-gui show #t)

で、Run のボタンをクリックする。

26

Page 27: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

のような絵を描きます。

#lang racket/gui

(define size-x 400)

(define size-y 400)

(define frame-gui (new frame%

[label "Picture Language"]

[width (+ size-x 10)]

[height (+ size-y 35)]))

(send frame-gui show #t)

で Window が表示されます。

27

Page 28: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

Run をクリックする。

これに canvas を追加するのに

(define canvas (new canvas%

[parent frame-gui]

[paint-callback

(lambda (canvas dc)

(send dc draw-bitmap picture 0 0))]))

を追加しますが、更に canvas に絵を描くために bitmap と bitmap-dc を

(define picture (make-bitmap size-x size-y))

(define bm-dc (new bitmap-dc% [bitmap picture]))

(send bm-dc clear)

と定義して、上の canvas の定義で

(send dc draw-bitmap picture 0 0))]))

と canvas の dc と bitmap picture に作った bitmap-dc bm-dc を結びつけます。

28

Page 29: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

後は

(send bm-dc set-brush "green" ’solid)

(send bm-dc set-pen "blue" 1 ’solid)

(send bm-dc draw-rectangle  50 150 300 100)

のように、描きた矩形や線や楕円を描けばいいです。

Run をクリックします。

29

Page 30: Scheme のプログラミング - Kochi Utyamag/jyohou/sheme.pdf高知大学教育学部の情報数学のテキスト 文責 : 高知大学名誉教授 中村 治 Scheme のプログラミング

Scheme が面白いと思った学生さんは Harold Abelson and Gerald Jay Sussman with Julie

Sussman Structure and Interpretation of Computer Programs Second Edition The MIT Press

1996 (翻訳は「計算機プログラムの構造と解釈」ピアソンエデュケーション)をこのテキストに

続いて読めばいいです。この本が Scheme の最も有名な教科書です。マサチューセッツ工科大学

(MIT)の教科書だけあって読み応え有ります。これと次の本の原文はインターネットで読むかと

が出来ます。この本が難しければ、Matthias Felleisen, Robert Bruce Findler, Matthew Flatt and

Shriram Krishnamurthi HOW TO DESIGN PROGRAMS The MIT Press 2001 もあります。一

通り読んでみましたが、これもアメリカのいくつかの大学の教科書ですが、そこそこ良い本だとは

思いますが、プログラムの一部が DrRacket に含まれていて(すべてのプログラムが提示される

わけではなく、教育的配慮ということで複雑な部分は DrRacket で準備されていて、却って教育的

でなく)全体像が分からずちょっと苛々しました。更に、吉田武著「素数夜曲 女王陛下の LISP」

東海大学出版会3600円という本も出版されています。整数論と Scheme に興味があれば読んで

みるといいです。

参考文献

Matthias Felleisen, Robert Bruce Findler, Matthew Flatt and Shriram Krishnamurthi

HOW TO DESIGN PROGRAMS The MIT Press 2001

Harold Abelson and Gerald Jay Sussman with Julie Sussman

Structure and Interpretation of Computer Programs Second Edition The MIT Press 1996

(翻訳は「計算機プログラムの構造と解釈」ピアソンエデュケーション)

Harold Abelson und Gerald Jay Sussman mit Julie Sussman

Struktur und Interpretation von Computerprogrammen Eine Informatik-Einfuhrung 4.

Auflage Springer 2001

30