エレファント・ビジュアライザー調査記録

ビジュアルプログラミングで数式の変形を表すことを考えていくブロクです。

ラムダ計算と無限ラムダ多項式(15)

フィボナッチ数列の例

繰り返しの回数が有限のときに、(有限個の)変更しない変数で書けることや関数の再帰的定義を取り除くことができること(また変数や関数定義を取り除くことができること)を示すための例を考えます。

平方根の例は長いので、ここではフィボナッチ数列の例を考えます。フィボナッチ数列は以下のように定義されます(wikipedia:フィボナッチ数を参照)。

  •  F_0 = 0
  •  F_1 = 1
  •  F_{n+2} = F_n + F_{n+1} \ (n \ge 0)

フィボナッチ数列の例(1)

C#イテレーターを使って擬似的な無限の列を考えます。

            IEnumerable<int> fib()
            {
                int x = 0;
                int y = 1;
                yield return x;
                yield return y;
                for (; ; )
                {
                    int z = x + y;
                    yield return z;
                    x = y;
                    y = z;
                }
            }

最初の30項は以下のようになります。

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229

繰り返しの回数が8回以内として「for」を「展開」して取り除き、変数を変更しないようにすると以下のようになります。

            IEnumerable<int> fib()
            {
                int x_0 = 0;
                int y_0 = 1;
                yield return x_0;
                yield return y_0;
                int z_0 = x_0 + y_0;
                yield return z_0;
                int x_1 = y_0;
                int y_1 = z_0;
                int z_1 = x_1 + y_1;
                yield return z_1;
                int x_2 = y_1;
                int y_2 = z_1;
                int z_2 = x_2 + y_2;
                yield return z_2;
                int x_3 = y_2;
                int y_3 = z_2;
                int z_3 = x_3 + y_3;
                yield return z_3;
                int x_4 = y_3;
                int y_4 = z_3;
                int z_4 = x_4 + y_4;
                yield return z_4;
                int x_5 = y_4;
                int y_5 = z_4;
                int z_5 = x_5 + y_5;
                yield return z_5;
                int x_6 = y_5;
                int y_6 = z_5;
                int z_6 = x_6 + y_6;
                yield return z_6;
                int x_7 = y_6;
                int y_7 = z_6;
                int z_7 = x_7 + y_7;
                yield return z_7;
            }

結果は以下のようになります。

0, 1, 1, 2, 3, 5, 8, 13, 21, 34

フィボナッチ数列の例(2)

fib() の中で fib() を参照するようにします。2つの数の和を取らなければいけないのですが、「foreach (…)」 の中では1つしか書くことができないので以下のようになります。

            IEnumerable<int> fib()
            {
                yield return 0;
                yield return 1;
                IEnumerator<int> fib_itr = fib().GetEnumerator();
                fib_itr.MoveNext();
                foreach (int n in fib())
                {
                    fib_itr.MoveNext();
                    yield return n + fib_itr.Current;
                }
            }

foreach をやめて for で書き直すと以下のようになります。

            IEnumerable<int> fib()
            {
                yield return 0;
                yield return 1;
                IEnumerator<int> fib_itr1 = fib().GetEnumerator();
                IEnumerator<int> fib_itr2 = fib().GetEnumerator();
                fib_itr2.MoveNext();
                for (; ; )
                {
                    fib_itr1.MoveNext();
                    fib_itr2.MoveNext();
                    yield return fib_itr1.Current + fib_itr2.Current;
                }
            }

