https://youtu.be/8zG891wbEHo
前回は勉強会の導入的なオリエンテーションを振り返った後で少しだけ Language Guide
を見始めたところで終わった感じでしたので、今回はその続きというのか、その最初の The Basics
ほとんど最初のところから眺めていく回になります。
せっかくの The Swift Programming Language 本の導入部分を見終わって本格的に学習に入っていくパートでもありますので、腰を据えていろんな疑問を見渡していく感じにできたらいいなと思いながら Language Guide
をこれから見ていってみますね。どうぞよろしくお願いします。
—————————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #78
00:00 開始 02:11 C や Objective-C の開発経験で親しみがある 03:50 フレームワークがもたらす親しみ 04:04 C 寄りの言語な印象 04:39 Objective-C 08:28 Objective-C without the C 10:34 Objective-C との相互運用 12:48 Swift が提供する型 13:03 第一級の型 13:23 プリミティブ型とユーザー定義データ型 14:33 値型 20:27 イミュータブルクラス 20:49 Swift 標準な型の特別扱い 22:07 unknown error 23:51 Swift 標準な型の self 25:19 参照型 27:31 unknown error 続報 30:51 Swift 標準な型の特徴 32:34 BinaryInteger プロトコル 34:01 BinaryFloatingPoint プロトコル 34:16 文字列型 35:36 基本的なデータ型の特徴を示すプロトコル 37:09 StringProtocol の特徴 38:12 プリミティブ・メソッド 39:51 StringProtocol と BidirectionalCollection 40:31 BidirectionalCollection の条件 43:21 Objective-C では UTF-16 45:43 サロゲートペア 46:12 次回の展望 ——————————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #78
はい、では始めていきますね。
今日は前回の最後の方に少しだけ触れた新しい単元、ランゲージガイドの「ザ・ベーシックス」に入っていこうと思います。前回も少しお話ししましたが、本書のテイストはこれまでのSwiftツアーとは少し異なり、説明が詳細になっている感じがします。皆さんと一緒に、じっくりと考えながら進めていければ面白くなると思っています。
難しい内容に感じるかもしれませんが、所詮は基本的な内容なので、表面的には比較的シンプルに進んでいくと思います。どの程度詳しく掘り下げるかによって、この資料の充実度が変わってくると思います。
前回表示したスライドには、Appleのプラットフォームに特化しているような感じが見受けられるという話をしました。次の行に進むと、面白い部分もあるので振り返ってみましょう。
Swiftは全く新しい言語として登場しましたが、多くの部分はCやObjective-Cの開発経験で親しみがあると「The Swift Programming Language」に記載されています。他にも、「About Swift」などでも同様の話が出てきたことを覚えている方もいるでしょう。C言語とObjective-Cの開発経験があると親しみを感じるかどうかについてですが、自分は感じる方です。人によっては、JavaやKotlinなど他の言語と親しみを感じるかもしれません。
私はC言語の煩わしさがなくなって、Objective-Cの制約も緩和された感じがしてすっきりしました。UIキットがあったので全く新しい感じではなかったですが、Swift言語単体で見ると全く異なる言語だと感じました。
フレームワークのおかげで親しみが保たれている感じですね。設計自体は変わっていないので、言語が変わっても理解しやすい部分が多いです。
私はObjective-CよりもC寄りだと感じました。Objective-Cの嫌なところを取り除いて、C言語のようにドットメソッドを使えるようになったのが良かったです。Objective-CはCと完全互換する言語なので、Cの予約語を拡張するために作られたのかもしれません。
Objective-C++は完全互換で素晴らしいですよね。C++11の企画が制定されたときも、Objective-C++が成り立っていたのは驚きです。ラムダ式や型推論など新しいキーワードが追加されても、互換性が保たれていました。Objective-Cを考案した人はすごいなと改めて感じます。
元々のObjective-Cは今の書き方ではなかったですね。NextStep系のObjective-Cで現在の書き方になっていますが、本来のObjective-Cは別の書き方だったはずです。詳しい方はほとんどいないかもしれませんが、もし知っている方がいましたらぜひ教えてください。
いずれにせよ、Swiftの学習を進めるにあたっては、こうした背景を知っておくと理解が深まると思いますので、引き続き学んでいきましょう。 自分が知っているObjective-Cとは全然違うぞって感じることがありますよね。 あの言語も結構進化していますからね。ただ、構文が大きく変わるという経験はあまりなかったです。個人的に覚えている構文の変更と言えば、プロパティぐらいですかね。ドット構文で呼べるようになったという。ただのシンタックスシュガーですが。
引数なしのメソッドはドットで呼べる。あと、@property
というキーワードを使って、もうちょっと現代的なコードが書けるようになったぐらいです。全然歩み寄っている感じはないですけどね。今お話してて思い出しましたが、SwiftでObjective-Cらしさが消えていってCっぽいという話がありましたね。でもSwiftのキャッチフレーズって、そういえば「Objective-C without C」でしたよね。まさに逆のコンセプトを推し出していました。そのキャッチフレーズを聞いたとき、自分が最初に思い浮かべたのが「それスモールトークじゃね?」でした。
だってObjective-Cって、Cにスモールトークを語ったサブセット言語じゃないですか。「Objective-C without C」ってスモールトークじゃね?と。その辺も一緒に思い返してみると、確かに「without」したのはObjective-Cな感覚で、そっち寄りな感じですね。
そんな感じで振り返ってきましたが、あまり面白い発見はなかったです。とりあえず、親しみという面では微妙ってことですかね。でも確かにSDKやフレームワークのおかげで親しみが持てているというのは大事かもしれませんね。Appleのプラットフォームって、特にフレームワークがObjective-Cをベースにした考え方をしていましたから。
Objective-Cって、今時の一般的な考え方からすると特有のオブジェクト指向の性格を見せていて、単純にモダンな言語を作っていったとすると、やっぱり「without Objective-C」というわけですね。そうなったとしたら、独自のフレームワークになっていたはずですね。実際にObjective-Cとの相互運用をSwift1の頃から積極的に推し出していたコンセプトがあるので、そのおかげで今まで親しんできた価値観も大事にしながら組み上げていくというところまでひっくるめると、親しみというのがしっかりと考慮されつつ、できたのでしょう。
SwiftはCに似ているという感覚もあり、言語としてはCと親しみがあり、フレームワークでObjective-Cとも親しみがあるといった感じで捉えても良いのかもしれないですね。さらにモダンな考え方が積極的に取り込まれることで、今時の言語を使っていた人とも親しみがあるものになったりしますね。
このスライドには書いてないですが、そのため色々な人が自分のやっていた言語と似ていると感じたことがありました。そういうところからこの親しみが来ているのかもしれませんね。
「The Basics」に近い話ですが、CやObjective-Cでのすべての基礎的な型をSwift独自の型として提供しているという点も面白い特徴ですね。すべての型がファーストクラスというか、対等に存在していて。今までの言語、少なくとも自分がよく触っていた言語では、組み込み型とそれ以外の型で立ち位置が違っていました。
シンプルに言えば、プリミティブ型とそうじゃない型とで立ち位置が違っていたのが、Swiftでは全く特別扱いしていないわけではないですが、基本的にはint
型だろうとdouble
型だろうと構造体で作られて定義されています。今となっては何の新鮮味もないですが、当時は独特な印象を持ちましたね。それゆえに使い勝手が不思議な感じで戸惑ったことを覚えています。
例えば、配列の扱いも結構戸惑いました。同様に書き換えたタイミングでどうなるのかが不安で仕方なかったですね。特にオブジェクトにプロパティとして配列を持たせた時、そのオブジェクトをどう扱っていいのか。昔は変数に一度受け取ってから操作するのが一般的だったような気がしますが、今はドットを打ってやっていく?でもそうでもないか。昔何に引っかかっていたのかをすっかり忘れてしまっています。 よくオブジェクトやデータを受け取ったときに、コピーを使いますよね。そうですね。あ、ドットコピーではなくて、ただのコピーですね。プロパティを受け取ったとき、イニシャライザーなどでもよく使いますよね。インスタンスをコピーしておまないと、変更してよいかどうかわからない状態になりますから。
そうなんですよね、うっかり壊してしまうこともありますし。例えば、セルフの書き方を忘れてしまったんですが、定石としてはこういう感じですね。このコピー手法を理解するまでには慣れが必要でした。これを書ける発想が最初になかったりすると、非常に大変です。しかし、こういった発想が必要なくなり、うまく回るようになったのは素晴らしいことです。ただ、最初の頃は確かにこれが難しく感じましたね。
クラスがあり、配列に値を持たせてインスタンス化し、それを扱う際になんと言いますか、配列の値に対して処理を行う際に、例えば変数に一旦受け取ってから処理を行う流れがあったような気がします。現在はコピーされているので、オブジェクトの中身を直接書き換えたい場合でも反映されず、戸惑うことがありました。これは大きな進化かもしれませんね。
個人的にはまった記憶として、UIキットのバウンズやフレームに関するものでした。ビューのバウンズのサイズを直接書き換えられるかどうかについて混乱していました。バウンズはレクト型で、フレームの X
にはゲッターしかないので、フレーム自体の X
とサイズの X
を別々に設定しなければならないのです。この理解に時間がかかりました。
フレームの X
がゲッターしか持たないため、フレームの X
とサイズの X
を設定する必要があった。あの時の戸惑いは、値型との関係などで混乱したのかもしれないですね。しかし、今では特に問題なく扱えるようになっています。
今回は話が少し脱線したかもしれませんが、記憶をたどる中で得た知識も多いですね。 とりあえず、その型についてですが、配列も型として扱われます。それについては少し煩わしいと感じることもありますが、現代ではとても良い感じの安全性を担保できる機能だと思います。Objective-Cの時には、それらのミュータブル型とイミュータブル型をわざわざ分けていましたね。個人的には、そのモジュールの分け方が面白いので楽しんで書いていましたが、やはり面倒でした。
さて、コメントについてですが、プリミティブ型に対してユーザー定義データ型という言い方が記憶にありますね。Swiftでは、ユーザー定義型としての扱いになるということで、イント型なども用意されています。しかし、若干の組み込み型として優遇されている部分がありますね。例えば、struct
でイントを定義した時点で、サイズや64ビットなどの情報が既に存在します。
実際にユーザー定義の型を作った時のサイズは0ですが、Swiftの標準のイント型に対してはメモリーサイズが割り当てられます。そこで独自定義とSwiftのイント型の違いが出てきます。
さて、興味深いエラーが出ているようです。「Unkown Error」として、エクスプレッションフィールドが表示されています。どうやら何か間違っている可能性がありますが、特に気にするほどのことではなさそうです。
動作を確認してみると、どうやらint
という名前が良くなかったのかもしれません。この名前はプレイグラウンドで特に問題を引き起こすことがあるようです。それでも、構造体の中にプロパティがないとサイズは0になりますが、Swiftのイント型には8バイトのメモリが確保されるという特性があります。
イニシャライザーやセルフに対する値の代入など、組み込みの定義型には独特の動きが見られます。たとえば、ボイド型をセルフに入れようとすると動かない場合があるなどです。これも構造の違いによるものだと思われます。
さらにコメントを拾っていくと、「参照型で渡す」というのは、暗黙的なグローバル変数のようなものだと言えますね。元のスコープを超えて編集可能なため、昔はイミュータブルクラスを作ったり、今は値型を使ったりすることでその問題を防いでいます。特に、コンカレンシーなど並行処理においては、この「暗黙的なグローバル」が問題となります。
最近搭載されたコンカレンシーにおいては、値型やセンダブルなもので安全性を高めていますが、それはクラスも含め、渡せるようにするための工夫です。並行処理を行う際に大変なことにならないようにするには、適切な型の使用が必要です。 なので、そういったことをいろいろ考慮して、値型とかイミュータブルクラスなどが作られ、Twiftはそれをしっかり意識した言語にすることによって、並行処理とかも安全に遂行していけるようにしています。それどころか、コンパイルタイムに安全性を検証するという点もあり、なかなかすごいことですよね。
コメントをもう一ついただきました。あ、さっきのエラーですかね。デプロイ環境でやってみると、ストラクトイントはもう少し詳細なエラーが出てくれるんですね。なぜインテジャー型が出てきていたんでしょう。わからないですが、曖昧になってしまうことがあるのかもしれません。昔はローカルのデータを優先的に使ってくれる記憶だったんですよね。そうですよね。イント型とか重なっていたことがあった気がするんですよね、遊んでいたときに。昔は普通にそれを作っていました。イントではないんですけども。他のデータ型もよく作っていました。
あ、なるほど。データ型、またツイスト標準ライブラリと違うんですかね。データファンデーションか。そうそう、データファンデーションなんですよ。謎の分類な気が個人的にはするんですけど。ダブル型だとエラーが出ないんですね。どういうことだろう、これは。多分プログラムが少し違うんですが、内容的には同じコメントが出ている気がしますね。なるほど、確かに同じですね。アンノウンではなくなって、ちゃんとエラーが出たんですね、今回は。なるほど。データ型についても少しやってみますか。データ型ではエラーは出ませんでしたね。ツイスト標準ライブラリで何らかの最適化が行われているのかもしれないですね。でも、最近かもしれない。昔は通った気がするんですよね。
色々な最適化が進んでいるのは嬉しいところですね。裏方でかなりの最適化を頑張ってくれているので、安心して使えますね。たとえば、値型のコピーオンライトとかもそうですよね。実装側で頑張っている部分ですが、コピーオンライトは面白いですよね。試したいことがありますが、時間がかかるので後の方でスライドで出てくると思いますので、それはまた後にしましょう。今回のスライドを進めていきますか。
定数型としてINT型があり、ほかにもInt8
やInt16
、UInt8
などがあります。不動小数点数型としてはDouble
とFloat
型があります。また、Intel CPUならFloat80
型というのもあります。真偽値はBool
型で、テキストデータのためのString
型。さらに、強力なコレクション型としてArray
、Set
、Dictionary
があります。これらの型はすべて値型として設計されています。つまり、Twift標準ライブラリで提供されているデータ型はすべて値型です。昔はクラス型が1つだけありましたが、今はそれも排除され、表向きには存在しないことになっています。すべて値型、つまり構造体または列挙型として定義されています。オプショナル型もありますが、特別扱いされるのでしょう。
次に進みますか。まず、補足したいところとしては、Twiftの整数型はBinaryInteger
というプロトコルに属していることです。つまり、BinaryInteger
プロトコルの規格をすべて持っているのがTwiftのInt
型ということになります。これにより、ビットで表現可能な整数型になっています。ビットで表現可能ということは、データ長が限られており、表現できる数字の範囲が限られるため、それを超えたり下回ったりするとオーバーフローの性質を持つことになります。それがTwiftの整数型の特徴です。 少なくとも標準ライブラリの中では、可変長整数型みたいなものは存在していません。必要ならば自分で作る必要がありますね。
特筆すべきところですが、特に言及されていないものの、不動小数点数型もバイナリーフローティングポイントというプロトコルに準拠しています。ダブル型もフロート型も同様です。
次にデータ型のテキストに関してのお話です。String
型だけでなく、Substring
型というのも存在しています。Substring
型は最適化が図られた文字列型の部分文字列を表現するための型です。あくまで補助的に使う型であり、長期間いろんなプロパティとともに保持して使うものではありません。
言葉だけではあまり印象に残らないかもしれませんが、おそらくストリングの章とかがどこかにあると思うので、その時に詳しく見ていくとよいでしょう。
String
プロトコルの話をします。Substring
型もString
プロトコルに準拠しており、文字列としての基本機能やインターフェースを提供しています。特徴としてはみな似ています。Int
型はバイナリーインテジャープロトコルを参照するとどんなことができるかわかります。Double
型やFloat
型、つまり不動小数点数型はバイナリーフローティングポイントプロトコルを参照するとわかります。ブーリアン型はBool
一本に絞られています。
String
やSubstring
のようなテキスト表現はString
プロトコルを見ると理解できます。Array
、Set
、Dictionary
についてはCollection
というプロトコルがあり、それを参照すると特徴を把握できます。
もし独自に整数型を作るなら、例えばInt128
を作りたいときにはバイナリーインテジャープロトコルに準拠させることで、うまく作ることができます。不動小数点数型も同様にバイナリーフローティングポイントプロトコルに準拠させるとよく扱えます。ただし、テキストデータのString
プロトコルについては、このコメントに明記されているように、独自の型に使用することは禁止されています。
なぜこのような制約がかけられているのかわかる方はいますかね。考えてみてもわからないのですが、独自に準拠させると何か世界観が壊れるのでしょうか。
昔、今も当たり前に存在しますが、オブジェクト指向の特徴として、NSString
などのクラスを継承する場合には、必ずこれとこれをオーバーライドしてくださいというような規定があったのを思い出します。そういった絶対にオーバーライドしないと他のメソッドを使用した時に結果に矛盾が出てしまうような重要なメソッドも存在します。オブジェクト指向でクラスを作る際には、そういったことを意識して作らないといけないのです。
もしかすると、内部でプロトコル指向プログラミングで複雑なことをやっているため、内部設計を変える可能性があるからかもしれません。
いずれにせよ、String
プロトコルを自分の独自の型に適用することは禁止されており、標準的なプログラムを作る人にとっては重要なポイントです。
現在、String
はバイディレクショナルコレクションに準拠しています。Swift 3の時に一度コレクションではなくなり、Swift 4で復活しました。現在も将来も、文字列型は文字のコレクションと捉えて問題ありません。次に見ると、String
のカウントは効率的になっています。 複雑ですね。バイディレクショナルコレクションの準拠の条件の一つに、カウントが計算量 \(O(1)\) である必要がありますよね。でも多分、ストリング型は内部的にキャラクター型で1文字を持っていて、複雑な絵文字もキャラクターで表現できるのではないでしょうか。そうすると、ストリング自体は \(O(1)\) で計算できるはずです。
昔はまさに UTF-8 の文字数のカウントが複雑だったため、 \(O(1)\) ではなかったのです。それでバイディレクショナルコレクションの準拠を外したのでしょうね。UTF-8 でキャラクターの構成もできるのではないでしょうか。普通のキャラクターでも、U の上にウムラウトがついているやつとか、普通に UTF-8 の1つのスカラーで表現することも可能ですし、U プラスウムラウトの2つのキャラクターで表現することも可能です。そういう事情があるため、文字数の計算って結構面倒なのです。
昔はカウントを呼び出されるまでは文字数をカウントしないで、その代わりにカウントを呼び出すときに計算するため、計算量が \(O(n)\) だったのです。なるほど。内部事情はわかっていないので想像でしかないですが、もしかすると代入時に既に正規化をかけて、キャラクターとして持っているかもしれませんね。そうしたら、内部的にキャラクターで管理していれば、1文字はキャラクター1つで表現できるかもしれません。しかし、内部事情を見ていないとわからないので、想像でしか考えられませんよね。
もしそうだと、文字列を合成するときのパフォーマンスが下がる可能性がありますよね。そうですね、確かに。どうなってるのでしょう。昔はね、例えば Objective-C の頃は内部コードは UTF-16 になっていたので、 \(O(1)\) では無理だったのですよね。そうでしたっけ?UTF-16 は決まったやつじゃなかったですか?16バイト…あ、そうですね。UTF-16 で捉えればそうです。UTF-16 で全部16ビットで1文字でしたっけ?そうでしたっけ?超えるのがあったような気がしますが、絵文字ですかね。
32ビットは間違いなく全て同じ、32ビットで1文字のはずですね。基本的には使わないのですが、あまりにも数字が大きすぎるためです。そうですね。UTF-16 が一番厄介ですね。UTF-16 も1文字が1ワードを超えることがありましたが、そこもまた別の話が出てきますね。Objective-C で文字数を取ると、1文字なのに2と返ってきたり、いろいろありました。
まあ、バイディレクショナルコレクションに話を戻すと、そういった計算量の制約もあるため、標準ライブラリーとして準拠しているということはクリアしているのでしょうね。カウントだけでなく、サブスクリプトも \(O(1)\) でなければならないはずです。サブスクリプトの \(O(1)\) も、バイディレクショナルコレクションの条件ですから。だから本当に作るときに全てのキャラクターに落とし込まないといけないですね。結構困難そうですが。どうなるのか、その辺りいろいろ試してみるのも面白そうですね。
UTF-16 は基本的には2バイト固定長ですが、サロゲートペアが4バイトになることもあります。確かによく聞く言葉ですね。可変になると計算量に影響が出るので、いろいろありそうです。文字列型は確かに難しいですね。
少し時間になりましたので、文字列型が難しいというところを残しつつ、いろいろ見ていくと、文字列を扱うのが上手になっていくかもしれませんね。複雑なことをするととても難しいですが、とりあえず今日は Swift の標準の型を見ていきましたので、次回はもう少し高度な型に関するスライドを見ていくことにしましょう。
少々オーバーしてしまいましたが、今日はこれで勉強会を終わりにしようと思います。お疲れ様でした。ありがとうございました。