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

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

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

Calc クラスの IterateGenerator と RepeatServer です。

        public static Func<string> IterateGenerator = () =>
            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.Func("number", 
                    Exp.Func("take", Exp.Name("count"), Exp.Func("GenerateDecimal"))
                )
            }.ExpEval(new Env("count", Exp.Number(count), "GenerateDecimal", Exp.FuncExp(GenerateDecimal))).Print();

        public static Func<string> RepeatServer = () =>
            new ExpBlock {
                Exp.Let("numbers", new ExpList { Exp.Number(0), Exp.Number(3), Exp.Number(0) }),
                Exp.Define("repeat", new NameList{"number","e","count" },
                    Exp.If(Exp.From("e < count"), new ExpBlock{
                        Exp.Let(new NameList{"dd", "ns"}, Exp.Func("GetNextDecimalDigitAndNumbers", Exp.Name("numbers"))),
                        Exp.Subst("numbers", Exp.Name("ns")),
                        Exp.Func("repeat",
                            Exp.Func("+",
                                Exp.Name("number"),
                                Exp.Func("*",
                                    Exp.Name("dd"),
                                    Exp.Func("^", Exp.Number(10), Exp.Func("u-", Exp.Name("e")))
                                    )
                                ),
                            Exp.Func("+", Exp.Name("e"), Exp.Number(1)),
                            Exp.Name("count")),
                        },
                        Exp.Name("number"))
                ),
                Exp.Func("repeat", new ExpList{Exp.Number(0), Exp.Number(0) , Exp.Name("count") })
            }.ExpEval(new Env("count", Exp.Number(count), "GetNextDecimalDigitAndNumbers", Exp.FuncExpExp(GetNextDecimalDigitAndNumbers))).Print();

上記の文字列版です。

        public static Func<string> IterateGenerator = () =>
            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)); " +
                "number(take(count, GenerateDecimal())) " +
            "}").ExpEval(new Env("count", Exp.Number(count), "GenerateDecimal", Exp.FuncExp(GenerateDecimal))).Print();
        public static Func<string> RepeatServer = () =>
            Exp.From("{" +
                "let numbers = (0, 3, 0);" +
                "def repeat(number, e, count) = " +
                "if e < count then { " +
                    "let (dd, ns) = GetNextDecimalDigitAndNumbers(numbers);" +
                    "subst numbers = ns;" +
                    "repeat(number + dd * 10^(-e), e + 1, count) " +
                "} else " +
                    "number;" +
                "repeat(0, 0, count)" +
            "}").ExpEval(new Env("count", Exp.Number(count), "GetNextDecimalDigitAndNumbers", Exp.FuncExpExp(GetNextDecimalDigitAndNumbers))).Print();

無名関数の構文を追加しました。

Exp.Fun ( new NameList { 変数名, 変数名, … , 変数名 }, 式 )

無名関数を表します。

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

Calc クラスの RepeatGenerator と、それに必要な関数の文字列版です。

        private static Func<Exp, Exp> GetNextDecimalDigitAndNumbers = (Exp numbers) =>
            Exp.From("{" +
                "let (number, square_difference, scale) = numbers;" +
                "def maxdd (dd) =" +
                    "if dd >= 0 then { " +
                        "let zd = dd * 10^(-scale); " +
                        "let number_sq_diff = number * zd * 2 + zd * zd; " +
                        "if number_sq_diff <= square_difference then " +
                            "(dd, (number + zd, square_difference - number_sq_diff, scale + 1)) " +
                        "else " +
                            "maxdd(dd - 1) " +
                    "} else " +
                        "(0, (0, 0, 0)); " +
                "maxdd(9)" +
                "}").ExpEval(new Env("numbers", numbers));
        public static Func<Exp> GenerateDecimal = () =>
            Exp.From("{" +
                "let nums = (0, 3, 0); " +
                "let current_digit = 0; " +
                "def next() = {" +
                    "subst (current_digit, nums) = GetNextDecimalDigitAndNumbers(nums); " +
                    "true" +
                "};" +
                "def getCurrent() = current_digit; " +
                "def setCurrent(val) = {};" +
                "(next, getCurrent, setCurrent)" +
            "}").ExpEval(new Env("GetNextDecimalDigitAndNumbers", Exp.FuncExpExp(GetNextDecimalDigitAndNumbers)));
        public static Func<string> RepeatGenerator = () =>
            Exp.From("{" +
                "let generator = GenerateDecimal();" +
                "def GetNextDecimalDigit() = { generator.next(); generator.getCurrent() };" +
                "def repeat(number, e, count) = " +
                "if e < count " +
                        "then repeat(number + GetNextDecimalDigit() * 10^(-e), e + 1, count) " +
                        "else number;" +
                "repeat(0, 0, count)"+
            "}").ExpEval(new Env("count", Exp.Number(count), "GenerateDecimal", Exp.FuncExp(GenerateDecimal))).Print();

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

