はじめに
独自演算子を定義する時には、演算子の優先順位についての問題が気になってきます。
例として、Rにあるような、符号を被除数に一致させる剰余演算子を作成しました。
infix operator %%
func %%(left: Int, right: Int) -> Int {
((left % right) + right) % right
}
(参考:Swiftで負の数に対する%演算が正にならない件 - Qiita)
しかし、上記のコードでは問題があります。
単体での使用は問題ありませんが、他の演算子と併用するとコンパイルエラーが発生します。
let n = -7
print(n %% 5) // 3
print(10 + n %% 5) // error: adjacent operators are in unordered precedence groups 'AdditionPrecedence' and 'DefaultPrecedence'
%
と同じように、n %% 5 = 3
が先に計算されて10 + 3 = 13
という計算を行うには、優先順位に関する情報を設定しておく必要があります。
既存のprecedencegroupを利用する
既存の演算子と同じ優先順位を設定するのは簡単です。
例えば、%
の優先順位は MultiplicationPrecedence なので、以下のように書くだけです。
-infix operator %%
+infix operator %%: MultiplicationPrecedence
func %%(left: Int, right: Int) -> Int {
((left % right) + right) % right
}
これで先ほどのエラーは解消され、思い通りの結果になりました。
let n = -7
print(n %% 5) // 3
print(10 + n %% 5) // 13
このMultiplicationPrecedenceというものは、protocolではなくprecedencegroupと呼ばれるものです。
以下は、既存のprecedencegroupの一覧です。(優先順位の高い順)
- BitwiseShiftPrecedence
<<
>>
&<<
&>>
- MultiplicationPrecedence
*
/
%
&
&*
- AdditionPrecedence
+
-
|
^
&+
&-
- RangeFormationPrecedence
..<
...
- CastingPrecedence
-
is
as
as?
as!
- NilCoalescingPrecedence
??
- ComparisonPrecedence
<
<=
>
>=
==
!=
===
!==
~=
.<
.<=
.>
.>=
.++
.!=
- LogicalConjunctionPrecedence
&&
.&
- LogicalDisjunctionPrecedence
||
.|
.^
- DefaultPrecedence
何も設定しなかった場合の優先順位。
これより下の優先順位よりは高いが、何より低いかは未定義。 - TernaryPrecedence
三項演算子(?:
) - AssignmentPrecedence
代入演算子(=
+=
-=
*=
/=
%=
<<=
...)
独自のprecedencegroupを定義する
独自の優先順位では、以下の4つを設定することができます。必須項目はありません。
- lowerThan ... どの優先順位より低いか
- higherThan ... どの優先順位より高いか
-
associativity ...
left
|right
|none
-
assignment ...
true
|false
いくつか例を示します。
例1
先述の%%
にカスタムの優先順位を設定してみます。
precedencegroup CustomPrecedence {
lowerThan: MultiplicationPrecedence
higherThan: AdditionPrecedence
associativity: left
}
infix operator %%: CustomPrecedence
func %%(left: Int, right: Int) -> Int {
((left % right) + right) % right + 1
}
これは、乗算よりも低く加算よりも高い優先度です。
associativityは、left
(右結合), right
(左結合), none
(非結合) のいずれかを設定できます。
left: a %% b %% c -> (a %% b) %% c
right: a %% b %% c -> a %% (b %% c)
none: a %% b %% c -> コンパイルエラー (error: adjacent operators are in non-associative precedence group 'CustomPrecedence')
例2
次は、assignmentを使った例を紹介します。
precedencegroup MyPrecedence {
lowerThan: TernaryPrecedence
higherThan: AssignmentPrecedence
assignment: false
}
infix operator %%=: MyPrecedence
func %%=(left: inout Int, right: Int) {
left = left %% right
}
struct Test {
var value: Int
}
var test: Test? = Test(value: -10)
test?.value %%= 3 // error: cannot convert value of type 'Int?' to expected argument type 'Int'
assignmentでは、代入演算子を定義する際にオプショナルチェーンで表現された変数に対して代入することを許すかを設定することができます。
AssignmentPrecedenceでは、この設定はtrue
になっています。
参考