https://www.youtube.com/watch?v=0RxnwOpfMYQ
今回も引き続き Apple 公式の Swift 言語解説書 The Swift Programming Language
を眺めていきます。最初に Swift の特色的なところをおさらいしてから、Xcode 13 の互換性みたいなところを少し確認し、それから A Swift Tour
へと進んでいく流れになりそうです。どうぞよろしくお願いしますね。
—————————————————————————— 熊谷さんのやさしい Swift 勉強会 #24
00:00 開始 00:38 最大限にハードウェアを活用 02:45 明瞭なコードを書けるように促す 04:05 同じことをするなら1つの方法で行う方針 06:32 あらゆるものに最適な選択肢 07:09 強力な型推論とパターンマッチング 08:41 コードの可読性と保守性 09:55 マクロ 13:25 進化を続ける言語 14:27 @available 24:46 バージョン互換性 26:23 Swift 5.5 特有の機能 27:24 不透明な戻り値の型 32:50 try? の戻り値の扱い 40:16 switch におけるオプショナルの扱い 44:35 大きな整数リテラルによる初期化 48:07 並列処理のサポート 50:12 異なるバージョンで依存可能 51:03 次回の展望 ——————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #24
今日も引き続き「The Swift Programming Language」の中から見ていきます。それの「About Swift」の章の最後のスライドがこれなので、まずこの辺りを見ていきます。コードを入れるような内容はなさそうなので、軽く文字を読み上げる感じになります。
まず、Swiftは「最大限にハードウェアを活用できるように最適化されている」と書かれています。リフレッシュレートが上がれば、UIキットなどのフレームワークが自然と手こいでされて活用できるとか、コンパイラーの視点で効率よくビルドできるようにマルチスレッドを最適に活用してコンパイルするとか、そういうことを言いたいのかもしれません。コンパイラー自身の動きなのか、コンパイラーが作るバイナリーの話なのか、そのあたりが文章からは読み取れませんでしたが、とにかく最新のハードウェアを最大限に活用できるように最適化されているという特徴がSwiftにはあるらしいです。
あまり追従が早すぎると、例えばXcodeが2世代前のOSでは動かないという話を言いたいのか、少なくともSwiftコンパイラーだけを見たときには、そこまでプラットフォームがどんどん古い環境で動かなくなるという雰囲気は感じないので、やっぱりバイナリーのほうかもしれません。どちらにしても、最適化されているので安心して使ってねとか、積極的に使ってねということを言いたいのだと思います。
次に、「コードを書くための明瞭な道筋」という部分です。Swiftの言語構文と標準ライブラリーは、明瞭にコードを書くことを促進し、それが大量のパフォーマンスを生むという信念のもとで作られているようです。Swiftの構文は実際に書いていくと、気持ちよくサクサクとイメージ通りに組んでいけると感じます。ただ、自分が慣れているだけかもしれませんが。
標準ライブラリーについては、統一された雰囲気で使えるように作られているため、このフレームワークを使っているからこういう雰囲気の書き方になると、自然と歩調が合わせられるのだと思います。Swiftの言語構文には「同じことができるなら書き方は一つでいい」というコンセプトがあります。例えば、文字列の長さを取るのにcount
とlength
の二つの手段は持たせないというのもその一環です。他の言語、例えばC++ではlength
とsize
の二つを持たせて、視点に応じてどちらを使うか選べるようですが、Swiftではそういうアプローチは前向きには捉えません。
標準ライブラリーについては、フレームワークを使えば特定の書き方や雰囲気が出てくるという話ですが、もともとフレームワークというのはそういうものです。例えば、SwiftUIを使えばある雰囲気のコードになりますし、UIKitを使えば別の雰囲気になります。また、低レベルな部分ではDispatch
を使えば並行処理がディスパッチの雰囲気になります。特別なことではないかもしれませんが、そういう一貫性があるのは良いことです。
次に、「安全性と速度」の話です。Swiftは小さなハローワールドから大きなOS全体まで、あらゆるものに最適な選択肢であると言っています。この部分からも、Swiftに対する自信が垣間見えます。ハローワールドからOS全体まで自信を持っているというのは面白い表現ですね。この後、「Swiftツアー」で詳しく見ていくので、その時にまた確認しましょう。
最後に、「強力な型推論とパターンマッチングを現代的で軽量な構文と組み合わせることで、複雑なアイデアを明瞭かつ簡潔に表現できる」と書かれています。Swiftの型推論は非常に強力で、パターンマッチングも強力です。パターンマッチングについては、特にswitch
文の時に詳しく見ていきたいと思います。タプルとの組み合わせで、複雑な条件判定をスマートに書けるようになっているのがSwiftの特徴です。これもまた「switch」文の際に注目してもらえればと思います。 コードを簡単に書けるだけでなく、それを読んだり保守したりするのも簡単になりますよ、と言っています。実際のところ、Swiftのコードは非常に簡潔に書けて、読みやすさも考慮されていますので、あとで振り返るときにかなり読みやすくなっています。だから、この文には一応賛同できますが、現場感覚ではどうでしょうか。保守しやすいですか?
どうなんでしょうね。あまり複数人数で大きなコードを書いたことがないので、その辺りの感触を持っている方がいっぱいいるんじゃないでしょうか。オブジェクティブ-Cの時よりはやりやすいかな、と感じます。自由度が低い分、保守がしやすいところもありますよね。あとはSwiftLintのようなリントツールもサードパーティー製ですが、SwiftLintでガチガチに設定すれば、ある程度コーディングルール的な意味でも保守がしやすいですね。
また、Swiftにはマクロがないのも良い点です。マクロがあると、安全性が保たれないことが多いですが、Swiftではそれができないように言語仕様として明確にされている気がします。最初はマクロがなくて不便だなと思ったんですが、意外といつの間にかマクロを使わない形で解決策を見つけられるようになりました。それでもなお、安全なやり方が他にもあるとわかります。
マクロを使うと安全性が確保されない場合が多いです。具体的にどういう風に危険になるかについてはパッと思い出せないんですが、単純にマクロは変数やコードを埋め込むことで何でもできてしまうため、保守が難しくなります。例えば、昔のC言語ではPCのパワーが限られていたのでマクロを使っていた面もありますが、今だとマシンパワーがあるので、Swiftのような少し重い方向で解決する方が良いですね。
Swiftではマクロがなく、#define
で定義するものもないのは大きな功績です。それがなかったことで、言語の安全性と保守性が高まりました。Swiftのコンセプトがしっかりしていたのでしょう。
それでは次に、Swiftは何年にもわたって開発され、もう10年も経つんですね。新しい機能や性能を備えて進化し続けています。Swiftは積極的に更新され、アップルが培ってきた文化の影響で古いものをどんどん切り替えることができるのが強みです。オープンソースになってからも更新は続いています。
切り捨てる時も丁寧で、例えば関数の名前が変わった時などはデベロッパーに対してきちんと通知が行われます。例えば、ある関数が変更された場合、新しい関数に置き換えるためのオプションが提供され、古い関数を使おうとすると警告を出してくれます。具体的には、@available
アトリビュートでバージョンなどを書き、renamed
で新しい関数名を指定してあげると、古い関数を使おうとした時に警告が出るはずです。Playgroundではうまく動かないかもしれませんが、プロジェクト内であればきちんと動くことでしょう。 多分、そこが合っていれば、とりあえずは動かなくてもいいと思います。どちらかというと、この @available
は Xcode の機能というか、プロジェクトの機能というか、Xcode の言語というより機側の命令な気がします。もしかしたら @available
は、これも Swift の Xcode ですけど、Xcode の通常プロジェクトだけの解析、古文解析のときに何か警告を出しているだけの可能性があります。
確かですね、コンパイラのほうに詳しい人がいれば補足をもらえればと思うんですけど、コンパイラがちゃんと書いてくれたような気がします。このコードをターミナルでやってみたらどうでしょう?もしかしてね。
だからどうなる、どうなる?もういいや。Swift のコードを入れる、入れてたかな?入ってたね、これで保存してあげて、これで Swift でテストを動かしたときに、何も出ないですね。ここ出ないか、やっぱり Xcode 側かな。
swiftc
はどうですか?swiftc
か、そうかそうか。何も出ないね。何か勘違いしてるのかな。@available
が違う気はしないな、ここは合ってるはず。合ってそうよね。結構そういう気がするんだけど出ないね、これね。Xcode でまともなプロジェクトでやってみればいいのかな、気持ち悪いからちょっとやってみましょう。せっかくなので、ここでそんなに手こずるものか。何でもいいか、コマンドラインツールでいいかな。
deprecated
が抜けてる?deprecated
が抜けてる?そう言われるとそんな気もするが。そう言われてパッと思いつかないけど、deprecated
確かにそうですね。どこだっけ、ここだっけ、どうですか?これで macOS だとバージョン、これだけ。出た、出た、出た。これをすっかり忘れていました。ありがとうございます。出ましたね。
こんなふうに向こうだから、リネームしてねってちゃんと言ってくれるんですね。さらに Fix Issue
の機能が付くおまけ付き。あれでも変な動きをした時々ありますね、こういうこと。うん、これで、なんかダメそう。まあいいや、こういうことはある。普通はちゃんと直しますけどね。
Playground でやってみましょうか。スペシファイドバージョン、スペシファイドしなければいいのか。出た、出た。で、やっぱちゃんと直せないね。どうしたんだろう。まあいいや、普通はちゃんとセキュア使ってくれって出るはずなんですよ、ここにね。まあいいや、こういうふうな感じでいろいろとメッセージを添えてあげることができるので、ここリネームじゃなくてメッセージで伝えたりとかね、いろいろとあるんです。
せっかくだからこっちもちゃんと直してみよう。ターミナルのほう。こうですね、これで Swift を動かすと、deprecated
の綴りだ。いいですよ。ほらほら、コンパイラ自体もちゃんとね、セキュア使ってくれって言ってくれるからね。コンパイラも配慮して作られてて、それを受けて Xcode が Fix Issue
っていう機能を提供してるみたいな。
ここもね、Swift のとても面白いところで、今までコンパイラって言ったらコンパイラの仕事をやるっていう、エディターならエディターの仕事をやるっていうのが常識だったような気がするんですけど、Swift はエディターに対する助言みたいなのもちゃんとやるようになってて、より高度なコンパイラになってる。
今までもコンパイラとか言いながらリンカーとか、そういった役目は持ってましたけど、それをさらに大きな要素に持っていってるって感じがするところも Swift の面白いところですね。だから統合コンパイラ環境みたいな、そんな感じに呼んであげてもいいんじゃないかっていうような、とても豊富な機能を持ったものになっている感じですね。
そんな感じで、Apple 的にはこんな素敵な言語を使ってたくさんの人にいろんなものを生み出してほしいって書いてる文章の中で。なのでぜひ Swift をたくさん使って、いいものを送り出していきましょうって iOS エンジニアはやってることですけど、そんな感じが「The Swift Programming Language」における About Swift でした。
次にバージョン互換性のお話がちょっと含まれていたので、紹介してみましょうか。この「The Swift Programming Language」ウェブ上で Swift 5.5 のやつが公開されてたので、そこを元にスライド作っていたんですけど、その中で Xcode 13 の話が出てきていました。Xcode 13 には Swift 5.5 が搭載されていて、Swift 4.2 と 4.0 で選択可能なんです。Swift 4.0 や 4.2 を使うときに Swift 5.5 の機能のほとんどが使えるよって言ってた。そうなのかなという気はしますけど、基本的には互換性はあるからね、そういうことを言いたいのかな。
もちろん 5.5 の新機能は以前では基本的に使えないわけで、そのあたりも触れられてました。この中でまず 5.5 って銘打たれてるんですけど、ここに記載されているのは 5 からの特有の機能が記されてる印象。色付けしておいた不透明な戻り値の型、これは Swift 5.1 から搭載された opaque return type
のお話なんですけど、これも掲載されてたんで必ずしも 5.5 だけの資料じゃないみたいに捉えてもらったらいいかな。
オプショナルを返す関数で try?
を使ったときに、オプショナルでの nil
ラップを捨てるみたいな話も、これも前のバージョンからあったやつですよね。 では、次に大きな整数リテラルによる初期化で正確な型推論が可能な点について説明します。すべて古い情報でしたね。ここからは新しい内容になります。そして、紹介したい内容がいくつかあります。
まず、オペイクリターンタイプについてです。オペイクリターンタイプとは、何かしらのアクションを実行したときに、戻り値が特定の型ではなく、例えばバイナリーインテジャー(整数)を返す場合についてのお話です。具体的には、戻り値の型が確定していないが、少なくともバイナリーインテジャーに準拠した値が返されるというものです。
このように、アクションの戻り値がバイナリーインテジャー型であるとします。Swift 5.5からは、SAM
バイナリーインテジャーが必要なくなりました。ただし、互換性のためにまだサポートされている場合もあります。
また、Xcode 13
では、SAM
がなくてもコードが通るようになりました。例えば、INT
型のように見えるが実際にはバイナリーインテジャーに準拠した値が返ってくる場合があります。バイナリーインテジャー型の値を扱うことで、様々な機能が利用可能です。例えば、数値間の距離を計算するなどの操作が可能です。
バージョン違いにより表示の仕方が変わる場合がありますが、一貫してオペイク型の戻り値を利用することでコードが清潔に保たれます。そして、この情報はXcode 13
に関するものです。自分の環境がXcode 12
の場合、表示が異なるかもしれませんが、基本的な機能に違いはありません。
次に、オプショナルを返す関数でtry
を使った場合についてです。これは非常に簡単で、例えば、アクションがInt
型のオプショナルを返す場合を考えます。この場合、エラーハンドリングも合わせて利用することがあります。
let value = try? action()
このように書くことで、エラーハンドリング付きの操作が行えます。try?
を使用することで、エラーが発生した際にnil
を返し、それ以外の場合は正常な値を返します。
以前のSwiftバージョンでは、オプショナル型として別途エラーハンドリングが必要でした。しかし、Swift 5.5以降では、戻り値がオプショナル型であっても、さらに包むことなくそのまま戻り値として利用可能です。このように、型が一重のオプショナルとして返され、操作が簡単に行えるようになっています。 やっぱりクイックヘルプがないと図体的に見せられないですね。再起動すれば復活するんでしょうか…。復活していませんね。何とかして修正しないといけないですね。
さて、オプショナル型の Int
についてですが、こういうフラットな感じになったということを話したいと思います。このあたりは、イメージ的に賛否が分かれる可能性も考えられるかと思いますが、個人的にはなかなか上手な論理的な組み立てだと思います。ただ、そもそもエラーハンドリングとオプショナルが両方存在するという場面は少ないので、こういった場面はあまり出てこないかと思います。
戻り値がオプショナル型を取る場合のAPIの作り方にもよりますが、基本的には何かしらの値があって、それが空であるかどうかというよりも、空の状態も持つ一つの表現になっているような気がします。この Int?
で一つの意味を持っているということです。つまり、あるかないかではなく、この Int?
で値を持つ、と考えると、これをさらに括ったときに、話が矛盾しているように感じますね。とりあえず二重になるといろいろと弊害がありますが、この構文で解消したかったわけではないような気がしています。
具体例として説明します。例えば、A
という変数があって、これがオプショナルのオプショナル(Int??
)のように二重で定義できます。ただし、この書き方が好みではない人もいますね。この二重のオプショナルに nil
を代入しようとすると、どっちの nil
なのかという問題が出てきます。全体的に nil
なのか、それとも値としての nil
なのか、という問題です。let
で定義していると、そもそも変更できないので動かしにくいかもしれません。
A == nil
とするとどうなるのか?結果は false
です。これを A? == nil
でチェックすると結果は true
になります。このような問題があるため、二重のオプショナルを避けたい場合もありますが、それだけが理由ではないと思います。
ちなみに、スイッチ文もオプショナルの展開に対応しています。例えば、何らかの列挙型があって、case A
と case B
のように定義した場合、それをオプショナル型としてスイッチ文で扱うことができます。以前は、値があるかどうかを case case case some(let x)
と case none
という書き方をしていましたが、オプショナルパターンを使って case let x?
と書くこともできます。
また、列挙型で case A
と case B
に加えて nil
を含めることで、スイッチ文がフラットに使いやすくなります。オプショナルを二重に包まない風習が増えており、大きな整数リテラルによる初期化で正確な型推論が可能になります。
コメントもたくさん来ていますが、二重オプショナルについての話題ですね。普段の場面で二重オプショナルを使うことは少ないかもしれませんが、知っておくと表現の幅が広がることもあります。例えば、Int?
型のオプショナルを要素として持つコレクションなどでは出てくることもあります。勉強会としても盛り上がっているところですね。 あともう一つ、大きなリテラルのお話です。ここはちょっと軽く触れておきましょうかね。安心材料として見ておいてもらえればと思います。
例えば、大きい数値リテラルについてです。32ビットの整数型を考えた場合ですが、例えばアンダースコアは桁区切り用のリテラルに使われるもので、あってもなくても無視されます。これが Int
型、特に Int64
型だと、最後のフラグの分だけオーバーフローして扱えないわけです。だから UInt
として扱って正常にパスするわけです。
ここが大事なポイントです。もし何らかの事情で型としてではなく、リテラル自体を UInt64
として扱いたいケースがあるとします。昔の書き方では問題なくパスしていたものも、一般的な型変換の方法ではエラーが出ていたんです。これは、リテラルがデフォルトの型に置き換えられるという特性があり、Int
型として扱われるからです。
しかし、Swift 4 の頃から、UInt64
型が整数リテラルを受け入れる設定になっている場合、そのリテラルを Int
に変換せず、そのまま UInt64
に渡す仕様に変わりました。これにより、安心してこの書き方ができるようになったわけです。パフォーマンス的にも劣化なくリテラル変換がスムーズに行われます。Swiftでこのような型変換はポピュラーな書き方であり、リテラルも一貫して安全に使用できます。
次に、Swift 5.5 以上で新しい並列処理(コンカレンシー)が話題になっています。async
や await
、actor
などの機能が追加されています。これがバックポートされる噂もあり、ほぼ確定のような話もありますが、詳しい情報はまだ出ていないようです。
一度、Swiftコンパイラにバックデプロイのオプションが実装されてマージまでされた話もありましたが、最終的にギリギリでそのマージが外されたとのことです。まだバグが残っている可能性があり、取りやめではなく一旦保留という感じでしょう。Xcode 13.1 ぐらいには実装されることを期待しています。
また、Swift 5.5 で作ったターゲットと Swift 4.2 や 4.0 のターゲットは相互に依存ができ、段階的に Swift 5.5 にシフトしていけると書かれていました。なので、一気に全てを新しいバージョンに上げる必要はなく、4以上であれば段階的にバージョンアップができるとのことです。
これで次回からいよいよ Swift の具体的な書き方についての勉強に入ります。基本的な説明や概略を見てから、細かい言語仕様の部分に入っていく予定です。興味がある方は、ぜひ次回も参加してください。
では、これで今日の勉強会を終わりにします。ありがとうございました。お疲れ様でした。