https://youtu.be/X7sIhG9xDVo
前回に 文字列補完構文
について眺めていっていましたけれど、その出力をカスタマイズする手段について見ていたときに自分がそれをちゃんと把握できていない感じがあってそれが気になりました。そこで今回はその周辺視野を広げてみるべく、それに関連するプロトコルまわりを闇雲に調べていってみようと思います。どんなことが見つかってくるかは見ていってみないとわからないですけれど、そんな感じでみんなでいろいろ気づくことを集めてみるのも楽しそうな予感がするので、どうぞよろしくお願いしますね。
—————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #107
00:00 開始 00:37 今回の展望 02:40 ExpressibleByStringInterpolation 07:04 ExpressibleByStringInterpolation の定義 09:15 文字列補完構文に用意されている機能を探す 11:10 ドキュメントコメントの捉え方 12:22 NSAttributedString の文字列補完 20:45 StringInterpolationProtocol 22:38 文字列補完構文で取り得る型 24:50 アンダースコアで始まるシンボルについての補足 27:28 リテラル互換の型 31:40 暗黙的に求められる API 32:51 StringInterpolationProtocol に準拠してみる 39:21 クロージング ——————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #107
はい、じゃあ今日は前回お話ししていた文字列補完(String Interpolation)について確認していきます。自分の中で記憶をたどりながらプロトコルなどを見ていたのですが、結局のところ記憶と全然違って微妙な感じになりました。それで思い出したのですが、あんまり触れない部分だったので、すっかり忘れてしまっていたんです。
改めてプロトコルなどを眺めてみて、どんな感じのものなのかの雰囲気をつかむのは良いことだなと思いました。今日は少し脈絡なく、文字列補完周りのコードを眺めていこうかと思います。コードと言ってもソースコードではなくて、Swiftの仕様の表面からうかがえるところを見てみたいと思います。
自分が把握していないところを見ていくことになるので、その過程を初心者の方々にも見てもらい、発想の仕方やアイデアを共有できれば良いと思います。では、早速文字列補完を見ていきましょう。
まず一つ、前回の勉強会が終わってから試したコードを紹介します。自作の型を文字列リテラルに対応させることって時々ありますよね。そのときに皆さんはどうやっていますか? 自分は ExpressibleByStringLiteral
に準拠させていました。
例えば、自作の型 MyValue
が文字列リテラルから変換できるようにしたいとき、ExpressibleByStringLiteral
を疑いなく使っていました。これで型を文字列リテラルから変換できるようにしていました。ところが、こうすると文字列補完を使った途端に受け付けなくなるんです。
struct MyValue: ExpressibleByStringLiteral {
typealias StringLiteralType = String
init(stringLiteral value: StringLiteralType) {
self.value = value
}
}
これは当たり前だと思う人もいるかもしれませんが、私はこれにちょっとびっくりしました。文字列補完を受け付けるためには、ExpressibleByStringInterpolation
にも準拠させてあげる必要があります。
struct MyValue: ExpressibleByStringLiteral, ExpressibleByStringInterpolation {
typealias StringLiteralType = String
typealias StringInterpolation = DefaultStringInterpolation
init(stringLiteral value: StringLiteralType) {
self.value = value
}
init(stringInterpolation: DefaultStringInterpolation) {
self.value = stringInterpolation.description
}
}
このようにすれば、文字列補完も受け取れるようになります。この3行目の実装を疑いなく使うべきだと気づきました。今後は文字列リテラルから変換できる型を自作する場合、ExpressibleByStringInterpolation
にも準拠させるという方法を試してみたいと思います。
ちなみに、ExpressibleByStringInterpolation
のプロトコルの定義はどんな感じになっているかというと、ExpressibleByStringLiteral
に準拠しているので、元々の目的も果たせつつ、文字列補完用のアソシエイティブタイプが規定されていて、初期化子で受け取る仕組みになっています。これにより、デフォルトの DefaultStringInterpolation
を使った文字列補完が展開されるという実装がされています。
今回はこの辺りを押さえつつ、実際にどんどん試して不都合があればフィードバックをもらえればと思います。 ちょっとこのような部分が自分自身で新たな発見だったので紹介します。これから、ストリングインターポレーションに関する内容を見ていこうと思います。
例えば、ストリングインターポレーションについて知りたい場合、検索フィールドに interpolation
と入力してみると、それっぽいものがたくさん出てくることがあります。このようにして自分は探しがちです。
今回は、デフォルトストリングインターポレーションが題材になります。これは前にも登場しましたね。ストリング補完構文からストリング型を生成するための生成器です。ストリングインターポレーションプロトコルというのは補完構文からストリングを生成するための型であり、そのAPIが規定されているプロトコルです。これに関しての基本的な知識を既に把握しているのですが、具体的に分からないときには定義をたどっていきます。
今の時代だと、コメント部分を読むとより分かりやすいですよね。コメントを活用する際には、APIデザインガイドラインによると、最初の空行前の一文を読むだけで概要を把握できるように書かれているといいです。したがって、ここだけ読めば、概ね大丈夫です。
例えば、インターポレーションについて調べる場合、プロトコルがどんなものがあるか知りたいときには、インターポレーションと入力すると、その最初の一行が表示されます。これにより、概要をつかむのがあまり難しくありません。手続きはそんな感じです。
例えば ExpressibleByStringInterpolation
について、イニシャライザがストリングインターポレーションから変換できるとだけ書かれています。他にも Interpolation
という言葉が関係するものがありますね。
ファンデーションに関する InterpolationOptions
や InterpolationQuality
、OSLogInterpolation
といった項目も見受けられますが、これらは初耳です。NSImageInterpolation
もありますが、これも目新しいです。
一つだけ気になるものがありました。それはインポートされているはずのファンデーションの定義が見つからないというものです。これについてネットで調べましたが、なかなか情報が出てこないですね。
インポートが自動で追加されたのですが、結局定義が見つからないという状況です。調査の結果、AttributedString
配下のものである可能性がありますが、標準のインターポレーションとは異なるかもしれません。試してみましたが、やはり標準的なものとは違うようです。
再確認したところ、これは ExpressibleByStringInterpolation
ではなく、AttributedStringInterpolation
に関するサポートのようです。
どうやらローカライズドフォーマッティングの際に使うもののようですが、標準のインターポレーションとは異なるようですね。 ここにありましたね、これです。これでいろいろ搭載されていて、インターポレーションする中で一部オプションが指定できるインターフェースが用意されているのですね。なるほど、なるほど。それでオプションはどんな選択肢があるのでしょうか。今たどったものか、オプションセットでサティスファクトリーがないのですか、ローバリューしかないのですか。
あ、ここにありました。「インサーツ」「アトリビュート」「リザードマージング」など何をしたいのかわかりませんが、何かありますね。基本的なところを見る予定でしたが、随分先のほうを見てしまいましたね。面白いですね。他の方でストリングインターポレーションに対応しているのを初めて見つけた気がします。だから、さっきのオプション取るやつもう一回見てみたいですね。
インターフェースのどれだったか、うーん、これ戻ってしまいました。アトリビュートでストリングを見ていたからですね。そっか、なるほど。それで、一つ確認したいのですが、アトリビュートでストリングをパラメーターに取って、オプションも一緒に取れるということですね。アトリビュートでストリング、この色はなんだ、ああ、イニシャライズですね。だから、基本的にバリューとしてストリングを指定して、テキストを入れられるのでしょう。そして、イニシャライズできるんですね。ここでストリングインターポレーションが使えるということですか。
なるほど、それでストリングインターポレーションの中でオプションが指定できるという感じですか。私はあまり NSAttributedString
に縁がないので、使ったことはあるのですが、ここでインターポレーションを使うことは普通にある気がします。でも、別の NSAttributedString
があった時はどうするんでしょうか。今、オプションが選べないです。補完が出ないだけかもしれません。オプションじゃなかったかな。ちょっとラベルを忘れてしまいました。補完が出ないので自分の勘違いかな。
Ascent Interpolation
の話でしたね。ここではないのか。まだ全然把握できていないので、話が変になってしまいますね。これはインターポレーションからビルドする側のインターフェースで、ExpressibleByStringInterpolation
のこととはまた別のようです。混乱してきました。Ascent Interpolation
ってオプション……いやいや、ちょっと待って、混乱しているだけで合っているのではないか。
やっぱり合っていましたね。だから options
ってやって渡せるということですね。それで、例えば init
ローバリューでも渡してみようかな。options
だからこれでいいのか。これで動くのか。コンパイルエラーのまま String
から AttributedString
に変えようとしてますね。やっぱり自分の勘違いしてましたね。うん、結局わからなかったですけど、オプションができそうな感じはありました。うまく動かせなかったけど、まあいいです。
この辺りをちょっと違和感覚えつつ、具体的にもう少しフラットなところから見ていきますかね。ファンデーションの話は置いておいて、ストリングインターポレーション、それの ExpressibleBy
はさっき一番最初に見た通りなので、これはOKですね。そうすると見るものはこの二つぐらいに限られてきて、それで文字リテラルの標準のインターポレーションのビルダーというのかな、それが今選択中のやつです。それが準拠しているのが、下のプロトコルですね。これのはずです。 とりあえず、デフォルトのストリングインターポレーションを見ていきますかね。こちらはストリングインターポレーションプロトコルに準拠していて、センダブルも入っています。センダブルが入っているのは、単純にストリング型がこれを内包していて、ストリング型がセンダブルだからなのかな。それはちょっと先走りすぎかもしれません。まあいいや。
とりあえずセンダブルになっていて、それをインスタンス化して、appendLiteral
でリテラルを追加していったり、appendInterpolation
でインターポレーションの部分を解析していったり、そういうふうな動きを実装してあげて終わりなのかな。そうみたいですね。
生成するストリングリテラルタイプがストリング型ですよっていうふうになっていて、ストリングに特化したインターポレーションビルダーのようなものになっているのでしょう。ビルダーと言った方が正しいのかもしれないです。自分で作っていってみますかね、独自の型用のインターポレーションを。
それを見る前に気になったのが、このappendInterpolation
が結構いろんなオーバーロードがされていました。Any
を取るもの、メタタイプを取るもの、Any
を取るけれどそれがカスタムストリングコンバーティブルなもの、そしてテキストアウトプットストリーマブルに準拠しているもの。それぞれ分ける必要があるのかな?最適化でも頑張っているのかもしれませんね。
さらに、自分で実装したい場合には、このインターフェースがジェネリクスなので、ここに自分の契約をかけたappendInterpolation
を追加していくとカスタマイズできるよってことです。これは前回の最後の方で話した内容ですね。いろいろとジェネリクスで搭載されているというのを見ましたが、具体的にどんなインターフェースを備えるかはプロトコルの方に定められています。そのプロトコルの規定を辿ってみると、どんな型に変換するか、互換性のある型に限られるという契約があります。
ちなみに、アンダースコアから始まる型やシンボルは、便宜上用意されているもので、Swift公式が使う分には構わないけれど、一般ユーザは使わないほうがいいということが暗黙の了解として示されています。AppleのSwiftを開発している人は自己責任で使ってねといったもので、バージョンアップで変わったりする可能性があるから、あまりおすすめはされていないのです。プロダクトに入れるのはちょっと怖いですが、個人的な用途ならいいかもしれません。
とりあえず、アンダースコアで始まるものを使うとAppleストアの審査に通らないのではないかという意見もあります。確かプライベートAPIの利用にあたるため、最初の機械審査で通らないかもしれません。それはちょっとやってみないと分からないですね。
オブジェクティブCの頃のように隠されているわけではないけれども、例えばObjectiveCBridgable
みたいにアンダースコアが入っていると、実装まで隠蔽されていたりします。興味がある部分ですね。 とりあえず置いておいて、ブリッジャブルはこのコンテキストでは使えませんとか出ていましたね。
ちなみに、Swiftの白いブラウザリ、これは何か王道な気がしますが、まあいいや。とりあえずアンダースコアのプロトコルや型を使う時には自己責任でお願いします。個人的にはおすすめしたいですけどね。こういうのを使って遊んでいると、いろいろ新しい発見がありますので、遊ぶ分にはとてもおすすめです。
例えば、このExpressible by Built-in String Literal
というのはなかなか面白いです。インテリアとかにもあるんですよ、こういうの。これはリテラルを受けるために使われるプロトコルで、これに準拠している型だけがリテラルとして扱えます。
ここでちょっと脱線させていただくと、リテラルって例えばExpressible by Integer Literal
とありますよね。知っている方はご存知かもしれませんが、このプロトコルに対応させる時にはイニシャライザーを実装する必要がありますよね。そしてその中でInteger Literal Type
が求められています。普通はここでInt
と書くと思いますが、実はこれInt8
やUInt8
でも良いわけです。リテラルを受け取れる、つまりリテラルから変換できる互換性のある型であれば何でも良いということです。
ここで、Expressible by Built-in Integer Literal
に準拠しているとどうなるかというと、遊びたくなりますよね。自分で作れるのかなとか、準拠させてみたらどうなるんだろうとか考えて、実際にやってみるとエラーが出てきたりします。なんか補完できそうですよね。例えばBuilt-in Integer Literal
やBuilt-in Int Literal
が問題だったりします。
ただし、このBuilt-in型はSwiftの表側からは使えないと思います。使い方を知っている人がいたらぜひ教えてほしいのですが、自分は今のところ使えないものだと思っています。だから、受けようがなくなってしまうんですね。型がないと言われるのかな、そうですね、Built-in
がない。Swiftのソースコードを見ると、インポートしている部分があった気がしますが、もう忘れてしまいました。確かS
から始まっていたような気がします。Swiftじゃなくて別の何かだった気がしますが、まあいいや。以前試した時にダメだった気がするので、Built-inモジュールもないですし、ここでちょっと手詰まりです。
ここでInt
と書いたら通るとかそういうことはないはずなので、準拠していないとなってしまいますね。まあいいや。こうやって突き詰めていくと時々突破口が見えたりして面白いですね。
リテラルの話になりましたが、今はStringInterpolationProtocol
について見ていたんですよね。そうそう、ここで脱線しました。自分で実装していくと独自のインターポレーションが作れて、このプロトコル自体にはAppendInterpolation
が求められていないんですね。これは把握していなかったので驚きました。
ということは、どこで呼ばれているのか、定義されていないのに呼び出されているのか、実装してみましょうか。struct MyInterpolation
をStringInterpolationProtocol
に準拠させて、イニシャライザーを搭載するわけです。init(literalCapacity: Int, interpolationCount: Int)
ですね。これは前回もよく分からないねと言って終わった部分ですが、具体的にどういうことが求められているのでしょう。イニシャライザーについては、その上のコメントでEmpty Instance Initializer
みたいなことが書いてありましたが、具体的にどう使うのか見てみましょう。
とりあえず、推定のサイズかリテラルキャパシティか、All Literal Segments リテラルの長さの制限を制するのか。それでリザーブキャパシティ、リザーブキャパシティにリテラルキャパシティを渡すのか。ああ、そういう意味か、なるほど。じゃあ、適当でいいんですね、きっとね。
で、それでインターポレーションカウント、Appendするインターポレーションのカウント。これは推定するのに使われるのか、キャパシティのね。じゃあ、そんなに重みのあるパラメータではないという感じですか。でも、これ搭載した後どうするんだろうね。求められてるのがこれとAppendでしょ。Append Literalで、ストリングと、これかな。この二つが求められて、これで終わりっていうことは。
で、さらにAppend Literalはミューティングでしたね。っていうことは、インターポレーションが自分の中にいろいろデータを蓄積していく必要があるっていうことですよね。あれ、Append Interpolation Method? これを載せろって言われますね。あれ、どうだっけ。書いてなかったですよね。イニシャライザーでしょ。それでAppend Literalでしょ、これはどういうことだ。Append Interpolation Methodを求めてきた。なんか勘違いしてるところあります? なんか変な感じしません? なんか隠蔽されてるのかな、動きが。
仮にこれ実装したとするじゃないですか、Append Interpolation。これで何型を取る、何でもいいんだ、だからAny
でしょ。こうやってこれがファンクションのミューティングか。こうすると全部実装できたことになる、これで準拠はできるんだ。ただこうしたところで、まず内部バッファーがないとミューティングできないので、こう持たせますよね。自分で自由にプライベートとかでね。それでイニットするときに、これを初期化するっていうことなのかな。キャパシティとか、キャパシティ取るイニシャライザー確かありましたよね、ストリングにね。
リザーブだ、あれ、ないか。まあいいや。なんかね、容量キャパシティ取ってバッファー初期化したとして、それでバッファーにプラスイコールとかでね、足していける。ここまではまあいいんですけど。えーと、こうね、これで最後にストリングを取り出すインターフェースがなくないですか、これ。だからこれでストラクト、マイバリューでエクスプレッシブル by ストリングインターポレーション、実装してあげて。それでね、求められてるのなんだっけ、これか、イニシャライザー。
ああ、分かった、分かりました。とか、ここで変換するんだ。イニシャライザーでストリングインターポレーションで、これで独自に作ったマイインターポレーションだ。ああ、なるほど、分かってきた、分かってきた。で、それでセルフに対して、マイインターポレーションのインターストリングインターポレーションか。これのバッファーを使いたいから、ここでファイルプライで、こうだ。これでできたんじゃない。ちょっとまだエラー出てる。マイバリューに変換しろって言われちゃってるんだ。だからセルフのString
として。
例えばね、でこうで、これでできたんだ。ここがまだなんかうるさくしてる、ストリングリテラルに変換する、ストリングリテラルから変換するっていうのを忘れてるから、こういう感じだね。なんか動きそうね、これでね。こう書くんだ、なるほど。
理解できた。で、これでコンパイルが通るから、あとは普通にねlet myValue = MyStruct(value: "\\(test10)")
とかね。こういう風にやって動くと、まあきっと動くでしょ。コンパイルエラーはね、出てないので。なんか一人でいろいろやってた感がすごいけれど、とりあえず理解できた気がする。うん、まあこんな感じだ。
この辺りはどうしようかな、ちょっと整理して、また話すのもあれだからな。まあ、これだけ分かれば、きっと興味湧いた人で、かつ、まだ理解できてない人がいたとしても、まあ調べていけるでしょう。これだけのコードがあればね。あとで、この辺はOJTチャンネルにそのまま貼っておくとして。まあ、こんなぐらいでストリングインターポレーション周りは、大体いいですかね、そんな気がする。
はい、時間にもなりましたしね、今日の勉強会、これで終わりにしようと思います。お疲れ様でした。ありがとうございました。