LoginSignup
12

More than 5 years have passed since last update.

Custom Operator in Swift3

Last updated at Posted at 2016-09-18

Swift3におけるカスタムオペレータについて気になったので調べてみました。

構成

検証を行うため、以下の構成を準備しました。

160917-0002.png

OperatorFrameworkCocoa Touch Frameworkであり内部にOperator.swiftを持っています。Operator.swift内にてカスタムオペレータを定義しています。ApplicationiOS Applicationです。Applicationの内部でimport OperatorFrameworkをすることでカスタムオペレータを使用するという構成です。

検証

  1. 通常使用動作確認
  2. Application側でprecedencegroupを再定義する
  3. Application側でカスタムオペレータ実装を書き換える
  4. OperatorFramework側で使用しているオペレータをApplicationで再実装する
  5. 同じオペレータを実装する別のFrameworkをimportする

通常使用動作確認

Operator.swift内の実装は以下のようにします。

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側で使用します。

ViewController.swift

import UIKit
import OperatorFramework

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        print(" ")
        print(" -------------------------------- ")
        print(2 <+> 3 <*> 4)
        print(" -------------------------------- ")
        print(" ")

    }
}

ログはこんな感じに。

160917-sysoutImage_01.png

左結合なので<+>である和が先に計算され、その後<*>である積が計算されます(20 = (2+3)*4)。

Application側でprecedencegroupを再定義する

では次にApplication側でprecedencegroupを再定義してみます。具体的には以下の通りに修正します。

ViewController.swift
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

すると出力がこう変わります。

160917-sysoutImage_02.png

左結合であるのは変わらないのですが、優先度がHacked>Baseであるため14=2+(3*4)の順番に処理されます。つまり、framework側で複数のオペレータを実装している場合、 意図しない順序で実行される可能性がある ということです。

ちなみに、今回はHackedという名前にしましたが、Baseという同じ名前にしても問題なくビルドが通り、同様の挙動になります。prefix等は気にしないで良さそうです。

Application側でカスタムオペレータ実装を書き換える

次に実装まで記述します。

ViewController.swift
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
}

すると出力はこうなります。

160917-sysoutImage_03.png

Application側で実装された<*>が呼ばれ、5=2+3と成っています。では、<*>OperatorFramework側で使用していた場合はどうなるのでしょうか。

OperatorFramework側で使用しているオペレータをApplicationで再実装する

Application側はそのままにOperatorFramework側を以下のように書き換えます。

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
    return lhs <*> rhs  // <- New
}

public func <*> (lhs: Int, rhs: Int) -> Int {
    print("<*>: called `Base`")

    return lhs * rhs
}

すると出力は以下のようになります。

160917-sysoutImage_04.png

OperatorFramework.<+>内で使用している<*>OperatorFrameworkのものを呼び出しています。

同じオペレータを実装する別のFrameworkをimportする

新たにFramework(ここではOtherFrameworkとする)を定義し、その中で<+><*>を実装します。その状態でApplication側でimport OtherFrameworkするとビルドNGとなります。ただし、Application側で再実装するとビルドOKとなります。

ViewController.swift
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側が提供するのは避けた方がいいのかもしれません。

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
12