Scheme修行 第14章 名前をつけましょう(その2)
letを使って余計な計算を避ける方法を習います。
ノート:
depth*
depth*は
- 木に適用する関数で
- 数を値とする
その値は
- 木が空リストであれば、1
- 木の先頭要素がアトムであれば、木の残り部分にdepth*を適用した値
- そうでなければ、
- 木の先頭より後ろにdepth*を適用した値が、木の先頭部分に*depthを適用した値に1加えた値よりも大きい場合、木の先頭より後ろにdepth*を適用した値
- そうでない場合、木の先頭部分に*depthを適用した値に1加えた値
木の階層の深さを表す。
最初の定義をdepth1*として動作確認をする。
> (let ((l '((pickled) peppers (peppers pickled)))) (depth1* l)) 2 > (let ((l '(margarine ((bitter butter) (makes) (batter (bitter))) butter))) (depth1* l)) 4 > (let ((l '(c (b (a b) a) a))) (depth1* l)) 3 >
letは値に名前を結びつける時点で値の部分を評価してしまうので、引数のチェック等が必要な場合はletの位置を良く考える必要がある。
二番目の定義をdepth2*として動作確認をする。
> (let ((l '(() (((bitter butter) (makes) (batter (bitter))) butter)))) (depth2* l)) . . mcar: expects argument of type <mutable-pair>; given () >
letで値に名前を結びつける時点で、最低1回は実行される事が分かっている関数でないと冗長になる。
letを正しい位置に置いた版をdepth3*として動作確認をする。
> (let ((l '(() (((bitter butter) (makes) (batter (bitter))) butter)))) (depth3* l)) 5
同じ考え方をすると(depth* (cdr l))の部分はもう一段前で実行する事は可能。この定義をdepth4*として動作確認をする。
> (let ((l '(() (((bitter butter) (makes) (batter (bitter))) butter)))) (depth4* l)) 5 >
あまり効率を求めるとコードの可読性は落ちる。
if、max
condで条件が一つとelseしかないのであればifが使える。elseを書く必要がない。
maxを使った版のdepth*をdepth5*として動作確認をする。
> (let ((l '(() (((bitter butter) (makes) (batter (bitter))) butter)))) (depth5* l)) 5 >
それぞれの引数はmaxを呼び出す時に一回ずつ評価されるのでletを用いるのと同じ効果が得られる。
scramble
内部関数Pを使ったscrambleでも(cons (car tup) rup)の部分が分岐のどちらに行っても1回実行されるのでletを使って関数定義を短く出来る。
> (scramble '(1 1 1 3 4 2 1 1 9 2)) (1 1 1 1 1 4 1 1 1 9) > (scramble '(1 2 3 4 5 6 7 8 9)) (1 1 1 1 1 1 1 1 1) > (scramble '(1 2 3 1 2 3 4 1 8 2 10)) (1 1 1 1 1 1 1 1 2 8 2) >
本の定義はPの引数rpに対して次のPの呼び出しで使うリストにもletで同じ名前rpを割り当てているので注意。似ていても異なる名前にした方が良い気がするが。