java 8 - Nashorn

http://www.atmarkit.co.jp/fjava/special/javaonetokyo2012_02/03.html

ここに少しだけ書いてあった。結論から言うと今採用されているRhinoと互換性がありません。


まず以下のようにjavaとjsの通信は基本LC3と同じ仕組のようです。

  1. NashornのオブジェクトでなければJavaのオブジェクトとして扱う
  2. フィールドや配列へのアクセスはJavaと同様の方法を提供
  3. メソッドの引数は適切な型に暗黙で変換
  4. メソッドのオーバーロードをサポート
  5. プロパティの読み書きはJavaBeansのgetter/setterによって行う


3はLCで拡張されているjsの暗黙の型変換と同じですね。
4はおそらくLC3で導入されたjavaのメソッドとのシグネチャに曖昧に一致する件に関する改善と同じでしょう。

java側にfunc(int)とfunc(String)とfunc(long)とfunc(float)があった場合それぞれ...

var n= 10;
javaInstance["func(int)"](n);
javaInstance["func(String)"](String(n));
javaInstance["func(long)"](n);
javaInstance["func(float)"](10.5);

と呼び出します。
5はおそらくjava beans プロパティのサポートのことでしょう。

var f = new java.swing.JFrame;
f.visible=true;//JFrame.setVisbleを呼び出す

これはbeanプロパティ名でアクセスすることでメソッドのプレフィックスset/get/isを省略し関数呼び出しの書式を取らず代入式と普通の参照としてjavaのメソッドを扱えます。

というかそもそもいま現在、LCはnew Java Plugin側にあるのでLCの仕様に合わせない筈はありません。
がしかし、1と3についてCLの変換規則と異なります。Type compatibilityという表。まず、undefinedがソルインスタンスとして扱われています。
本来undefinedは変換する型に応じて値が変化します。そしてjsのArrayとObject。
この2つはjavaになるとき純粋にアンラップされホスト言語上では実装レベルのクラスとして扱われます。
ただし、Arrayに関しては配列の要素と長さにアクセス可能な擬似的なオブジェクトとだけは決まっています。この表でもそれに従っているらしいところが見て取れます。
ObjectはMapと書いてありこれはjava collection apiのMapでしょう。実はRhinoもjsのObjectはMapインターフェイスを実装しているのでjavaからはMapとして扱えます。

同じようにRhinoのArrayはListを実装しているのでListとして扱えますがNashornではListという文字が見当たりません。
ということはRhinoと同じように扱えない可能性があります。
それとnullの変換対応とObjectのCallとConstructが呼び出せるか書かれていません。
このことから推察するにType compatibilityという表は実装レベルでのJavaのクラスの話でjavaとjsの暗黙の型変換の表ではない可能性があります。
しかし、そうだとしてもこれらから読み取れる範囲ではArrayがListを実装していないのは変わらず、javaからはjsの配列を拡張for文で回すといったことが出来ないかもしれません。
java.util.Listで定義された各種メソッドが使えないと言うことも意味します。

そして言語仕様はECMA-262 Edition 5とのことです。javascript 1.7/1.8ではないようです。
イテレータもジェネレータもletもconstも分割代入も配列内包もありません。構造化代入のない関数型言語の出来上がりです。
もしかするとjavascript詳しくない層へのアピールのため今流行のes5準拠をアピールしているだけで実際には使える可能性もありますが。

そして最大の問題を最後に


Nashornでは、オブジェクトを表すルートクラスとして「ScriptObject」クラスが定義されている。
らしいですがmap,prototype,spillというプロパティをもつそうです。prototypeはそのままっぽいですがmapとspillの用途が不明です。
mapはオブジェクトのプロパティを格納するようなので、これはおそらく実装レベルの話でmapはスロットの実体と考えられます。
だとすると、これはプロトタイプオブジェクトだと思われます。となればspillはインスタンスが持つプロパティのスロットと考えてよさそうです。

そうするとここで一つ気になることが、NashornはLC同様javaのメソッドにアクセスできるようなので、もしこの3つのプロパティをスクリプト側に見せる抜け道があると

__proto__だとか__parent__だとかそんなチャチなもんじゃあ断じてねえ、もっと恐ろしいものの片鱗を味わうことになります。

いろいろ検索してみたのですがどのレポートもNashornはほとんど触れられていません。もともと踏み込んだ説明はなかったのでしょうか?
もしそうなら言語間通信のセキュリティに関する話をもっとして欲しかったです。
詳細を知るにはいつか公開されるソースを読むしかないようです。