Calc クラスの RepeatGenerator と、それに必要な関数を書き直しました。

        private static Func<Exp, Exp> GetNextDecimalDigitAndNumbers = (Exp numbers) =>
        {
            Exp n0 = Exp.Number(0);
            Exp n1 = Exp.Number(1);
            Exp n2 = Exp.Number(2);
            Exp n9 = Exp.Number(9);
            Exp n10 = Exp.Number(10);
            return new ExpBlock {
                Exp.Let(new NameList{ "number", "square_difference", "scale"}, numbers),
                Exp.Define("maxdd", new NameList{"dd"}, Exp.If(
                    Exp.Func(">=", Exp.Name("dd"), n0),
                    new ExpBlock
                    {
                        Exp.Let("zd", Exp.Func("*", Exp.Name("dd"), Exp.Func("^", n10, Exp.Func("u-", Exp.Name("scale"))))),
                        Exp.Let("number_sq_diff", Exp.Func("+",
                            Exp.Func("*", Exp.Func("*", Exp.Name("number"), Exp.Name("zd")), n2),
                            Exp.Func("*", Exp.Name("zd"), Exp.Name("zd"))
                        )),
                        Exp.If(
                            Exp.Func("<=", Exp.Name("number_sq_diff"), Exp.Name("square_difference")),
                            Exp.List(Exp.Name("dd"), new ExpList
                            {
                                Exp.Func("+", Exp.Name("number"), Exp.Name("zd")),
                                Exp.Func("-", Exp.Name("square_difference"), Exp.Name("number_sq_diff")),
                                Exp.Func("+", Exp.Name("scale"), n1)
                            }),
                            Exp.Func("maxdd", Exp.Func("-", Exp.Name("dd"), n1))
                            )
                    },
                    Exp.List(n0, Exp.List(n0, n0, n0))
                    )),
                Exp.Func("maxdd", n9)
            }.ExpEval(new Env());
        };
        public static Func<Exp> GenerateDecimal = () =>
            new ExpBlock {
                Exp.Let("nums", new ExpList { Exp.Number(0), Exp.Number(3), Exp.Number(0) }),
                Exp.Let("current_digit", Exp.Number(0)),
                Exp.Define("next", new NameList { }, new ExpBlock {
                    Exp.Subst(new NameList{ "current_digit", "nums"}, Exp.Func("GetNextDecimalDigitAndNumbers", Exp.Name("nums"))),
                    Exp.Bool(true),
                }),
                Exp.Define("getCurrent", new NameList { }, new ExpBlock {
                    Exp.Name("current_digit"),
                }),
                Exp.Define("setCurrent", new NameList { "val" }, new ExpBlock
                {
                }),
                new ExpList { Exp.Name("next"), Exp.Name("getCurrent"), Exp.Name("setCurrent") }
            }.ExpEval(new Env("GetNextDecimalDigitAndNumbers", Exp.FuncExpExp(GetNextDecimalDigitAndNumbers)));
        public static Func<string> RepeatGenerator = () =>
            new ExpBlock {
                Exp.Let("generator", Exp.Func("GenerateDecimal")),
                Exp.Define("GetNextDecimalDigit", new NameList{}, new ExpBlock{
                    Exp.Func(Exp.Func(".", Exp.Name("generator"),Exp.Name("next")), new ExpList{}),
                    Exp.Func(Exp.Func(".", Exp.Name("generator"),Exp.Name("getCurrent")), new ExpList{}),
                }),
                Exp.Define("repeat", new NameList{"number","e","count" },
                    Exp.If(Exp.From("e < count"),
                        Exp.Func("repeat", 
                            Exp.Func("+",
                                Exp.Name("number"),
                                Exp.Func("*",
                                    Exp.Func("GetNextDecimalDigit"),
                                    Exp.Func("^", Exp.Number(10), Exp.Func("u-", Exp.Name("e")))
                                    )
                                ),
                            Exp.Func("+", Exp.Name("e"), Exp.Number(1)),
                            Exp.Name("count")),
                        Exp.Name("number"))
                ),
                Exp.Func("repeat", new ExpList{Exp.Number(0), Exp.Number(0) , Exp.Name("count") })
            }.ExpEval(new Env("count", Exp.Number(count), "GenerateDecimal", Exp.FuncExp(GenerateDecimal))).Print();

