目次
Nemerle における演算子
Nemerle には 3 種類の演算子があります。 1 つ目は言語レベルであらかじめ定義された演算子, 2 つ目はクラスでオーバーロードされた演算子, 3 つ目はマクロにより定義された演算子です。演算は,演算子とその演算子が作用する対象 (オペランド) のペアの関数とみなすこともできます。 Nemerle においては,オペランドが 1 つである単項演算と,オペランドが 2 つである二項演算がサポートされています。単項演算はオペランドが演算子の左側にくる場合と右側にくる場合があります。
演算子の結合性
単項演算における演算子とオペランドの位置関係,二項演算におけるオペランドの評価順序,演算子の優先順位は,演算子ごとに定められたオペランドと演算子の結びつきの強さ (結合性) によって決まっています。結合性は演算子から見て左側と右側のそれぞれで整数値で定義されており,この値が大きい方が結合性が高くなります。
単項演算子の場合,左右の結合性の大小は演算子とオペランドの位置関係に影響し,結合性の高い側に演算子が来ます。すなわち,左結合性の方がが高ければ演算子は左側に,逆に右結合性の方が高けれ右側に演算子が来ます。二項演算子の場合,結合性の大小はオペランドの評価の順番に影響し,結合性の低い側のオペランドが先に評価されます。
標準演算子
以下に Nemerle で標準に定義された演算子を示します。演算子とは言い難いものもありますが,優先順位の情報は役に立つでしょう。また,調査が不完全のため抜けがあります。
演算子 | 左結合性 | 右結合性 | 概要 |
---|---|---|---|
+ |
(未調査) | (未調査) | プラス |
- |
(未調査) | (未調査) | マイナス |
! |
281 | 280 | 論理否定 |
~ |
281 | 280 | ビット反転 |
++ |
283 | 284 | インクリメント |
? |
283 | 284 | (未調査) |
-- |
283 | 284 | デクリメント |
演算子 | 左結合性 | 右結合性 | 概要 | |
---|---|---|---|---|
in |
120 | 121 | 列挙 | |
when |
130 | 131 | パターンマッチの制約 | |
with |
130 | 131 | パターンマッチの変数束縛 | |
= |
141 | 140 | 左辺に右辺を代入 | |
*= |
141 | 140 | 左辺と右辺の乗算の結果を左辺に代入 | |
/= |
141 | 140 | 左辺を右辺で除算した結果を左辺に代入 | |
%= |
141 | 140 | 左辺を右辺で除した結果の余りを左辺に代入 | |
+= |
141 | 140 | 左辺と右辺を加算した結果を左辺に代入 | |
-= |
141 | 140 | 左辺から右辺を減算した結果を左辺に代入 | |
<<= |
141 | 140 | 左辺を右辺ビットだけ左にシフトして左辺に代入 | |
>>= |
141 | 140 | 左辺を右辺ビットだけ左にシフトして左辺に代入 | |
&= |
141 | 140 | 左辺と右辺をビット AND 演算した結果を左辺に代入 | |
^= |
141 | 140 | 左辺と右辺をビット XOR 演算した結果を左辺に代入 | |
|= |
141 | 140 | 左辺と右辺をビット OR 演算した結果を左辺に代入 | |
=> |
145 | 120 | ラムダ式 | |
?? |
146 | 145 | 左辺が null でない場合は左辺, null ならば右辺 |
|
|| |
150 | 151 | 真偽値 OR (ショートサーキット評価) | |
&& |
160 | 161 | 真偽値 AND (ショートサーキット評価) | |
== |
165 | 166 | 等しい | |
!= |
165 | 166 | 等しくない | |
| |
170 | 171 | 真偽値 OR | |
%| |
170 | 171 | 整数値 (列挙型含む) のビットワイズ OR | |
^ |
180 | 181 | 真偽値 XOR | |
%^ |
180 | 181 | 整数値 (列挙型含む) のビットワイズ XOR | |
& |
190 | 191 | 真偽値 AND | |
%& |
190 | 191 | 整数値 (列挙型含む) のビットワイズ AND | |
%&& |
190 | 191 | 整数値 (列挙型含む) のビットワイズ AND (結果は真偽値) | |
is |
210 | 211 | 右辺型は左辺値の規定型またはインターフェイスか | |
< |
210 | 211 | 比較 (左辺が右辺より小さい) | |
> |
210 | 211 | 比較 (左辺が右辺より大きい) | |
<= |
141 | 140 | 比較 (左辺が右辺より小さいか等しい) | |
>= |
141 | 140 | 比較 (左辺が右辺より大きいか等しい) | |
as |
215 | 301 | パターンマッチ | |
:: |
221 | 220 | リスト結合 | |
<| |
226 | 225 | パイプ演算子 | |
|> |
227 | 228 | パイプ演算子 | |
<< |
230 | 231 | 関数合成 | |
>> |
230 | 231 | 関数合成 | |
.. |
230 | 231 | 範囲 | |
+ |
240 | 241 | 加算 | |
- |
240 | 241 | 減算 | |
-> |
251 | 250 | ||
/ |
260 | 261 | 除算 | |
% |
260 | 261 | 剰余 | |
* |
261 | 260 | 乗算 | |
: |
270 | 246 | キャスト | |
:> |
270 | 246 | キャスト | |
where |
284 | 300 | パターンマッチの条件 | |
?. |
285 | 285 | メンバーアクセス (null の場合にデフォルト値を返す) |
|
. |
285 | 301 | メンバーアクセス |
独自定義の演算子
オーバーロードまたはマクロにより,独自の演算子を定義することができます。いずれの方法でも,演算子の優先順位を指定することができます。
優先順位の指定
演算子の優先順位は,その演算子を公開するアセンブリにメタデータとして保持されます。具体的には Nemerle.Internal.OperatorAttribute
により定義されます。
using Nemerle.Internal; [assembly: Operator("SomeNamespace", "+++", false, 240, 241)] [assembly: Operator("AnotherNamespace", "---", true, 284, 283)]
OperatorAttribute
のコンストラクターのパラメーターは左から順番に「演算子が公開される名前空間」「演算子」「単項演算子かどうか (単項演算子なら
true
,二項演算子なら false
)」「左結合性」「右結合性」です。単項演算子の場合,結合性の方が低い方にオペランドが来ます。上の例の ---
は右の結合性が低いので ---value
のようにオペランドが右に来ます。
演算子のオーバーロード
演算子のオーバーロードは,ある型において,その型を含むような演算を定義可能です。例えば次のような形をとります。
using Nemerle; using Nemerle.Utility; [Record] public struct Vector2D { public X : double; public Y : double; public static @-(vector : Vector2D) : Vector2D { Vector2D(-vector.X, -vector.Y); } public static @+(v1 : Vector2D, v2 : Vector2D) : Vector2D { Vector2D(v1.X + v2.X, v1.Y + v2.Y); } public static @*(scalar : double, vector : Vector2D) : Vector2D { Vector2D(scalar * vector.X, scalar * vector.Y); } public static @*(scalar : double, vector : Vector2D) : Vector2D { Vector2D(scalar * vector.X, scalar * vector.Y); } public static @^*^(v1 : Vector2D, v2 : Vector2D) : double { v1.X * v2.X + v1.Y * v2.Y; } } def v1 = Vector2D(1.0, 2.0); def v2 = Vector2D(3.0, -1.0); _ = -v1; // Vector2D(-1.0, -2.0) _ = v1 + v2; // Vector2D(4.0, 1.0) _ = 1.5 * v2; // Vector2D(4.5, -1.5) _ = v1 ^*^ v2; // 1.0
マクロ
マクロで演算子を定義する場合は,通常のマクロ定義と同じで特別なことはありません。
[assembly: Nemerle.Internal.Operator("", "+-", false, 240, 241)] macro @+-(e1, e2) { def plus = <[ $e1 + $e2 ]>; def minus = <[ $e1 - $e2 ]>; <[ ($plus, $minus) ]>; } def solve(b, c) // x^2 + bx + c = 0 の解を求める { def b' = b / 2.0; -b' +- System.Math.Sqrt(b' * b' - c); } _ = solve(3.0, 2.0); // (-1, -2)
実装時の注意
演算子のオーバーロードとマクロのいずれの方法を用いて実装するを考えてみます。既存の型に対して演算子を定義するには,オーバーロードは利用できません。必然的にマクロを選択することになります。独自定義の型に対して演算子を定義する場合はどちらを選ぶべきでしょうか。
もし Nemerle 以外の言語との相互運用を考えるのであれば,他の言語からマクロで定義された演算子を利用することはできません。したがってオーバーロードを利用するほかありません。注意すべき点として,言語によっては演算子として使用できる記号が限られています。外部に公開する演算子については特に気を付けましょう。
Nemerle のみで開発する場合,オーバーロードもマクロも利用できます。オーバーロードが単なる実行時の演算しか定義できないのに対し,マクロは +=
のように代入と評価を同時に行うような複雑な処理も行うことができます。対してオーバーロードは単純な演算しか定義できませんが, private
メンバーを直接利用するなど,より型の実装と密接な処理を容易に行うことができます。どちらが良いかは場合によりますが,高度な機能を求めないのであれば,オーバーロードを使った方が無難でしょう。