今回は3の平方根を1桁ずつ計算するC#の例をJavaScriptで書き直したものを考えます。
JavaScript の例
以下のコードのクラス Numbers の関数 GetNextDecimalDigit() で1桁分の処理を行っています。LongDecimal は小数点以下の任意の桁数を計算することができるクラスとします。
class Numbers { constructor(number, square_difference, scale) { this.number = number; this.square_difference = square_difference; this.scale = scale; this.current_digit = 0; } get CurrentDigit() { return this.current_digit; } GetNextDecimalDigit() { var two = new LongDecimal(2); for (var dd = 9; dd >= 0; dd--) { var zd = new LongDecimal(dd, this.scale); var number_sq_diff = this.number.Product(zd).Product(two).Sum(zd.Product(zd)); if (number_sq_diff.LessOrEqual(this.square_difference)) { this.number = this.number.Add(dd, this.scale); this.square_difference = this.square_difference.Difference(number_sq_diff); this.scale++; this.current_digit = dd; return dd; } } return 0; } }
サーバーで無限に計算するバージョン
サーバーで無限に計算していてブラウザーで1桁ずつ取り出す場合を考えます。サーバーは無限に状態が変化していくもので、それをクラスで表しています。ここでは以下のようにJavaScriptのイテレーターを使ったクラスとしています。GenerateDecimal() がジェネレーターとなっています。
class NumbersGenerator { GetNextDecimalDigit() { return generator_gen.next().value; } *GenerateDecimal() { var number = new LongDecimal(); var square_difference = new LongDecimal(3); var nums = new Numbers(number, square_difference, 0); for (; ;) { yield nums.GetNextDecimalDigit(); } } }
JavaScriptの仕様は複雑なのでよくわからないのですが、クラスのメンバーにジェネレーターを保持することはできないようで、ここではクラスの外部に generator_gen を定義して、これをクラスの内部で参照するようになっています。クラスのインスタンスは1個しかないので、これでも動作します。generator はサーバーを表すクラスです。これらは以下のように定義されます。
var generator; generator = new NumbersGenerator(); var generator_gen; generator_gen = generator.GenerateDecimal();
サーバーから1回ずつ値を取り出す方法
以下の関数を呼び出すと、result_number に1桁ずつ結果が追加され、その結果の文字列を返します。
function NextDecimalByGenerator() { result_number.AddLast(generator.GetNextDecimalDigit()); return result_number.Print(); }
result_number は以下のように定義されます。
var result_number; result_number = new LongDecimal();
イテレーターを直接使う方法
以下の関数の generator.GenerateDecimal() はジェネレーターで Take はその最初の部分を取り出す、Haskell の take と同様の関数を定義したものとなります。この関数を呼び出すと、呼び出した回数分の結果の文字列を返します。
function NextDecimalByGeneratorTake() { var res = new LongDecimal(Take(scale, generator.GenerateDecimal())).Print(); scale++; return res; }
scale は以下のように定義されます。
var scale;
scale = 0;
ブラウザーで計算するバージョン
サーバーにデータがあってそれをブラウザーで取り出して1桁ずつ計算する場合を考えます。ここでも(サーバーから1回ずつ値を取り出す場合は)クラスで表しています。以下のようにJavaScriptのイテレーターを使ったクラスとしています。JavaScriptではイテレーターの実行中に値を返すことができます。GenerateDecimalServer() のイテレーターのループ内の current_numbers で受け取っています。
class NumbersServer { GetNumbers(numbers) { var current = generator_server_gen.next(numbers); return current.value; } SetNumbers(numbers) { this.current_numbers = numbers; } *GenerateDecimalServer() { var number = new LongDecimal(); var square_difference = new LongDecimal(3); var current_numbers = new Numbers(number, square_difference, 0); for (; ;) { current_numbers = yield current_numbers; } } }
ここでもクラスの外部に generator_server_genを定義して、これをクラスの内部で参照するようになっています。generator_server はサーバーを表すクラスです。これらは以下のように定義されます。
var generator_server; generator_server = new NumbersServer(); var generator_server_gen; generator_server_gen = generator_server.GenerateDecimalServer();
サーバーから1回ずつ値を取り出す方法
以下の関数を呼び出すと、result_number に1桁ずつ結果が追加され、その結果の文字列を返します。
var current_numbers = null; function NextDecimalByServer() { current_numbers = generator_server.GetNumbers(current_numbers); var dd = current_numbers.GetNextDecimalDigit(); generator_server.SetNumbers(current_numbers); result_number.AddLast(dd); return result_number.Print(); }
result_number は以下のように定義されます。
var result_number; result_number = new LongDecimal();
Iterate 関数を使う方法
イテレーターを直接使うとイテレーターに値を返す方法がないので「サーバーで無限に計算するバージョン」のような「イテレーターを直接使う方法」はできません。ここではサーバーのクラスを使わずに、Haskell の iterate と同様の関数 Iterate を定義して使います。Iterate は指定された初期値から指定された関数を繰り返して適用した無限の列を返すもので、Iterate で生成されるものがジェネレーターで生成されるものと同様のものとなります。Map_ は Haskell の map に相当するものを定義しています。JavaScript にも map はあるのですが、使い方がよくわからないので使っていません。この関数を呼び出すと、呼び出した回数分の結果の文字列を返します。
function NextDecimalByServerTake() { var number = new LongDecimal(); var square_difference = new LongDecimal(3); var init_numbers = new Numbers(number, square_difference, 0); init_numbers.GetNextDecimalDigit(); var res = new LongDecimal(Map_(numbers => numbers.CurrentDigit, Take(scale, Iterate(NextNumbers, init_numbers)))).Print(); scale++; return res; }
scale は以下のように定義されます。
var scale;
scale = 0;