アプリを作成していると、規定の演算子(+, ==, ! など)では不十分な処理を記述しなくてはならない場合があります。
例えば、Swiftでのべき乗などです。
ここでは、演算子の上書き(operator overloading)、そして作成方法について紹介していきます。
Operator Overloadingとは
例えば、DiffableDataSourceのItemIdentifierに各CellのViewModelが入る場合には、各CellはHashableでなくてはなりません。
final class DataSource: UICollectionViewDiffableDataSource<Int, ItemIdentifier> {
init(_ collectionView: UICollectionView) {
super.init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell",
for: indexPath) as! CollectionViewCell
cell.viewModel = itemIdentifier.viewModel
return cell
}
}
}
// Type 'ItemIdentifier' does not conform to protocol 'Equatable'
// Type 'ItemIdentifier' does not conform to protocol 'Hashable'
struct ItemIdentifier: Hashable {
let viewModel: CollectionViewCellViewModel
let id: String = UUID().uuidString
}
このようにEquatableやHashableに準拠したいけど、そのままだとできない場合、以下に示すような演算子の上書き(Operator Overloading)が使用されます。
struct ItemIdentifier: Hashable {
...
// Equatableに準拠。元々存在する'=='をItemIdentifierで使う際の定義に上書きしている。
static func == (lhs: ItemIdentifier,
rhs: ItemIdentifier) -> Bool {
lhs.hashValue == rhs.hashValue
}
// Hashableに準拠するために必要な関数定義。一意にするための各種パラメータをhasher.combine()に渡す。
func hash(into hasher: inout Hasher) {
hasher.combine(viewModel.cellText)
}
}
Custom Operatorについて
既存の演算子にはない演算をしたい場合、自分で演算子を作成することができます。
例えば、べき乗などは多くのプログラミング言語に搭載されていますが、Swiftではまだありません。
ここではCustom Operatorの作り方について詳しく見ていきたいと思います。
前提
演算子には大きく分けて「単項演算子」、「二項演算子」、「三項演算子」の三種類があります。
- 単項演算子…一つの対象に付与する演算子です。
!
が代表例で、!value
のように前側に付けば否定に、value!
のように後ろ側につけばForce-unwrappingとして使うことができます。 - 二項演算子…二つの対象に付与する演算子です。
+, -, *, /, %, ==, <
など、二つの対象に挟まれる形で使われます。ほとんどの演算子はこれにあたります。 - 三項演算子…三つの対象に付与する演算子です。Swiftでは1行条件分岐(A ? B : C)のみがこれにあたります。
ここでは、二項演算子の作り方を紹介します。
実際の作り方
他の言語同様、「**」でべき乗を表現したいものとします。
まず、**が演算子と認識してもらうために、以下を記述します。
// infixは二つに挟まれる形で使用されるoperatorに使用する修飾子。
infix operator **
そして、実際の処理の内容をOperator Overloadingと同様に記述します。
func **(base: Int, power: Int) -> Int {
precondition(power >= 2)
var result = base
for _ in 2...power {
result *= base
}
return result
}
すると、結果は以下のようになります。
print(2 ** 2) // 4
print(2 ** 8) // 256
ちなみに、単項演算子を作りたい場合は以下のようにします。
// %を作る
postfix operator %
postfix func %(num: Float) -> Float {
return num / 100
}
print(1.0%) // 0.010000
様々な型に適応させる
Intだけでなく、Int32など様々なIntegerに適用したい場合、Integer型はBinaryInteger型に準拠することを利用して、以下のように記述します。
func **<T: BinaryInteger>(base: T, power: Int) -> T {
precondition(power >= 2)
var result = base
for _ in 2...power {
result *= base
}
return result
}
また、自身をべき乗した結果に移し替えたい時、*=
のような演算子が欲しくなります。そちらは以下のように記述できます。
// 既出の定義は省略
infix operator **=
func **=<T: BinaryInteger>(lhs: inout T, rhs: Int) {
lhs = lhs ** rhs
}
struct CustomOperatorView: View {
@State var currentValue = 2
var body: some View {
Button("exponent") {
currentValue **= 2
}
Text("\(currentValue)")
}
}
一回押下時 | 二回押下時 |
---|---|
![]() |
![]() |
|
計算順について
以下のような場合を考えます。
struct CustomOperatorView: View {
...
var body: some View {
...
// Adjacent operators are in unordered precedence groups 'MultiplicationPrecedence' and 'DefaultPrecedence'
Text("\(currentValue * 2 ** 3)")
}
}
計算の優先順位がわからないため、コンパイルエラーが発生しています。()で囲うことによって順番は指定できますが、いちいち指定するのも面倒です。
Swiftでは、Precedence Groupという、計算順に関するグループが存在します。例えば、+にはAdditionPrecedence
が、*にはMultiplicationPrecedence
が割り当てられています。
Precedence Groupは以下のような書き方で、自分で定義することができます。
precedencegroup ExponentiationPrecedence {
// 連続した場合、右方向に計算するか、左方向に計算するか
associativity: right
// Precedence Groupの立ち位置の指定
higherThan: MultiplicationPrecedence
}
Precedence Groupはこちらに、優先度順に載っています。
(DefaultPrecedenceGroup
はTernaryPrecedence group
の一つ上、と出ていますね。)
まとめ
- Operator Overloadingによって既定の演算子の定義を上書きできる。
- Custom Operatorによって既定の演算子に存在しない演算子を定義できる。
- 各演算子は
postfix
,prefix
,infrx
のいずれかの形で定義できる。 - 計算の優先順位を決めるため、Precedence Groupを定義する必要がある。
よく使う処理を関数よりも簡潔に書くことのできる方法なので、CustomOperatorは是非とも使いこなしたい方法です。
最後に
こちらは私が書籍で学んだ学習内容をアウトプットしたものです。
わかりにくい点、間違っている点等ございましたら是非ご指摘お願いいたします。