以下の構文を追加しました。

( 式, 式, … , 式 )

式のリストを表します。

Exp.Let ( 変数名, 変数名, … , 変数名 ) = 式のリスト

複数の変数に式のリストの各要素の値を代入します。文字列バージョンは以下のようになります。

let ( 変数名, 変数名, … , 変数名 ) = 式のリスト

Exp.Subst ( 変数名, 変数名, … , 変数名 ) = 式のリスト

複数のすでに存在する変数に式のリストの各要素の値を代入します。文字列バージョンは以下のようになります。

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

Calc クラスの RepeatGenerator をラムダ式のようなもので書き直したものの動作するバージョンです。

        public static Func<string> RepeatGenerator = () =>
            new ExpBlock {
                Exp.Let("generator", Exp.From("GenerateDecimal()")),
                Exp.Define("GetNextDecimalDigit", new NameList{}, new ExpBlock{
                    Exp.Func(Exp.Func(".", Exp.Name("generator"),Exp.Name("next")), new ExpList{}),
                    Exp.Func(Exp.Func(".", Exp.Name("generator"),Exp.Name("getCurrent")), new ExpList{}),
                }),
                Exp.Define("repeat", new NameList{"number","e","count" },
                    Exp.If(Exp.From("e < count"),
                        Exp.From("repeat(number + GetNextDecimalDigit() * 10^(-e), e + 1, count)"),
                        Exp.From("number"))
                ),
                Exp.Func("repeat", new ExpList{Exp.Number(0), Exp.Number(0) , Exp.Name("count") })
            }.ExpEval(new Env("count", Exp.Number(count), "GenerateDecimal", Exp.FuncIterator(GenerateDecimal))).Print();

文字列でも書けるようになっています。以下のようになります。

        public static Func<string> RepeatGenerator_ = () =>
            Exp.From("{ let generator = GenerateDecimal();" +
                "def GetNextDecimalDigit() = { generator.next(); generator.getCurrent() };" +
                "def repeat(number, e, count) = " +
                "if e < count " +
                        "then repeat(number + GetNextDecimalDigit() * 10^(-e), e + 1, count) " +
                        "else number;" +
                "repeat(0, 0, count) }"
            ).ExpEval(new Env("count", Exp.Number(count), "GenerateDecimal", Exp.FuncIterator(GenerateDecimal))).Print();

Exp.From ( 文字列 ) という形式で書きます。仕様は以下のようになります。

数値、真理値、名前、または演算子

数値、真理値、名前、または演算子を書くことができます。

関数名 ( 引数, 引数, … , 引数 )

関数を実行します。関数名は名前または関数を表す式です。

イテレーター . 名前

イテレーターから next、getCurrent、setCurrent を取得します。

let 変数名 = 式

変数に式の値を代入します。

subst 変数名 = 式

すでに存在する変数に式の値を代入します。

If 式(条件) then 式(真のとき) else 式(偽のとき)

「式(条件)」が真のとき「式(真のとき)」、「式(偽のとき)」が評価されます。

def 関数名 ( 変数名, 変数名, … , 変数名) = 式

関数を定義します。

{ 式; 式; … ; 式 }

式を順に実行します。

単項演算子

符号を変える ( - )

二項演算子

足し算 ( + )
引き算 ( - )
掛け算 ( * )
正の整数の割り算の商 ( / )

x と y が正の整数のとき割り算の商を求めることができます。

正の整数の割り算の余り ( % )

