パターンマッチング「func ~=」のデフォルト実装は「func ==」

TL;DR

パターンマッチングのルールを自分で定義するときは、~=演算子、つまり「func ~=」を定義することになるわけですが、未定義の場合のデフォルト実装は「func ==」を使うようになっている、という話です。そして、NSObjectのデフォルトはisEqual()が使われる、という話です。

Swiftのパターンマッチング

Swiftでオブジェクトを使ったパターンマッチングをする場合、次のように書きます。

struct A {}
let a = A()
let b = A()

switch a {
case a: print("a") // → a
case b: print("b")
defalut: print("default")
}

このときのパターンマッチングに使われるのは、「func ~=」なる演算子です。この演算子がBool型を返すことによってパターンマッチするかどうかが決まります。

それでは、目的の型が~=を用意していなかった場合にはどのようになるのでしょうか。この場合、デフォルト処理が使われることになり、「func ==」が使われます。==の結果を、~=の結果として利用しているのです。そして、もしユーザーやライブラリで独自の~=を定義すると、そちらが使われます。

ケース 使われる処理
何も定義していない デフォルト実装の~=を使う。実態は==
~=を定義 定義した~=を使う
==だけ定義 ~=のデフォルト実装から定義した==を使う

この振る舞いはドキュメントにもきちんと書いてあり、The Swift Programming Languageの[Language References]-[Expression Pattern]にて以下のように書かれています。

By default, the ~= operator compares two values of the same type using the == operator.

それでは実装はどうかというと、swiftソースの stdlib/public/core/Policy.swift を見ると次のように定義されていて、ドキュメントのとおりになっているとわかります。

//===----------------------------------------------------------------------===//
// Standard pattern matching forms
//===----------------------------------------------------------------------===//

// Equatable types can be matched in patterns by value equality.
@_transparent
@warn_unused_result
public func ~= <T : Equatable> (a: T, b: T) -> Bool {
  return a == b
}

以上です。

ちなみにこのstdlib/public/core/Policy.swift、他にもいろいろな定義がありとてもおもしろいソースファイルになっています。ぜひ一度読んでみることをおすすめします。

なぜ調べてみたのか

せっかくなので、なぜこんなことを調べたかも記録に残しておきます。

NSObjectを継承したクラスでパターンマッチングをしていたところ、マッチングが行われるたびにNSObjectisEqual()メソッドが呼ばれていることに気づきました。

class Foo: NSObject {
    override func isEqual(object: AnyObject?) -> Bool {
        return super.isEqual(object) // ←ここにbreakpointを貼ると止まる
    }
}
let a = Foo()
switch a {
case a: print("a") // a
default: break
}

これはなぜだろう、isEqual()~=とは、どのようにして繋がっているのだろうということが気になり、調べてみたというのがことの発端です。

stdlib/public/SDK/ObjectiveC/ObjectiveC.swift の末尾で、

// NSObject implements Equatable's == as -[NSObject isEqual:]
// NSObject implements Hashable's hashValue() as -[NSObject hash]
// FIXME: what about NSObjectProtocol?

extension NSObject : Equatable, Hashable {
  :
@warn_unused_result
public func == (lhs: NSObject, rhs: NSObject) -> Bool {
  return lhs.isEqual(rhs)
}
   :

となっていて、==isEqual() と呼ばれていました。