https://youtu.be/3uti8xpayGc
今回は The Basics の タプル
に関する補足事項を少しみてから、その次の節のオプショナル
について眺めていこうと思います。タプルの使いどころを決める判断材料を持っている人がいらしたらいろいろ聞かせてくださいね。オプショナルについては、Swift を大きく支える技術ですので、まずはその基礎的なつくりみたいなところを見ていってみようかと思ってます。どうぞよろしくお願いしますね。
———————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #135
00:00 開始 00:34 タプルと構造体の使い分け 02:07 何を以って複雑なデータ構造とする? 03:33 タプルを使うのが適切そうな場所は? 04:39 switch のタプルパターンで使う 06:34 タプルと識別子パターンを組み合わせて使う 07:39 タプルを構造体で表現し直してみる 13:21 タプルを構造体で表現するまでもなさそうな場面 14:16 関数型を構造体で表現してみる 17:32 代入式とタプルパターン 19:35 代入式でタプルを使わない場合 21:32 どういうところで複雑と見るのかの所感 22:04 単純なグループとするか、振る舞いを備えるか 24:03 非公称型に振る舞いを所属させられない 25:18 タプルにするか構造体にするかの判断基準についての意見 25:46 Apple はどこでタプルを使っている? 26:41 GitHub から記号を含めて検索する 34:35 タプルと構造体のパフォーマンス優位性 36:04 タプルを用いた比較とスワップの話 37:36 クロージング ————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #135
タプルの説明をさらに深めるためにこのスライドを見てみます。まず、タプルは関連する値の単純なグループを表現するのに便利です。これはよく聞く話ですね。即席の値を与える表現方法としても役立ちますが、複雑なデータ構造を表現するのには向かないとも書いてあります。
なぜタプルが複雑なデータ構造を表現するのに向かないのかということについて、これを具体的に説明するのは難しいと感じる人も多いでしょう。複雑なデータ構造が必要になる場合は、クラスや構造体を使用してモデル化するのが良い、と書かれています。
つまり、タプルは基本的には比較的シンプルなデータの集まりを表現するのに使うとよいのです。例えば、2つか3つの関連する値をまとめて一時的に扱う場合に適しています。しかし、データが多くなり、役割や関係が複雑になってくると、タプルでは管理が難しくなります。この場合はクラスや構造体を使う方が適しています。特に、データに名前をつけて管理する必要がある場合には、タプルよりもクラスや構造体が適しています。
具体的な例として、スイッチ文でタプルを使う場合を考えてみましょう。例えば、レスポンスのバリューとエラーのように、2つの関連する値を一度に扱いたい場合があります。このようなケースでは、タプルを使って両方の値を持つ一時的なデータを表現し、スイッチ文で判定するのが便利です。
ただし、このような使い方は特定の用途に限られるため、一般的にデータが複雑になりそうであれば、クラスや構造体を使うことを検討するべきです。
タプルの適用範囲を考えると、名前をつける必要がないほど簡単なデータの組み合わせで、一時的にそのデータを使用する場合に適しています。例えば、特定の関数の中だけで使う一時的なデータをタプルとして表現し、それ以外では使わないことが多いでしょう。
最終的にタプルを使うかどうかの判断はデータの複雑さとスコープに依存します。扱うデータがシンプルで短期間だけ必要な場合にはタプルが最適ですが、長期間にわたり利用する複雑なデータ構造が必要な場合は、クラスや構造体を使用することを推奨します。
このように、タプルの使いどころや、どこからが「複雑なデータ構造」となるかを理解することが重要です。これを踏まえて、次に進む場合はオプショナルについて学んでいきたいと思います。 つまり、この手法はモデル化を単純にコード化するのとは少し異なりますね。しかし、話していて気づいたのですが、例えば何かが複雑になってきた場合というのはどういう時でしょうか。判定パターンが多くの場所で使用される場合、毎回そのコードを書くのは大変ですよね。
例えば、同じような機能をファンクションとしてディスクリプションを書くときに、バリューとエラーを含む状況を毎回書くのは効率が悪いです。このようなコードを何度も書くのではなく、もっと効率的な方法を模索するべきです。
具体的には、リザルト型を作成し、Result<Value, Error>
のようにバリューとエラーをオプショナルで持つようにします。そして、例えば以下のようなプロパティを追加します。
var isValid: Bool {
return value != nil && error == nil
}
このプロパティを使うことで、バリューとエラーの状況に応じて適切なコードを記述することができます。また、リザルトを返す関数内で、例えばバリューが存在する場合には特定の処理をし、エラーが存在する場合には別の処理を行うことができます。このようにすることで、一貫してリザルトを扱うコードが書きやすくなります。
さらに、ストリングコンバージョンのために説明文をプロパティに持たせ、例えば以下のように新しくメソッドを定義することも可能です。
var description: String {
if let value = value {
return "Value: \\(value)"
} else if let error = error {
return "Error: \\(error)"
} else {
return "Invalid"
}
}
このようにメソッドやプロパティを使うことで、ディスクリプションを取り出す際にも簡単にリザルトのディスクリプションを返すことができるようになります。
反対の観点で考えると、バリューとエラーが両方オプショナルで渡ってきた場合に、タプルパターンを使用して判定することもできます。タプルを使用してパターンマッチングを行う方法もありますが、構造体を作成する必要がない場合は、タプルで十分です。
let result: (value: Value?, error: Error?) = (value, error)
switch result {
case (let value?, nil):
// Valueが存在する場合の処理
case (nil, let error?):
// Errorが存在する場合の処理
case (nil, nil):
// どちらも存在しない場合の処理
case (_, _):
// 両方とも存在する場合の処理(ありえないと思うが)
}
このように、タプルを使うことで、必要に応じて柔軟にコードを記述することができます。この方法を選択することで、大規模な構造体を作る必要がなく、よりシンプルにコードを書くことができますね。 ここまでいくつか話してきましたが、引数としてタプルを使うのか、それとも構造体を定義してそれを受け取る設計にするのかという問題がありますね。確かに、こういった設計選択も一つの考えどころです。たとえば、コールバックメソッドを取る関数の場合でも同じです。例えば、関数としてリクエストでレスポンスを処理する場合、引数として Int
と Error
を受け取って Void
を返す関数を使う場合があります。このとき、引数を構造体としてまとめるのかどうかという判断もありますね。
これは引数リストに関する話ですが、構造体にすることを推奨する場合もありますし、シンプルな関数型を使いたい場合もあるでしょう。ノミナルタイプ(名義タイプ)の型エイリアスを作るか、構造体で表現するかの選択も重要です。
例えば、次のような ResultHandler
を使う例を考えてみます。
struct Result {
var value: Int
var error: Error?
}
let resultHandler: (Result) -> Void = { result in
if let error = result.error {
// エラー処理
} else {
// 成功処理
}
}
この場合、 Result
構造体を使うことで、複数のパラメータを一つにまとめてコードの可読性を高めることができます。また、タプルを使う場合との違いを見てみましょう。
let tupleHandler: (Int, Error?) -> Void = { value, error in
if let error = error {
// エラー処理
} else {
// 成功処理
}
}
タプルを使う場合は、シンプルな定義となりますが、意味づけが構造体に比べて弱いため、パラメータが増えた場合などに可読性が低下する可能性があります。
また、タプルの要素に対してパターンマッチングを行う場合の例として、次のようなコードを見てみましょう。
let value: (Int, String) = (100, "Hello")
let (number, message) = value
print("Number: \\(number), Message: \\(message)")
この場合、タプルのパターンマッチングを使って値を分解しています。興味深いことに、Swiftではタプルの要素を直接変数に分解することができ、これによりコードが非常に簡潔になります。
ただし、複雑なデータ構造を扱う場合などでは、タプルではなく構造体を使う方が適切かもしれません。前述したように、構造体により関連するデータを一つにまとめて管理しやすくすることができます。
このような設計選択は、ソフトウェア開発の重要な部分となります。どちらを選択するかは、その場面に応じて慎重に判断することが求められます。 ここでは、タプルの使用について述べていますが、その具体的な用途や使用方法を理解するのは難しい部分もあります。また、単純なグループを表現するためにタプルを使うことが多いですが、複雑なデータ構造や振る舞いを持たせる場合には、タプルではなくクラスや構造体を使用する方が適切です。
例えば、構造体にはCustomStringConvertible
やEquatable
などのプロトコルを実装できますが、タプルにはそうしたプロトコルを適用することはできません。言語仕様としてタプル型にはデフォルトの振る舞いが組み込まれていますが、カスタムの振る舞いを追加することはできません。
そのため、タプルを使う場合は、単純なグループを表現するのに留まります。一方、クラスや構造体を使えば、メソッドやプロパティを定義してより複雑な振る舞いを持たせることができ、再利用性も高まります。
例えば、Swiftの場合、ノミナルタイプ(名義型)の使用として、メソッドやプロパティを使って振る舞いを規定できるというのが大きなポイントです。これによりプロトコルに準拠し、ジェネリックプログラミングにも役立てることができます。しかし、タプルや関数型などでは振る舞いをプログラマーが定義することはできません。
具体的な使用例として、Appleの実装でもデータを構造体として持ち、タプルとして保持しないことが多いです。理由としては、タプルが持つ機能の不足や再利用性の低さが挙げられます。従って、タプルの利用が少ないレベル優先的に構造体やクラスを導入した方がよいです。
まとめると、タプルは単純なグループ表現には有用ですが、複雑なデータ構造や振る舞いを持たせる場合には構造体やクラスを利用するのが良いでしょう。Appleの実装でもそのような方針を取っていることが多いです。 データカプリのアプローチとして、まず狙いたいのは標準ライブラリ(STD)の利用です。標準ライブラリには数多くの関数がありますので、その関数のラベルや引数リストに着目することが重要です。関数の前にスペースが1つ以上存在する場合は、それを利用すれば適用するサプリとして使えることがあります。
標準ライブラリの利用例として、関数型プログラミングの活用が挙げられます。関数型プログラミングを詳細に分解してみると、非常に役立つことがわかります。たとえば、オプショナルの分割やデフォルト引数の使用などが考えられます。これにより、コードをより簡潔に書くことができ、テストやメンテナンスが容易になります。
一部のコードには、PredicateやCollectionのユニットテストなど、他の関係ない要素も含まれているかもしれません。これはプレイグラウンドでの例ですが、ディファレンテーション(微分)やその他のソフトウェア理論においても、適切な選択をするためにさまざまなアプローチが必要です。
ディファレンテーションに関連して、プルバックという概念も出てきます。プルバックを理解することは重要で、そのための具体的な例として、タプル(tuple)を使った様々な操作が紹介されました。タプルは、多くの構造タイプを1つにまとめるのに非常に有用です。特に、複数の値を一度に扱う際に便利です。
加えて、プロトコルとジェネリックプログラミングのパフォーマンスにも触れました。プロトコルはパフォーマンスに影響を与えることが多く、ジェネリックプログラミングの場合も同様です。しかし、タプルの使用はメモリ管理の面で利点があります。関数テーブルを使用してメソッドを呼び出すため、インライン展開が行われることでパフォーマンス向上が期待できます。
また、ディクショナリストレージの利用例として、タプルを使って2つの値を比較する方法も紹介されました。特に、左手と右手の値を一度にハンドリングする際のスワップ操作(値の交換)は、タプルを使うことで簡潔に行うことができます。
最後に、標準ライブラリの活用方法について実際に検討し、検索してみることが役立つという結論に達しました。標準ライブラリがどのように使われているのかを理解するために、具体例を探してみることが重要です。
これで今日の勉強は終了です。お疲れ様でした。ありがとうございました。