0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftのCustomOperator概要とその使い方

Posted at

アプリを作成していると、規定の演算子(+, ==, ! など)では不十分な処理を記述しなくてはならない場合があります。

例えば、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)")
    }
}
一回押下時 二回押下時
Simulator Screenshot - iPhone 15 Pro - 2024-05-16 at 11.06.34.png Simulator Screenshot - iPhone 15 Pro - 2024-05-16 at 11.06.36.png

|

計算順について

以下のような場合を考えます。

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はこちらに、優先度順に載っています。
(DefaultPrecedenceGroupTernaryPrecedence groupの一つ上、と出ていますね。)

まとめ

  • Operator Overloadingによって既定の演算子の定義を上書きできる。
  • Custom Operatorによって既定の演算子に存在しない演算子を定義できる。
  • 各演算子はpostfix, prefix, infrxのいずれかの形で定義できる。
  • 計算の優先順位を決めるため、Precedence Groupを定義する必要がある。

よく使う処理を関数よりも簡潔に書くことのできる方法なので、CustomOperatorは是非とも使いこなしたい方法です。

最後に

こちらは私が書籍で学んだ学習内容をアウトプットしたものです。
わかりにくい点、間違っている点等ございましたら是非ご指摘お願いいたします。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?