ところでRhinoもinvokDynamic対応が入ればインタプリタモードだけじゃなくコンパイルモードでtail-callと継続が使える!と思っていたらなんとJavaに先を越されました。

駆け足で進めるjavascriptのポリモーフィズム


駆け足シリーズは動作や仕組みから捉えたjavascriptのポリモーフィズムの話

その2

その3

その4

新しい、理屈シリーズは型理論などの理論から捉えたjavascriptのポリモーフィズムの話

理屈ではじめるjavascriptのポリモーフィズム



まず基本中の基本から

var x=100;
function f(){return this.x;}
f()//->100
f.call({x : 200})//->200

javascriptはオブジェクト指向です。環境にあるものは全てオブジェクトです。
大人の事情でプリミティブ値を持っていますがあれは型に属するの一要素とでも考えてください。
でなければ話が進みません。というわけで進めます。

つぎにjavascriptはマルチパラダイムであり関数型のパラダイムを持っています。
そもそもmozillaの実装では関数の引数の個数を表すプロパティはlengthではなくarityです。

(function(n) n<=2 ? 1 : arguments.callee(n-1) + arguments.callee(n-2))(10)//->55

おなじみのフィボナッチ数列です。
ここでarguments.calleeは無名関数の不動点です。Yコンビネータは要りません。
ちなみにarguments.calleeはes5のstrict modeで機能しなくなりました。
実行速度が半分くらいに低下し視認性もすこぶる悪くなりますがYコンビネータを書いてください。

関数型言語のポリモーフィズムと言えば型付きラムダです。
javascriptは型なしラムダですがこれは型が一つしかない型付きラムダとみなせるので型付きラムダっぽいことをしてみましょう。

var x=100
var env={x : 10};
function f(y){return this.x+y}
f(10)//->110

var f2 = f.bind(env);
f2(10)//->20

var f3 = f.bind(env,20);
f3()//->30

f3をみれば何をしているか分かりますね。部分適用です。引数を束縛します。では第一引数のenvは何を束縛しているのでしょうか?
細かいこと言うと違いますが答えは環境です。スタックフレームとも言います。一つ前のf.call({x : 200})も同じです。
javascriptではスタックフレームをプログラマが差し替えられます。func.call(foo)のfooは間接的にスタックフレームを差し替えています。
間接的というのは実際にはスコープを差し替えています。スコープは呼び出されるたびに連鎖します。
では呼び出されるとは何が呼び出されているのかというと呼び出し可能なオブジェクトです。javascriptではそれを関数オブジェクトと呼びます。
javascriptでは関数をファーストクラスとして扱うことで高階関数を実現してるんですね。

関数の呼び出しは連鎖するので呼び出されるたびにスコープが作られ結果スコープが連鎖するわけです。
呼び出し可能なオブジェクトは実行可能コードというものを持ちます。これは関数の手続きに相当します。
手続きを処理するには状態が必要ですが、この状態はどこに存在するのかというのが環境(スタックフレーム)です。
環境はスコープ毎に関数に紐付けられますのでjavascriptはブロックスコープではありません。
そしてここからが重要で、スコープとは何かというと一つ上のオブジェクトです。言い換えると呼び出し元オブジェクトです。
呼び出し可能オブジェクトが呼び出された時この元オブジェクトは呼び出し可能オブジェクトに渡されます。
これがthis演算子です。これとは別に仮引数に結びつけた実引数と関数のローカル変数を持たなければいけないのでここで環境が必要になります。
この環境の正体がCallオブジェクトでecmaではActivationオブジェクトと呼びます。
関数が必要になるとまずCallオブジェクトがnewされます。そのプロパティに関数のローカル変数をputします。
そしてCallオブジェクトは次にArgumentsオブジェクトというものをnewして自分が持ちArgumentsは関数の実引数を持ちます。

そして呼び出し可能オブジェクトにスコープとなるオブジェクトとCallオブジェクトを渡して呼び出し可能オブジェクトが持つ内部[[call]]メソッドを呼び出します。
javascriptの関数はこのようにして実行されます。よってこのスコープを差し替えればthis.fooの値は変化します。
説明が長くなりましたがそれをやっているのがcallやbindです。bindの方は渡すだけではなく部分適用するのを思い出すと、
スコープまで部分適用されているのでbindで作られた新たな関数はスコープが変わっても束縛されたスコープで実行されます。

