作者: Tsutomu Hiroshima
日時: 2003/4/24(09:10)
廣島です.

>  最近、Scheme をかじって「もしかしたら、再帰プログ
> ラムをループにしてくれるのかな?」と思い、以下のプ
> ログラムを書いてみました。
> 
> def func(x):
>   if x == 0:
>     return 0
>   else:
>     return 5 + func(x - 1)

この func を Scheme で書くと

(define (func x)
  (if (zero? x)
      0
      (+ 5 (func (- x 1))))) ; 末尾再帰でない!!

となります.Scheme の実装の 1 つ SCM の場合,
x = 1000000 を与えるとセグメンテーション違反になりました.
guile でも Stack overflow になります.

理由は再帰が 末尾再帰 (または 終端再帰 とも言う) になっていないからです.

func の呼び出しが 次の func の返り値を待ち,
それに 5 を加えて値を返しています.
つまり,次の func の呼び出しが,
前の func の最後の処理(末尾)になっていません.


そこで,内部関数 foo を使って func を

(define (func x)
  (define (foo x y)
    (if (zero? x)
	y
	(foo (- x 1) (+ y 5)))) ; 末尾再帰
  (foo x 0)) ; 内部関数呼び出し(これも末尾呼び出しなので goto で置き換わる)

と定義すれば,次の foo の呼び出しが,
前の foo の最後の処理になり,
この再帰はループに置き換わります.

内部関数にしない方がコードが見やすく,
分かりやすいかも知れませんね.

(define (foo x y)
  (if (zero? x)
      y
      (foo (- x 1) (+ y 5))))

(define (func x)
  (foo x 0))


python で書くなら
# 動作確認出来ませんので間違っているかも...

def foo(x, y):
    if x == 0:
       return y
    else:
       return foo(x - 1, 5 + y)

def func(x):
    return foo(x, 0)

と言う感じでしょうか.

ちなみに Scheme では,
foo を「名前付き let」でバインドするのが普通だと思います.

(define (func x)
  (let foo ((a x) (b 0))
    (if (zero? a)
	b
	(foo (- a 1) (+ 5 b))))) ; やはりこれも末尾再帰

-----------------------------
	廣島 勉
	(tsutomu@...)