[Swift] Tuple タプルの七不思議

# Swiftのタプルには謎が多い

具体的に7つの不思議があるわけではありませんが、コード例を交えながらタプルの不思議<i>（魅力）</i>をお伝えできればと思います。

_特に断りがない場合は、執筆時点の最新である[**Swift 5.9.2**](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/aboutthelanguagereference)を前提とします。_

<br>まず、はじめは、

いきなりですが、

# `` `Tuple型` ``は存在しません

タプルに`` `型` ``は存在しますが、`Tuple`といった<i>基底型</i> や<i>プロトコル</i> は`定義されていない`、ということです。

例えば、`x`が`Int`型かを判定する場合に`` `x is Int` ``と書きますが、
任意のタプル**x**を`` `x is Tuple` ``では判定はできません。なぜなら、共通的な（汎用的な）Tuple型は存在しないからです。

```swift
print(10 is Int) //true
print((10, 20) is Tuple) //error: cannot find type 'Tuple' in scope
```

- タプルはそれぞれが`` `型` ``

```swift
typealias Tuple1 = (Int, Int)
typealias Tuple2 = (Int, String)
let tuple1 = (10, 20)

print((10, 20) is Tuple1) //true
print(tuple1 is (Int, Int)) //true

print((10, 20) is Tuple2) //false
```

```swift
print(type(of: (10, 20))) //(Int, Int)
print(type(of: (a: 10, b: 20))) //(a: Int, b: Int)
print(type(of: (10, s: "A"))) //(Int, s: String)
```

強いてタプル型を判定するなら、『`(`（左カッコ）で始まる』型、でしょうか。

# タプルの「名前」はオプション

_タプルの各項目には項目名を付けることができますが、オプションのため、項目名のある/なしの混在が可能です。_

- 名前付きタプルでも、名前による区別がされない場合がある

```swift
//typealias Tuple1 = (Int, Int)
print((a: 10, b: 20) is Tuple1) //true

typealias Tuple3 = (a: Int, b: Int)
typealias Tuple4 = (c: Int, d: Int)

print((10, 20) is Tuple3) //true
print((10, 20) is Tuple4) //true
print((a: 10, 20) is Tuple3) //true
print((10, d: 20) is Tuple4) //true

print((b: 10, a: 20) is Tuple3) //false ∴名前（の順序）が異なる
print((c: 10, 20) is Tuple3) //false ∴名前が異なる
```



# タプルの「名前」はオプション（その2）

- 「比較」において、<ins>名前は区別されない</ins>

要素（値）の並びだけで比較される。
（名前を無視して、要素の型の並びが同じであれば比較できる）

```swift
var tuple3 = (a: 10, b: 20)
var tuple4 = (c: 10, d: 20)
print(tuple3 == tuple4) //true
print(tuple3 < (x: 10, y: 21)) //true

print(tuple3 == (10, "A")) // error: tuple type '(Int, String)' is not convertible to tuple type '(Int, Int)'
```


# 代入時、名前付きタプルの扱いは要注意

- 名前付きタプルの変数に、名前なしタプルの代入が可能
- 名前付きタプルの変数に、異なる名前付きタプルの代入は不可（型が異なる）

```swift
//var tuple3 = (a: 10, b: 20)
tuple3 = (20, 30) //OK
tuple3 = (x: 10, y: 20) //error: cannot assign value of type '(x: Int, y: Int)' to type '(a: Int, b: Int)'
```

ここまでは、まだ理解できるが、次が問題。

- 名前が同じであれば、<ins>名前の順序が異なっていても「代入」可能</ins>

```swift
//var tuple3 = (a: 10, b: 20)
let tuple5 = (b: 10, a: 20)
tuple3 = tuple5 //OK
print(tuple3 == tuple5) //false　⭐️
// マジで代入できちゃう、しかも代入後はイコールじゃない??
```



⭐️ 普通に考えると、代入すれば両者は同じになるはず。しかし、false とは何故？
中身を見ると、その理由が分かります。

```swift
print(tuple5) //(b: 10, a: 20)
print(tuple3) //(a: 20, b: 10)）
```

名前の並び（10と20の並び）が異なっています。だから、同値では有りません。
つまり、右辺が名前付きタプルの場合の代入においては、名前の順序は無視されて、<ins>対応する名前の要素に代入される</ins>、ということです。

```swift
//let tuple1 = (10, 20)
//右辺が名前なしタプル`tuple1`の代入は、（位置（順序）で代入）
(tuple3.0, tuple3.1) = (tuple1.0, tuple1.1) //と等価に対して、

tuple3 = tuple5 //この代入は、（名前で代入）
(tuple3.a, tuple3.b) = (tuple5.a, tuple5.b) //と等価となる
//こう書くと、なぜか納得感がある
```