これはつまり、スコープを束縛して元のfunction型から派生した別のfunction型が同じシグネチャでも(状態が異なるので)別の振る舞いをするということです。
これがjavascriptのポリモーフィズムです。これはC#のデリゲートと同じことを実現できます。

ちなみにCallオブジェクトは呼び出された関数に閉じているので変更できません。元々、関数のローカル変数はArgumentsインスタンスのプロパティにputされていたので、
関数のローカル変数に自由にアクセスできたのですがボロッカスに叩かれた後callerとともにver1.3で廃止されました。
しかし、Function.callerが追加されたためarguments.calee.callerで呼び出し元が取れてしまいます。
なぜこれをボロッカスに叩いたかというと実行速度を落としあまつさえメモリリークを起こす危険性があるからです。あと仕様を複雑にしすぎているからでもあります。

callerとともに標的にしたのはwith文です。これはスコープチェーンを辿ってプロパティを見つけられなかった場合にGlobalオブジェクトを見に行くのではなく指定したオブジェクトを見に行きます。
このためwith文内の変数を見つけられないとき非常に実行速度が遅くなります。今風に言うと実行コンテキストが動的に変化するため実行してみないと結果がわからないので最適化できません。

それ以降netscapeはcallerとwithを危険で遅いので非推奨とし今に至ります。特に最近では最適化を困難にするということでこの2つを非常に嫌っています。
ただし、ecmaの仕様ではcallerはありません。es5に至ってはcalleeまでも排除しようと目論んでいます。

calleeは無名再帰に必須であり無名再帰のために存在します。mozillaは今では外の関数に名前を付けろと言っていますがそれではダメです。
名前をつけてしまうとそのスコープに定義されてしまうため外部から汚染される可能性があります。
また本来その場限りのコードを実行するための無名関数でありながら他から参照可能となってしまうため余計な定義が残り続けます。
さらに他から参照できるため予期せぬ実行をされる可能性があります。
インジェクションの問題は動的言語では致命的といえるでしょう。Smalltalkなんて恐ろしい事態です。

mozillaがこれらを禁止する理由はメタ的な操作を可能としてしまうという事ですがそもそもspidermonkeyにはwatch/unwatchも__noSuchMethod__もProxyオブジェクトも存在します。
メタ的な操作が非推奨というのはいささか説得力不足なところがあります。本音はよりアグレッシブな最適化を行いたいがためなのは誰もが承知の事実でしょう。
メタプログラミング自身が悪の権化というわけではないのでcalleeだけは残して欲しかったですがstrict modeで禁止されたということは将来廃止されます。

80文字で取るに足るコードを画面いっぱいにだらだらと垂れ流すか、はたまた、うっかり者に右の頬を差し出し攻撃者に左の頬を差し出すことを喜んで受け入れるしかありません。

さてjavascriptのポリモーフィズムの話しに戻りますがなぜこんな仕様になっているのかというとjavascriptがSelfの影響を少なからず受けているからです。
Selfはスロットと呼ばれる集合を持っていてそこにメッセージを送ります。受け取るのはスロットを持っているレシーバです。
SelfはSmalltalkを改良した言語でクラスによる継承ではなくプロトタイプによる移譲によってオブジェクトを作ります。
レシーバとはすなわちオブジェクトのことでjavascriptではスロットのことをプロパティと呼びます。
Smalltalkではブロックで処理を行いますがjavascriptでは関数で処理を行います。
Smalltalkにおけるvalue:はcall()、valueWithArguments:はapply()です。当然ですがクロージャーです。
オブジェクトですのでメッセージさえ送れれば反応します。

var receiver = new Object();
receiver.p = "イヤッハー!";

これでプロパティpはイヤッハー!という値となります。したがって、

function halo(o){ print(o.p) }
halo(receiver)

は画面にイヤッハー!と出力します。これはこう変えても動作します。

var r2 = new Object();
r2.p = "舞い忍びます!"
halo(r2)//画面に「舞い忍びます!」と出力

メッセージさえ送れればいいので当然ですね。もう少し踏み込むと

print(Object.prototype.toString.call(r2)) //-> [object Object]
print(Object.prototype.toString.call([])) //-> [object Array]
Array.prototype.slice.call([1,2,3], 0, 2) //->[1,2]
Array.prototype.slice.call("123", 0, 2) //->[1,2]