for を展開することはできるのですが、fib_itr1.MoveNext()、fib_itr2.MoveNext() で fib_itr1、fib_itr2 が更新されるので、C#イテレーターを直接使うと変数が変更されないようにはできません。

            IEnumerable<int> fib()
            {
                yield return 0;
                yield return 1;
                IEnumerator<int> fib_itr1 = fib().GetEnumerator();
                IEnumerator<int> fib_itr2 = fib().GetEnumerator();
                fib_itr2.MoveNext();
                fib_itr1.MoveNext();
                fib_itr2.MoveNext();
                yield return fib_itr1.Current + fib_itr2.Current;
                fib_itr1.MoveNext();
                fib_itr2.MoveNext();
                yield return fib_itr1.Current + fib_itr2.Current;
                fib_itr1.MoveNext();
                fib_itr2.MoveNext();
                yield return fib_itr1.Current + fib_itr2.Current;
                fib_itr1.MoveNext();
                fib_itr2.MoveNext();
                yield return fib_itr1.Current + fib_itr2.Current;
                fib_itr1.MoveNext();
                fib_itr2.MoveNext();
                yield return fib_itr1.Current + fib_itr2.Current;
                fib_itr1.MoveNext();
                fib_itr2.MoveNext();
                yield return fib_itr1.Current + fib_itr2.Current;
                fib_itr1.MoveNext();
                fib_itr2.MoveNext();
                yield return fib_itr1.Current + fib_itr2.Current;
                fib_itr1.MoveNext();
                fib_itr2.MoveNext();
                yield return fib_itr1.Current + fib_itr2.Current;
            }

フィボナッチ数列の例(3)

C#イテレーターでは変数を変更しないようにはできなかったので、自作のイテレーターを作ります。

            IEnumerable<int> fib()
            {
                int x = 0;
                int y = 1;
                int next()
                {
                    int z = x + y;
                    x = y;
                    y = z;
                    return z;
                }
                yield return 0;
                yield return 1;
                for (; ; )
                {
                    yield return next();
                }
            }

この next を変数を変更しないように書き直します。

            IEnumerable<int> fib()
            {
                (int,int) next(int x, int y)
                {
                    int z = x + y;
                    return (y, z);
                }
                int x = 0;
                int y = 1;
                yield return 0;
                yield return 1;
                for (; ; )
                {
                    (x, y) = next(x, y);
                    yield return y;
                }
            }

そして for を展開すると変数を変更しないようにすることができます。

            IEnumerable<int> fib()
            {
                (int, int) next(int x, int y)
                {
                    int z = x + y;
                    return (y, z);
                }
                int x_0 = 0;
                int y_0 = 1;
                yield return 0;
                yield return 1;
                (int x_1, int y_1) = next(x_0, y_0);
                yield return y_1;
                (int x_2, int y_2) = next(x_1, y_1);
                yield return y_2;
                (int x_3, int y_3) = next(x_2, y_2);
                yield return y_3;
                (int x_4, int y_4) = next(x_3, y_3);
                yield return y_4;
                (int x_5, int y_5) = next(x_4, y_4);
                yield return y_5;
                (int x_6, int y_6) = next(x_5, y_5);
                yield return y_6;
                (int x_7, int y_7) = next(x_6, y_6);
                yield return y_7;
                (int x_8, int y_8) = next(x_7, y_7);
                yield return y_8;
            }

ラムダ計算と無限ラムダ多項式(14)

有限ラムダ多項式 Lisp

平方根の例」を移植するために Common Lisp のサブセットを目指した Lisp を作成します。これを「有限ラムダ多項式 Lisp」と呼ぶことにします。仕様は以下のようになります。

定数・変数

定数には数値、t (真を表す)、nil (偽を表す)があります。数値は任意の桁数の小数を使うことができます。定数と演算子以外の文字列は変数として使うことができます。

( 関数名 式 式 … 式 )

「式 式 … 式」を引数として関数を実行します。「関数名」は

  • 最初から定義されている関数名(car、cdr、cons、first、second、third、atom、null、list、progn、expt、equal、not、and、or など)
  • 演算子(+、-、*、/、=、/=、<、<=、>、>= など)
  • 定義された関数名
  • 評価されると関数になる式
  • 無名関数

のどれかです。

' 式

または (quote 式) とも書きます。式を評価しないものを表します。「'( 式 式 … 式 )」と書くとリスト「( 式 式 … 式 )」を表します。

(lambda (変数名 変数名 … 変数名) 式 式 … 式)

無名関数を表します。評価されるとクロージャーに変換されます。正式には #'~、または (function ~) と書くようですが、(Common Lisp では)単にこう書いても変換されるようなのでここでもそうします。

setf 変数名 式

(できるだけ Common Lisp に合わせたいのですが Common Lisp の仕様がよくわからないので)ここでは単に変数に式を対応させるものとなっています。setq も同様です。変数は「レキシカルスコープ」とします。これは以前「環境」で定義した変数と同じもので、書いた通りの意味の変数(関数内関数や名前呼びの変数のような)で関数が終了しても使える変数となります。「レキシカルスコープ」の変数が存在すればその変数を変更し、存在しなければ新規作成するとします。