x と y が正の整数のとき割り算の余りを求めることができます。

累乗 ( ^ )

正の整数の正の整数乗(結果は整数の範囲)、または 10 の整数乗を求めることができます。

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

C# で以下のような式を書くことができるようにします。

Exp.Number ( 数値 )

数値を表します。「数値」は整数、文字列、または BigNum です。

Exp.Bool ( 真理値 )

真理値を表します。

Exp.String ( 文字列 )

文字列を表します。

Exp.Name ( 文字列 )

変数名などの名前、または演算子を表します。

Exp.Func ( 関数名, 引数リスト ) または Exp.Func ( 関数名, 引数, 引数, … , 引数 )

関数を実行します。関数名は名前または関数を表す式です。

Exp.Let ( 変数名, 式 )

変数に式の値を代入します。

Exp.Subst ( 変数名, 式 )

すでに存在する変数に式の値を代入します。

Exp.If ( 式(条件), 式(真のとき), 式(偽のとき) )

「式(条件)」が真のとき「式(真のとき)」、「式(偽のとき)」が評価されます。

Exp.Define ( 関数名, 変数名リスト, 式 )

関数を定義します。

Exp.From ( 文字列 )

式を表す文字列で式を作ります。

new ExpBlock { 式, 式, … , 式 }

式を順の実行します。

new ExpList { 式, 式, … , 式 }

式のリストを表します。

new NameList { 式, 式, … , 式 }

名前のリストを表します。

