https://youtu.be/Z_OiTq1epXU
前回は予定していた内容から急遽 Any
と AnyObject
周りの挙動を確認する回になりましたので、今回こそは改めて再び本流に戻って The Swift Programming Language の The Basics の続きを眺めていきます。そんなセクションの始まりの辺りの About Swift 的な内容をおさらいな感じで「高度な型の導入」と「型安全」の紹介的なところを見ていきますね。よろしくお願いします。
——————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #84
00:00 開始 01:46 Swift が提供する型 02:23 プリミティブ型 05:54 高度な型の導入 07:10 タプル型 07:30 複数の戻り値が使われる場面は? 08:24 標準ライブラリーで複数の戻り値は使われている? 10:11 複数の戻り値を扱う 10:33 引数で追加の戻り値を返していた時代 14:02 URLSession で複数の戻り値が使われる場面 18:49 引数リストとタプルスプラット 26:09 単一のタプルと複数の引数リストを区別して扱う 37:05 タプルにラベルを含める 39:10 タイプエイリアスを活用する 39:49 型エイリアスの活用場面 42:29 複数の戻り値に対する印象 45:12 オプショナル型 46:45 次回の展望 ———————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #84
はい、じゃあ行きますね。今日は前回予定していて急遽別の話題になって話さなかった内容について話します。テーマは『The Swift Programming Language』の中にある「Language Guide」の「The Basics」、要はスイフトについての非常に基本的なお話です。この勉強会ではそんな内容ばかりやっている気もしますが、改めて見ていくと新たな発見がいろいろあるなと少なくとも自分は感じるので、皆さんそれぞれ思うところがあるんだろうなと思いつつ進めていこうかなと思います。
話題が簡単だとついてくるのも楽だと思われますので、気軽に聞けるとまたいろいろと思い浮かぶことがあるかと思います。そういう風に、せっかくの簡単な題材のうちにいろいろと思い浮かぶことを出してもらえれば、そこから派生してさまざまなことを細かく見ていくことができるので、良いきっかけになるかなと思います。
さて、今日の話題は「Swiftが提供する型」についてです。まず、Swiftではどんな型が提供されているのかを見ていきましょう。
まず、「Objective-Cでのすべての基礎的な型をSwiftの独自の型として提供している」というところ。この一文を見るだけでも、参加してくれていた方は「About Swift」でも同じような話をしたなと感じるかもしれません。とりあえず、Swiftでは独自の型として提供しているというのは、Int
型などが構造体として提供されていることを指しているようです。
C言語やObjective-Cなどの基礎的な型はプリミティブ型として提供されており、例えばint
型のように用意されています。多くの言語ではこうした基礎的な型はプリミティブ型として特別に用意されていますが、Swiftでは一部特別扱いされています。それでもユーザー定義データ型と同じような重みで、Int
型などは標準ライブラリーで用意されています。これが特徴的なところですね。
まずはスライドを見ていきます。次のような型が提供されています:
- 定数のための
Int
型 - 不動小数点数のための
Double
型とFloat
型 - 真偽値のための
Bool
型 - テキストデータのための
String
型 - 重要なコレクションとして
Array
型、Set
型、Dictionary
型
これ以外にも大量の型が定義されていますが、主なところとしてはこんな感じです。このスライドではObjective-CやCといった視点で基礎的な型を取り上げていますので、その感覚でいうとここに表示されている型が標準的な型と言えるでしょう。
ここでちょっと次のスライドに行きますが、その前におさらいしておきますね。
Swift独自の定数型はバイナリーインテージャープロトコルに準拠しています。不動小数点数型はバイナリーフローティングポイントプロトコルに準拠、String
はストリングプロトコルに準拠しています。これも前の勉強会で既に注目してお話ししました。Array
、Set
、Dictionary
はコレクションプロトコルに準拠していることも前回か前々回で話しました。これくらいで軽く済ませておきましょう。
次のスライドに進みます。高度な型の導入についてです。Objective-Cには見られない高度な型として、タプル型とオプショナル型があります。この2つが取り上げられています。
タプル型とオプショナル型は確かに、Swiftでは基礎的な型の一部として広く使われており、もう高度な型という気がしないかもしれません。しかし、Objective-CやC言語、C++11以前のC++などと比べると、これらは確かに高度な型と言えますね。
以上で今日の話題について軽く見てきました。次回は更に深く掘り下げていく予定です。 とりあえず、タプル型とオプショナル型について見ていきます。タプル型は値をグループ化できるので、複数の値を返したいときにとても役立つと紹介されています。確かにそうだなとは感じますが、タプル型を使って複数の値、要はマルチプルリターン、を返す機会はあまりないと思います。連載のときは便宜上使うこともありますが、実際には機会は少ないですよね。
多くの場合、構造体やクラスなど、すでにまとまった形で1つの値を返すことが一般的です。標準フレームワークにもそのような例が見当たりませんし、それが普通だと思います。とはいえ、そういう設計があれば面白いと思いますし、その設計を真似したいと感じます。しかし、実際にはあまり使われないのではないでしょうか。
また、リターンがタプルのときに、import Swift
して検索をかけられるか試してみましたが、大抵は構造体やクラスでまとまって返されるので、タプルが返る例は少ないですね。例えば、オブジェクティブCのNSエラーのように、エラー処理のためにオプショナル型を返す手法が一般的でしたが、Swiftではエラーハンドリングが自動化されています。
次に、マルチプルリターンタイプがピンと来ない方のために、コード例を挙げます。通常、Int
型やString
型のように単一の型を返すのが一般的でしたが、複数の値を返す場合には涙ぐましい努力をしていました。例えば、Int
型を返しつつ、さらにパラメータとしてString
型を返すようなことですね。リターンでInt
を返しつつ、パラメータでString
も返すという感じです。以下のような書き方です。
func exampleFunction() -> (Int, String) {
return (42, "Example")
}
このような書き方をオブジェクティブCではよく見かけましたが、今ではパラメータを使わなくてもタプルを使うことで簡単に複数の値を返せるようになりました。その際、タプルにラベルをつけることもできるので、コードの可読性が一気に上がります。
例えば、次のようにラベル付きのタプルを返すこともできます。
func labeledTupleFunction() -> (code: Int, message: String) {
return (code: 200, message: "Success")
}
この場合、帰ってきた値をresult.code
やresult.message
のように直接参照できるため、可読性が非常に高くなります。タプルは非常に便利なのですが、やはり実際に使われる頻度は少ないように感じます。
例えば、URLセッションのコンプリーションハンドラーでは、タプルが返されることがあります。以下のような感じですね。
URLSession.shared.dataTask(with: url) { (data, response, error) in
// ここでタプルの各要素を取り出して使う
}.resume()
レスポンスデータとエラーをタプルとして返しているので、これもタプルの一つの利用例と言えます。それでは、次の話題に移りましょう。 「これですが、複雑なインターフェースになっていますね。というか、これオブジェクティブCの部分になっているんですね。何故オブジェクティブCにたどり着いたのか、よくわかりませんが、まあいいか。ネタルトガタとかじゃないってことですか?
今はコンプリエーションハンドラーについて話しているんですけど、Issue Correctの自動変換があるじゃないですか。Issue Correctの自動変換の結果、返り値がタプルになっています。データタスク移動俯瞰後、これでいいのか。コンプリエーションハンドラーのあるものとないものね。これオブジェクティブCですね。
定義をきちんと追えば、今度こそSwiftに行くのかな。これオブジェクティブCですよね。変なところに書いているわけではないよね。プレイグラウンドで試してみます。一回Xcodeを落としますか。もともとコンプリエーションハンドラーのないAPIもあるので、インポートを適切に書かないといけないかもしれませんね。
まず再起動して確認してみます。表示されなくなっちゃいましたね。あ、違いますね、遅かっただけだ。コアファンデーションのせいかな。ファンデーションなら出てきましたね。これでクイックヘルプも出てきた。本当だ、タプルだ。スロースになっています。データとURLレスポンスですね。他のケースだとコンプリエーションハンドラーの場合、データとURLレスポンスとエラーが返ります。ですから本来、コンプリエーションハンドラーには3つの値を返す必要があるんです。これは無理やりエラーハンドリングのための構文に機械的に置き換えたんでしょうね。でもタプルを使わないとバグになるので、ちゃんと考えての方法だと思います。面白いですね。
例えば、アクションとして1つの戻り値をタプルで返す方法と、ハンドラーとしてコードとメッセージ(イントとストリング)を返す方法。全然動きの違うコードを書いていますけど、ハンドラーに対してパラメーターを渡してくる感じですね。この2つのインターフェースがあったとして、同じ名前だと分かりにくいかもしれないので、とりあえず進めていきます。
例えば、アクションを戻り値として受け取ってリザルトのコードとメッセージを使う方法と、アクションに対してクロージャを渡してコードとメッセージを受け取る方法。ちょっと比べるのがアンバランスすぎるかもしれませんが、タプルに注目してみましょう。こっちもタプルにした方が分かりやすいかな。コードとメッセージ、こういう風に受け取れば動きますね。
あまりタプルでリターン値を分解してもらうことはなかったのですが、動きますよね、たぶん何も表示されないのはなぜか。まあいいか、プリントしてみればいいですね。コードとメッセージを出力します。あー、ちゃんと動いていますね。
もうちょっと詳しく書くと、タプルで受け取ってボイドを受けているわけですが、こっちは自然な印象を持ちますが、使いにくいことはないです。ただ、馴染まないだけかもしれません。普通にやれば良い気もしますね。非常に漠然とした投げかけになりますが、このコードを見てどう思いますか?どうも思わないですかね。
このタプルの部分、例えばハンドラーとして受けるところを分解しないで、普通にリザルトとして受け取ります。警告が出るのか、明記しちゃうとエラーが出ちゃうんですね。なるほど、タプルスプラットについて昔お話ししたことを覚えている方は思い出してもらうと良いですが、タプルスプラットでは受け取れないんですね。ただ、何も記載しないとタプルスプラットで受け取って、 $0.0
と $0.1
って出来ますよね。確かね。
今、それが出来ないのですか?二つの値を受け取ることになってしまいますが、確か出来たと思うんですけど。出来ましたよね。そうです、そのケースではタプルとなる場合です。今はタプルというより、個別の値として $0
と $1
を受け取ります。ああ、そっか、括弧で書けば受け取れますね。なるほど、ありがとうございます。勘違いして喋ってました。そうですね、関数として...いや、ちょっと待った。勘違いしているかも。確かに丸括弧すればタプルで受け取れますね。このときは大丈夫ですね。」 返す値も変わってきちゃうんですね。そうだったかな? ここはオッケー。確かに教えてもらったとおりで納得です。タプルが $0
表示してみればいいんだ $0
のときエラーになるんでしたっけ? これもエラーになるのか。 $1
を使ってないからですね。2つ使ってないということです。 $0
は $1
にすると、必須の2つがあるのに2つ使ってないから、まだスコア2しなきゃいけない。やっぱりそういう解釈になりますよね。
そこが書いてあった気がするんですけど、ちょっと再現できてないですね。そうか、いいよ。お二人がちょっと考えているのは推測しかないですけど、さっきのようにまずタプルにする場合ですね。タプルにして $0
で受け取るのではなくて、ステートとメッセージで別々で受け取ることができないで悲しいかなと。
タプルだったとして、それでここで(○で)コードとメッセージとはできないよっていうお話ですね。悲しそうになっちゃったと思う。まあそれとも違う気がするけれど。自分自身もうまくいってない。今画面でいろいろとやっていて思ってるコードが書けてないところを見ると、自分自身が変なコードを書いてそうな気がするので、伝わらなくて当然な気がしてくる。
あれ、分解できなかったかなというのか? 位数二つが。まあいいや。これ、コメントもらえてた。昔のSwiftバージョンでできましたっけ。なるほど。このURLをみんなにも見せるにはどうしたらいいんだ。
これ、単一のタプルって扱いだったんですか? そう、単一のタプル。そういうことだね。多分単一のタプルっていうことですかっていうセリフも自分の都合よく解釈してるんで勘違いしてる可能性もないとは言えないですが。
今教えてもらったウェブサイトを写せそうな気がする。これを押して、この写しながらしゃべるっていうのをすっかり怠ってて、聞いてる人に分かりにくかったんじゃないかなと思うんで、ちょっと今日頑張って写してみましたが、これですね。というか教えてもらったやつ。
シングルタプルとマルチプルアーギュメントファンクションタイプス SE-0110っていつ頃のだ? ヒストリー見れば気付きは出てくるんでしょうけど。これを押して、この写しながらしゃべるっていうのをすっかり怠ってて、聞いてる人に分かりにくかったと思うんで、ちょっと今日頑張って写してみましたが、これですね。シングルタプルとマルチプルアーギュメントファンクションタイプス SE-0110っていつ頃のだ? ヒストリー見れば気付きは出てくるんでしょうけど。21年か、結構前か。
パラメータ x として。うーん、これはまた妙な書き方。まあいいや。でも、こういうふうに Int
、Int
のものをマルチプルタプルとして受け取れる。それが可能だって書いてあったんですね。現在は。それで分解して取ることもできて。
それでさっきのコードのお話でいくと、ここのアクションのところ、これがシングルで取ることもできるし、マルチで取ることもできるし、逆か。
言葉の見る方向によってマルチとシングルの意味が逆になるから、今のでっていうのがある。いやっていうのができて、それでそれを読めようっていう話なのかな。そうそう、今言いたかったのはこれなんですよ。複数の型、要素の型があるものを1個で受け取ってタプルとして使っていくっていうことが昔できた。Swift 3あたり、もうちょっと先でも最近でも使えてた気がするんですけどね。まあいいか。
それで、このプロポーザルは何だ? このプロポーザルは…こっちか。区別する必要がある。アプリで一つと複数の引数の区別ができてなかったけども、それを区別しましょうっていう話ですね。どっかの昔のSwiftバージョンで実装された感じだと思います。この区別しましょうっていう意味がうまく汲み取れないんですけど。つまり、書けなくするよっていう区別。そうですね。
このように一つのタプルとしては使うことができなくしましょう、ということですね。なるほどね。SE-0029のリンクがあるんですけど、今のちょっと下ですね。もっと下。これですね。
タプルスクラットをやめましょう。それのインパクトオンエグジスティングコード(Impact on existing code)のところが分かりやすいかもしれない。下のほう、なるほどね。インパクトは下のほうだ、もっと下だ。これができなくなったってことですよね、前は。
はい、関数に複数のパラメーターを渡す場面で、その同じ数のタプルを一気に渡しちゃおうっていうやつね。これができなくなりました。関連して受ける側もっていうんですかね。受ける側も明確にタプルなのか、バラバラで受けるかってこと。バラバラでしか受けられないってことですよね。
そうみたいですね。 29で渡せないことになったし、受け取る側も困ってしまうんですね。上記のようにタプルで引数を一つにする場合は、カッコで括って引数をまとめる必要があると考えました。ありがとうございます。
さて、2021年の5月6日というのはかなり前のことですね。それについて話しましたが、実際には Swift 4 ぐらいの時から、もしくはもっと前から始まったようです。この歴史をさらに遡ると、2016年あたりに実装されたようです。これはかなり前ですね。実装された後、徐々に更新が続けられているということが分かります。
実装後のやり取りも気になりますね。具体的に言うと、どのような変化があったのでしょうか。「実装された」という部分を見てみると、ステータスが遅れて更新されたようです。実際には Swift 3 でインプリメントされ、ステータスが後に更新されたという状況です。本流に取り込まれる際に何らかの変更があったのかもしれません。
Swift 4 の頃には既にメインブランチにインプリメントされていたようです。ただ、それ以前の提案書(プロポーザル)上では Swift 3 として取り込まれていたようです。切り替わり時期には若干の遅延があったのかもしれません。
この話を掘り下げる必要はないと思いますので、次に進みたいと思います。タプルの応用範囲が徐々に狭まっているように感じますね。例えば、タプルスプラット系の機能が使えなくなってきています。しかし、18行目のマルチプルリターンタイプだけに注目すれば良いでしょう。
特に URLセッションの例では、2つの値(データとレスポンス)が返ってくることが主流です。しかし、タプルを使うと複雑になることがあります。型が明確でないと情報が欠落してしまうため、基本的には避けるべきです。
例えば、Async の場合にデータの文脈上で何となく理解できる部分もありますが、タプルの戻り値にラベルを付けることができます。こうすると、リザルトを受け取り、リザルト.データ
やリザルト.レスポンス
とアクセスすることができます。
さらに、タプルにタイプエリアスを設定することができます。例えば、特設のタプルを作ってそれに名前を付けることで、クロージャーを書くときにはこのタイプエリアスを使用することができます。見た目も良くなりますし、複雑になるときもタイプエリアスを作り直すだけで対応できます。
以上が、タプルの使い方や注意点に関する話でした。質問やコメントがあれば、ぜひ教えてください。 本当ですね。コンプリーションハンドラーとかでよくやるやつです。だから、オブザーバーみたいなクラスがあって、それでこれがコンプリーションハンドラーです。何かしらのアクションの中でハンドラーとして、コンプリーションハンドラーがあります。
例えば int
と、さっきのリアコードとね、メッセージをリクエストが終わった後にコンプリーションハンドラーで読んで、それでまあ今回はブールでも書いちゃいますかね。こういうふうなコードを書いたときに、このタプルのところを毎回いろいろ書いていくとかなり大変なんですよ。要は使いたいときにハンドラーを定義するときに、これが int
とストリングのタプルだったなとか、これぐらいで済むならわかりやすいですが、全部把握するのは難しいです。
こういったときに、今教えてもらったやつはハンドラー型としてね、このタプルをタイプエリアにしておいてあげると、もうこれでハンドラーはハンドラー型だし、ここで自分で定義するときも書きやすくなります。型があったときにこれもオブザーバーのハンドラー型みたいにこういう風に書けます。
今さらに教えてもらったやつとして、このハンドラーが複雑になることはないですが、コードが間違ってたんで直します。で、ハンドラーはシンプルなものですけど、もっと高度なことをやっていこうってなったときに、ここを Tract で定義し直せばより高度なことができるよってことで、コードを書くときもハンドラー型として定義して、この中でいろいろ実装を書いていって、タイプエリアをやめてしまえば、これだけで全ての箇所がちゃんと適切に直っていくというわけです。
うんうん、なるほど。そうするとまあ見やすくなります。しかし逆にこれをしないと、とても見にくく、使いにくいということもありそうですね。その結論だと変に思うかもしれませんが、まあそれでいいでしょう。
あまり戻り値をマルチプルリターンタイプとして使わないですね、やっぱりね。しかしハンドラー型を返すと即席で構造体に直すことが容易で、タイプエリアを活用すればそのタプルを返す形もありかもしれません。しかし、そうすると分解して受け取ることができないからここはまた別の話です。やっぱり、あまり使う機会がないかなということです。
一般的に使わないというのもありますが、マルチプルリターンタイプとしての価値観が根付いていないだけということもあるかもしれません。なので、誰かが積極的にマルチプルリターンタイプを使ってみて、その感想を他の勉強会とかで聞かせてもらえたら面白いかなと思います。まあ、それを試してみるのは面白いかもしれませんが、個人的には気が進まないですね。
そんなわけで、タプルについては一応、高度な型の導入という意味でこんな感じです。複合的な値として複数の値を一つにまとめられるというお話です。
あとオプショナルも軽く見ておきましょう。オプショナルについては色々とお話しましたし、今後も多分いっぱい出てくるので、要所として。とりあえず、値があり、それがある値と等しいという表現と、値がないという二つを表現できるようになっています。
このオプショナルは色々なことができるように言語仕様がサポートしているため、高度な機能を提供するものです。しかし型自体はシンプルです。実際に定義を見るとわかるとおり、列挙型になっていて、二つのケース、値があってその値が付属しているケースと、何にも値がないことを意味するケース、その二つだけで定義されています。とてもシンプルな型です。
でも言語仕様によってはタプルは少し違うかもしれませんが、他の周辺のものによって高度なものとして作り上げられているという感じです。こういうふうに、基本から広がっていく感じがなかなか面白いですね。
はい、ではいい具合に時間になったので、今日はサクッとしたお話でしたがこれぐらいにして、次回また型安全のところを見ていこうと思います。
では、時間になりましたのでこれで終わりにします。お疲れ様でした、ありがとうございました。