kb84tkhrのブログ

何を書こうか考え中です あ、あと組織とは関係ないってやつです 個人的なやつ

人類の生産性を向上させた偉大なテクノロジーに乾杯(大げさ

ちょっと話題になっていたので鎌倉シャツでシャツを買ってみました
初めて洗濯したシャツを見てあっ、と気付きました
これは、形態安定シャツじゃない

生地にこだわると形態安定じゃなくなるのでしょう
いつも買ってるシャツより高級感があっていい感じ
そこについては満足度は高いです

形態安定シャツが出たとき(当時は形状記憶シャツって言ってたかな)は
飛びついて買ったものですが
自分の中で形態安定シャツが当たり前となってしまった今はいちいち
形態安定かどうか確認しなくなってました
油断です
痛い

アイロンがけというものが発明された日は呪われていいと思っています
布を伸ばしたり折り目をつけたりするためになぜ貴重な時間を使うのでしょうか
人類の生産性を下げようという宇宙人の陰謀に違いありません

昔は形態安定シャツだったら洗濯して乾いたらそのまま着ていったものですが
ヨメがシャツにはアイロンがかかってるべきという人なので
結婚してからまたかけるようになってました

#クリーニングに出すという発想はなぜかあまりありません

で今回形態安定じゃないシャツにアイロンをかけてみたらなんと面倒な

アイロンをかけるのに必要な時間が全く違います
端から端までアイロンをかけないといけません
ここ10年くらいは意識にも登らなかった「糊付け」という単語まで思い出してしまう始末

形態安定テクノロジーのありがたさを痛感しました
形態安定じゃないシャツにアイロンをかけてから形態安定なシャツを見ると
もうアイロンがけが終わっているようにしか見えません

すばらしい
人間の生産性向上に大きく寄与しています
感動
今日はアイロンを発明した人を呪うより、形態安定を発明した人を祝福するとしましょう

シャツを買った時にクリーニング無料券がついてきましたがそういうことかと納得
これクリーニング屋の売上にけっこう貢献するんじゃないでしょうか

今回の話しはここで終わりなんですが
ちょっと調べてみたら形態安定シャツの性能を発揮させるための使い方があることを
知りました

脱水はしないか、しても軽く
アイロンは中低温で

今持ってるシャツ(形態安定な方)はもうずっと気にせず洗濯・アイロンしてましたから
手遅れかもしれません

それでも十分効果は発揮してくれていますけど
次に買ったら気にしてみようと思います
段取りを考えておきましょう
すすぎまででいったん止めて、シャツだけ出すか
シャツは1週間分まとめて取っておいて別で洗うか
形態安定じゃなくても脱水しないで干せば多少は伸びてくれるでしょうか

脳とか瞑想とか集中とか直感とか

何かいろんなものが頭のなかでつながっている

いつかまとまった文章にできるだろうか

これくらい

Scheme手習い (5) 数のリスト

次は数のリストを扱います
数のリストはタップ(tup)と呼ばれています
扱い方はラットとほとんど同じ

タップに含まれる数の合計を返すaddtup

(define addtup
  (lambda (tup)
    (cond ((null? tup) 0)
          (else (o+ (car tup) (addtup (cdr tup)))))))

()の代わりに0、consの代わりにo+を使っています

(addtup '(3 5 2 8))
(o+ 3 (addtup '(5 2 8)))
(o+ 3 (o+ 5 (addtup '(2 8))))
(o+ 3 (o+ 5 (o+ 2 (addtup '(8)))))
(o+ 3 (o+ 5 (o+ 2 (o+ 8 (addtup '())))))
(o+ 3 (o+ 5 (o+ 2 (o+ 8 0))))
(o+ 3 (o+ 5 (o+ 2 8)))
18

次は二つのタップを項目ごとに加算して和のリストを作るtup+
第1版は二つのタップの長さが同じであるという仮定をおいたバージョンです

(define tup+
  (lambda (tup1 tup2)
    (cond ((and (null? tup1) (null? tup2)) (quote ()))
          (else (cons (o+ (car tup1) (car tup2))
                      (tup+ (cdr tup1) (cdr tup2)))))))

実行の様子

(tup+ '(1 2 3) '(4 5 6))
(cons 5 (tup+ '(2 3) '(5 6)))
(cons 5 (cons 7 (tup+ '(3) '(6))))
(cons 5 (cons 7 (cons 9 (tup+ '() '()))))
(cons 5 (cons 7 (cons 9 '())))
'(5 7 9)

仮定を外した第2版
長さが異なる場合の処理が((null? tup1) tup2)と一発で終わるのが気持ちいいです
tup1もtup2も()であれば、((null? tup1) tup2)で()が返されるので
特別な処理も必要ありません

(define tup+
  (lambda (tup1 tup2)
    (cond ((null? tup1) tup2)
          ((null? tup2) tup1)
          (else (cons (o+ (car tup1) (car tup2))
                      (tup+ (cdr tup1) (cdr tup2)))))))

タップの長さが違う例で確かめます

(tup+ '(1 2) '(3 4 5 6))
(cons 4 (tup+ '(2) '(4 5 6)))
(cons 4 (cons 6 (tup+ '() '(5 6))))
(cons 4 (cons 6 '(5 6)))
(cons 4 '(6 5 6))
'(4 6 5 6)

少しずつパターンを変えながら、ラットの長さを求めるlength、ラットのn番目の要素を
抽出するpick、ラットのn番目の要素を削除するrempickを作ります
少しずつ違うとはいえこのあたりはもうお手の物

次は引数が数であるかどうかを判定するnumber?のご紹介
これはschemeにもともと備わっている基本関数で、他の関数で表すことはできません
使い方の例として、ラットから数を消すno-numsとラットから数字だけを取り出すall-numsを作ります
これも問題ありません

o=は数だけ、eq?は数以外のアトムだけしか比べられません1
数でもアトムでも等しいかどうかを判定できるeqan?を作ります

o=は数だけ、eq?は数以外のアトムだけしか比べられませんので
あらかじめnumber?で数かどうかを確認してから比較します

(define eqan?
  (lambda (a1 a2)
    (cond ((and (number? a1) (number? a2)) (o= a1 a2))
          ((or (number? a1) (number? a2)) #f)
          (else (eq? a1 a2)))))

1かどうかを判定するone?をを定義して、rempickを書き直すとこの章は終わりです

(define one?
  (lambda (n)
    (cond
      (else (o= n 1)))))

なんすかねこれは

どうやったらもっと簡単に、この関数を1行で書けるかどうか考えてみましょう。

ていうかなんで(cond (elseなんて書いてるのかそっちのほうがわかりません
関数は必ずcondで始める、みたいな決まりでもあるんでしょうか
第0の戒律?

(define one?
  (lambda (n)
    (o= n 1)))

  1. Racket(ていうか普通のschemeでも)ではeq?で数字も比較できます 

Scheme手習い (4) 計算

第4章「数あそび」では四則演算や比較などを定義しつつ数や数のリストの使い方を学びます

この本で使うSchemeは非常に限定されていて、なんと足し算や引き算も比較もできません
数に関してできるのは1を足す(add1)、1を引く(sub1)、0かどうかを調べる(zero?)だけです 1
関数の構造はいままでとかわりなく、使う関数が変わるだけです

今回は、計算に関する部分をまとめてやってしまいます
本に出てくる順番とは少し変わります

+という関数は普通のSchemeでは定義済みですので、重複を避けてo+という名前で定義します

(define o+
  (lambda (n m)
    (cond ((zero? m) n)
          (else (add1 (o+ n (sub1 m)))))))

null?の代わりにzero?が、consの代わりにadd1が、cdrの代わりにsub1が使われていると思えば
これまでの関数とそっくりの構造です

実行の様子です

(o+ 3 2)
(add1 (o+ 3 (sub1 2)))
(add1 (o+ 3 1))
(add1 (add1 (o+ 3 (sub1 1)))
(add1 (add1 (o+ 3 0)))
(add1 (add1 3))
(add1 4)
5

引き算も同様

(define o-
  (lambda (n m)
    (cond ((zero? m) n)
          (else (sub1 (o- n (sub1 m)))))))

実行の様子

(o- 3 2)
(sub1 (o- 3 (sub1 2)))
(sub1 (o- 3 1))
(sub1 (sub1 (o- 3 (sub1 1))))
(sub1 (sub1 (o- 3 0)))
(sub1 (sub1 3))
(sub1 2)
1

初めて見た時はなんとなく意外な進み方と感じたんですが
考えてみるとこれしかないですね

かけ算

(define o*
  (lambda (n m)
    (cond ((zero? m) 0)
          (else (o+ n (o* n (sub1 m)))))))

途中は少しずつ省略していきます

(o* 12 3)
(o+ 12 (o* 12 2))
(o+ 12 (o+ 12 (o* 12 1)))
(o+ 12 (o+ 12 (o+ 12 (o* 12 0))))
(o+ 12 (o+ 12 (o+ 12 0)))
(o+ 12 (o+ 12 12))
(o+ 12 24)
36

本ではこんな書き方がしてあります

  (o* 12 3)
= 12 + (o* 12 2)
= 12 + 12 + (o* 12 1)
= 12 + 12 + 12 + (o* 12 0)
= 12 + 12 + 12 + 0

数式寄りの書き方ですが言ってることは同じ

この技法は、上のような数を使うものだけでなく、すべての再帰関数に使えます。

これまでもすべて同じようにやってきました

このアプローチを使って正しさを証明するだけでなく、関数を書くこともできます。

どういうアプローチで関数を書こうというのかよくわかりません
数学ぽく正しさを証明するなら数学的帰納法がぴったりですね

(o* n m) が n * m に等しいことを示す。
i) m = 0 のとき、明らかに (o* n m) = n * m = 0
ii) m = k - 1 のとき成り立つとすると (o* n (sub1 k)) = n * (k - 1)
両辺に k を加えると
左辺 = (o+ k (o* n (sub1 k)))
これは o* の定義と見比べると (o* n k) に等しい。
右辺 = k + n * (k - 1) = n * k
したがって (o* n k) = n * k
i)、ii)から 0 以上のすべての m について (o* n m) = n * m が成り立つ。

プログラム書く前にこれを書いてたら
(o* n k) が (o+ k (o* n (sub1 k)))に等しいはず、ってことがわかりますね
そういうアプローチ、ってことでしょうか

さてここで

o*でなぜ0が最終条件行に対する値となるのですか。
0が+に影響しないからです。つまり、n + 0 = nだからです。

0でなければならないのはわかるんですが
「n + 0 = nだからです」というのはいまひとつわかった気がしません

第5の戒律
o+で値を作らんとせしときは、行を終えるときに常に値として0を用うべし。なんとなれば、0を加うるは加算の値を変えぬからなり。
o*で値を作らんとせしときは、行を終えるときに常に値として1を用うべし。なんとなれば、1を掛けるは乗算の値を変えぬからなり。
consで値を作らんとせしときは、行を終えるときに常に値として()を考えるべし。

単位元とかモノイドとか、ほとんど言葉を知ってるだけですがそういう関係なんでしょうね
モノイドだったらどううれしいのか
モノイドクラスが作れるから、とか?
誰か教えてください

ところでconsだけ「考えるべし」となっているのはなんでしょうね
consのときは例外もありうるとか?

さらにべき乗

(define o^
  (lambda (n m)
    (cond ((zero? m) 1)
          (else (o* n (o^ n (sub1 m)))))))

構造は o* とまったく同じ
o* で値を作るときは1からですからね

勢いに乗って o^^ なんてものも定義してみましょうか
o^の値を変えないのは1ですからこうですかね

(define o^^
  (lambda (n m)
    (cond ((zero? m) 1)
          (else (o^ n (o^^ n (sub1 m)))))))

でもべき乗は結合法則が成り立たないからモノイドじゃなかった(無念

> (o^^ 3 0)
1

> (o^^ 3 1)
3

> (o^^ 3 2)
27

> (o^^ 3 3)
7625597484987

たいへんよい増えっぷりです
実際やってみると(o^^ 3 3)は計算が終わりません
なにしろadd1を7625597484987回実行するわけですから
そこでo^の代わりにexptを使いました
これなら一瞬
しかし(o^^ 3 4)はメモリ不足で終了
巨大数の世界の入り口をほんのちょっと垣間見た気がします

次は比較です
二つの数について再帰します

(define o<
  (lambda (n m)
    (cond ((zero? m) #f)
          ((zero? n) #t)
          (else (o< (sub1 n) (sub1 m))))))

(define o>
  (lambda (n m)
    (cond ((zero? n) #f)
          ((zero? m) #t)
          (else (o> (sub1 n) (sub1 m))))))

o=は2通りの書き方で。

(define o=
  (lambda (n m)
    (cond ((zero? m) (zero? n))
          ((zero? n) #f)
          (else (o= (sub1 n) (sub1 m))))))

(define oo=
  (lambda (n m)
    (cond ((o> n m) #f)
          ((o< n m) #f)
          (else #t))))

ほとんど同じですのでo<についてやってみます

(o< 3 2)
(o< 2 1)
(o< 1 0)
#f

(o< 2 2)
(o< 1 1)
(o< 0 0)
#f

(o< 2 3)
(o< 1 2)
(o< 0 1)
#t

正しい結果を出すには(zero? m)と(zero? n)の順番に気をつける必要があります

次は少し趣向を変えて

この関数にふさわしい名前は何ですか。

(define ???
  (lambda (n m)
    (cond ((o< n m) 0)
          (else (add1 (??? (o- n m) m))))))

再帰の仕方も趣向が変わっていますがよく見るとo/です

(o/ 38 12)
(add1 (o/ 26 12))
(add1 (add1 (o/ 14 12)))
(add1 (add1 (add1 (o/ 2 12))))
(add1 (add1 (add1 0)))
3

これで0以上の整数については四則演算・べき乗・大小比較がすべてできるようになりました
部品としては0とzero?、add1とsub1が増えただけです
面白いですね(人によっては


  1. 逆に、add1、sub1は普通のSchemeには定義されていないので別途定義してやる必要があります  

Scheme手習い (3) リスト作り

第3章「偉大なるCons」では再帰しながらconsを使ってリストを作ることを学びます

リストから指定した要素を取り除く、rember という関数を題材にします
最初は間違った定義を与えられちゃったりします

(define rember
  (lambda (a lat)
    (cond
      ((null? lat) (quote ()))
      (else (cond ((eq? (car lat) a) (cdr lat))
                  (else (rember a (cdr lat))))))))

が、作者に敬意を表して素直に追いかけてみます

(rember 'and '(bacon lettuce and tomato))
(rember 'and '(lettuce and tomato))
(rember 'and '(and tomato))
'(tomato)

(eq? (car lat) a) が成り立ったところで (car lat) を捨てるのはいいんですが
それまでに出てきた要素を捨ててはいけませんのであらためてくっつける必要があります
どうやってくっつけるかというと

第2の戒律
リストを作るにはconsを用いるべし。

というわけで正しい値を返す定義です

(define rember
  (lambda (a lat)
    (cond
      ((null? lat) (quote ()))
      (else (cond
          ((eq? (car lat) a) (cdr lat))
          (else (cons (car lat) (rember a (cdr lat)))))))))

今度は再帰の様子が今までとすこーし違って、再帰から戻るときにそのまま値を返すだけでなく
ひと仕事してから戻っています
本の中でもかなりのボリュームを割いて追いかけてくれています
たぶん大事なところ

(rember 'and '(bacon lettuce and tomato))
(cons 'bacon (rember 'and '(lettuce and tomato)))
(cons 'bacon (cons 'lettuce (rember 'and '(and tomato)))
(cons 'bacon (cons 'lettuce '(tomato)))
(cons 'bacon '(lettuce tomato))
'(bacon lettuce tomato)

このremberの定義はcondが二重になっています
ひとつにまとめることも可能です

(define rember
  (lambda (a lat)
    (cond
      ((null? lat) (quote ()))
      ((eq? (car lat) a) (cdr lat))
      (else (cons (car lat) (rember a (cdr lat)))))))

当然結果は同じです
実行の様子も同じ

ではなぜすぐに簡単化しないのですか。

そのときに関数の構造が引数の構造と一致しないからです。

以下のふたつを分けて考えようぜ、ってことかと思われます

  • 外側のcond
    carを取る前にはnullかどうか確認しなければならないというcarの掟や
    nullであるかまたは1個以上のアトムを持つというラットのデータ構造を反映
  • 内側のcond
    ラットのデータ構造一般についてではない、rember独自の基準を反映

次はリストのリストを取り、各リストの先頭の要素のリストを返すfirstsという関数を作ります
イチから新しいリストを作る場合は()にconsしていきます

(define firsts
  (lambda (l)
    (cond
       ((null? l) (quote ()))
       (else (cons (car (car l)) (firsts (cdr l))))))

実行の様子は以下のとおり
再帰の形としてはremberと似ています

(firsts '((a b) (c d) (e f)))
(cons 'a (firsts '((c d) (e f))))
(cons 'a (cons 'c (firsts '((e f)))))
(cons 'a (cons 'c (cons 'e (firsts '())))))
(cons 'a (cons 'c (cons 'e '())))
(cons 'a (cons 'c '(e)))
(cons 'a '(c e))
'(a c e)

これからも(cons (car l) (f (cdr l)))の形は繰り返し現れます
これが第3の戒律

第3の戒律
リストを作らんとせしときは、最初の要素になるものを記述し、しかる後にそれを
自然なる再帰にconsすべし。

「自然なる再帰」はこの場合リストのcdrということです

このあと、insertR、insertL、subst、subst2、multirember、multiinsertR、
multiinsertL、multisubstと繰り返し同じパターンで再帰の練習をします
かなり慣れます

第4の戒律(仮)
再帰のときは、常に少なくとも1つの引数を変えるべし。必ず終わりへと近づくこと
間違いなし。変えた引数は最終条件で必ずテストすべし。すなわち、cdrを用いるときは、
null?でテストすべし。

2章、3章はひたすらcdrとnull?を使いました

マインドフルネスと「ていねい」

最近は「マインドフルネス」ってやつが気になっています
それでこういうのを読みました

 以前から、座禅や瞑想には何かあると思っていて本を読んだりやってみたり
してはいたんですがいまひとつ続かず
いきなり高尚なものは無理かなあ&なにか自分を納得させられる説明が
ないかなあと思ってました

つまり
最初はたったこれだけでいいんですよ、とか調査した結果こんなでした、と
言ってくれるようなものってことです
そこへ最近はマインドフルネスってやつが盛り上がってきて、お、これは?と
乗っかってみました

上の本はそういう私にちょうどいいレベル

マインドフルネスといえば瞑想、みたいな感じのイメージですけれども
歩いたり、何かしている時それだけに集中してやるというのもマインドフルネスらしいです

でマインドフル歯磨きとかマインドフルひげそりとかマインドフルアイロン掛けを
やっていて思いました
これは、「ていねいにやる」ってことじゃないか、と

すごくピンときたんですがじゃあ「ていねい」ってなんだっけ?
大辞泉で調べてみると「細かいところまで気を配ること。注意深く入念にする
こと」と書いてありました
まあ説明するとそうなんですが、「ていねい」にはもっとなんともいえない
ニュアンスがあって、そこがマインドフルネスとマッチする気がしました

歩いてる時に足裏の感覚を「味わいながら」ゆっくり歩くということも書いてあります
「味わう」にも「ていねい」ということばに共通するなにかがありますね

「ていねい」は英語にしろと言われてもなかなか難しい
和英辞書にはpolite、courteous、carefulあたりが乗っていますが
「職人のていねいな仕事」というときの「ていねい」はどう言えばいいでしょうか

マインドフルネス上級者ともなると、瞑想している時間だけがマインドフルなのではなく
生活全体がマインドフルなのだといいます
それは、「立ち居振る舞い」というこれまた直訳しても何も伝わらなそうな
言葉で表せそうな気がします
立ち居振る舞いが美しい、というとなんか憧れますね

英語にしきれないそこが日本古来のマインドフルネスなんじゃないか
日本がスゴいと言われてるところって実はそういうところくるものなんじゃないか
そんなことも思いました

オレオレ解釈ですがすごくしっくりきているので、ひとつひとつていねいに、を
意識していこうと思います

Scheme手習い (2) 初めての再帰

第2章「一度、もう一度、さらにもう一度、またもう一度、……」ではラットの再帰を学びます
ラット(lat)はこの本だけの用語で、アトムだけが並んだリストのことです
List of ATomsのことだと思われます

引数がラットであるかどうかを判定する関数lat?です

(define lat?
  (lambda (l)
    (cond
      ((null? l) #t)
      ((atom? (car l)) (lat? (cdr l)))
      (else #f))))

この関数に引数として(bacon and eggs)を与えてやったときの動きを1行ずつ
じっくりと追いかけていきます
こんな感じです 一部略してます

(lat? l)の最初の質問は何ですか。
(null? l)です。(略)
次のcond行 ( (null? l) #t) の意味は何ですか。
(null? l)は引数lが食うリストかどうか質問します。(略)
次の質問は何ですか。
(atom? (car l))です。
行( (atom? (car l)) (lat? (cdr l))) の意味は何ですか。(略)
(atom? (car l))は、リストlの最初のS式がアトムかどうかをたずねています。(略)
(lat? (cdr l)) の意味は何ですか。
...

この調子で (lat? '(bacon and eggs)) の値を得るまでに4ページ半を使い
21回の質問を繰り返してます
ある意味ここがこの本のクライマックスのひとつと言えるかもしれません

再帰のことがなんとなくわかったようなわからないような、という人がいたら
このレベルで1行1行ていねいに追いかけるというのをやってみると理解度が
上がるんじゃないかと思います

こういうところは得手不得手みたいなのが激しいっぽいので
引っかかる人がどういうところに引っかかるのか想像しづらいところではありますが
(lat? (cdr l))が何をやっているかが腑に落ちるかどうか、かなあ
逆に、本当にわかっている人からすると自分も何かの理解が抜けている気もします

頭の中ではこんな感じですかね

(lat? '(bacon and eggs))を評価する
(null? l)ではなくて(atom? (car l)だから
  (lat? '(and eggs))を評価する
  (null? l)ではなくて(atom? (car l)だから
    (lat? '(eggs))を評価する
    (null? l)ではなくて(atom? (car l)だから
      (lat? '())を評価する
      (null? l)だから値は#t

普通の言語の感覚だと、呼び出しを逆にたどって

      (略)
      (null? l)だから値は#t
      だから(lat? '())の値は#t
    だから(lat? '(eggs))の値は#t    
  だから(lat? '(and eggs))の値は#t
だから(lat? '(bacon and eggs))の値は#t

な風になりそうなものですが、ここは書かれてません
これは末尾再帰だからわざとそうしてるんでしょうか

慣れてきたらこんな感じに

(lat? '(bacon and eggs))
(lat? '(and eggs))
(lat? '(eggs))
(lat? '())
#t

もう一度、こんどは値が#fになる例を同じように追いかけます

(lat? '(bacon (and eggs))')
(lat? '((and eggs))')
#f

ここで

)))は何ですか。
(略)カッコの数を合わせるためのものです。

ちょっと笑いました

次にラットの中にアトムが含まれているかどうかを返すmember?を定義します

(define member?
  (lambda (a lat)
    (cond
      ((null? lat) #f)
      (else (or (eq? (car lat) a)
                (member? a (cdr lat)))))))

本では#fのところがnilとなってますが誤植です
この本、最初はSchemeではなくてCommon Lispで書かれてたそうなので
その時のものが修正されずに残ってしまったんでしょうか
けっこう誤植が多い気がします

(or A B)は↓と同じです
(本では例示したうえでかみくだいて書いてくれてます)

(cond (A #t)
      (else (cond (B #t)
            (else #f))))

member?も例のしつこさで追いかけてくれます

(member? 'meat '(mashed potatoes and meat gravy))
(member? 'meat '(potatoes and meat gravy))
(member? 'meat '(and meat gravy))
(member? 'meat '(meat gravy))
#t

かと思いきや、#tが求まってからも追いかけ続けているようです
ということはこういう解釈?

(member? 'meat '(mashed potatoes and meat gravy))
(or #f (member? 'meat '(potatoes and meat gravy)))
(or #f (or #f (member? 'meat '(and meat gravy))))
(or #f (or #f (or #f (member? 'meat '(meat gravy)))))
(or #f (or #f (or #f #t)))
(or #f (or #f #t))
(or #f #t)
#t

この形でも末尾再帰だと思ったんだけどな・・・
なにか理解が違ってるかな

あと#fになる例

(member? 'liver (bagles and lox))
(or #f (member? 'liver (and lox)))
(or #f (or #f (member? 'liver (lox))))
(or #f (or #f (or #f (member? 'liver ()))))
(or #f (or #f (or #f #f)))
(or #f (or #f #f))
(or #f #f)
#f

まあこれくらい繰り返して1行ずつ追えば初めての人でも再帰の感じがつかめるんじゃないでしょうか
私は初めてじゃなかったので本当のところはわかりませんが

戒律というのが出てきます

第1の戒律
(仮)いかなる関数を表現するときも最初の質問はすべてnull?にすべし。

10個の戒律をマスターすればあなたも再帰の達人に
というわけ

ちなみにlat?は普通こんな風に書くと思います
こちらのほうが1行とインデント1段とを節約できて読みやすくなりますし

(define (lat? l)
  (cond
    ((null? l) #t)
      ((atom? (car l)) (lat? (cdr l)))
      (else #f)))

ただこの書き方は関数を作る部分(lambda)と、モノに名前を付ける部分(define)が
ごっちゃになってますのでちょっとピュアな感じに欠けます
計算機科学の世界だとこっちが普通なんでしょうか
できるだけシンプルにしたいというだけかもしれません
そのあたりけっこう原理主義かもしれませんこの本