https://youtu.be/OFsUCDUFXuU
今回はThe Basics
の 数値型変換
というセクションを見ていきます。型変換の作法というのか、それより手前の型変換の必要性みたいな初歩的なところを、数値型に着目して眺める回になりそうです。どうぞよろしくお願いしますね。
———————————————————————————— 熊谷さんのやさしい Swift 勉強会 #118
00:00 開始 01:38 numericCast 02:52 確定初期化 11:00 整数における特別な型変換機能 11:59 numericCast の命名について 13:16 数値演算に関する標準ライブラリーの機能 14:58 numericCast は使う? 16:44 numericCast が必要な場面 22:43 IntMax 廃止との兼ね合い 27:07 表現範囲を考慮するには 30:17 サイズが分からない場合の両者比較 33:23 違う型同士の数値演算 34:35 numericCast と型キャストと型変換 36:25 整数の幅を意識しないプログラミング 38:45 クロージング ————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #118
はい、じゃあ始めていきますね。今回の最初のテーマは「通知型変換」、特にニューメリックタイプコンバージョンについてです。要は型変換における通知に着目して見ていきます。型変換全般についてまずは基本を押さえていきます。
型変換という言葉は、プログラミング言語に少しでも触れたことがある人なら馴染みがあると思いますし、実際に使う機会も多い部分です。型変換の概念を理解することで、Swiftや他のプログラミング言語でもスムーズにコードを書けるようになります。
例えば、Swiftのニューメリックキャストについて見てみましょう。知っている人は多いかと思いますが、実際に使う機会はそれほど多くないかもしれません。例えば以下のようなケースを考えてみます。
let a: Int8 = 10
let b: Int16 = a // これはエラーです
この場合、Int8
型変数 a
を Int16
型変数 b
に直接代入しようとするとエラーになります。そのため、Int16
への型変換が必要です。
次に、Swiftの「確定初期化」について触れておきます。Swiftでは、変数や定数に初期値を与えなくてもコンパイルを通すことができます。例えば次のようなコードが考えられます。
var b: Int
b
の初期値を設定せずに宣言しています。このように、確定初期化を使って後回しにできるという特徴があります。
具体的な例として以下のようなコードがあります。
let flag = true
var b: Int
if flag {
b = 1
} else {
b = 0
}
この例では、flag
の値に応じて b
の初期値が異なります。このような場合、確定初期化を使うことで、b
の初期値を後で設定することができます。
このように、Swiftでは確定初期化や型変換をうまく活用することで、より柔軟なプログラミングが可能になります。これらの技法を意識的に使うことで、変数や定数の扱い方が変わってくるでしょう。
以上が、本日の勉強会の初めのテーマです。今後も実際のコードや具体例を交えて、さらに詳しい説明をしていきますので、ぜひ楽しみにしていてください。 Swiftでは、変数の初期化に関する仕様が非常に厳密です。例えば、初期値を設定せずに変数を宣言して、その後に条件分岐で値を設定するという形を取ることができます。しかし、Swiftではすべてのパスで初期化が完了していない場合にエラーが発生します。
C言語などと比較すると、C言語では初期化されていない変数は未定義動作になります。初期化を忘れた場合、メモリの状態に依存した不確定な値が出る可能性があります。一方でSwiftは、このような未定義動作を避けるために、確定初期化の仕組みが組み込まれています。
具体的には、以下のようなコードを考えてみます。
var b: Int
if someCondition {
b = 5
}
print(b)
この場合、someCondition
がfalse
の場合、変数b
は初期化されないままprint
関数で使用されることになります。Swiftのコンパイラはこのようなケースを検出し、コンパイルエラーを報告します。これにより、ランタイムで不自然な値が出る前に、コンパイルタイムで問題を未然に防ぐことができます。
この特性のおかげで、Swiftの開発者は初期化に対する安心感を持ってコードを書くことができます。また、わざわざクローザーを使って即席で変数を初期化するなどの冗長なコードを書く必要も少なくなります。
次に、ローカル変数とプロパティについてですが、変数とプロパティの確定初期化のタイミングが異なることがある点も重要です。例えば、クラスや構造体の場合、プロパティはイニシャライザ内で初期化されますが、ローカル変数は関数内で初期化されます。この違いは意識しておくべきです。
もう一つのトピックとして、型変換についても触れておきましょう。例えば、Int8
からInt16
への変換は、次のように行います。
let a: Int8 = 42
let b = Int16(a)
これにより、a
の値はInt16
型のb
に変換されます。ただし、数値の型変換は慎重に行う必要があります。特に浮動小数点数については、numericCast
が使えないことに注意が必要です。以下のコードで試してみるとわかります。
let floatNum: Float = 10.5
let intNum = Int(floatNum) // エラー
この場合、numericCast
を使っても期待通りの結果にはなりません。整数型の変換はnumericCast
を利用できますが、浮動小数点数の変換には別途考慮が必要です。
以上、Swiftの確定初期化の概念と型変換に関する基本的な内容について説明しました。これらの特性を理解して活用することで、より安全で安定したプログラムを書くことができるでしょう。 不等数の点数はできないのですが、int8
キャストみたいなことはないのかなと思いました。名前に問題があるかどうかについてはどうでしょうか。意外とよくないって感じることもありますよね。例えば、「ニュメリックキャスト」と名前をつけるのと「int8
キャスト」とつけるのではどちらが良いと思いますか。どちらも意味は通じるんですけど、「フォート」を取らないなら「インテジャーキャスト」という言葉の雰囲気が微妙な気もします。それは自分の単なる偏見かもしれませんが、どうでしょうね。名前の付け方って結構重要ですよね。
APIデザインガイドラインに従った名前の付け方が重要です。「ニュメリックキャスト」というのがAPIデザインガイドラインに対して微妙なんです。しかし、これが標準ライブラリに入っていたと思います。
「スウィフト」の「マス」(SwiftのMath)というカテゴリーがあるのかもしれませんが、その詳細が気になります。スウィフトにおけるコントロールブロックや演算の話、例えば「ニュメリックサイン」や「ニュメリック」といった数値系があることはわかりました。ファイナリーインテジャーといったものも含まれています。
自分はあまり使い慣れていないんですが、それは慣れの問題かもしれません。とりあえず、スウィフトの標準ライブラリに「ニュメリックキャスト」が入っています。数値変換の際に「ニュメリックキャスト」は便利だと思います。
しかし、実際に「ニュメリックキャスト」を使いまくっている人は少ないかもしれません。自分も便利だと思いつつ、頻繁に使うことはあまりないです。「ニュメリックキャスト」が便利だったらしょっちゅう使っていくことになると思いますが、今後どうなるかはわかりません。
他の人はどうでしょうか。例えば、「int16」や「int8」の型変換が具体的にできるかどうかについても議論があります。数値変換の場面では「ニュメリックキャスト」が役立つこともあります。例えば電力関数のときに役立つ場合もあります。
また、「サムバイナリーインテジャー」で何らかの二進数データを返すときなどに利用できるかもしれません。ただ、この場合、「バイナリーインテジャー」のイニシャライザーが必要になりますが、これがサポートされていないとエラーになります。
このように、「ニュメリックキャスト」が有用ですが、反省する点もあります。「ニュメリックキャスト」を適用する場面は限られているかもしれません。今後どう使うかについてはさらに検討が必要です。 バイナリインテージャー同士を変換するときに、Numeric Castが使えるんですよ。そうすると、このアクションに対して具体的な値を渡すことができたり、できなかったりします。それと同時に、型推論も必要となりますね。とりあえず、今回の例では限界があるんですが、もっと汎用的な型、例えばGenerics型を使ってパラメータを複数持つ場合、その型がバイナリインテージャーであるようなケースでは、プロトコルやストラクトを使用することが考えられます。
例えば、プロトコルエクステンションを使って汎用的なメソッドを定義する場合、そのメソッド内で型を揃える必要があります。例えば、AとBという型があり、どちらもバイナリインテージャー型であるとき、Numeric Cast
を使って型変換することが可能です。例えば、次のようなプロパティを考えます:
var a: A
var b: B
このようにして、A
をB
にNumeric Cast
で変換することができます。例えば、以下のようになります:
let temp = B(a) // Numeric Cast
このようにして型を揃えることが可能です。ただし、この方法は必ずしもデータサイズが一致していないと変換はできません。例えば、Int16
やInt32
に変換することはできますが、最終的に型を揃えるためには注意が必要です。
例えば、A
とB
の値を比較したい場合、以下のように型を揃える必要があります:
let isEqual = Int64(a) == Int64(b)
ただし、この方法だと例えばA
がUInt64
の場合、オーバーフローのリスクがあります。オーバーフローしないように注意しながら、型変換する必要があります。関数内で汎用的なコードを書く際に、このような型合わせが可能になります。
例えば、Static func ==
メソッドを用いて、次のように両方がバイナリインテージャー型であるときにTとUを比較するコードを記述できます:
static func == <T: BinaryInteger, U: BinaryInteger>(lhs: T, rhs: U) -> Bool {
return Int64(lhs) == Int64(rhs)
}
このような形で最大の型に揃えて比較することが一般的でした。かつてはIntMax
という型エイリアスがありましたが、現在ではInt64
がそれに相当します。このようにして、型を揃えて比較する方法が昔のコードでは使われていました。
最近ではNumeric Castが活用されることで、型変換がより簡潔に行えるようになっています。Numeric Castが導入されたのは比較的新しいため、かつてのコードから現在への移行も含めて注意深く扱う必要があります。 とりあえず、これでニュメリックキャストで変換できて、どっちの型が大きいかによって動きが変わりそうですね。例えば、反対符尾を使いますけど、癖でさっきからずっと繋ごれてないRightHandSideをニュメリックキャストでRightHandSideT
型に揃えちゃって、そうすると型が一致するから比較ができる。こういうノリで活用できるわけですよ。
これはInt
型を具体的にさっきのどこだっけ、これ、4行目ができなかったんですよね。一応、こういった感じでニュメリックキャスト活用の場面はあります。話が長くなりましたが、今のイコールのやつって、言った通りですが、PとUどっちが渡す時点でそれぞれInt16
が渡せますよね。そうすると、どっちの型がのほうが大きいかに左右されますよね。それで、この辺を頑張るとすると、バイナリーインテージャーがさらに結構入れされていますので、Pのサイズというのか、まずこの段階、とてもいいですね、面白いですね。
この点はね、33行目のバイナリーインテージャーっていうのは、ビットサイズに制限がないっていう前提の型なんですよ。なのでPもUもオーバーフローっていう書き換えがない世界なので、この場合は基本的には問題なく、どっちのビット数が多いとか、そういう世界観じゃないんですよね。33行目はなので、おかしいことがあった時にはもうロジック的に反しているから、ペイタベラーっていう世界観。ここがね、サイズのどっちが大きい、大きくない本当は判断して大きい方に合わせたいんですけど、そういう世界じゃないってことですね。とりあえずね。
まあ、大きいか分かんないけど、もう一個いってみよう。そういったサイズの世界観が入ってくるのがこちらのXWTJR
で、こうなってくるとやっとUのほうが本当に大きかったらどうしようとかね、そういった話にやっとなってくるんですよ。このとき、こうしたときにはPにビット幅っていうものがありまして、例えばこうやってどっちに揃えるかってランタイムで判断できますよね。比較偏差だから話が早くなれば、それでいいんだな。こうだったらリターンとして大きいほうに揃えたいから、こうですね。これでニュメリックキャストのライトハンドサイド、そうじゃなかったらリターンのニュメリックキャスト、レフトハンドサイド以降ライトハンドサイド、これ通るんじゃないですか。通るよね。こういう書き方でいってますね。
そうですね、バイナリーインテージャーのほうがどうやるんだろうっていうのが分かりやすいですよね。そうですね、バイナリーインテージャーはビット幅がどうかという世界観じゃないんでね。そうやる以前の問題、人間というか数学的に言うとゼロが発見されていなかったときに、何ものをどう表すかという発想がないという、そういう段階。それをそういう問題が認識されて、フィックスクイズインテージャーが新たにそっちに進化したみたいなイメージかな。なので考えるようになったら、バイナリーインテージャーよりも未来に存在しているフィックスクイズインテージャーを持ち出してくる。
バイナリーインテージャーの比較はできないってことになるんですかね。バイナリーインテージャー同士の比較はできますね。それと標準ライブラリのバイナリーインテージャーだけに準拠して、フィックスクイズインテージャーに準拠していない定数型が簡単には存在しないもので試しにくいんですけど、もしバイナリーインテージャーの比較演算子どっちに出てきているんでしょうね、標準ライブラリね。先にそれ見てみますか、マスのところにありそうね。イコールイコール演算子、これをパンプレッションでたどって、型ごとに突破したのがあって・・・。 左辺側が具体的な型で固定されていますね。これについて誤解することはないでしょう。左辺が主導権を握るような形が意図されています。例えば「other」が32ビットであろうと16ビットに合わせて計算されるということですね。少し不安がありますが、ランタイムエラーが発生しやすいところでエラーが起こる可能性があると感じます。しかし、「other」がバイナリーインテジャーとして定義されている以上、どちらか一方に主導権を取らせて他方に合わせる形でしか限界があります。
バイナリーインテジャー同士の場合でも、どちらに合わせるかは仕様で決める必要があります。同じようなバイナリーインテジャー同士の計算をどちらに寄せるかは、例えば「前者を優先する」といった仕様に基づいて決めますね。これを実現するのがニューメリクキャストです。
バイナリーインテジャーやフィクスドビットインテジャーを使って数値演算をする際には、ニューメリクキャストを使うと異なる型同士の数値演算が簡単にできるようになります。まさにこのニューメリクキャストが、異なる型同士の数値演算を基礎的に使うための非常に便利な機能です。
続いて、このような数値演算についてもう少し詳しく見ていきます。基本的に数値演算に関しては、必ずしも型を揃えるという方針がなくなってきているのが面白いところですね。このニューメリクキャストを使うことで、異なる型の数値演算が簡単に行えるようになります。
会話の中で、例えば「as」や「ニューメリクキャスト」について議論がありましたが、ここでのポイントは、バイナリーインテジャーを取る時のニューメリクキャストが重要だということです。また、インテジャー型を使うことのメリットについても触れられました。特定のビット幅に囚われない数値変換ができる点で、イント型(Int型)を使うとプラットフォームに依存せずにコードが簡単になるという話です。
イント型の使用により、ビット幅を考慮せずに開発できるメリットがあります。これにより、型推論がうまく作用してコードがシンプルになるという利点があります。例えば、ビット幅を意識せずに「Int型」を使うことで、プラットフォームごとのサイズの違いを吸収しつつ、コーディングが楽になる点が挙げられます。
今日の勉強会はこの辺りで終了とします。ありがとうございました。