Edited at

SwiftのTupleの基本とtypealias

Swift の HTTP ライブラリで苦しまないための自作 API クライアント設計 - Qiitaを読んでいた時に何故構造体を使わずにtypealiasを使ってTupleに別名をつけるのかわからなかったのでTupleの基本的な使い方とtypealiasを使ってTupleに別名を与える書き方についてわからなかったことを調べました。


Tupleの基本


Use a tuple to make a compound value—for example, to return multiple values from a function. The elements of a tuple can be referred to either by name or by number.

https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html#



func getPersonalInfo() -> (name: String, address: String, age: Int) {
// ...
}

let myInfo = getPersonalInfo()
// 以下は同じ値が取り出せる。
myInfo.name
myInfo.0

こういう風に関数の返り値に複合値として使用できます。

ドキュメントではAPIへのリクエストの結果をstatus codeを表すIntとエラー詳細を返すStringを持つTupleを返り値として使っていました。


You can decompose a tuple’s contents into separate constants or variables, which you then access as usual:

https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html#



変数名をつけずにまとめて変数宣言


var (name, address, age) = ("hoge", "Tokyo", 20)
print("\(name), \(address), \(age)")


取得したくない位置に_をつけて任意の値のみ取り出し


var (name, _, age) = ("hoge", "Tokyo", 20)
print(name)
print(_) // '_' can only appear in a pattern or on the left side of an assignment
print(age)


値に名前をつけて宣言すると返り値のときと同じ様に名前と連番の両方で取得

    let personalInfo = (name: "nabe", address: "Tokyo", age: 20)

print(personalInfo.name)
print(personalInfo.address)
print(personalInfo.age)


Switch(case)でパターンマッチング

Optional型な複数の値の組み合わせで場合分け。パターンマッチング。

1つの文で複数の値の型、存在、値を照合出来ます。


let age: Int? = nil
let name: String? = nil

switch(age, name) {
// age, nameともにOptional型でnil以外にマッチ
case let (age?, name?):
print(age)
print(name)
// ageがOptional型でnil以外、nameはnilにマッチ
case let (age?, _):
print(age)
// ageがnil、nameはOptional型でnil以外にマッチ
case let ( _, name?):
print(name)
// 両方nilにマッチ
case (_, _):
print("両方nil")
}


Optional binding


let name: String? = "nabe"
let age: Int? = 20
let address: String? = "Tokyo"

if let name = name, let age = age, let address = address {
print("name: \(name), age: \(age), address: \(address)")
}

if case let (name?, age?, address?) = (name, age, address) {
print("name: \(name), age: \(age), address: \(address)")
}


構造体やクラスではなくTupleを使った方が良い時ってどういう時?

Appleのドキュメントによると関連する値を一時的にグループ化して扱うならTupleは便利とのことでした。


Tuples are useful for temporary groups of related values. They’re not suited to the creation of complex data structures. If your data structure is likely to persist beyond a temporary scope, model it as a class or structure, rather than as a tuple.

https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html#


個人的にはTupleは単にインスタンスとして存在するけど構造体やクラスは宣言と初期化の両方が必要なので関数の返り値のような一時的に複数の型を内包する複合値として使うならTupleの方が良いんじゃないかという理解をしています。今までアプリケーションの開発にはRubyしか使ったことがなかったので構造体に加えてTupleはなかなか便利に使えていませんでした。関数の返り値用に構造体を何度か使った覚えもあります。今開発中のアプリケーションでもTupleでで置き換えられ置き換えられる部分が多々合ったのでこの記事を書くにあたっていくつかの構造体をTupleに書き換えました。

※公式のドキュメント(The Swift Programming Language(Swift 4.2))以外に参照した記事

SwiftのTupleを使う - Qiita

Tuples in Swift, Advanced Usage and Best Practices

Tuple vs Struct in Swift

SwiftでTupleとCaseを組み合わせて使う - xykのブログ


typealiasを使ってTupleに別名を付与


typealias PersonalInfo = (name: String, address: String, age: Int)

let personalInfo = PersonalInfo(name: "nabe", address: "Tokyo", age: 20)
print(personalInfo.address)
print(personalInfo.age)
print(personalInfo.name)

単純な構造で受け渡し程度に利用するなら構造体よりTupleを使うべき。また、Tupleにextensionを使うとエラーになった。


// extensionは使えない
// extension PersonalInfo {
// Non-nominal type 'PersonalInfo' (aka '(name: String, address: String, age: Int)') cannot be extended
// }

struct StructPersonalInfo {
var name: String
var address: String
var age: Int
}

let structPersonalInfo = StructPersonalInfo(name: "nabe", address: "Tokyo", age: 20)
print(structPersonalInfo.address)
print(structPersonalInfo.age)
print(structPersonalInfo.name)

extension StructPersonalInfo {
// extension使える
}


typealiasを使ってTupleに別名を付与して使う理由

Tupleは構造体と以下のような点で違いがあります

- パターンマッチングが出来る

- protocolを実装出来ない

- extensionを実装出来ない

- 匿名のmemberにindexでアクセス出来る

- memberのクロージャから他のmemberを参照出来ない(例は↓)


var personalInfo: (name: String, age: Int, hello: () -> Void) = (name: "nabe", age: 20, hello: {() -> Void in
print(name) // Use of unresolved identifier 'name'
print(age) // Use of unresolved identifier 'age'
})

構造体とTupleの共通する部分を利用しながら、Tupleにしか出来ないことを行いたい and 構造体に出来てTupleに出来ないことを行う必要がない時に、構造体ではなくtypealiasを使って別名を付与したTupleを使うのが適切と理解しました。プログラミングの経験が浅いこともあり誤りや非効率なコードがあるかと思います。何かお気づきの際はコメントにてご指摘お願いします。