演算子


目次

  1. Nemerle における演算子

    1. 演算子の結合性
  2. 標準演算子
  3. 演算子を定義する

    1. 優先順位の指定
    2. 演算子のオーバーロード
    3. マクロ
    4. 実装時の注意

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 メンバーを直接利用するなど,より型の実装と密接な処理を容易に行うことができます。どちらが良いかは場合によりますが,高度な機能を求めないのであれば,オーバーロードを使った方が無難でしょう。