# 「型の判定」、「比較」、「代入」では、名前の扱いが違う

このことによって、アプリ開発で混乱することはあまり無いとは思いますが、
名前あり/なし混在のタプルを使う場合は、注意が必要です。

それにしても、どうしてこのような言語仕様になったのでしょうか？？



<br>話は変わって、

# タプルの要素をKeyPathでアクセスできるか？

普通にKeyPathでアクセスできます。

```swift
let tuple4KeyPath_c = \Tuple4.c
print(type(of: tuple4KeyPath_c)) //WritableKeyPath<(c: Int, d: Int), Int>

print(tuple4[keyPath: tuple4KeyPath_c]) //10
print(tuple4[keyPath: \Tuple4.d]) //20
print(tuple4[keyPath: \Tuple4.0]) //10
print(tuple4[keyPath: \Tuple4.1]) //20

print(tuple4[keyPath: \Tuple4.2]) //error: value of tuple type 'Tuple4' (aka '(c: Int, d: Int)') has no member '2'
```

`tuple4.0`を`tuple4[0]`のようにsubscriptでアクセスできないか考えましたが、簡単にはできそうにありません。
_もし、できるならば、`tuple4[n]`のように**動的**に要素にアクセスできるのだが・・・_


# タプルの比較

初期のSwiftでは、タプル同士の比較ができませんでしたが、
どこかのバージョンでタプル同士の比較ができるようになり、すごく便利になりました。
タプルの要素を左から順に比較していきます。

```swift
let compareTuple = (1, 10, 10.0, "Z") < (1, 11, 1.1, "A")  //true
```

これを応用すると、複数のソート項目によるソートの比較関数が簡単に定義できます。ソート対象がタプル配列である必要はありません。structやclassの配列にも使えます。

```swift
typealias Point = (x: Int, y: Int)
let points: [Point] = [(8, 2), (1, 9), (7, 5), (5, 4), (5, 0)]
let sotredPoints = points.sorted(by: <) // $0 < $1
print(sotredPoints)
//[(x: 1, y: 9), (x: 5, y: 0), (x: 5, y: 4), (x: 7, y: 5), (x: 8, y: 2)]

//（タプル以外の配列でも可）X小さい、Y大きい順
let sotredPoints2 = points.sorted { ($0.x, $1.y) < ($1.x, $0.y) }
print(sotredPoints2)
//[(x: 1, y: 9), (x: 5, y: 4), (x: 5, y: 0), (x: 7, y: 5), (x: 8, y: 2)]
```

タプルを使わずに「X小さい、Y大きい順」の比較関数を定義すると、下記のように書く必要がありました。
```swift
//let sotredPoints2 = points.sorted { ($0.x, $1.y) < ($1.x, $0.y) }
let sotredPoints2 = points.sorted { 
    ($0.x == $1.x) ? ($0.y > $1.y) : ($0.x < $1.x)
}
```

ソート項目が3つ4つと増えると、ネストが増えて 可読性が下がります。タプルで書く方が断然分かり易いです。


# タプルは`Equatable`や`Comparable`ではない

タプルは、単独では、`==`で同値判定したり、`<`や`>`などで大小比較できるのですが、Swiftプロトコルの`Equatable`や`Comparable`には準拠していません。
このため、タプル配列に対してcontainsメソッドは使えないし、sortも比較関数を書かないと使えません。

```swift
var points = [(8, 2), (1, 9), (7, 5), (5, 4), (5, 0)]
if points.contains((8, 2))  { /*・・・*/ } //error: cannot convert value of type '(Int, Int)' to expected argument type '((Int, Int)) throws -> Bool'
points.sort() //error: type '(Int, Int)' cannot conform to 'Comparable'
```

# タプルは`Hashable`でもない

さらに、`Hashable`にも準拠していないため、Set型の要素に使えないし、Dictonary型のkeyとしても使えないです。


```swift
let pointSet = Set(points) //error: type '(Int, Int)' cannot conform to 'Hashable'
var pointDict: [(Int, Int): Int] = [:] //error: type '(Int, Int)' does not conform to protocol 'Hashable'
```

これらを不便と感じる方も多いと思います。自分もその一人です。


# タプルを`Equatable`、`Comparable`や`Hashable`に準拠させる

しかし、タプルは`extension`が使えません💢。

```swift
typealias Point = (x: Int, y: Int)
extension Point { } // error: non-nominal type 'Points' (aka '(x: Int, y: Int)') cannot be extended
```

↓構造体を定義するしかないのです。
（Genericには出来ないが、マクロ化はできるかも？）

