[Swift] ~= 演算子についての考察

# はじめに

自分が物心ついたときから、Swiftには、**~=** と表記する**二項演算子**がありました。しかしながら、この演算子に『本来,右辺と左辺が逆じゃ無いの？』とずっと疑問を抱いていました。この度、それが解決したので**周知の事実**とは思いますが、ここにまとめておきます。

# ~= 演算子とは？

この演算子は、次のように書きます。

```swift
var value: Int
  :  :
if  0 ..< 10 ~= value { 真 } else { 偽 }
if 10 ... 19 ~= value { 真 } else { 偽 }
```

つまり、**ある値**が**期待する範囲**に**収まっているか**どうかを調べる演算子で、次のコードと等価です。

```swift
if  0 <= value && value <  10 { 真 } else { 偽 }  // ..<
if 10 <= value && value <= 19 { 真 } else { 偽 }  // ...
```

**~=** はvalue(変数)を2度書く必要がなく、コードも読みやすくなると思います。ですが、**右辺と左辺が逆**ならもっと**スッキリ**すると思いませんか？ 次のような書き方です。

```swift
if value ~= 0 ..< 10 { 真 } else { 偽 }
```

for文だとこう書きますしね。[^1]

```swift
for item in 0 ..< 10 { 処理 }
```

_それなら独自に演算子を定義すれば_・・・となるわけですが、そもそも、**なんで最初から**こうなってないのか調べてみました。


# ~= の本来の目的(？)

