3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Swift] 独自演算子の優先順位を設定する

Last updated at Posted at 2023-11-23

はじめに

独自演算子を定義する時には、演算子の優先順位についての問題が気になってきます。

例として、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になっています。

参考

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?