メッセージさえ送れればいいので当然です。ユーザー定義オブジェクトに関しても同じです。
文字列は配列ではないですがプロパティとして要素へのインデックスと長さを表すlengthを持っているのかのように振る舞うので配列のように扱えます。
こういうのをarray like objectと言います。文字列のarray like objectはesにも取り込まれました。
実は関数内から参照できるargumentsや正規表現の結果が入った配列も今はarray like objectです。
Array like objectはarray objectではありません。そのプロトタイプはObjectプロトタイプオブジェクトです。
これはクラスで言うメタクラスとでも思ってください。SmalltalkのHoge classと同じです。
ただし固有のクラスが生成されます。したがって、各インスタンス毎にプロトタイプオブジェクトを持っているということです。
よってarray objectはArrayプロトタイプオブジェクトを持っているのでObjectプロトタイプオブジェクトを持っているarray like objectとは別物です。
しかし、例外がありFunctionプロトタイプオブジェクトは自身が常にundefindを返すFunctionオブジェクトでもあります。

各プロトタイプオブジェクトのプロトタイプはObjectプロトタイプオブジェクトです。
Objectプロトタイプオブジェクトのプロトタイプはnullです。プロトタイプの移譲がここまで到達すると失敗します。
プロパティprototypeはオブジェクト自身のプロパティの集合への参照であってプロトタイプオブジェクトそのものではありません。

プロパティはプロトタイプオブジェクト、オブジェクト、インスタンスが個別に持つこととなりますが、
これはそれぞれメタクラス、クラス、インスタンスのメンバに相当します。下位のメンバ検索に失敗したら上位に委譲しているのです。
これがプロトタイプベースというやつです。

プロトタイプオブジェクトそのものの変更は内部[[Prototype]] プロパティを変更するしかありませんがこれは標準化されていません。
しかし、内部[[Prototype]] プロパティを取得する方法はObject.getPrototypeOfメソッドによって標準化されました。
ただしGlobalオブジェクトのプロパティObjectはObjectオブジェクトのビルドインコンストラクタなのでObject.getPrototypeOf(Object)としてしまうとそれはFunctionプロトタイプオブジェクトです。
ビルドインコンストラクタのprototypeプロパティは各オブジェクトのプロトタイプオブジェクトの参照を持っているためgetPrototypeOfメソッドを使う必要はありません。
ではなぜ、getPrototypeOfメソッドがあるかというとインスタンスのプロトタイプオブジェクトを取得するためです。

非標準な方法で内部[[Prototype]] プロパティを変更できますがこれはいわばクラスが属するメタクラスを再定義することと同じなので決してやってはいけません。
前に言われたprototype.jsのObject.prototype汚染とはオブジェクト自身のプロパティ検索を汚染することです。この問題を解決した仕様がes5です。

話を戻しますがこのメッセージさえ送れればいいという考え方は最近ではダックタイピングとも呼びます。
関数はオブジェクトなのでcallというメッセージをスコープを伴って送っているのです。
ではスコープはどのタイミングで作られるのかというと処理系がコンテキストにエンターしたときです。
コンテキストにエンターするタイミングは実行可能コードが実行される時です。
実行可能コードは呼び出し可能なオブジェクトが持つと言いましたがjavascriptはmain関数を持たないのでプログラムを実行するときにいきなりスコープを作ります。
よくトップレベルと呼ばれているものです。これはGlobalオブジェクトのインスタンスです。
ここにある実行可能コードをGlobalコード、関数が持つ実行可能コードを関数コード、evalはevalコードと呼びます。
関数の定義(functionbodyと言います)はGlobalコードには属しませんがevalコードはGlobalコードに属します。
つまりevalはGlobalを汚染します。そのため、strict modeではevalはサンドボックス化されました。

ホストオブジェクトの組み込みコンストラクタと組み込み関数はGlobalオブジェクトのプロパティです。大域変数も同じです。
MathオブジェクトだけはMathというプロパティにputされたMathオブジェクトです。コンストラクタは持ちません。
Mathオブジェクトのメソッドもいわゆるクラスメソッドです。
そして、Globalオブジェクトは他のオブジェクトとして振る舞っていいと仕様で許されているのでブラウザの場合はWindowオブジェクトです。

最後のポリモーフィズムに入る前にひとつ説明が必要です。

