逆演算とインクリメンタル付与
具体的な実装について議論する理論を捨てると抽象的すぎるため、以下とその後に特に説明がない場合、デフォルトはlarvaの現在の実装を背景に、python実装のコンパイラとjava実行に移行する
CPUは基本的なデータ型のみを直接処理でき、簡略化すればアドレス、整数(一般的にALU、乗算器、除算器、シフト器などで計算され、ポインタ/アドレス計算を含む)と浮動小数点数(一般的には専用のFPUがある)は、1つのプログラムには数百以上のタイプがあるかもしれないが、結局はアドレス、整数、浮動小数点数などからなる複合クラス型(classまたはstruct)である
前に述べたダイナミックタイプ言語の設計によれば、すべてのデータ型がobjectのサブクラスである場合、上記のベースタイプはすべて包装されるべきであるため、直接計算を行うことはできない.解決策は、larvaのやり方のように、方法呼び出しで計算を実現することである.~aはa.op_に実現するinvert()、a+bはa.op_として実現add(b)、その他の計算は類似している
aおよびb参照オブジェクトの実際のタイプは任意のタイプである可能性があるため、インタフェースを呼び出すときにのみ動的に判定することができ、例えば、aおよびbはint型であり、a.op_addメソッドでは,bがintであれば整数加算を行い,和を表す整数オブジェクトを返し,戻りタイプはobjectであるが,aがint,bがfloatであれば,また,aがfloat,bがintである場合にfloatクラスのop_addメソッドでも判断を下すのは面倒で、さらに面倒なことに、複数の数値タイプを追加する場合は、以前のすべての混合演算可能なタイプ、int、long、floatなどを修正しなければなりません.数値以外のタイプも似ています.例えば、tuple、list、stringなどのシーケンスタイプは整数の乗算をサポートしています.例えば「ab」*3の結果は「ababab」であり、この演算が交換則を満たすとintのop_mulメソッドでは、上記の3つのシーケンス関数があるかどうかを判断する必要があります.これは面倒で効率的ではありません.
この問題ではlarvaはpythonのやり方を参考にして,逆演算を導入した.すなわち,a+bに対して,a.op_add(b)またはb.op_reverse_add(a)は、前者を優先し、すなわち、前者を計算してみて、だめになってから後者を計算してみるが、実際には「試行」の過程はなく、objectクラスでこのように実現される.
次の問題は、いつ逆演算を使用するかということです.一般的には、2つの演算成分は、結果のタイプまたは両方のタイプに基づいて演算の「開始者」を決定し、言い換えればタイプに「定級」を与え、1つのタイプの順方向演算方法では、それと同級またはそれより低いタイプを考慮するだけで、逆演算メソッドでは、そのレベルよりも低いタイプを考慮するだけです.
たとえば、int、long、floatのレベルが低いから高いまで、intのop_addなどの二元演算では,パラメータがintであるか否かを判断するだけで,そうでなければ,入力パラメータの逆演算,すなわちintとlong演算がlongによって開始され,彼らの位置にかかわらず実現とコード管理が簡素化される.
この文法は実際の状況に応じて異なる実装が可能であり、larvaには例がある.例:a.f()は、オブジェクトaを呼び出す方法fのように見えるが、aのf属性を取り、オブジェクトをcall演算する場合もある.ダイナミックタイプ言語では、コンパイル段階でどの状況であるかをチェックするのは難しいため、実行時に実際の状況に基づいて判断するしかない.上記と同様に,まずいずれかの場合を仮定し,実現しなければ別の場合にルーティングする処理を行う.
この場合、pythonは、この式が属性fを先に取り、call演算を実行することを規定するので、aがfメソッドのみである場合、pythonはmethodオブジェクトを構築し、callを作成して解放する利点は、a.fがクラスと関数がオブジェクトであるように、メソッドオブジェクトとして値を求めることができ、悪い点は効率が低いことである.
Larvaは別のスキームを採用し、この式について、文法的にaを呼び出すfメソッドであると考えられ、実行時にaにfメソッドがなければ、f属性を取り、呼び出すまでルーティングする.
もう1つの理由は,経験的に見ると,この式はほとんどの場合メソッド呼び出しであり,属性に対してcall演算を行うことは少ないため,可能性の高い処理方式を優先的に採用することで効率を向上させることができるからである.
演算に関するもう一つの問題は、a+=bのような増分賦値であり、文法上の演算子であるが、2つのステップが必要であり、先に計算してから賦値し、演算成分のタイプによって2つの状況に分けられる.
1 aは変えられない、a+=bはa=a+bに相当する
2 aタイプの+=演算の定義は、場合1とは異なり、例えばaがlistである場合、a+=bはbの内容をaの末尾に追加することに相当し、このときa=a+bはリストを再構築してaに値を付与することに相当する.
上記のメソッド呼び出しの処理と同様に、+=を例に、演算メソッドop_を定義する2つの方法が互換性がある場合を見つけることができます.inplace_addは、コードa+=bを演算に変換する.
ケース2では、1つのタイプのop_を規定するだけです.inplace_add操作は自身,すなわちreturn thisを返し,これにより最後の付与値はしなかったに等しく,状況2のニーズに合致する.
最後の問題は、aが単純な変数でない場合、a[f()]+=bのように、上記変換は2回実行するが、元のコードの意味は、f()のように1回のみ実行することである.a+=bにもこの問題があります.したがって、左の値は[]または[.]です.演算には、一時変数が左の成分を計算してから、f()[g()]+=bのように、上記の変換を行う必要があります.
Larvaでは、通常変数、[]および「.」のみ3つの演算は増分賦値の左値に使用でき、残りの左値(解包賦値の左値形式、スライス賦値の左値形式)などは増分賦値に使用できません.
CPUは基本的なデータ型のみを直接処理でき、簡略化すればアドレス、整数(一般的にALU、乗算器、除算器、シフト器などで計算され、ポインタ/アドレス計算を含む)と浮動小数点数(一般的には専用のFPUがある)は、1つのプログラムには数百以上のタイプがあるかもしれないが、結局はアドレス、整数、浮動小数点数などからなる複合クラス型(classまたはstruct)である
前に述べたダイナミックタイプ言語の設計によれば、すべてのデータ型がobjectのサブクラスである場合、上記のベースタイプはすべて包装されるべきであるため、直接計算を行うことはできない.解決策は、larvaのやり方のように、方法呼び出しで計算を実現することである.~aはa.op_に実現するinvert()、a+bはa.op_として実現add(b)、その他の計算は類似している
aおよびb参照オブジェクトの実際のタイプは任意のタイプである可能性があるため、インタフェースを呼び出すときにのみ動的に判定することができ、例えば、aおよびbはint型であり、a.op_addメソッドでは,bがintであれば整数加算を行い,和を表す整数オブジェクトを返し,戻りタイプはobjectであるが,aがint,bがfloatであれば,また,aがfloat,bがintである場合にfloatクラスのop_addメソッドでも判断を下すのは面倒で、さらに面倒なことに、複数の数値タイプを追加する場合は、以前のすべての混合演算可能なタイプ、int、long、floatなどを修正しなければなりません.数値以外のタイプも似ています.例えば、tuple、list、stringなどのシーケンスタイプは整数の乗算をサポートしています.例えば「ab」*3の結果は「ababab」であり、この演算が交換則を満たすとintのop_mulメソッドでは、上記の3つのシーケンス関数があるかどうかを判断する必要があります.これは面倒で効率的ではありません.
この問題ではlarvaはpythonのやり方を参考にして,逆演算を導入した.すなわち,a+bに対して,a.op_add(b)またはb.op_reverse_add(a)は、前者を優先し、すなわち、前者を計算してみて、だめになってから後者を計算してみるが、実際には「試行」の過程はなく、objectクラスでこのように実現される.
LarObj op_add(LarObj obj)
{
return obj.op_reverse_add(this);
}
すなわち、タイプがこの方法を上書きしていない場合、自動的に逆演算の実装にルーティングされ、明らかに逆演算が実装されていない場合、順方向演算をルーティングすることができず、エラーを投げ出すべきである.そうしないと、無限に再帰する次の問題は、いつ逆演算を使用するかということです.一般的には、2つの演算成分は、結果のタイプまたは両方のタイプに基づいて演算の「開始者」を決定し、言い換えればタイプに「定級」を与え、1つのタイプの順方向演算方法では、それと同級またはそれより低いタイプを考慮するだけで、逆演算メソッドでは、そのレベルよりも低いタイプを考慮するだけです.
たとえば、int、long、floatのレベルが低いから高いまで、intのop_addなどの二元演算では,パラメータがintであるか否かを判断するだけで,そうでなければ,入力パラメータの逆演算,すなわちintとlong演算がlongによって開始され,彼らの位置にかかわらず実現とコード管理が簡素化される.
この文法は実際の状況に応じて異なる実装が可能であり、larvaには例がある.例:a.f()は、オブジェクトaを呼び出す方法fのように見えるが、aのf属性を取り、オブジェクトをcall演算する場合もある.ダイナミックタイプ言語では、コンパイル段階でどの状況であるかをチェックするのは難しいため、実行時に実際の状況に基づいて判断するしかない.上記と同様に,まずいずれかの場合を仮定し,実現しなければ別の場合にルーティングする処理を行う.
この場合、pythonは、この式が属性fを先に取り、call演算を実行することを規定するので、aがfメソッドのみである場合、pythonはmethodオブジェクトを構築し、callを作成して解放する利点は、a.fがクラスと関数がオブジェクトであるように、メソッドオブジェクトとして値を求めることができ、悪い点は効率が低いことである.
Larvaは別のスキームを採用し、この式について、文法的にaを呼び出すfメソッドであると考えられ、実行時にaにfメソッドがなければ、f属性を取り、呼び出すまでルーティングする.
LarObj meth_f()
{
return op_get_attr_f().op_call();
}
f属性さえなければ異常です.これは、メソッド呼び出しは「属性を取る」と「呼び出す」の2つのステップに分解できますが、逆に属性を取る操作は呼び出しに分解できません.python方式を採用する場合は、一時的なオブジェクトを導入するか、すべてのメソッドを呼び出し可能なオブジェクトに変更する必要があるため、効率が低下します.もう1つの理由は,経験的に見ると,この式はほとんどの場合メソッド呼び出しであり,属性に対してcall演算を行うことは少ないため,可能性の高い処理方式を優先的に採用することで効率を向上させることができるからである.
演算に関するもう一つの問題は、a+=bのような増分賦値であり、文法上の演算子であるが、2つのステップが必要であり、先に計算してから賦値し、演算成分のタイプによって2つの状況に分けられる.
1 aは変えられない、a+=bはa=a+bに相当する
2 aタイプの+=演算の定義は、場合1とは異なり、例えばaがlistである場合、a+=bはbの内容をaの末尾に追加することに相当し、このときa=a+bはリストを再構築してaに値を付与することに相当する.
上記のメソッド呼び出しの処理と同様に、+=を例に、演算メソッドop_を定義する2つの方法が互換性がある場合を見つけることができます.inplace_addは、コードa+=bを演算に変換する.
a=a.op_inplace_add(b)
は見ることができて、これは実は1つの普通のa=a op bのモードで、まず情況1を見て、op_inplace_add実装とop_addは同じでよく、実際にはデフォルトの動作はop_に直接ルーティングされます.add,これにより,ほとんどの適合状況1のタイプに対してop_を実現する必要がなくなる.inplace_*シリーズが操作されましたケース2では、1つのタイプのop_を規定するだけです.inplace_add操作は自身,すなわちreturn thisを返し,これにより最後の付与値はしなかったに等しく,状況2のニーズに合致する.
最後の問題は、aが単純な変数でない場合、a[f()]+=bのように、上記変換は2回実行するが、元のコードの意味は、f()のように1回のみ実行することである.a+=bにもこの問題があります.したがって、左の値は[]または[.]です.演算には、一時変数が左の成分を計算してから、f()[g()]+=bのように、上記の変換を行う必要があります.
tmp_a = f()
tmp_b = g()
tmp_a.set_item(tmp_b, tmp_a.get_item(tmp_b).op_inplace_add(b))
//
Larvaでは、通常変数、[]および「.」のみ3つの演算は増分賦値の左値に使用でき、残りの左値(解包賦値の左値形式、スライス賦値の左値形式)などは増分賦値に使用できません.