まずはSwiftの標準ライブラリのドキュメントには[**こう**](https://developer.apple.com/documentation/swift/closedrange/2963623)説明されています。`No overview available.`　えっ！、`ClosedRange`には何の説明もありません。しかし`Range`の方には[**次の説明**](https://developer.apple.com/documentation/swift/range/2428743)がありました。

> ![スクリーンショット 2020-04-18 7.57.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/41d7e872-f74c-717d-adef-eddcc20225d4.png)

重要なのは2つ目の説明です。
> _**〜=** 演算子は、パターンマッチングのcase文で内部的に使用されます。 case文の範囲と照合すると、この演算子はバックグラウンドで呼び出されます。_ (Google翻訳)


これですね。「**case**文のパターンマッチングに使われる」と明記されています。しかし、まだ左辺右辺の**謎**は解明されません。そこで、Swift **言語仕様**はどうなっているのか、これもAppleのドキュメントで確認しました。**〜=** 演算子に関する説明が[**ここ**](https://docs.swift.org/swift-book/ReferenceManual/Patterns.html#grammar_expression-pattern)にあります。


![スクリーンショット 2020-04-18 8.11.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/ee0cd8e9-ad15-b28f-6ca8-f050db1e4ae7.png)

ここで分かったことは、2つ目の説明の方で「**〜=** 演算子をオーバーロードして、カスタマイズできる」ことです。これは独自のクラスなどをこのパターンに適合させる場合の説明であって、まだ**謎**は解けません。次はズバリ**if**文の文法を確認しました。[**ここ**](https://docs.swift.org/swift-book/ReferenceManual/Statements.html#grammar_if-statement)にあります。
<img width="455" alt="スクリーンショット 2020-04-18 9.13.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/421cd9d1-c156-3bdb-ac8b-58b96f5cb50f.png">
`condition-list`のところです。以降はスクショを省略しますが、`condition-list` → `condition` →　`case-condition` →　`case pattern` → `pattern` →　`expression-pattern`と続きます。
前述の**case**文のところに出てきた`expression-pattern`にたどり着きました。
えっ！、どういうこと？　**if文の条件式にcaseラベルが書ける**ってこと？　どうやらそうらしいです（気にしたことがなかった）。ネットをググると（_2015年とちょっと古い情報ですが_）以下の[**コード例**](http://austinzheng.com/2015/09/23/pmatch-control-flow/)を見つけました。
![スクリーンショット 2020-04-18 9.59.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/40d180ed-e2c7-545b-bb7e-4932eb335e57.png)

上のコードをよく眺めると、冒頭で説明した**~=**の使い方と似たような構文です。

```swift
     if 0 ..< 10 ~= value { 真 } else { 偽 }  //冒頭の ~= の例
if case 0 ..< 10  = value { 真 } else { 偽 }  //if case の例
```

要は`case =`が`~=`に置き換わったということです。つまり、構文を似たようにする明確な意図があって、**~=**演算子の時に左辺と右辺を入れ替えたりしてはダメだったんです（多分）。これを知って『_**なるほど、それで範囲の方が左なのか**_』と納得しました。
前述の説明で**~=**を**オーバーロード**できるとあったので、ちょっと試してみました。

```swift:テストコード
import Foundation

for value in [9, 10] {

    if 0 ..< 10 ~= value { print("true") } else { print("false") }
    
    if case 0 ..< 10 = value { print("true") } else { print("false") }
    
    switch value {
        case 0 ..< 10: print("case match")
        default: print("default")
    }
    
    print()
}

extension Range {
    static func ~= (left: Range<Bound>, right: Bound) -> Bool {
        print("Range(\(left.lowerBound)-\(left.upperBound)) ~= \(right)")
        return left.lowerBound <= right && right < left.upperBound
    }  
}
```

すごいです、本当にオーバロードした**~=**関数が呼ばれています。

```log:結果
Range(0-10) ~= 9
true
Range(0-10) ~= 9
true
Range(0-10) ~= 9
case match

Range(0-10) ~= 10
false
Range(0-10) ~= 10
false
Range(0-10) ~= 10
default
```

というわけで、

# 独自に =~ 二項演算子を定義する

**~=** 演算子がcase文のパターンマッチングの為にあるとするなら、自分が思う**右辺に範囲**がくる独自の **=~** 演算子を定義することにしました。
定義は次のようになります。

```swift
infix operator  =~ : ComparisonPrecedence
infix operator !=~ : ComparisonPrecedence
func  =~ <T: FixedWidthInteger>(left: T, right: Range<T>) -> Bool { right ~= left }
func !=~ <T: FixedWidthInteger>(left: T, right: Range<T>) -> Bool { !(right ~= left) }
func  =~ <T: FixedWidthInteger>(left: T, right: ClosedRange<T>) -> Bool { right ~= left }
func !=~ <T: FixedWidthInteger>(left: T, right: ClosedRange<T>) -> Bool { !(right ~= left) }
```

ついでに、**範囲外**を意味する否定形の演算子 **!=~** も定義しました。
対象を`FixedWidthInteger`として、範囲指定の**2**つの書き方`..<`と`...`で`Range`と`ClosedRange`に分かれている [^2] ため、それぞれ**2**組書きました。
~~シンプルに下記のように書きたいのですが、`error: use of undeclared type 'Bound'`と怒られ**1**組で書く方法が分かりませんでした。~~ 
@MasasaM_shiさんに間違いを教えていただき、無事に1組みで定義できるようになりました。

```swift
func  =~ <T: FixedWidthInteger, R: RangeExpression>(left: T, right: R) -> Bool where R.Bound == T { right ~= left }
func !=~ <T: FixedWidthInteger, R: RangeExpression>(left: T, right: R) -> Bool where R.Bound == T { !(right ~= left) }
```



# 終わりに

**~=** の**謎**は自分としてはスッキリ解決しました。いろいろ調べていくと先輩たちが情報を残してくれていて助かりました。ここにまとめたのも、これから同じ謎にぶち当たる方の助けになればとの思いです（_もうそんな人は居ないかも・・・_）。

本当は`for in`に倣って`in`（と`!in`）にしたかったのですが、**`in`**は**Swift**では演算子としては定義できない**予約語**でした。
間違いとか、もっと良い書き方があるとかあれば、ぜひ教えてください。


以上


[^1]: `for in`はなぜ右側に**範囲**がくるのか、この謎も言語仕様と標準ライブラリ仕様を読むと理解できます。`for in`の右にくるのは`collection`で`Sequence`プロトコルに適合する必要があります。`Range`、`ClosedRange`も`Sequence`プロトコルに適合しているからです。
[^2]: 範囲の指定の仕方(記号)は、`..<`、`...` の**2**つですが、両端に指定する数値の書き方にバリエーションがあって、実際はもっと複雑です。これは@koherさんの[**こちら**](https://qiita.com/koher/items/4ae98d71b8eb06ab1b79#range)の記事に解説があります。(Swift4.1)