(define ひとりSICP読書会#02 '("1.1.8" "1.2.1"))

[初回](https://qiita.com/hisawotakase/items/8327d7f28b9fe0820abe)から迷走している『ひとりSICP読書会』の第2回目です。隔週で投稿できればと思っていますが、早くも息切れ気味です。。
![SICP_cover.jpg](https://upload.wikimedia.org/wikipedia/commons/9/9d/SICP_cover.jpg)
さて、前回行き詰まった 練習問題 1.6 は、前回の考察以外に思いつかないのでそれを解答とし、1.7, 1.8 は特に問題なく解いて、次の 1.1.8 節に進みます。

## 束縛 (bind)

束縛とは変数の局所化のための仕組みで、「[初めての人のためのLISP](https://www.shoeisha.co.jp/book/detail/9784798119410) (以降『竹内本』)」では『代入』の流れから出てきてます[^1]。関数 (手続き) を実行するときに新しい変数を用意して引数を代入し、関数の評価が終わればその変数は捨ててしまう、と。そして、

> 束縛によって名前が定義される式のセットは、その名前のスコープ (scope) と呼ばれます。

これは多くの言語でいう『関数スコープ』ですね。

と `スコープ` という語が出たところで、前回 [環境 (environment)](https://sicp.iijlab.net/fulltext/x112.html) が出てきて『スコープのようなもの？』として進めたのですが、やはり違っていたようです。第3章で明らかになる[^2]ようですが、それまではふわっとした理解で進めます。

[^1]: ちなみに竹内本では、思わずA君が『エッ、縄で縛るのですか？それなら僕の趣味だ。』と口走ってしまうのですが、それで覚えていたというのは竹内郁雄先生の策にはまった感じです (この本は、そういうサブカルっぽいネタが多い)。

[^2]: 脚注で『第 3 章では、インタプリタがどのように動作しているかを理解するため、またインタプリタを実装するために、この環境という概念がキーポイントになることを示します。』と補足されています。

また、試しに ブロック構造 (block structure) / レキシカルスコーピング (lexical scoping) で練習問題 1.8 を書き換えてみると、
```scheme
(define (square x) (* x x))
(define (cube x) (* x x x))

(define (cbrt x)
  (define (good-enough? guess)
    (< (abs (- (cube guess) x)) 0.001))
  (define (improve guess)
    (/ (+ (/ x (square guess)) (* 2 guess)) 3))
  (define (cbrt-iter guess)
    ;for debug
    (display (string-append (number->string guess) " "
                            (if (good-enough? guess) "#t" "#f") "\n"
                            )) 
    (if (good-enough? guess)
	guess
	(cbrt-iter (improve guess))))
  (cbrt-iter 1.0))
```
こんな感じ (前回入れたデバッグ出力付き) 。実行すると、
```scheme
> (cbrt 8)
1.0 #f
3.3333333333333335 #f
2.462222222222222 #f
2.081341247671579 #f
2.003137499141287 #f
2.000004911675504 #t
2.000004911675504
>
```
です。たぶんポイントは「引数 x に対しての『再代入』が行われてない」ことだと思います。

## 1.2.1 線形再帰と反復

再帰と反復と言っても、[どちらも "再帰**手続き**" で実装されています](https://sicp.iijlab.net/fulltext/x121.html) (LISP でいう prog 形式ではない)。違いは、

+ 再帰プロセス: まず展開して、展開が完了すると (計算しつつ) 縮約して結果を求める
+ 反復プロセス: 都度計算を実行して、末尾に達したときの値を結果とする

です。私は2つの区別を、前者が (縮約時に) 計算結果を戻り値で渡しているのに対して、後者は都度計算した値を (再帰呼び出し時に) 引数として渡している、と考えました。そして、

+ "再帰**プロセス**" と "再帰**手続き**" の概念を混同しない: それぞれの内部の処理とメモリ/計算量を理解する
+ 反復プロセスは **状態** を持つ
+ Scheme の反復プロセスは、再帰手続きとして記述されていても、固定の (メモリ) 空間で実行できる: (末尾再帰)

がポイントと思います。

### 練習問題 1.9

これは特に問題ないと思います。ちょっと邪道かもしれませんが、デバッグ出力をつけて実行すると、よくわかります。

```scheme
> (define (+ a b)
  (display (string-append "a:" (number->string a) " b:" (number->string b) "\n"))
  (if (= a 0)
      b
      (inc (+ (dec a) b))))

> (+ 3 4)
a:3 b:4
a:2 b:4
a:1 b:4
a:0 b:4
7
```
```scheme
> (define (+ a b)
  (display (string-append "a:" (number->string a) " b:" (number->string b) "\n"))
  (if (= a 0)
      b
      (+ (dec a) (inc b))))

> (+ 3 4)
a:3 b:4
a:2 b:5
a:1 b:6
a:0 b:7
7
```

前者が `a = 0` となった後に縮約して結果を出しているのに対し、後者は b の値を返して終了します。

余談ですが、このように『**+という基本的な演算**すら再定義できる』ということから、

```scheme
> (define (+ a b) (- a b))
> (+ 3 4)
-1
> (define (+ a b) (* a b))
> (+ 3 4)
12
```
ということが出来てしまうわけですね。。

**(2022-09-20 追記)** 再定義できてしまいますが『言語仕様としては規定されてない』とコメントでお教えいただきました。[そちらもご参照ください！](https://qiita.com/hisawotakase/items/568d5ede8b7b16e31b35#comment-03f0500572fa40cb7dc2)

### 練習問題 1.10

ここで行き詰まりました。`else` 時の手続き `(A (- x 1) (A x (- y 1)))` と、その手続きの引数 `(A x (- y 1))` で再帰呼び出しされていますので、先に引数 `(A x (- y 1))` が評価されるのだと思うのですが、処理を追うのに頭が追いつきません。。例によってデバッグ出力を入れて動作を追っても、どうもわからず。

そもそも『アッカーマン関数って、何？』と調べてみたら、なんか練習問題の定義と違うような。。SICPでは、

```scheme
(define (A x y)
  (cond ((= y 0) 0)
        ((= x 0) (* 2 y))
        ((= y 1) 2)
        (else (A (- x 1) (A x (- y 1))))))
```

ですが、[Wikipedia によると](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%83%E3%82%AB%E3%83%BC%E3%83%9E%E3%83%B3%E9%96%A2%E6%95%B0)　

```math
A(m, n) = \left\{
  \begin{array}{ll}
    n + 1, & \textrm{if } m = 0 \\
    A(m - 1, 1), & \textrm{if } n = 0 \\
    A(m -1, A(m, n - 1)), & \textrm{otherwise}
  \end{array}
\right.
```

で、コードに起こすと下記です。

```scheme
(define (A x y)
  (cond ((= x 0) (+ y 1))
        ((= y 0) (A (- x 1) 1))
        (else (A (- x 1) (A x (- y 1))))))
```
実行して [アッカーマン関数の値の表](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%83%E3%82%AB%E3%83%BC%E3%83%9E%E3%83%B3%E9%96%A2%E6%95%B0#%E3%82%A2%E3%83%83%E3%82%AB%E3%83%BC%E3%83%9E%E3%83%B3%E9%96%A2%E6%95%B0%E3%81%AE%E5%80%A4%E3%81%AE%E8%A1%A8) と見比べたところ、これで正しいようです。

といっても、条件分岐はシンプルになっているものの `(= y 0)` 時にも再帰呼び出ししたり、キモの `手続きと、その手続きの引数で再帰呼び出し` は変わらないですね。ということは行き詰まりポイントも変わらないということで、、

今回はここまでにします。進まないニャー。

***
※ 本稿で使用した画像 [Cover of the book Structure and Interpretation of Computer Programs (second edition). Harold Abelson and Gerald Jay Sussman with Julie Sussman — MIT Press](https://ja.wikipedia.org/wiki/%E8%A8%88%E7%AE%97%E6%A9%9F%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E6%A7%8B%E9%80%A0%E3%81%A8%E8%A7%A3%E9%87%88#/media/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:SICP_cover.jpg) は [CC 表示-継承 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.ja) で公開されています。