名前つきlet

cf. 7. 繰り返し – もうひとつの Scheme 入門

以前 my-reverse という関数を書いた。

(define my-reverse
  (lambda (lis)
    (define rev
      (lambda (l1 l2)
        (if (null? l1)
            l2
            (rev (cdr l1) (cons (car l1) l2)))))
    (rev lis (quote ()))))

この my-reverse では rev という名前の再帰する局所関数を定義して、それを呼び出している。

名前つきlet はこの再帰する局所関数の代わりになるものだ。ループに名前をつけたものといってもいい。名前つきlet は (let name binds body) という形をしている。name がループの名前で、body の中で再帰的に呼び出すことができる。binds は普通の let と同じように局所変数とその値(ループするときの初期値になる)の組からなる。body の中で再帰するときには新たな引数を与える。
なんだかうまく説明できないけど、例を見たほうがはやいだろう。上の my-reverse を名前つきlet を使って書き直すと次のようになる。

(define my-reverse
  (lambda (ls)
    (let loop ((l1 ls) (l2 (quote ())))
      (if (null? l1)
          l2
          (loop (cdr l1) (cons (car l1) l2))))))

(print (my-reverse '(1 2 3 4 5)))

3行目の loop がループにつけた名前だ。このとき引数の l1 は ls、l2 は (quote ()) で初期化される。そして6行目で再帰的に呼び出している。新しい引数は (cdr l1) と (cons (car l1) l2) だ。
実行してみよう。

^o^ > gosh my-reverse3.scm
(5 4 3 2 1)

let – 局所変数を使う

関数内で局所変数を使いたいときには let を使って定義できる。

 cf. 6. 局所変数 – もうひとつのScheme入門

let 式は、(let binds body) という形をしている。具体例を示そう。

gosh> (let ((x 2)
            (y 3))
           (* x y))
6

これを使って同ページにある練習問題をやってみよう。

練習問題 1
Scheme 入門 4 の練習問題を1つの関数で書いてみてください。
つまり、初速度 v, 角度 a 度で投げたボールの飛ぶ距離を求める関数を書いてください。

おっと、その前に入門4の練習問題を示しておかないと。

練習問題 2
ボールを投げたときに飛ぶ距離を求める関数を以下の手順で書いてみようと思います。

  1. 角度の度を弧度法単位(ラジアン)に変換する関数。
    180 度は π ラジアンである。 π の定義は、
    (define pi (* 4 (atan 1.0)))
    を用いよ。
  2. 速度 vx で等速運動するものが t 秒間に移動する距離を求める関数。
  3. 垂直方向の初速度 vy で投げたものが落ちてくるまでの時間を 計算する関数。
    空気抵抗は無視し、重力加速度 g は 9.8 m s-2 とする。
    ヒント:落ちてくるときの速度は -vy になっているから、
    2 vy = g t
    が成り立つ。ここで t は落ちてくるのにかかる時間である。
  4. 1–3 の関数を利用して、初速度 v で角度 theta 度で投げたものが飛ぶ距離を求める関数。
    ヒント:まず、最初に関数を利用して角度 theta を弧度法単位に換算する(それを theta1 とする)。
    垂直、水平方向の初速度はそれぞれ v sin(theta1), v cos(theta1) で表される。 落ちてくるまでにかかる時間は関数3を用いて計算できる。 水平方向に加速度はかからないので、飛ぶ距離は関数2を用いて計算できる。
  5. 初速度 40 m/s, 角度 30 度で投げたボールが飛ぶ距離を上で定義した関数を用いて求めよ。 (肩の強いプロ野球選手が遠投したときの距離に近い値になります。)

この練習問題2の回答はこうなる。

(define pi (* 4 (atan 1.0)))

(define deg->rad
  (lambda (deg)
    (/ (* deg pi) 180.0)))

(define distance
  (lambda (vx t)
    (* vx t)))

(define vtime
  (lambda (vy)
    (/ (* 2 vy) 9.8)))

(define throw
  (lambda (v theta)
    (distance (* v (cos (deg->rad theta))) (vtime (* v (sin (deg->rad theta)))))))

(print (throw 40.0 30.0))

実行:

^o^ > gosh throw.scm
141.39190265868385

で、こっちが let の練習問題の回答。度で与えられた角度をラジアンに変換して局所変数 r に束縛している。

(define throw
  (lambda (v a)
    (let ((r (/ (* a 4 (atan 1.0)) 180.0)))
      (/ (* v (cos r) 2 v (sin r)) 9.8))))

(print (throw 40.0 30.0))

実行:

^o^ > gosh throw2.scm
141.39190265868385