クラスベースのオブジェクト指向では新しいオブジェクトは1から作る必要はなくクラスによるテンプレート処理で作られます。
プロトタイプベースではオブジェクトは1から作られますがそれは大昔の話です。最近は内部的にテンプレート処理を行なって効率化しています。
javascriptではそれはnew演算子を伴う関数呼び出しです。こういう関数をコンストラクタと言ってこのとき特殊な呼び出しが行われます。
new Hogeを評価した結果がオブジェクトで内部[[Construct]]メソッドを持つならthisに新しいオブジェクト、実引数を渡して呼び出します。それ以外はTyypeErrorです。
コンストラクタとしての呼び出しは内部[[Construct]]メソッドを呼び出すのでホストオブジェクトがコンストラクタとして呼び出せるかは実装依存です。

newを使う他にes5からObjectインスタンスにcreateメソッド他関連メソッドが追加されました。
そのなかでdefinePropertiesというメソッドはvar o=Object.defineProperties(obj,prop)と使います。
Object.createとの使い分けですがcreateは第一引数がプロトタイプとなりnewされ、第二引数はnewしたインスタンスにプロパティとして定義します。
definePropertiesはプロトタイプの操作は行わずに既存のインスタンスにプロパティを複数追加するだけです。
createの第二引数は省略可能でそのときはインスタンスのプロパティは作りません。プロパティはプロトタイプからのみ移譲されます。
definePropertiesは既存のプロトタイプを変更しないのでtraitsとして使えます。

定規戦争

たまたま懐かしいものを見つけた。
定規戦争
自分の頃は定規落としという呼び名だったしここに載っているルールのほうがより洗練されている。
何が一番驚いたかというとそもそもこれは小5のとき、消しゴム落としが流行っていた頃に友人がやりだしたのが自分の中では始まりだった。もしくは定規落としが先だったかもしれない。

その後、小6になって自分は引っ越したのだけどその引越し先では全く知られてなかった。「定規落としやろうぜ!」と言っても「何それ」と返され仕方なくルールを説明しながら遊びはじめた。
なのでずっとローカルな遊びだと思っていた。しかし、wikipediaに載るということは同時多発的に発生した全国区の遊びなのかもしれない。

ちなみのその時のはなし……

用意するもの
  • 定規
  • ペン類

を用意して複数人で遊ぶもので机の上に定規を並べてそれを予め決めた順番でペンを用いて弾いて相手の定規を落とすというものだった。ルールとしては始めは落とされればそこで負けだった。
その次に机の端で落ちきらずに一部分だけ中空に浮いた状態の定規は消しゴム落としの要領でペンでデコピンするように弾いて良いというルールが追加された。これは定規が落ちきっていないのでゲームが続行されるためだった。

その後、中に浮いた状態の定規の端をボールペンキャップのクリップ部を軽く曲げた部分で引っ掛けて机の中に戻す荒業をやりだすものが出てきた。
それがセコいということで物議を醸し出したが結局は定規の机に触れていない部分が机に触れるまでならペンで弾かずに移動してもいいことになった。
ちなみに手で触れてはいけない。そうすると今度は敵の定規の上が一番安全だということになる。上に乗っけられるとそいつには攻撃できないし身動きがとれなくなるからだ。

ここで頻発したのが現状を打破しようと思いっきり定規を弾こうとして勢い余って上に乗っかっている定規を弾いて外に落としてしまうものが続出することだ。
そしたら当然のように相手の定規を自分のペンで落としたら負けというルールが追加された。
そうすると加減誤って簡単に死ぬのでライフ制が採用されたライフの数だけ「負け」てもいいということだ。これは当時、自分たちの間でドッヂボールをするときによくライフ制が採用されていてライフ数だけあたってもOKでライフの譲渡も可能であったことに由来している。
そこから来ているが、ライフの譲渡が可能かどうかはまちまちであった。

つまりまとめるとルールはこのようになる

  • 定規を弾いて相手の定規を落とせば落とされた相手は負け
  • 自分以外の定規をペンで弾く・触れると負け
  • 地面に落ちていなくて空中に一部がある場合はそこから復帰可能
  • そのとき弾くのではなく移動させることも可能
  • ただし、移動させるのに手で触れてはいけない
  • 移動できる範囲は定規の机に触れていない部分が机に触れるまで
オプションとして
  • ライフ制(定番は3)
  • 定規の長さはnセンチまで
  • 段差上りの有無(高さの違う机をつなげた場合に高い方と低い方にまたがって止まった時高い方に復帰すること)
  • 給食袋などに引っかかって地面には付いていない定規の復帰の有無(ただし手で触れてはいけない)
  • 分度器の禁止
  • 三角定規の禁止
  • 変わった形の定規の禁止

