https://youtu.be/rHxb0CzqUBo
今回も引き続きThe Basics
の 数値型変換
について眺めていきます。前回は Swift の型変換に対する価値観ですとか、それによって導き出される Int
型の位置付けみたいなところを見ていきましたけれども、今回はより具体的な型変換の特色をおさらいします。ついこの間に見たばかりの性質とかも言及されたりしている様子ですけれど、せっかくの機会なので改めてじっくり確認しましょう。どうぞよろしくお願いしますね。
———————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #120
00:00 開始 00:16 前回のおさらい 01:02 今回の展望 01:40 整数の表現力 02:53 サイズにフィットしない値はコンパイルエラー 04:00 コンパイル時か実行時かの判断方法 05:03 実行時エラーが EXC_BREAKPOINT で中断される? 06:47 コンパイル時に計算を行う 10:54 Swift 5.7 11:15 Build-Time Constant Values 14:38 @const とコンパイラーの新時代 16:40 コンパイラーが重くなる可能性? 17:50 M1 Ultra はコンパイルが速いらしい? 21:49 M1 Ultra が必須になる可能性は? 23:09 @const は流行る? 24:50 StaticString 26:39 StaticString の使い道は? 31:05 次回の展望 ————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #120
では、始めていきますね。今日も数値型変換についてのお話の続きを見ていきます。前回、この辺りの数値型変換の扱いとして Int
型を推奨していますよ、という感じのお話がありました。これは前回の良い感じにまとまったお話なので、見ていない方は前回のアーカイブを見てもらえれば良いと思います。
とりあえず、Int32
や Int64
ではなく Int
型を推奨しています。細かい話を抜きにすれば、そういったビューティーを押さえておけば問題ないです。細かい事情は前回の話を見れば大分わかるかなと思います。
今日はどういったところを見ていくかというと、具体的に変換、定数型、型変換というところを見ていくことになりそうです。とは言っても、前々回やその前にもこの部分を見た気がしますが、再び話題に出てきたので、この辺りを改めて見ていこうかなと思います。
まず型変換において、定数の表現力について挙げられていたので見ていきましょう。定数や変数に格納できる範囲は、それぞれの数値型によって異なります。例えば Int8
型なら -128
から 127
まで、UInt8
型なら 0
から 255
までなど、型によって違ってきます。
また、ビット幅によっても決まってきます。型があるビットの幅によって表現範囲が変わってくるというところですね。この勉強会でもよく話題に上がりますが、サイズが特定された数値型の定数や変数にフィットしない数値はコンパイル時にエラーとして報告されます。
リテラル値についても同じく、コンパイル時にエラーが報告されます。例えば、UInt8
に対して -1
のリテラル値や Int8
の最大値を超えるリテラル値を入れようとしたときです。これもコンパイルタイムエラーになるかどうか見てみましょう。
まず、Playground で試してみましょう。このとき、コンパイルタイムエラーかランタイムエラーかを判断するために便利なのが、最後に何かしらの print
文を入れて、その手前でエラーがどう起こるかを確認することです。例えば、以下のようにします。
print("Start")
let a: UInt8 = -1
print("End")
このコードで、print("Start")
が実行されるかどうかでエラーの状況がわかります。もしエラーがコンパイルタイムエラーであれば Start
は出力されませんし、ランタイムエラーであれば Start
は出力されるけれども、End
が表示されないということになりますね。
試してみると、print("Start")
は出力されていないのでコンパイルタイムエラーということがわかります。その後、他の例も試してみましょう。最初に実行されるプリント文を見て、エラーが発生するかどうかを確認してください。
print("Start")
let b: Int8 = 128
print("End")
こちらも、同じように Start
が表示されないのでコンパイルタイムエラーということになります。Playground で実行した場合、右側のコンソールにはエラーについての詳細が表示されるはずですが、最近は表示されないこともあるみたいですね。
試しに do-try-catch
ブロックを使ってみても、状況は変わりません。print("Start")
が表示されるけれども、右側には何も表示されない状態です。このまま進めても問題はないでしょう。 とりあえずコンソールに出てるからいいやと思ってスタートは出てるけどエンドは出てないですね。まあまあ、これでランタイムかコンパイルタイムかを判断できるという状況です。では、実際に先ほどのコードを見てみましょう。
let a: UInt8 = -1
これはコンパイルタイムエラーになります。ランタイムに入る前にエラーが出ているようです。スタートが出ていないので、つまり実行前にエラーになっています。
次に、アップの値がオーバーフローする場合についてです。例えば、UInt8に対しては以下のように設定します。
let a: UInt8 = UInt8.max + 1
これはやはりコンパイルタイムエラーになります。次に、Int8
に対して Int8.max + 1
を試してみましょう。このコードもコンパイルタイムエラーになることが確認できます。
これが何を意味しているかというと、Int8
の最大値に 1
を足すとオーバーフローになるということがコンパイルタイムで分かるという点です。これは非常に画期的です。普通はオーバーフローがランタイムエラーとして検出されるのですが、Swiftはこれをコンパイルタイムで検出できるのです。
例えば、以下のコードを見てみましょう。
let a: Int8 = Int8.max
let b: Int8 = a + 1
これはランタイムエラーになります。スタートが出たけれど、この部分でエラーが起こります。このように、必ずしもコンパイルエラーにはならないケースもあります。テキストに書いてある内容がちょっと過剰表現かもしれませんね。事前に検出できる場合はコンパイルエラーになりますが、基本的にはランタイムエラーという感じです。
改めて、この部分について話していて思うことは、Int8.max + 1
がコンパイルタイムエラーになるというのは、Swiftがコンパイルタイムにリテラル値をしっかり捉えて判断しているからで、その概念を意識している点がとても画期的です。このあたりがさらに進化すると、例えばこの例で出したコードもランタイムエラーではなくコンパイルタイムエラーになるかもしれないと思います。
Swift Evolutionを斜め読みしたところ、新しいSwiftのバージョンで何か関連する変更が入るかもしれないと感じました。せっかくなので、これについてもう少し調べてみますね。本当にそうかどうかわかりませんが、何か新しい情報があるかもしれませんので、確認してみます。
ちなみになぜ京都の話が出てきたのかは分かりませんが、Swiftの進化を見守りつつ学んでいきましょう。 Swift 5.7のお話の中で、なかなか面白そうなトピックがたくさんありますね。この勉強会でも紹介されたif let
のショートハンドも面白そうです。それに続いて出てきた「ビルドタイムコンスタントバリューズ」が関係するかどうかはわかりませんが、コンパイル時に決まる値についての話らしいです。
たとえばコンパイル時に計算が完了する値であれば、実行時にその値が保証されるという話のようです。概念的には難しそうですが、たとえばコンパイルタイムに値を入れておくことが良いのかもしれません。具体的には、ある関数に@const
(コンパイルタイムに決まる値を表す属性)を付けるなどして、コンパイルタイムに評価されるようにすることができるでしょう。
このような新しい属性が増えてくると、言語がどんどん複雑化してくる印象があります。しかしそれは言語が進化しているとも言えます。審査は難しいかもしれませんが、実際のプロポーザルを読み解きながら学んでいくと良いかもしれませんね。
さて、スライドに戻ると、コンパイル時にエラーとして報告される点が話題になっていました。@const
を使うことで、より良いコンパイルエラーの検出が可能になるかもしれませんね。これにより、コンパイルタイムにエラーが見つかれば、ランタイムエラーを避けることができるので、非常に面白い変化だと思います。
昔はランタイムで行っていた計算や処理を、コンパイルタイムで実施するように変わってきているのが興味深いです。これにより、UInt.max
のような値もコンパイル時に計算され、それがコンパイルタイムで見つかるというのは、非常に革新的です。将来的には、プログラマーが「ここまではコンパイルタイムで、ここからはランタイムで」という判断を自ら行えるようになる可能性もあり、これは個人的にとても面白いと感じました。
ただし、コンパイルが重くなる可能性もあります。C++のテンプレートのように複雑化する懸念もありますが、うまく折り合いをつけて進化していくことでしょう。コンパイルタイムで計算が終わるメリットとしては、ランタイムがより速くなることが挙げられます。
現在のSwiftのコンパイルは、以前と比べて重いと言われています。これはオブジェクティブCと比べてもそう感じます。一時期の重さからは改善されているものの、今後コンパイルがさらに重くなると、プログラマーは高性能なマシンが必要になるかもしれません。
M1 Ultra
を持っている人に聞くと、コンパイル速度の改善について具体的な話は少ないですが、おそらく動画編集など特定の領域ではパフォーマンスが向上していると言われています。プログラマー向けのコンパイル速度に関しても、実際に試してみないとわからない部分が多いですね。
このように、新しい言語仕様やコンパイルタイムの改善は非常に興味深いトピックです。今後も勉強会でさらに深掘りしていきたいと思います。 なので、これを見るとプログラマーにとっては「ウルトラ」も一つの選択肢になりそうな数値が出ていますね。大体、ハイエンドなんてこんなもんですから、コストパフォーマンスなんて良いほうじゃないですか。これ、デジタルモノでしょ?普通、最低でもこれくらいの価格帯ですけど、それでもそんなに高くないですね。パフォーマンスにしては。
毎年ミドルとハイエンドで何年か使おうとしたほうがいいと思います。そうですよね。今回だとM1 Maxぐらいですかね。M1 Maxも結構高いですけど、快適なのは間違いないでしょう。コメントでもいただいてますが、Intel Macから移行して特に満足しているユーザーも多いようです。M1は素晴らしいですね。僕も最近はM1のものを使っていますが、メモリも64GB積んでいて、ベンチマークほど差はあるものの、Macのほうが良いと感じることが多いです。実際、他のツールでもそれほど変わりませんし、メモリは重要ですからね。
様々なアプリを開いていると、スワップが足を引っ張ってくることもあると思います。実際、一つのアプリを軽く開くだけで1GBぐらいメモリを消費していますからね。その調子でいろいろ開けば、16GBのメモリでは足りないこともあるでしょう。
ウルトラも貴重な選択肢ですが、値段が高いのが難点です。学生さんには手が届かなくなるかもしれませんが、そういうアプリはさすがにないでしょう。未来のことを考えると、Swift Playgroundsのような軽量なアプリも大切ですね。
M1やM2が今後どう進化していくか楽しみです。M1 Maxよりも早いM2が登場すれば、また新たな時代の幕開けですからね。総じて、次のプレビューも楽しみにしておきましょう。ありがとうございました。
次に「@const」の話ですが、これはコンパイルタイムの負荷を軽減して、コンピューターの性能を最大限に引き出そうとする技術の一つですね。普通の感覚だと「@const」を使うのはまだ少数派かもしれませんが、今後の進展に期待したいところです。
コンカレンシーとの相性も良さそうですし、コンパイルタイムで決められた値がランタイムに影響を及ぼさないという利点があります。Immutable(不変)でもコンカレンシーと相性が良いので、爆速なアプリが期待できるかもしれません。
URLをランタイムで作る場合もありますが、「@const」を使ってStatic String(静的文字列)にすることでパフォーマンスを向上させることが可能です。
「Static String」を使っている方、いらっしゃいますか?ここから新しい話題に移っていきます。 これは何でしょうか。コメントを見ると、明らかにStatic感を保っていますが、コンパイルタイムにリプレゼントされるテキストが書いてあります。要は、これのことですよね。アップってマップ、コンストって雰囲気で、インスタンス Static String 型はイミュータブル、まさにそうですね。
Static Stringはローベルのアクセスができます。こんなのが定義できるのかと思うと、今のコンパイルだと、どこかで使うことがありそうですね。アサートで使っていたのをなんとなく見たことがあります。
アサートのタグファンクションやラインなどにStatic Stringがあったんですよ。正直、なぜStatic Stringを使うのかはわかりません。コンパイルタイムに決まるのでStatic Stringにふさわしいという感じはありますが、特に大きなメリットがあるのかはわかりにくいですね。アサートはそんなにパフォーマンスを必要としないですし、理由があるのかな。
結局、コンパイルタイムに文字列が決まろうとランタイムで文字列を入れようと、プログラム領域には文字列データが入るわけですよね。アサートランタイムというのは何でしょうか。動作するのはわかりますが、なぜStatic Stringを使っているのかが明確ではありません。特にコメントにも記載はないですね。
リテラルコンバーティブルでコンパイル時の最適化を狙うなら、リテラルで十分な気がします。普通のプログラマーが使う場合、変数に入れるときにコンパイルタイムを引き継ぐような感覚でしょうか。Static Stringでもね。今のCopaflowだといろいろ書いてありますが、ベースポイント長くなってもキープリ存在しないなどの理由で、最適化される可能性があるといった内容があります。リファレンスカウンターがいらないというのは、大きな利点かもしれません。
コンパイルタイムの利点を重視するのであれば、コメント通りに行かない気もします。結局コンパイルタイムの恩恵を受けることが少なそうですし。
スタティックストリングに関しては注目どころですが、一見したところ、コンパイルタイムの最適化以上の効果を期待しない方が良いかもしれませんね。使いこなせたら面白そうではありますが、その程度の予測で終わってしまった感じです。
はい、いい具合に時間になりましたので、今回はこれで終わりにしましょう。ありがとうございました。