(defun 関数名 (変数名 変数名 … 変数名) 式 式 … 式)

関数を定義します。再帰的定義もできます。「レキシカルスコープ」で関数名とクロージャーを対応させます。この後関数名を評価するとクロージャーに変換されます。

(if 条件式 式1 式2)

条件式を評価した結果が nil でなければ式1を評価、nil ならば式2を評価します。

平方根の例

(defun GetNextDecimalDigitAndNumbers
       (numbers)
       (progn (setf number (first numbers))
              (setf square_difference (second numbers))
              (setf scale (third numbers))
              (defun maxdd (dd)
                           (if (>= dd 0)
                               (progn (setf zd (* dd (expt 10 (- scale))))
                                      (setf number_sq_diff (+ (* (* number zd) 2) (* zd zd)))
                                      (if (<= number_sq_diff square_difference)
                                          (list dd
                                                (list (+ number zd)
                                                      (- square_difference number_sq_diff)
                                                      (+ scale 1)
                                                      )
                                                )
                                          (maxdd (- dd 1))
                                          )
                                      )
                               (list 0 (list 0 0 0))
                               )
                           )
              (maxdd 9)
              )
       )
(defun GenerateDecimal
       ()
       (progn (setf nums (list 0 3 0))
              (setf current_digit 0)
              (defun next
                     ()
                     (progn (setf current_digit (first (GetNextDecimalDigitAndNumbers nums)))
                            (setf nums (second (GetNextDecimalDigitAndNumbers nums)))
                            t
                            )
                     )
              (defun getCurrent () (progn current_digit))
              (defun setCurrent (val) (progn))
              (list next getCurrent setCurrent)
              )
       )
(defun take
       (count gen)
       (if (= count 0)
           ()
           (progn (setf next (first gen))
                  (setf getCurrent (second gen))
                  (setf setCurrent (third gen))
                  (if (next)
                      (cons (getCurrent) (take (- count 1) gen))
                      ()
                      )
                  )
           )
       )
(defun zipn
       (f n list)
       (if (null list)
           ()
           (cons (f (first list) n)
                 (zipn f (+ n 1) (cdr list))
                 )
           )
       )
(defun nlist
       (list)
       (zipn (function (lambda (n e) (* n (expt 10 (- e))))) 0 list)
       )
(defun foldr
       (f n list)
       (if (null list)
           n
           (f (first list) (foldr f n (cdr list)))
           )
       )
(defun number
       (list)
       (foldr (lambda (x y) (+ x y)) 0 (nlist list))
       )

サーバー側で計算し、一桁ずつ取得する例

(setf generator (GenerateDecimal))
(defun GetNextDecimalDigit
       ()
       (progn ((first generator)) ((second generator)))
       )
(defun repeat
       (number e count)
       (if (< e count)
           (repeat (+ number (* (GetNextDecimalDigit) (expt 10 (- e)))) (+ e 1) count)
           number
           )
       )
(setf count 21)
(repeat 0 0 count)

サーバー側で計算し、イテレーターを使う例

(setf count 21)
(number (take count (GenerateDecimal)))

ブラウザー側で計算し、(サーバー側の)データを更新する例

(setf numbers (list 0 3 0))
(defun repeat
       (number e count)
       (if (< e count)
           (progn (setf dd (first (GetNextDecimalDigitAndNumbers numbers)))
                  (setf ns (second (GetNextDecimalDigitAndNumbers numbers)))
                  (setf numbers ns)
                  (repeat (+ number (* dd (expt 10 (- e)))) (+ e 1) count)
                  )
           number
           )
       )
(setf count 21)
(repeat 0 0 count)

ブラウザー側で計算し、(サーバー側の)データを(ジェネレーターで)更新する例

(defun unfoldl
       (next init)
       (progn (setf src init)
              (setf dst 0)
              (defun next_
                     ()
                     (progn (setf dst (first (next src)))
                            (setf src (second (next src)))
                            t
                            )
                     )
              (defun getCurrent () (progn dst))
              (defun setCurrent (val) (progn))
              (list next_ getCurrent setCurrent)
              )
       )