ExpBlock、ExpList、NameList は以下のようなクラスを継承したものです。こうすると上記のような書き方ができます。

    internal class GenList<T> : IEnumerable<T>
    {
        private List<T> list;
        public GenList()
        {
            list = new List<T>();
        }
        public IEnumerator<T> GetEnumerator()
        {
            return list.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        public void Add(T x)
        {
            list.Add(x);
        }
        public IEnumerable<(T, U)> Zip<U>(GenList<U> list2)
        {
            for (int i = 0; i < list.Count && i < list2.list.Count; i++)
            {
                yield return (list[i], list2.list[i]);
            }
        }
    }

足し算
        public static Func<string> Test = () =>
            new ExpBlock {
                Exp.Let("x", Exp.Number("123.456")),
                Exp.Let("y", Exp.Number("12.34")),
                Exp.Let("z", Exp.Func("+", Exp.Name("x"), Exp.Name("y"))),
                Exp.Name("z") 
            }.ExpEval(new Env()).Print();

結果: 135.796

引き算
        public static Func<string> Test = () =>
            new ExpBlock {
                Exp.Let("x", Exp.Number("123.456")),
                Exp.Let("y", Exp.Number("12.34")),
                Exp.Let("z", Exp.Func("-", Exp.Name("x"), Exp.Name("y"))),
                Exp.Name("z") 
            }.ExpEval(new Env()).Print();

結果: 111.116

        public static Func<string> Test = () =>
            new ExpBlock {
                Exp.Let("x", Exp.Number("123.456")),
                Exp.Let("y", Exp.Number("12.34")),
                Exp.Let("z", Exp.Func("-", Exp.Name("y"), Exp.Name("x"))),
                Exp.Name("z") 
            }.ExpEval(new Env()).Print();

結果: -111.116

掛け算
        public static Func<string> Test = () =>
            new ExpBlock {
                Exp.Let("x", Exp.Number("123.456")),
                Exp.Let("y", Exp.Number("12.34")),
                Exp.Let("z", Exp.Func("*", Exp.Name("x"), Exp.Name("y"))),
                Exp.Name("z") 
            }.ExpEval(new Env()).Print();

結果: 1523.44704

整数の割り算の商
        public static Func<string> Test = () =>
            new ExpBlock {
                Exp.Let("x", Exp.Number("1234")),
                Exp.Let("y", Exp.Number("12")),
                Exp.Let("z", Exp.Func("/", Exp.Name("x"), Exp.Name("y"))),
                Exp.Name("z") 
            }.ExpEval(new Env()).Print();

結果: 102

整数の割り算の余り
        public static Func<string> Test = () =>
            new ExpBlock {
                Exp.Let("x", Exp.Number("1234")),
                Exp.Let("y", Exp.Number("12")),
                Exp.Let("z", Exp.Func("%", Exp.Name("x"), Exp.Name("y"))),
                Exp.Name("z") 
            }.ExpEval(new Env()).Print();

結果: 10

整数の整数乗
        public static Func<string> Test = () =>
            new ExpBlock {
                Exp.Let("x", Exp.Number("12")),
                Exp.Let("y", Exp.Number("3")),
                Exp.Let("z", Exp.Func("^", Exp.Name("x"), Exp.Name("y"))),
                Exp.Name("z") 
            }.ExpEval(new Env()).Print();

結果: 1728

10 の整数乗
        public static Func<string> Test = () =>
            new ExpBlock {
                Exp.Let("x", Exp.Number("10")),
                Exp.Let("y", Exp.Number("3")),
                Exp.Let("z", Exp.Func("^", Exp.Name("x"), Exp.Name("y"))),
                Exp.Name("z") 
            }.ExpEval(new Env()).Print();

結果: 1000

        public static Func<string> Test = () =>
            new ExpBlock {
                Exp.Let("x", Exp.Number("10")),
                Exp.Let("y", Exp.Number("-3")),
                Exp.Let("z", Exp.Func("^", Exp.Name("x"), Exp.Name("y"))),
                Exp.Name("z") 
            }.ExpEval(new Env()).Print();

結果: 0.001

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

以下は Calc クラスの RepeatGenerator をラムダ式のようなもので書き直したもののイメージです。まだ動作するものではありません。C# で書いてもエラーにはならない形式になっています。単純な式のときは文字列で書けるようになっています。

        public static Func<string> RepeatGenerator = () =>
            new ExpBlock {
                Exp.Let("nums", Exp.List(Exp.Number(0), Exp.Number(3), Exp.Number(0))),
                Exp.Let("generator", Exp.Func("GenerateDecimal", new ExpList{})),
                Exp.Define("GetNextDecimalDigit", new NameList{}, new ExpBlock{
                    Exp.From("next(generator)"),
                    Exp.From("getCurrent(generator)"),
                }),
                Exp.Define("repeat", new NameList{"number","e","count" }, new ExpBlock{
                    Exp.If(Exp.From("e < count"),
                        Exp.From("repeat(number + lshift(GetNextDecimalDigit(), -e), e + 1, count)"),
                        Exp.From("number"))
                }),
                Exp.Let("result_number", Exp.From("repeat(0, 0, count)")),
                Exp.From("Print(result_number)")
            }.Eval(null).ToString();

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

LISP と LOGO の本がいくつか見つかりましたが、LISP の本は古いのでクロージャーが使えるのかどうかよくわかりません。クロージャーの変数を変更できないようにしたらどうなるかを調べることが目的なので、この意味では LOGO を使って調べた方が良いかもしれません。最近の LISP ではクロージャーが使えるようなのでどちらでも良いかもしれません。

Calc クラスの RepeatGenerator をラムダ式で書き直しました。これが式だけのラムダ式で書くことができれば、式木(expression tree)というものに変換することができて、式の構造を扱うことができるらしいのですが、式だけのラムダ式で書くことは難しいのでこの方向はいったんやめます。

ラムダ式のようなものを自力で実装することにします。後でこれを LISP か LOGO のように書き直すことにします。

Calc クラスの RepeatGenerator をラムダ式で書き直したものは以下のようになります。

        public static Func<string> RepeatGenerator = () =>
          {
              BigNum number = new BigNum();
              BigNum square_difference = new BigNum(3);
              (BigNum, BigNum, int) nums = (number, square_difference, 0);
              (Func<bool> next, Func<int> getCurrent, Action<int> setCurrent) generator = GenerateDecimal();
              Func<int> GetNextDecimalDigit = () =>
                {
                    generator.next();
                    return generator.getCurrent();
                };
              Func<BigNum, int, int, BigNum> repeat = null;
              repeat = (BigNum number, int e, int count) =>
                {
                    if (e < count)
                    {
                        return repeat(number + new BigNum(GetNextDecimalDigit(), -e), e + 1, count);
                    }
                    else
                    {
                        return number;
                    }
                };
              BigNum result_number = repeat(new BigNum(), 0, count);
              return result_number.Print();
          };