https://youtu.be/5AP0fsjUrik
現在は Apple 公式 Swift 解説書 "The Swift Programming Language" の The Basics を見ていっています。前回はそのうちの タプル
を見ていきましたけれど、思うより見所があって予想していたその次の オプショナル
に入っていくまでには至らずでした。今回ももう少しだけ タプル
について眺めてから、その次のオプショナル
について見ていけたらいいなと思ってます。どうぞよろしくお願いしますね。
———————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #134
00:00 開始 01:00 タプルについての前回のおさらい 01:36 値の設定場所をラベルで入れ替えられる 03:52 ラベルが違えば代入できない 04:42 ラベルの順序が違えば入れ替えられる? 06:02 タプルの要素をシャッフルする機能は廃止予定 06:14 スワップ関数 06:37 スワップ関数の API デザイン 06:50 フリーな関数を選ぶ場面 07:47 Multiple Return Type 08:35 副作用に応じた関数の命名規則 09:14 スワップ関数における引数ラベルの命名規則は? 10:11 swap の英文表現を調べてみる 11:19 API デザインガイドラインで確認してみる 13:01 今回は swap をラベルも使って表現してみる 13:31 タプルを使わない swap 関数の実装例 14:17 swap 関数にラベルがあっても良いかもしれない? 15:16 inout を使った swap 関数の実装例 15:43 Call by Value Result 17:00 tmp を用いた入れ替えの定石 17:26 tmp を用いない入れ替え 18:11 タプルとパターンマッチによる代入 19:31 タプルの代入とシャッフルを併用すると? 20:20 パターンマッチングを使った既存の変数への代入と新たな変数への代入 21:57 タプルの要素の入れ替えに関するまとめ 22:32 異なる構造体間での値の入れ替え 23:46 構造体の振り替えは変換イニシャライザーで行う 24:15 変換イニシャライザーのデザインガイドライン 25:26 メモリー配置としての入れ替えとみるか、ラベルに対する入れ替えとみるか 28:08 構造体は変換の意図が表面に伝わる 29:09 引数の順番は入れ替えられない 30:47 引数に規定値がある場合も順番通りに 31:41 規定値を持つ引数を末尾に置く必要はない 32:51 複数の戻り値については話した通り 33:01 次回の展望 ————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #134
して二つの値を交換する際に、タプルを使って簡単にやり取りする方法が好まれていました。しかし、現在ではこの方法が警告されるようになっていますので、新しいSwiftのバージョンでは推奨されていないということになります。
さて、タプルの話から次に進めていく前に、もう少し具体的な例を見ていきましょう。以下は、タプルにラベルを付けた場合と、付けなかった場合のコード例です。
let tuple1 = (1, 2)
let tuple2 = (a: 1, b: 2)
// ラベルなしのタプルにラベル付きのタプルを代入
let x: (Int, Int) = tuple2 // エラーになります
// ラベル付きのタプルにラベル付きのタプルを代入
let y: (a: Int, b: Int) = tuple2 // 問題なし
このコード例では、ラベルなしのタプルにラベル付きのタプルを代入しようとするとエラーが発生します。しかし、ラベル付きのタプルに同じラベル付きのタプルを代入することは問題ありません。
次に、ラベルが異なる場合の例を見てみましょう。
let tuple3 = (a: 1, b: 2)
let tuple4 = (x: 1, y: 2)
// ラベルが異なるタプルを代入
let z: (a: Int, b: Int) = tuple4 // エラーになります
この例でも、異なるラベルのタプルを代入しようとするとコンパイルエラーが発生します。
さらにラベルの順番を逆にした場合の例です。
let tuple5 = (a: 1, b: 2)
let tuple6 = (b: 2, a: 1)
// ラベルの順番が逆の場合
let w: (a: Int, b: Int) = tuple6 // 一部のSwiftバージョンで警告が発生します
このコードでは一部のSwiftバージョンで警告が発生し、将来的には非推奨になる可能性があります。
タプルについて詳細に見てきましたが、複雑な利用方法や時代ごとの仕様変更により混乱することもあるので、公式ドキュメントなどを参照しながら最新情報を確認することが重要です。
では、次にオプショナルの話に移りましょう。オプショナルはSwiftにおいて非常に重要な概念で、変数に値が存在しない可能性を表現するために使われます。オプショナルの基本的な操作には、値が存在するかどうかを確認する方法や、存在する場合にその値を取り出す方法があります。
var optionalInteger: Int? = nil
optionalInteger = 42
if let integerValue = optionalInteger {
print("値は \\(integerValue) です")
} else {
print("値が存在しません")
}
この例では、optionalInteger
がオプショナル型の整数であり、初期値は nil
です。その後に42が代入され、if let
構文を使用して値が存在するかどうかを確認しています。値が存在すれば、integerValue
にその値が取り出されて処理が続行されます。
次回はさらに具体的なオプショナルの使い方や落とし穴、パターンマッチングなどについて詳しく見ていきましょう。 変数名についてのデザインガイドラインに戻ります。まず、「スワップ」を実装したい場合、二つの値を渡してそれを入れ替えたものを値として返したいイメージです。その場合、二つの値のうちどちらが主導権を握るかで判断し、主導権を持つものをレシーバーとしてメソッドを搭載します。しかし、スワップのように両方が同じ存在として機能する場合は、フリーな関数を使います。このフリーな関数というのは、モジュールのトップに定義するグローバル関数です。
具体例として、例えば Int
型の2つの値を swap
する場合を考えてみましょう。Int
型の2つの値を入れ替えた結果を返すために、Swiftのマルチプルリターンタイプを使用します。このマルチプルリターンタイプは、複数の値を一つのタプルとして返すものです。Swiftが登場した当初、このマルチプルリターンタイプはSwiftの大きな特徴の一つとして挙げられていました。エピアデザインガイドラインに沿えば、これは適切でしょう。
また、インアウト(インアウトパラメータ)を使う方法もありますが、そのニュアンスの違いについてはマルチプルリターンタイプの話の後に詳しく説明します。エピアデザインガイドラインに戻ると、関数名については副作用を伴うかどうかにより変わってきます。今回のようにパラメーターを受け取って関数の外側に影響を与えず、結果を戻り値として返すスタイルの場合、動詞に ed
または ing
を付けてメソッドまたは関数名を表現します。したがって、「swap」で良いかと思われます。
ラベル名に関してもエピアデザインガイドラインに則り、必要なら省略せずに表現するべきです。各引数に対して適切なラベル名を付けることは、関数の使用者にとっても理解しやすくなるため重要です。
もし、具体的な関数を実装するとしたら、次のようになります:
func swap<A, B>(_ a: A, _ b: B) -> (B, A) {
return (b, a)
}
ただし、ラベル名に関する部分についてはまだ明確ではない点があるため、後日さらに調査してお知らせする予定です。
あと、スワップの適切な英語表現についてですが、辞書を引いてみると、「交換する」という意味合いがあります。そのため、ラベル名や関数名を付ける際に英語のニュアンスも考慮する必要があります。最終的には、APIを利用する人が流暢な英語として理解できるようにデザインガイドラインに準じて実装することが望ましいです。
以上が、APIデザインガイドラインに基づくスワップ関数の実装についての説明です。また必要な情報を調査し、適宜更新していきます。 さて、この辺を見て行って、両方略すのかな、という話をしたんですが、そちらもありえそうな気がしますね。「with」として使う方法と、逆にする方法があります。今回はラベルとしてやってみましょう。タプルの話に戻りますが、タプルを使わない場合、テンポラリーとして変数Bを確保します。特に副作用のない関数の場合はあまり考えなくていいですが、タプルがない場合というのも考えなければなりません。タプルがあるときには全く問題なく、BとAを使うことでコンパイルが通ります。そして swap(A, with: B)
のように書くと、Bがないというエラーになります。ここは決め打ちで 1
と 2
を使って実行すると、 2
と 1
に逆転します。
次にタプルに注目して見た場合、APIデザインガイドラインに従い、ラベルがあったほうが分かりやすいかもしれません。これにより、引数AとBが逆順で返ってくることが明確になります。例えば、 A, B
で渡したものが B, A
で返ってくるという具合です。
また、タプルがなかったと仮定する場合を考えてみましょう。副作用を伴うバージョンのスワップ関数について考えると、戻り値として返さず、引数を直接入れ替える方法もあります。ここで、コールバイバリュー(値渡し)、コールバイリファレンス(参照渡し)、コールバイバリューリザルト(値戻り値渡し)などの概念がありますが、Swiftでは inout
パラメータを使って値を渡す方法があります。
従来の方法では、変数Bの値を一時保存しておき、AをBに代入し、その後一時保存した値をAに代入するという手順が一般的でしたが、Swiftのタプルを使うと、もう少し簡潔にスワップを行うことができます。例えば、次のように書くことで実現できます。
var A = 1
var B = 2
swap(&A, &B)
print("A: \\(A), B: \\(B)")
これにより、 A
と B
の値がスワップされます。
タプルを使う場合には、ラベルが付いていないので、変数の宣言順序によって代入が行われます。例えば、 let (B, A) = (A, B)
というように書くことでスワップ可能です。ただし、ここでラベルがないため混乱しにくいですが、ラベルを付けた場合にはラベルの整合性が求められます。
タプルを使ったスワップの例をもう一度確認すると次のようになります。
var A = 1
var B = 2
(A, B) = (B, A)
print("A: \\(A), B: \\(B)")
ここで、AとBの値が正しく入れ替わります。しかし、タプルの各要素にラベルを付けた場合には、ラベルが一致しないとエラーが発生する点に注意が必要です。例えば、次のように書くとエラーが発生します。
var A = 1
var B = 2
let (labelA: A, labelB: B) = (B, A) // エラーになります
このように、ラベル付きの場合は同じラベルが必要になります。 ラベルが違うとタプルの代入ができないので、エラーになりました。ラベルを指定したタプルの代入とラベルを指定しないタプルの代入では、順番の入れ替え方が異なります。プログラマーが明示的に A
と B
の順番を入れ替える方法を使うと、既存の変数に対してそのまま代入を行うことができます。
ここで let
を使えば、バリューバインディングパターンも使用可能になり、定数を同時に代入することができます。この場合、タプルの使い方としては、新たに宣言する方法もありますが、もちろん var
を使うこともできます。型の指定が変わらない場合には、このような書き方もあります。また、宣言済みの変数を活用する方法もありますし、タプルを使うと、一時的な変数を用意しなくて済むので、値の入れ替えが便利です。
ラベルを指定してランタイムの値を入れ替えることは重要です。変換を通して意図的に順番を入れ替える手段があるので、大した問題ではありません。また、タプルと構造体はメモリー構造が似ているため、特徴的にも類似点があります。しかし、構造体の場合は自動的な入れ替えが発生しないため、変換イニシャライザーを使って型Bへの変換を明示的に定義する必要があります。
APIデザインガイドラインに従ってイニシャライザーを定義することで、型変換が矛盾なく行われるようになります。例えば、以下のようにイニシャライザーを定義するとします:
struct B {
var x: Int
var y: Int
init(fromA a: A) {
self.x = a.x
self.y = a.y
}
}
このようにすることで、Aの x
と y
を適切にBの x
と y
にマッピングできます。メモリーの配置順序は異なりますが、このアプローチではそれを意識する必要はありません。
以上のように、プログラマーが意図的に値を入れ替える方法や型変換のアプローチを理解することが重要です。タプルや構造体の特性を理解し、適切な方法で値の代入や変換を行うことができます。 こういった風に見方によって色々と考え方が変わってくるんですね。発想が逆なら、こっちがバリュープリザービングになるかもしれないし、全然別のラベルに変換するということも可能です。マッピングは便利ですね。こうやって書いてあげることで、型変換が備わります。
要は、B型としてはラベルですが、フォートタイムの場合はラベルというよりは変数名になります。変数名が同じものをマッピングするイニシャライザを通して型を変換する、ということです。設計者の意図がしっかりと反映されます。言語仕様に従って自動マッピングが行われ、自動で入れ替わっていきます。ただし、これが配信になるかもしれないという話もあります。理由は明確には言えませんが、意図が不明確になるからかもしれませんし、コンパイラが複雑になるからかもしれません。
以前、他の言語やプラットフォーム入門の話で、パラメーターの入れ替えについて触れました。要は、関数やメソッドの引数もタプルとほぼ同等ですが、ジェネリクスではタプルスクラップが許されていません。昔は関数があって、パラメーターAとBがあったときに、ラベルを入れ替えることができたようだと思います。しかし、今はこれは警告やエラーになります。ラベルAはBよりも先に定義されている必要があります。
タプルとしては入れ替えは警告となりますが、関数の引数では便利な渡し方はできません。順番通りに渡さないといけないのです。デフォルト引数があっても、AとBが明記されているなら順番通りに渡す必要があります。デフォルト引数がある場合、間を飛ばして定数を渡すことはできますが、順番を変えることはできません。
言語によっては、デフォルト引数を持っている引数は後ろに並べる必要があり、後ろから順番にしていけないという仕様があります。たとえばC++がそうです。Swiftの場合、こういったところを省略して、API設定を簡単にすることができます。これが嬉しいところです。
さて、タプルで複数の戻り値はOKです。今まで何回か話してきましたが、タプルは関連する単純なグループとして便利です。ただし、複雑なデータ構造を表現するのには向いていません。複雑な構造である場合は、タプルよりもクラスや構造体を使ってモデル化した方が良いです。
この「複雑なデータ構造」がどこから複雑なのか、境目が難しいと思います。このあたりについての話は面白そうですが、今日は時間がないので次回に回します。次回、この「複雑なデータ構造」について見ていきたいと思います。何か思い浮かぶことがあれば、次回の話に持ってきてください。
というわけで、今日は勉強会はこれで終わりにします。お疲れ様でした。ありがとうございました。