(setf numbers (list 0 3 0))
(setf count 21)
(number (take count (unfoldl GetNextDecimalDigitAndNumbers numbers)))

ラムダ計算と無限ラムダ多項式(13)

有限ラムダ多項式によるクロージャーの定義

有限ラムダ多項式

有限ラムダ多項式は以下のものから構成されます。

演算子  f に対して演算子の項数  n が決まっているとします。定数は項数  0演算子とします。

クロージャーの定義

ラムダ式は以下のものから構成されます。

  • 定数
  • 変数
  •  f(x_1, x_2, \cdots , x_n)
  •  λ( (v_1, v_2, \cdots , v_n) . x, e)
  • let  v =  x in  y
  • subst  v =  x in  y
  • def  f (  v_1, v_2, \cdots , v_n ) =  x in  y
  • fun (  v_1, v_2, \cdots , v_n ) =  x
  • if  x then  y else  z

集合  E の元は変数  v、式  x E \cup \bot の元  e の組  \langle v, x, e \rangle とします。この元は再帰的定義にはなっていないとします。この元を  v \mapsto x : e と書くことにします。集合  E を「時代」、 E \cup \bot の元を「環境」と呼びます。

ある環境  e で変数  v に対応する式  e(v) を以下のように帰納的に定義します。

  •  e = \bot のとき  e(v) = \bot
  •  e = v \mapsto x : e' のとき  e(v) = x
  •  e = v' \mapsto x : e' \ (v \ne v') のとき  e(v) = e'(v)

ある環境  e に対して、変数  v に対応する式を  x で置き換えた環境  e(v \gets x) を以下のように帰納的に定義します。

  •  e = \bot のとき  e(v \gets x) = \bot
  •  e = v \mapsto y : e' のとき  e(v \gets x) = v \mapsto x : e'
  •  e = v' \mapsto y : e' \ (v \ne v') のとき  e(v \gets x) = v' \mapsto y : e'(v \gets x)

ある時代  E と環境  e \in E での式  x の値  \mathrm{val}(e, x) を考えます。

定数  a の値

定数  a の値は  \mathrm{val}(e, a) = a です。

変数  v の値

変数  v の値は  \mathrm{val}(e, v) = e(v) です。

 f(x_1, x_2, \cdots , x_n) の値

 f(x_1, x_2, \cdots , x_n) の値は、 \mathrm{val}(e, f) = λ( (v_1, v_2, \cdots , v_n) . y, e') (クロージャー)のときは
 \mathrm{val}(e, λ( (v_1, v_2, \cdots , v_n) . y, e')(x_1, x_2, \cdots , x_n)) \\
= \mathrm{val}( e', 
\operatorname{let} v_1 = \mathrm{val}(e, x_1) \operatorname{in}
\operatorname{let} v_2 = \mathrm{val}(e, x_2) \operatorname{in} \cdots
\operatorname{let} v_n = \mathrm{val}(e, x_n) \operatorname{in} y)
となります。このときクロージャーが持っている環境  e' を使います。

 \mathrm{val}(e, f)クロージャーではないときは、
 \mathrm{val}(e, f(x_1, x_2, \cdots , x_n)) 
= \mathrm{val}(e, f) ( \mathrm{val}(e, x_1), \mathrm{val}(e, x_2), \cdots , \mathrm{val}(e, x_n) )
とします。 \mathrm{val}(e, f) の部分は定数ではなくても良いものとします。

let  v =  x in  y の値

 v は変数、 x y は式です。

 E の「次の時代」 E' E' = E \cup \{v \mapsto x : e\} とし、 E'
 \mathrm{val}(e, \operatorname{let} v = x \operatorname{in} y) = \mathrm{val}(v \mapsto x : e, y)
とします。

subst  v =  x in  y の値

 v は変数、 x y は式です。

 E の「次の時代」 E' E' = (E \setminus \{v \mapsto x' : e\}) \cup \{v \mapsto x : e\} とし、 E'
 \mathrm{val}(e, \operatorname{subst} v = x \operatorname{in} y) = \mathrm{val}(e(v \gets x), y)
とします。このような  v \mapsto x' : e が存在しないときは
 \mathrm{val}(e, \operatorname{subst} v = x \operatorname{in} y) = \bot
とします。

def  f (  v_1, v_2, \cdots , v_n ) =  x in  y の値

関数定義を表します。 f は変数(関数名)、 v_1, v_2, \cdots , v_n は変数、 x y は式です。
 \mathrm{val}(e, \operatorname{def} f (v_1, v_2, \cdots , v_n) = x \operatorname{in} y) = \mathrm{val}( e', y)
とします。

ここで  e' = (f \mapsto λ( (v_1, v_2, \cdots , v_n) . x, e)) : e です(再帰的定義はしません)。 E の「次の時代」 E' E' = E \cup \{e'\} とします。

fun (  v_1, v_2, \cdots , v_n ) =  x の値

無名関数を表します。 v_1, v_2, \cdots , v_n は変数、 x は式です。
 \mathrm{val}(e, \operatorname{fun} (v_1, v_2, \cdots , v_n) = x) = \mathrm{val}(e, λ( (v_1, v_2, \cdots , v_n) . x, e))
です。

if  x then  y else  z の値

 x y z は式です。 \mathrm{val}(e, \operatorname{if} x \operatorname{then} y \operatorname{else} z) \mathrm{val}(e, x) T (「真」を表す定数)のとき  \mathrm{val}(e, y) \mathrm{val}(e, x) T ではないとき  \mathrm{val}(e, z) となります。

ラムダ計算と無限ラムダ多項式(12)

有限の場合から無限の場合へ

有限の場合(任意の再帰的定義の関数の呼び出しの深さが有限の回数  n 以内の場合)、関数定義、無名関数、クロージャーを取り除くことができて、式の値は以前定義した「一般マグマの多項式」のような形にすることができます。

これの無限の場合(任意の  n の場合)について考えることがこの記事の目的となります。

ラムダ計算と無限ラムダ多項式(11)

有限の場合のポインター除去

再帰的定義の関数が有限の回数で終わるとき、再帰的定義がない形に書き直すことができます。C# で階乗を求める関数で考えてみます。

        private int fac(int n)
        {
            if (n == 1)
            {
                return 1;
            }
            else
            {
                return n * fac(n - 1);
            }
        }

関数呼び出しの深さが3回以内の場合、以下のように書き直すことができます。

        private int fac1(int n)
        {
            return 1;
        }
        private int fac2(int n)
        {
            if (n == 1)
            {
                return 1;
            }
            else
            {
                return n * 1;
            }
        }
        private int fac3(int n)
        {
            if (n == 1)
            {
                return 1;
            }
            else
            {
                if (n - 1 == 1)
                {
                    return n * 1;
                }
                else
                {
                    return n * (n - 1) * 1;
                }
            }
        }

このように書き直すと、再帰的定義を含まない形にすることができます。また同様に、関数の呼び出しを含まない形にすることができます。

こうすることによって以下のような有限個のステートメントからなるブロックに書き直すことができます。これに以下のように上から順に番号を付けます。これを時刻と呼ぶことにします。

            {
                statement(); // 1
                statement(); // 2
                {
                    statement(); // 3
                    statement(); // 4
                    if (cond())
                    {
                        statement(); // 5
                        statement(); // 6
                    }
                    else
                    {
                        statement(); // 7
                        statement(); // 8
                    }
                    statement(); // 9
                    statement(); // 10
                }
                statement(); // 11
                statement(); // 12
            }

前回の集合  A は時刻ごとに存在するとします。これを  A_t とします。 A_t の元は  \langle (v \mapsto x) , e \rangle という形とします。 v は変数、 x は式、 e \in A_t は環境です。時刻  t に次の時刻  t' があるとします。

let  v =  x のとき

 A_{t'} = A_t \cup \{ \langle (v \mapsto x) , e \rangle \} とします。def のときも同様です。

ラムダ計算と無限ラムダ多項式(10)

クロージャーの定義

wikipedia:クロージャ などを見ると(できることは同じようですが)クロージャーの定義にはいろいろあるようです。ここでも定義しておいた方が良いと思われるので、ここで使うものを定義しておきます。

式は以下のものから構成されます。

変数と式を対応させたものを環境と呼ぶことにします。環境はポインターを使わないとうまく定義できないので、ポインターを使って定義します。ポインターを使わない方法は後で検討します。ポインターHaskell のリストのような形で表します。

集合  P によってインデックスが付けられた集合  A = \{a_p\}_{p \in P} A の元は変数  v、式  xポインター( P の元または  \botポインターと呼ぶことにします)  p' の組  a_p = \langle v, x, p' \rangle であるものを考えます。 a_pポインター  p が参照する内容と呼ぶことにします。 v x の組を  (v \mapsto x) と書いて、 a_p = \langle (v \mapsto x), p' \rangle と書くことにします。これをリストのような形で書きます。 p' = \bot のとき  a_p = (v \mapsto x) : \bot p' \ne \bot のとき  a_p = (v \mapsto x) : a_{p'} と書くことにします。

環境を(ポインターとして)

  •  \bot または
  •  (v \mapsto x) : e ( v は変数、 x は式、 e は環境)

帰納的に定義します。

ある環境  e で変数  v に対応する式  e(v) を以下のように帰納的に定義します。

  •  e = \bot のとき  e(v) = \bot
  •  e = (v \mapsto x) : e' のとき  e(v) = x
  •  e = (v' \mapsto x) : e' \ (v \ne v') のとき  e(v) = e'(v)

ある環境  e に対して、変数  v に対応する式を  x で置き換えた環境  e(v \gets x) を以下のように帰納的に定義します。

  •  e = \bot のとき  e(v \gets x) = \bot
  •  e = (v \mapsto y) : e' のとき  e(v \gets x) = (v' \mapsto x) : e' (ポインターの参照する内容が置き換えられます)
  •  e = (v' \mapsto y) : e' \ (v \ne v') のとき  e(v \gets x) = (v' \mapsto y) : e'(v \gets x)

ポインターの参照する内容が置き換えられるというのは、 eポインター  p と見たときに、上に書いたポインターの集合  P でインデックスが付けられた  A の元  a_p を同じ  p に対応する別の  a'_p で置き換えた  A' = A - \{a_p\} + \{a'_p\} を、これ以降(後述のブロックでこれより後の部分)は  A の代わりに使うということを表します。

ある環境  e での式  x の値(数値または真理値)  \mathrm{val}(e, x) を考えます。

定数  a の値

定数  a の値は  \mathrm{val}(e, a) = a です。

変数  v の値

変数  v の値は  \mathrm{val}(e, v) = e(v) です。

算術演算子、比較演算子を使った  x \triangle y \triangle x という式の値

 x \triangle y の値は  \mathrm{val}(e, x \triangle y) = \mathrm{val}(e, x) \nabla \mathrm{val}(e, y) となります。 \nabla \triangle に対応する数値の演算で  \mathrm{val}(e, \triangle x) または  \mathrm{val}(e, \triangle y) \bot のときは値は  \bot とします。 \triangle x の値は  \mathrm{val}(e, \triangle x) = \nabla \mathrm{val}(e, x) となります。 \nabla \triangle に対応する数値の演算で  \mathrm{val}(e, \triangle x) \bot のときは値は  \bot とします。

let  v =  x in  y の値

 v は変数、 x y は式です。
 \mathrm{val}(e, \operatorname{let} v = x \operatorname{in} y) = \mathrm{val}((v \mapsto x) : e, y)
です。

subst  v =  x in  y の値

 v は変数、 x y は式です。
 \mathrm{val}(e, \operatorname{subst} v = x \operatorname{in} y) = \mathrm{val}(e(v \gets x), y)
です。 y は置き換えられた環境で評価されます。

def  f (  v_1, v_2, \cdots , v_n ) =  x in  y

関数定義を表します。 f は変数(関数名)、 v_1, v_2, \cdots , v_n は変数、 x y は式です。
 \mathrm{val}(e, \operatorname{def} f (v_1, v_2, \cdots , v_n) = x \operatorname{in} y) = \mathrm{val}( e', y)
です。

ここで  e' = (f \mapsto c( (v_1, v_2, \cdots , v_n) . x, e')) : e です。再帰的定義のために  f の定義が追加されています。 c( (v_1, v_2, \cdots , v_n) . x, e')クロージャーです。
 \mathrm{val}(e'', c( (v_1, v_2, \cdots , v_n) . x, e')(x_1, x_2, \cdots , x_n)) \\
= \mathrm{val}( (v_1 \mapsto x_1) : (v_2 \mapsto x_2) : \cdots : (v_n \mapsto x_n) : e', x)
となります。このときクロージャーが持っている環境  e' を使います。

fun (  v_1, v_2, \cdots , v_n ) =  x

無名関数を表します。 v_1, v_2, \cdots , v_n は変数、 x は式です。
 \mathrm{val}(e, \operatorname{fun} (v_1, v_2, \cdots , v_n) = x) = \mathrm{val}(e, c( (v_1, v_2, \cdots , v_n) . x, e))
です。

if  x then  y else  z

 x y z は式です。 \mathrm{val}(e, \operatorname{if} x \operatorname{then} y \operatorname{else} z) \mathrm{val}(e, x) が真のとき  \mathrm{val}(e, y) \mathrm{val}(e, x) が偽のとき  \mathrm{val}(e, z) となります。

ブロックの記法

  • let  v =  x in  y
  • subst  v =  x in  y
  • def  f (  v_1, v_2, \cdots , v_n ) =  x in  y

が連続するとき

let v = x;
subst v' = x'
def f ( w1, w2, w_3 ) = x''
y

のようにブロックとして表すことができます。このブログの例のコードでは、C# のコードとして書きやすいためブロックで表しています。

変数の変更が不可能なクロージャーとポインターの調査

サーバーのプログラムを変数の変更が可能なクロージャーを使って書くと、サーバーのプログラムを更新することはブラウザーのプログラムを更新することと同様にできるという例を見てきました。これによってブラウザーのプログラムが複数あるときにも「同じことを二度書かない」ことを実現することができます。

これを変数の変更が不可能なクロージャーで書くとどうなるかということを考えていきます。これは、サーバーのプログラムを変更するとそれを使っているプログラムが使えなくなるので、それを避けることができるのかどうか調べるためです。

サーバーのプログラムが無限に続くものだ考えると、それを変数の変更が不可能なクロージャーで書くことはできないということになりますが、考え方を変えることによってできるのかどうかを調べてみます。サーバーのプログラムが有限の回数で終わるとすれば、変数の変更が不可能なクロージャーで書くことはできます。そのプログラムの無限に続く「バージョンアップ」のような考え方でできるかどうか調べてみます。

そのためにポインターを使っていたところをポインターを使わない形に書き直すことはできるかどうかを考えていきます。

ラムダ計算と無限ラムダ多項式(9)

Calc クラスの IterateServer です。

        public static Func<string> IterateServer = () =>
            new ExpBlock {
                Exp.Define("take", new NameList{"count", "gen"},
                    Exp.If(
                        Exp.Func("==", Exp.Name("count"), Exp.Number(0)),
                        Exp.Nil(),
                        new ExpBlock
                        {
                            Exp.Let(new NameList{"next", "getCurrent", "setCurrent"}, Exp.Name("gen")),
                            Exp.If(Exp.Func("next"),
                                Exp.Cons(
                                    Exp.Func("getCurrent"),
                                    Exp.Func("take", Exp.Func("-", Exp.Name("count"), Exp.Number(1)), Exp.Name("gen"))
                                    ),
                                Exp.Nil())
                        }
                    )
                ),
                Exp.Define("zipn", new NameList{"f", "n", "list"},
                    Exp.If(
                        Exp.Func("null", Exp.Name("list")),
                        Exp.Nil(),
                        Exp.Cons(
                            Exp.Func(Exp.Name("f"), Exp.Func("first", Exp.Name("list")), Exp.Name("n")),
                            Exp.Func("zipn", Exp.Name("f"), Exp.Func("+", Exp.Name("n"), Exp.Number(1)), Exp.Func("tail", Exp.Name("list")))
                        )
                    )
                ),
                Exp.Define("nlist", new NameList{"list"},
                    Exp.Func("zipn",
                        Exp.Fun(new NameList{"n", "e"},
                            Exp.Func("*",
                                Exp.Name("n"),
                                Exp.Func("^",
                                    Exp.Number(10),
                                    Exp.Func("u-", Exp.Name("e"))
                                )
                            )
                        ),
                        Exp.Number(0),
                        Exp.Name("list"))),
                Exp.Define("foldr", new NameList{"f", "n", "list"},
                    Exp.If(
                        Exp.Func("null", Exp.Name("list")),
                        Exp.Name("n"),
                        Exp.Func(Exp.Name("f"),
                            Exp.Func("first", Exp.Name("list")),
                            Exp.Func("foldr", Exp.Name("f"), Exp.Name("n"), Exp.Func("tail", Exp.Name("list")))
                        )
                    )
                ),
                Exp.Define("number", new NameList{"list"},
                    Exp.Func("foldr", Exp.Fun(new NameList{"x", "y"}, Exp.Func("+", Exp.Name("x"), Exp.Name("y"))), Exp.Number(0), Exp.Func("nlist", Exp.Name("list")))),
                Exp.Define("unfoldl", new NameList{"next", "init"}, new ExpBlock {
                    Exp.Let("src", Exp.Name("init")),
                    Exp.Let("dst", Exp.Number(0)),
                    Exp.Define("next_", new NameList { }, new ExpBlock {
                        Exp.Subst(new NameList{ "dst", "src"}, Exp.Func("next", Exp.Name("src"))),
                        Exp.Bool(true),
                    }),
                    Exp.Define("getCurrent", new NameList { }, new ExpBlock {
                        Exp.Name("dst"),
                    }),
                    Exp.Define("setCurrent", new NameList { "val" }, new ExpBlock
                    {
                    }),
                    new ExpList { Exp.Name("next_"), Exp.Name("getCurrent"), Exp.Name("setCurrent") }
                }),
                Exp.Let("numbers", new ExpList { Exp.Number(0), Exp.Number(3), Exp.Number(0) }),
                Exp.Func("number",
                    Exp.Func("take", Exp.Name("count"), 
                        Exp.Func("unfoldl", Exp.Name("GetNextDecimalDigitAndNumbers"), Exp.Name("numbers")))
                )
            }.ExpEval(new Env("count", Exp.Number(count), "GetNextDecimalDigitAndNumbers", Exp.FuncExpExp(GetNextDecimalDigitAndNumbers))).Print();

上記の文字列版です。

        public static Func<string> IterateServer = () =>
            Exp.From("{" +
                "def take (count, gen) = " +
                    "if count == 0 then " +
                        "nil " +
                    "else { " +
                        "let (next, getCurrent, setCurrent) = gen; " +
                        "if next() then " +
                            "cons(getCurrent(), take(count - 1, gen)) " +
                        "else " +
                            "nil " +
                    "}; " +
                "def zipn (f, n, list) = " +
                    "if null(list) then " +
                        "nil " +
                    "else " +
                        "cons(f(first(list), n), zipn(f, n + 1, tail(list))); " +
                "def nlist (list) = " +
                    "zipn(fun(n, e) = n * 10^(-e), 0, list);" +
                "def foldr (f, n, list) = " +
                    "if null(list) then " +
                        "n " +
                    "else " +
                        "f(first(list), foldr(f, n, tail(list))); " +
                "def number (list) = " +
                    "foldr(fun (x, y) = x + y, 0, nlist(list)); " +
                "def unfoldl (next, init) = {" +
                    "let src = init; " +
                    "let dst = 0; " +
                    "def next_() = {" +
                        "subst (dst, src) = next(src); " +
                        "true" +
                    "};" +
                    "def getCurrent() = dst; " +
                    "def setCurrent(val) = {};" +
                    "(next_, getCurrent, setCurrent)" +
                "}; " +
                "let numbers = (0, 3, 0);" +
                "number(take(count, unfoldl(GetNextDecimalDigitAndNumbers, numbers))) " +
            "}").ExpEval(new Env("count", Exp.Number(count), "GetNextDecimalDigitAndNumbers", Exp.FuncExpExp(GetNextDecimalDigitAndNumbers))).Print();

BigNum も変換したいと思うのですがたいへんなので、変換するツールを作ろうと思います。C# のコードを読み込むツールが本に載っていたのですが、あまり詳しく書かれていなかったので自力で作ろうと思います。BigNum (とそれに必要なコード)が変換できれば良いですし、関数プログラミングについて調べることが目的なのでその方が良いのではないかと思います。