段差上りに関しては高さの違う机をつなげた場合に適応されるもので高い方と低い方をブリッジして止まった場合、未だ落ちていないが定規は机に触れていない部分があるので挟んで(引っ掛けて)移動させることが出来るこのとき高い方に復帰すれば低い方に居る定規からは攻撃されない。
また、低い方から高い方へ攻撃するには一旦定規の一部を空中に出してそこから移動させて高い方へと復帰する必要があるので時間もリスクもかかり余りやりたがらない。

こうなるとゲームの進行が遅くなるのと不公平感があるので禁止される場合がある。

三角定規がなぜ禁止になるかというと普通のものさしが両端であるのに対し三角定規は3頂点なので3ヶ所から弾くことが出来る。
そして、三角定規は回転させるように弾くことが容易で穴もあり引っ掛けやすく回しやすく、さらに斜辺を持っているのでぶつけた側が非ぬ方向へと受け流されてしまうためとても強く慣れていないものがいる場合禁止にされることがある。

分度器も似たような理由だがこれは扇条の部分にぶつかると点で衝突し余計に非ぬ方向へ受け流される。さらに衝突力が分散されやすい。
もう一つ、分度器は扇状のところをペンの腹で擦りつけるように弾いても、角ばった部分をペンキャップの頭で押し付けるように弾いてもどちらも扱いやすく弾くパワーがある。よって禁止になることがある。

おまけに三角定規と分度器は軽く、設置面積が広いので摩擦も大きく静電気も溜まりやすい。

定規の長さ規定は長ければ長いほど弱いのだが適度に長いと初見殺しなので制限が設けられる場合がある。または単調な試合なってしまうため。

これらが身内で始まった定規落としの始まりから試行錯誤によるルール確立までの流れだがこれには定型的な勝利パターンがあって……

机端に行く近き、相手定規の形に完全に覆いかぶさるように移動させて乗る。
覆い被された方は覆いかぶさった方ごと自分を弾くしかないがこれは禁止行為なのでライフを1つ失い、覆いかぶさった方は元いた位置に戻る。

ライフ制なので自分より低いライフが覆いかぶさった時、自分のライフが2以上ある場合は相手ごと自分を外に落として道連れにすればいい。
それを参加者が順に繰り返せば生き残ったものは広い机で敵も少ない万全な状態で戦うことができる。

この流れをうまく作った者がより強いわけだが力量差がはっきりと出るのが欠点だった。ここが根本的に改善されることはなくせいぜい道連れ禁止が追加されるくらいだった。

それ以外だとライフ制に関して自分の陣地がありライフ喪失時の再開場所は自分の陣地内の任意の場所というものもあった。
陣地というのはゲームの参加人数分の机を用意しつなげ、その内のどれか、ほとんどは自分の机を自分の陣地とする。この場合、段差越えは自分の陣地のみであったりもした。

ところがwikipediaに書いてあるルールは欠点が改良されている。

ここの部分だ──

相手の定規の上に自分の定規を乗せた場合、乗せられた相手は順番に関係なく3回はじくことが出来る。3回はじいて上に乗っている定規から抜け出すことが出来無ければ退場である。抜け出すことが出来たならば元の順番に戻ってゲームが再開される。

これはより洗練されていてあの欠点がない。覆いかぶさられたら順番を変えて三回まで抜け出す権利が与えられるし

相手が定規をはじくことができないように定規を乗せてはならない。

とのことなのでライフを失わずに復帰することが出来る。つまり積んでいない。
それどころかなんとライフ制ですらない。その名残はおそらく、3回まで順番を無視して行動できるという部分に垣間見ることができる。
3という数字はライフ制で遊ぶときよく使われていたので誰でも同じ発想にたどり着くのだろう。

そもそも呼び名すら違うというのは面白い。定規戦争とは初めて聞いた。

別の場所で同時多発的に発生し別の経緯をたどって共通化、変化することはネットユミュニティではよくある。
昔はfjなどでよく「実は私も同じ事を考えていました」などと始まり同じ事を別々の場所で行なっている人たちが集結したものだ。
その後はパソ通からなだれ込んできた連中のせいでレス云々のフレイムが頻発した。レスではじまり無手順と来てネチケットとなったような気がする。

結論は情報は同時多発的に発生し集約され、洗練されるが一般化することによって混乱が起こるということ。