C# の例(11)
変数が変更不可能なプログラミング言語への移植のために変数を変更しないように書き換えていきます。NumbersGenerator クラスと NumbersServer クラスは「仮想サーバー」であるため変更するのは難しいのでそのほかのところから変更していきます。
Calc クラスの変数とコンストラクターは以下のようにます。generator と generator_server の参照しているものは変更可能ですが、まずはそれで良いことにします。
private readonly NumbersGenerator generator; private readonly NumbersServer generator_server; private const int count = 21; public Calc() { generator = new NumbersGenerator(); generator_server = new NumbersServer(); }
以下のメソッドも変数を変更しないようにします(generator と generator_server の参照しているものは変更可能)。
public string RepeatGenerator() { LongDecimal repeat(LongDecimal number, int e, int count) { if (e < count) { return repeat(number + new LongDecimal(generator.GetNextDecimalDigit()).Shift(e), e + 1, count); } else { return number; } } LongDecimal result_number = repeat(new LongDecimal(), 0, count); return result_number.Print(); } public string RepeatServer() { LongDecimal repeat(LongDecimal number, int e, int count) { if (e < count) { Numbers numbers = generator_server.Numbers; Tuple<int, Numbers> d_ns = numbers.GetNextDecimalDigitAndNumbers(); int dd = d_ns.Item1; generator_server.Numbers = d_ns.Item2; return repeat(number + new LongDecimal(dd).Shift(e), e + 1, count); } else { return number; } } LongDecimal result_number = repeat(new LongDecimal(), 0, count); return result_number.Print(); } public string IterateGenerator() { return new LongDecimal(generator.GenerateDecimal().Take(count)).Print(); } public string IterateServer() { LongDecimal number = new LongDecimal(); LongDecimal square_difference = new LongDecimal(3); Numbers init_numbers = new Numbers(number, square_difference, 0); return new LongDecimal(UnfoldL(NextDigitAndNumbers, init_numbers).Take(count)).Print(); }
UnfoldL 関数も以下のように変数を変更しないようにします。foreach で使っている変数 e は変更しているわけではないので、これは使っても良いことにします。
private IEnumerable<U> UnfoldL<T, U>(Func<T, Tuple<U, T>> next, T init) { Tuple<U, T> cpl = next(init); // 終了したとき next は null を返すとする if (cpl == null) { yield break; } yield return cpl.Item1; // ここは foreach を使わないと書けない foreach (U e in UnfoldL(next, cpl.Item2)) { yield return e; } }
さらにクロージャーを引数にすることができないプログラミング言語への移植のために引数にクロージャーを使わないようにします。しかし C# では型のチェックがあるのであまりうまくできません。クロージャーの代わりにあるオブジェクトを渡すようにします。
以下のクラスを作ります。
private abstract class UFunc { public abstract Tuple<int, Numbers> Apply(Numbers x); public class DNumbers { public class NextDigitAndNumbers : UFunc { public override Tuple<int, Numbers> Apply(Numbers x) { return NextDigitAndNumbers(x); } } } }
UnfoldL 関数の引数 next をこのクラスのインスタンスに変更します。この変更はあまり意味がないような感じもしますが、型のチェックをされるのでこれ以上うまく書けませんでした。型のチェックがなければ、この引数を定数にして、関数の外部でその定数と Apply を関連付ければ、引数を定数にすることができると考えられます。また、ここでは呼び出しているところが一つしかないのでこの引数はあまり意味がありません。
private IEnumerable<int> UnfoldL(UFunc next, Numbers init) { Tuple<int, Numbers> cpl = next.Apply(init); // 終了したとき next は null を返すとする if (cpl == null) { yield break; } yield return cpl.Item1; // ここは foreach を使わないと書けない foreach (int e in UnfoldL(next, cpl.Item2)) { yield return e; } }
IterateServer もこの関数を呼び出すように変更します。
public string IterateServer() { LongDecimal number = new LongDecimal(); LongDecimal square_difference = new LongDecimal(3); Numbers init_numbers = new Numbers(number, square_difference, 0); return new LongDecimal(UnfoldL(new UFunc.DNumbers.NextDigitAndNumbers(), init_numbers).Take(count)).Print(); }