（2024.7.16追加）マクロ化しました →　[[Swift] HashableTuple を生成する Swift Macro を作ってみた](https://qiita.com/nak435/items/81ebf1f89074cce5a1c5)

```swift
struct Tuple {
    typealias Element = (x: Int, y: Int) //カスタマイズする
    let tuple: Element
    init(_ _tuple: Element) { self.tuple = _tuple }
    func callAsFunction() -> Element { self.tuple }
}
extension Tuple: Equatable {
    static func == (lhs: Tuple, rhs: Tuple) -> Bool {
        lhs.tuple == rhs.tuple
    }
}
extension Tuple: Comparable {
    static func < (lhs: Tuple, rhs: Tuple) -> Bool {
        lhs.tuple < rhs.tuple
    }
}
extension Tuple: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(tuple.x)
        hasher.combine(tuple.y)
    }
}
extension Tuple: CustomStringConvertible {
    //おまけで`CustomStringConvertible`にも準拠しておく
    var description: String {
        "\(type(of: self))(\(tuple.x), \(tuple.y))"
    }
}
```

_（ ↑準拠するプロトコルと必要メソッドの対応を分かり易くするため、敢えてそれぞれをextensionしています。）_

```swift:使い方
let tupple = Tuple((10, 20))
let (x, y) = tupple()
print(x, y) //10 20

var array = [Tuple]()
if array.contains(tupple)  { /*・・・*/ } //ok
array.sort() //ok
```

しかし、上記の例`Tuple`の`(x: Int, y: Int)`ように、同じ型の要素だけで 要素数も 2つ3つ程度なら、わざわざ`Equatable`、`Comparable`や`Hashable`のためのコードを書かずに、単に配列にして対応する場合が多いです。

```swift
//本来の タプル (x, y) を、配列 [x, y] で代用。
let set = Set<[Int]>()
let dict = [[Int]: Int]()
```

要素の型が異なる場合には`Array<Any>`にすれば可能ですが、参照するときの型変換が面倒かも知れません。


# タプルは、`Codable`でもない

タプルは一切のどのプロトコルにも準拠していません。`Equatable`、`Comparable`、`Hashable`だけの話では無いのです。

当然、`Codable`にも準拠していません。これによる影響は、例えば、タプルをjsonに変換したり、jsonからタプルに変換することができません。
独自の型を定義して、`Codable`に準拠させる必要が有ります。ここまで来れば当然のことですね。

くどくなってきた感がありますので、プロトコルの話はここで終わりにします。

<br>話は変わって、

# タプルの要素をイテレートできるか？

`extension`が使えないため、`IteratorProtocol`に準拠することは難しいですが、一旦、配列に変換することで代用できます。

```swift
let tuple8 = (a: 10, c: "A", 1.0, e: (x: 10, y: 20), [10, 20])

let array8 = Mirror(reflecting: tuple8).children.map { ($0.label ?? "unlabeled", $0.value) }
print(type(of: array8)) //Array<(String, Any)>
print(array8)
//[("a", 10), ("c", "A"), (".2", 1.0), ("e", (x: 10, y: 20)), (".4", [10, 20])]

let type8 = array8.map { "\(type(of: $0.1))" }
print(type8)
//["Int", "String", "Double", "(x: Int, y: Int)", "Array<Int>"]
```

タプルがネストしている場合は、これを再帰的に変換する必要がありますが、名前、値、要素の型が取得できます。

```swift
for (type, (name, value)) in zip(type8, array8) {
    print((name, type, value))
}
//("a", "Int", 10)
//("c", "String", "A")
//(".2", "Double", 1.0)
//("e", "(x: Int, y: Int)", (x: 10, y: 20))
//(".4", "Array<Int>", [10, 20])
```

配列化出来たので、各要素の動的アクセスやランダムアクセスが可能になりますが、実用的かと問われると、？です。

<br>

これまでは、タプル型、タプル式についての内容でした。

次はパターンとしてのタプルについて、です。


# パターンとしてのタプル

switchやif文のcaseラベルに書くパターンのことです。

## if文（guard文）でのパターン

(x, y)で示す座標があったとして、その座標が範囲を超えているかどうかを、簡単にチェックできます。

```swift
let (Width, Height) = (20, 10)
let x = 10, y = 20
if case (0 ..< Width, 0 ..< Height) = (x, y) {
    //範囲内 
} else {
    //範囲外
}

//guard case (0 ..< Width, 0 ..< Height) = (x, y) else { return }
```

`=`演算子が代入に見えてしまうのが残念ですが・・・。


## switch文のパターン

switch文のcaseパターンには、ほんと多彩な書き方ができます。
（例を作るのが面倒だったので、[公式ドキュメント](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/controlflow)からのコピペで失礼します）

```swift
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("\(somePoint) is at the origin")
case (_, 0):
    print("\(somePoint) is on the x-axis")
case (0, _):
    print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
    print("\(somePoint) is inside the box")
default:
    print("\(somePoint) is outside of the box")
}
// Prints "(1, 1) is inside the box"
```

- Value Bindings

```swift
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
    print("on the x-axis with an x value of \(x)")
case (0, let y):
    print("on the y-axis with a y value of \(y)")
case let (x, y):
    print("somewhere else at (\(x), \(y))")
}
// Prints "on the x-axis with an x value of 2"
```

```swift
let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
    print("On an axis, \(distance) from the origin")
default:
    print("Not on an axis")
}
// Prints "On an axis, 9 from the origin"
```

- where （おまけ）

```swift
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
    print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
    print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
    print("(\(x), \(y)) is just some arbitrary point")
}
// Prints "(1, -1) is on the line x == -y"
```

<i>where句はホント便利ですね。for文で使うことが度々有ります。</i>

# enumにおけるタプル

Swiftのenumは、単に「場合分け」を表すだけではなく、それに応じた値（Associated Values）を保持できるところが特徴です。

```swift
enum Barcode {
    case upc(system: Int, Int, Int, Int)
    case qrCode(String)
}

var productBarcode = Barcode.upc(system: 8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
```

このenum変数をswitch文で判定するときに、前項のcaseパターンが活躍します。

```swift
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
    print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
    print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."
```

enumのAssociated Valuesは、if文やswitch文のcaseパターンでしか、読み取る（受け取る）ことができません。

<br>話が逸れますが、
`case let`のところは、`case var`とも書けることをご存知でしょうか？
`var`で受け取ると、それは**変数**となり「値の更新」ができます。
`if let`や`guard let`でも同じです。


<br>次は、またタプル型に戻りまして、

# 要素数0のタプル

Swiftでは、要素を一つも持たない要素数0のタプル（空のタプル）が認められています。

```swift
let emptyTuple = ()
```

実はこれは、`Void`の実体です。Swiftの言語仕様で`Void`は「空のタプル」と定義されています。

```swift
typealias Void = ()
```

`Void`は、関数定義で戻り値を定義しなかった場合に仮定される型です。

```swift
//どちらも戻り値はVoid
func noResultFounction() /* -> Void を省略 */ { /*・・・*/ }
func voidResultFounction() -> Void { /*・・・*/ }
```

`Void`を返す関数は、`return`で`Void`の関数を呼び出すことができます。
（こういった使い方はあまり無いと思いますが。。。）

```swift
func noResultFounction() { 
    return //普通は書かないが、
    return Void() // とも書ける。さらに、
    return print(0) // Voidを返す関数も書ける
}
```

あえて、付け加えておきますが、
`Void`を引数にとる関数を定義した場合は、これを呼び出すときは、引数として`Void`を渡す必要があります。戻り値`Void`のように、省略することはできません。

```swift
func funcVoid(_ arg: Void) { }
funcVoid(Void()) //ok
funcVoid(()) //ok
funcVoid() //error: missing argument for parameter #1 in call
```

<i>C / C++ の`void func(void);`では、呼び出す時に引数は書きませんが、それとは違うということです。念の為。</i>


# 要素数1のタプル

残念なことに、Swiftでは、要素数1のタプルは定義できません。

```swift
var t1 = (1) //と書いても、単にInt型の変数
print(type(of: t1)) //Int

var t2 = ("A") //と書いても、単にString型の変数
print(type(of: t2)) //String
```

また、Pythonのように `(1,)`とも書けません。

```swift
var t3 = (1, ) //error: unexpected ',' separator
```

<br>ちなみに、
どこかのバージョンで、Arrayの最終要素の後ろの`,`（カンマ）の記述を認めるようになりましたが、 タプルでは許していないようです。
（2個目以降のカンマならば認めて欲しいところですが、`(1,)`を認めないことに起因しているのかも知れません。）

```swift
let a4 = [1, 2, 3, 4, ] //OK
let t4 = (1, 2, 3, 4, ) //error: unexpected ',' separator
```


<br>以上で、タプルを語り尽くせたと思いますが、もし抜け漏れや間違いなどがあれば、ご指摘ください。

この記事が何かの参考になれば幸いです。


<hr>

最後に、 Swift言語[文法サマリー](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/summaryofthegrammar)に登場する「Tuple」の箇所を抜き出して終わりにします。

# Tuple出現箇所

![tuple.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/5d71994b-0e8f-e9e5-a6bc-5d3a55cc27a5.png)