Swift3におけるカスタムオペレータについて気になったので調べてみました。
構成
検証を行うため、以下の構成を準備しました。
OperatorFrameworkはCocoa Touch Frameworkであり内部にOperator.swiftを持っています。Operator.swift内にてカスタムオペレータを定義しています。ApplicationはiOS Applicationです。Applicationの内部でimport OperatorFrameworkをすることでカスタムオペレータを使用するという構成です。
検証
- 通常使用動作確認
-
Application側でprecedencegroupを再定義する -
Application側でカスタムオペレータ実装を書き換える -
OperatorFramework側で使用しているオペレータをApplicationで再実装する - 同じオペレータを実装する別のFrameworkをimportする
通常使用動作確認
Operator.swift内の実装は以下のようにします。
precedencegroup Base {
associativity: left
lowerThan: AdditionPrecedence
}
infix operator <+> : Base
infix operator <*> : Base
public func <+> (lhs: Int, rhs: Int) -> Int {
print("<+>: called `Base`")
return lhs + rhs
}
public func <*> (lhs: Int, rhs: Int) -> Int {
print("<*>: called `Base`")
return lhs * rhs
}
左結合にし、AdditionPrecedenceよりも優先度を低くしています。<+>と<*>はそれぞれ2つのIntパラメータを取り、print logを吐き出しつつ和と積の結果を返します。
これをApplication側で使用します。
import UIKit
import OperatorFramework
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(" ")
print(" -------------------------------- ")
print(2 <+> 3 <*> 4)
print(" -------------------------------- ")
print(" ")
}
}
ログはこんな感じに。
左結合なので<+>である和が先に計算され、その後<*>である積が計算されます(20 = (2+3)*4)。
Application側でprecedencegroupを再定義する
では次にApplication側でprecedencegroupを再定義してみます。具体的には以下の通りに修正します。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(" ")
print(" -------------------------------- ")
print(2 <+> 3 <*> 4)
print(" -------------------------------- ")
print(" ")
}
}
/// 以下を追加
precedencegroup Hacked {
associativity: left
higherThan: MultiplicationPrecedence
}
infix operator <*> : Hacked
すると出力がこう変わります。
左結合であるのは変わらないのですが、優先度がHacked>Baseであるため14=2+(3*4)の順番に処理されます。つまり、framework側で複数のオペレータを実装している場合、 意図しない順序で実行される可能性がある ということです。
ちなみに、今回はHackedという名前にしましたが、Baseという同じ名前にしても問題なくビルドが通り、同様の挙動になります。prefix等は気にしないで良さそうです。
Application側でカスタムオペレータ実装を書き換える
次に実装まで記述します。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(" ")
print(" -------------------------------- ")
print(2 <+> 3 <*> 4)
print(" -------------------------------- ")
print(" ")
}
}
precedencegroup Hacked {
associativity: left
higherThan: MultiplicationPrecedence
}
infix operator <*> : Hacked
/// 以下を追加
func <*> (lhs: Int, rhs: Int) -> Int {
print("<*>: called `Hacked`")
return lhs
}
すると出力はこうなります。
Application側で実装された<*>が呼ばれ、5=2+3と成っています。では、<*>をOperatorFramework側で使用していた場合はどうなるのでしょうか。
OperatorFramework側で使用しているオペレータをApplicationで再実装する
Application側はそのままにOperatorFramework側を以下のように書き換えます。
precedencegroup Base {
associativity: left
lowerThan: AdditionPrecedence
}
infix operator <+> : Base
infix operator <*> : Base
public func <+> (lhs: Int, rhs: Int) -> Int {
print("<+>: called `Base`")
//return lhs + rhs
return lhs <*> rhs // <- New
}
public func <*> (lhs: Int, rhs: Int) -> Int {
print("<*>: called `Base`")
return lhs * rhs
}
すると出力は以下のようになります。
OperatorFramework.<+>内で使用している<*>はOperatorFrameworkのものを呼び出しています。
同じオペレータを実装する別のFrameworkをimportする
新たにFramework(ここではOtherFrameworkとする)を定義し、その中で<+>と<*>を実装します。その状態でApplication側でimport OtherFrameworkするとビルドNGとなります。ただし、Application側で再実装するとビルドOKとなります。
import UIKit
import OperatorFramework
import OtherFramework
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(" ")
print(" -------------------------------- ")
print(2 <+> 3 <*> 4)
print(" -------------------------------- ")
print(" ")
}
}
precedencegroup Hacked {
associativity: left
higherThan: MultiplicationPrecedence
}
infix operator <*> : Hacked
infix operator <+> : Hacked
// 以下がないと`OperatorFramework`と`OtherFramework`の実装が衝突してビルドNG
func <*> (lhs: Int, rhs: Int) -> Int {
print("<*>: called `Hacked`")
return lhs
}
func <+> (lhs: Int, rhs: Int) -> Int {
print("<+>: called `Hacked`")
return lhs
}
まとめ
- importする側で同じ演算子を再定義すると優先度が変化する
- 同じオペレータを定義している2つのframworkをimportしようとするとビルドNG
部分的にimportするか部分的にimportしない仕組みがないうちは、カスタムオペレータをframwork側が提供するのは避けた方がいいのかもしれません。




