https://youtu.be/cJhWgzXzV4k
本日も引き続き The Basics
の 定数と変数の出力
について眺めていきます。前回に「Swift では全てのインスタンスをテキスト表現可能」という話をしましたけれど、今回はそのテキスト表現を独自にカスタマイズする仕組みの辺りに着目していってみようと思います。どうぞよろしくお願いしますね。
—————————————————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #103
00:00 開始 02:06 インスタンスのテキスト変換 03:25 CustomStringConvertible 04:27 インスタンスが表現する値を文字列に置き換える 06:18 統一された値表現を提供可能 10:33 テキスト表現を使用する場面 14:15 闇雲には CustomStringConvertible は使わない 16:35 CustomDebugStringConvertible を使う場面 21:13 インスタンスを文字列表現するためのその他の仕組み 22:31 リフレクション 23:23 Mirror のテキスト表現 25:37 CustomDebugStringConvertible が添える付加情報 27:30 Double にはなぜ CustomDebugStringConvertible が存在するのか 30:45 通常用とデバッグ用とで同じテキスト表現のとき 40:22 次回の展望 ——————————————————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #103
はい、じゃあ始めていきますね。今日は引き続きですが、係数と変数の出力、その辺りのスライドを見ていく回になります。特に前回お話しした中で、Swiftの特徴として「全てのインスタンスはテキスト表現可能」という点がありました。これはSwiftのなかなか面白い特徴です。それを前回は紹介しましたね。今回はその辺りの最後のほうにちょこっと話していた「標準でテキスト表現が可能だけれど、そのテキスト表現をカスタマイズできる」というところに焦点を当てていきます。
今日のスライドにはその辺りの細かい内容は書いてないので、スライドというよりはPlaygroundで見ていく形になると思います。なので、Swiftプログラミングランゲージから見ると脱線した話かもしれませんが、非常に重要な部分です。このテキストカスタマイズの機能は、Swiftに慣れていればかなり目にする機能のはずですので、皆さんの中でも既に知っているよという方も多いかもしれません。改めて見直して、何か気になるところや気づくことを探してみてください。
ではまず、前回のお復習です。例えば、インスタンスがあったとして、Date
型を使っちゃおうかな。まず参考にDate
型のインスタンスがあるとします。このインスタンスはテキスト表現可能です、といった話でしたね。でもDate
じゃない他のものにしましょう。例えば、何らかのStruct
があって、これがプロパティとして整数値を持っているとします。このインスタンスを出力する時に、インスタンスを一回丁寧に取ってきます。例えば、
let value = valueStruct(a: 1, b: 2)
のようにして、これを以下のようにプリントします。
print(value)
このように何も断りなくテキスト表現が可能です。これが前回の話です。
この「何の断りもなくテキスト表現できる機能」のことを「カスタムストリングコンバーティブル」と呼びます。これを使って、独自のテキスト表現にカスタマイズする機能があります。この標準の文字列変換機能は、先に説明した通り、コンパイラーに組み込まれている手続きに従って文字列化を行います。しかし、それに支障がある場合には、「カスタムストリングコンバーティブル」を使ってプログラマーがカスタマイズできるわけです。
まず、カスタマイズの方法を見ていきましょう。例えば以下のように変数a
とb
があり、このテキスト表現が標準の形で表示されているとします。しかし、これをカスタマイズして、もっと見やすい形にしたいとします。エクステンションを使って以下のようにします。
extension valueStruct: CustomStringConvertible {
var description: String {
return "a: \\(a), b: \\(b)"
}
}
このようにして、description
プロパティを実装すれば、出力がカスタマイズされます。例えば、
print(value)
として出力すると、ちゃんと意図した形に成形されます。
このカスタムストリングコンバーティブルの大きなメリットは、何と言ってもテキスト表現をどの場面でも統一できる点です。例えば、ラベルやテキストフィールドに値を表示する場合でも、また別の画面でログを出す場合でも、一貫した表現が可能になります。このようにカスタムしておくことで、普段の作業が格段に楽になりますし、コードの管理もしやすくなります。
また、特定の場面でだけ独自に出力をカスタマイズしたい場合もあります。その場合も自由に自分で書けばいいので、非常に柔軟に対応できますね。今回の話が皆さんのSwiftプログラミングに少しでも役立てば嬉しいです。 なので、こういう値表現の統一を測るフォーマットの場面でも、このCustomStringConvertible
が使えますし、実際のところ、そういった役割を持つはずです。単純に統一させたいという意味よりは、もう少し狭い意味の機能になっていますけど。
具体的にどういったときにこのカスタマイズをするかというと、この型が表現している値、この値がそのままの形でテキスト表現可能なときにCustomStringConvertible
を使って、その値を的確なテキスト表現に変換するための機能です。
具体例を挙げると、例えば構造体Value
だと意味が広すぎて、具体的なインスタンスの値をテキストとして表現できるのか分かりにくいですね。ただ、この構造体がポイント、つまり点を表す構造体だったとしましょう。そして、x
座標とy
座標を持っているとします。
数学で座標を表示する方法は忘れてしまいましたが、例えばこのようにします。
struct Point {
var x: Int
var y: Int
}
そして、原点O
をポイントのようにします。
let O = Point(x: 0, y: 0)
これをprint
してみるとしましょう。標準のストリング変換だと、現在では「Point(x: 0, y: 0)
」のように表示されます。この形式では少し冗長ですよね。ここでCustomStringConvertible
を使ってカスタマイズします。
カスタマイズの方法としては、例えばこのようにします。
extension Point: CustomStringConvertible {
var description: String {
return "(\\(x), \\(y))"
}
}
これで、print(O)
で「(0, 0)
」のようにシンプルに表示されます。こうして、このインスタンスが持っている内容を正確にテキスト表現できるようになります。これがCustomStringConvertible
の大事な約束ごとです。
テキスト表現を独自にカスタマイズするときに、闇雲にCustomStringConvertible
を使ってはいけないという制約があります。今回のように、そのインスタンスが確実に特定のテキスト表現をするものである場合に限って使います。
ではそうでないときにカスタムのテキスト表現をしたい場合はどうするかというと、そのときはCustomDebugStringConvertible
を使います。 ここで重要なのは、「存在は知っているけれど、実際にはあまり使わない」という状況です。この場合、カスタムストリングコンバーチャブル(CustomStringConvertible
)ではなく、デバッグディスクリプション(CustomDebugStringConvertible
)を使うべきです。
具体的な搭載方法は前回とほとんど同じですが、名前が若干違ってデバッグディスクリプションを搭載します。このとき、例えば x
は何、y
は何といった表現にしてあげると、CustomStringConvertible
をわざわざ使わなくても、意図したとおりにテキスト表現をカスタマイズすることができます。
たとえば、バリュー(value
)のテキスト表現が必要なとき、バリューはテキストとして表現できるものもあればできないものもあります。その場合、一般的には +
(プラス)記号で結合する方法があります。ここで、controller
がデリゲート(delegate
)を持っているとき、これをインスタンス化して表示しようとすると、デリゲートに何が入っているのか分からないので、テキスト表現をカスタマイズしたくなることがあります。NSLog などに出力するときに分かりやすいログとして残しておきたい場合です。
このような状況では、CustomStringConvertible
ではなく、CustomDebugStringConvertible
を使うのが適切です。これにより、テキスト表現を柔軟にカスタマイズすることができます。もし間違って使っていたら、教えてくださいね。
デバッグディスクリプションの実装が抜けている場合、次のようになります:
class Controller: CustomDebugStringConvertible {
var x: Int
var y: Int
var delegate: Delegate?
var debugDescription: String {
return "Controller: x = \\(x), y = \\(y), delegate = \\(String(describing: delegate))"
}
}
こうやって記号や構文を忘れずに書くのは意外と難しいですね。例えば int
型を書くのを忘れたり、記号を忘れたりするミスが多いです。しかし、最近ではコンパイラー(例えば Swift の場合)はリアルタイムでチェックしてくれるので非常に助かっています。また、こういう勉強会で仲間に教えてもらえるのも大きいです。
昔のパンチカード時代には、自分には想像もつかないほど大変だったでしょうね。話が脱線しましたが、カスタムデバッグストリングコンバーチブルを使うことで、インスタンスのテキスト表現を正確にコントロールすることができます。
クラスの情報を出力するとき:
var debugDescription: String {
return "Controller: x = \\(x), y = \\(y), delegate = \\(String(describing: delegate))"
}
これがテキスト表現の例です。String(describing:)
で delegate の内容を表示しつつ、クラスとしての情報をテキスト出力できるようになっています。
このように、カスタムストリングコンバーティブルを間違って使わずとも、デバッグ出力をカスタマイズして行うことができるという点が、カスタムストリングコンバーチブルの大事な注意点です。 なので、やみくもに使わずにカスタムデバッグストリングコンバーティブルの存在をちょっとしたときに思い出してほしいなというところです。今、二つ紹介しましたけれど、この他にもストリングコンバーティブル系用意されているものが一つあります。あとはもう一つ入れていいのかな、無理やり入れるともう一つ、あとはもう一個。テキスト表現可能とはちょっと遠くなってきますけど、インスタンスについてをより詳しく見ていくためのものを入れると三つ。この手のプロトコルであと三つあるんですけれど、思いつくもの何がありますか?
どうだろう、話を進めちゃっていいかな。漠然とした質問なので、思い浮かんだら頭の中でこれが今から話されそうだなってことを当ててもらえればと思うんですけど、どこから行こうかな。一般的なやつから行くかなと思ったけど、どれも一般的ではあんまりないですね。
インスタンスの状態をより詳しく知りたいときには、要は言語に標準機能が備わっていて、カスタマイズできるものとして「ミラー」。そうだな、Swiftには「ミラーリフレクション」といって、読み取り専用のちょっとしたインスタンスの状態を確認するためのミラーっていう機能があって、これがリフレクティングっていうラベルを持ったイニシャライザーを持っています。ここにインスタンスを渡してあげると、ミラー型として情報が取れて、このミラーにプロパティがいろいろあって、ディスクリプションだと何が表示されるんだ?ディスクリプションだからミラーのテキスト表現か。
ミラーのテキスト表現。ミラーは本当にディスクリプションでそのインスタンスの状態を表現できるのか?ついさっき自分が話したやつですね。もしこれで中途半端な表現がされたとすると、自分のさっきの説明は公式見解からするとちょっと厳密に言い過ぎなんじゃないみたいな、そういったものになるかもしれないですね。
コントローラーもういいな。こうしてあげて、何が出るんだろう? ミラーのディスクリプションって概要とか出るのかな?ちょっとした全部を表示するには、ミラーの内部情報って多すぎるはずなんですよ。たぶんね。あとしたら、もしかするとデバッグディスクリプション。そこまで厳密に持ち出さなくてもいいかもしれないな。喋っておいてなんだけれどね。この辺の加減がちょっとまだ分かんないな。自分は喋ったとおりで、このインスタンスがズバリテキスト表現じゃなければデバッグディスクリプションのほうがいいかなと思ったんですけど。
なるほどね、こういう表示を見せられると気持ちが分かる気がしてきた。確かにポイント型のミラーですって。確かにインスタンスを的確に表現していますね。言い過ぎですかね。これならカスタムストリングコンバーティブルでいい気がしてきた。じゃあ、デバッグディスクリプションってあるのかな。デバッグ、あったあった、間違った。ディスクリプションのデバッグディスクリプション。これはストリングのね、ストリングのデバッグディスクリプションね。ストリングのデバッグディスクリプションって何が出るんだろう?
ちょっと興味深い。ダブルクォートで囲むだけ、面白いね、これ。いい表現だね、これね。ちょっと紹介しとこう。例えば、"テスト"
っていうテキストのディスクリプションと、"テスト"
っていうテキストのデバッグディスクリプション。この2つを比べると面白さが出てくる。これね、"テスト"
っていう文字列のテキスト表現は、ずばり"テスト"
なわけじゃないですか。それに対して、デバッグディスクリプションはダブルクォーテーションで囲うっていうところまでをテキスト表現することによって、テキストリテラル風味の出力になる。これによってその値が文字なのかどうなのかっていう、デバッグ情報っていうとちょっと言い過ぎかな、けれども、そんな感じの付加情報を添えられています。
これ、上手ですね。数字とかなんかもこうやって比べてみると。こっちは両方一緒なんじゃないかな。違ったし。0.0とか出てくるのは、つまりダブル型しかデバッグディスクリプション持ってないでしょう、きっと。相当ですけど、イント型にちゃんとキャストしてあげると、たぶん19行目がエラーなんじゃない。そうね、これは純粋にデバッグのための文字列表現が普通の文字列表現で、ここがとれているからデバッグディスクリプションを載せていないという発想で、多分いいんだと思うんですが。
じゃあ、なんでダブル型にはデバッグディスクリプションがあるんだろう。ダブル型に変換してみましょう。こうやって、両方同じじゃないの。この表現も。そうだよね。不思議ですね。 これ、もしかして桁数とかが違ってくると影響を受けたりするのかな?どうかな?一緒ですね。なんでこっちはデバッグディスクリプションを持ってるんだろう?なんか想像できる人います?何か丸め誤差とか発生するような場合、丸め誤差か。丸め誤差で変な値になったときに何か出るとか?分かんない。
そうね、その可能性もないとは言えない。というか、考慮されててもおかしくない気がしますね。そうすると割り算で、普通は四捨五入か?1.1 × 3でしたっけ?1.1 × 3。1 ÷ 3 × 3とかは全然おかしくなるやつありますね。1.0 ÷ 3 × 3が1にならないみたいな。
なるほどね、ちょっとやってみますか。こうやって、ここでn
のディスクリプションとデバッグディスクリプション。確かに、不動小数点数は表現力が丸められてた、丸められたね。そう、ダブルだと。不動小数点数だとってすさまじいんですよね。誤差を含んだ状態で存在できてるからなのか、いい感じにやるんですよね。
結構ね。そうすると、0.1 × 3。0.1 × 3っておかしくなりましたっけ? 0.1 × 3。やってみちゃえばよかった。誤差は出ましたね。0.1 × 5がそういうのがあった気がするな。0.1 × 3が0.1 × 3.1だったのか。ちょっと覚えてないですけど。何にしてもちょっと誤差は出ましたね。
読んで、あけど変わんないですね。変わんないですね。話してて思い出したんですけど、インフィニティってどうなのかな?ダブルの無限大は。こういうのはsin
ファンクション?これも一緒か。
なんでしょうね。このデバッグディスクリプション。わざわざ両方に同じ表現をさせる必要はないんですよ。そういう場合、普通のプリントの場合、ディスクリプションがなければ、ちょっと表現悪いな。カスタムストリングコンバーティブルに準拠していなければ、カスタムデバッグストリングコンバーティブルのテキストを表示するっていう仕様になってるのと、あと逆にデバッグプリントっていう関数もあるんですよ。これ、あんまり馴染みない人も結構いるんじゃないかなと思うんですけど。こっちはこっちで、カスタムデバッグストリングコンバーティブルがあればそれを、なければデバッグストリングコンバーティブルで、カスタムストリングコンバーティブルのほうみたいな。さっきの普通のプリントとは逆に。
なんか違う。N ANが違う。面白いですね。どうなんだ、あれ一緒だ、多分。ゼロ割るゼロとか、そういう形。ゼロ割るゼロとか。
みたいな?今ドキュメントの方、読んだらそういうふうに書いてありました。本当?あ、でも出ちゃうな、ちゃんと。スタックオーバーフローでベストアンサーがありました。さっき全然、何も言ってたんだなくて。
とりあえずString(8.7)
。とりあえずノットアナンバー。変化なかったですね。プレイグラウンドで試す感じではね。なんか違うのかな?これではないのかな?
スタックオーバーフローって翻訳されたサイトが出るときありますよね。はい。それが出て、原文というか元へのリンクがなくて、元へのリンクが見つかんないんですけど。じゃあちょっとそれを共有して。ちょっと翻訳がおかしいんで、なんかあんま分かんないんですけど、これの元リンクに行ければいいんですが。
なんかやっぱりいろいろ書いてますね。デバッグディスクリプションとかの理由を。そもそもツイストだよな。ツイストですね。 では、動画の文字起こしを文体を整え、句読点を入れて自然な文章に直します。
タイトルってはSwiftですね。Swiftのやつですね。これ、元リンクが出てくる。そう、翻訳って分かんないですよね。じゃあ、これは英語が自分が苦手でも思うんですけど、原文見せてくれって思うぐらい分かんない。そうですね、良心的なサイトだと原文でのリンクがあるんですけど、ここはないので。
何を、Playgroundで8.7を変更します。8.7、8.7、8.7、8.7、8.7、8.7、8.7、8.7、8.7、はい。10.8、10.8、10.8、10.8、10.8、10.8、10.8、10.8、10.8、よくわからん、10.8、いけ。
よくわからん。CustomStringConvertible
のところと、あと、summary
がまとまったんですね。String
の8.7は8.7って出るけど、DebugDescription
だと8.6999みたいなのが出るみたいな。なるほど。
それがプリントだとDebugDescription
が使われるから、プリントで8.7になるけど、d.debugDescription
にすると8.6999みたいなやつが出るって書いてある気がします。なるほど。つまり、それをやってみればよいということですね。まあまあ、一つのアイディアとしてね。そうやって細かく誤差というか、ここを強制アンラップしようかな。
うん、ここがいいの? プリントしてます? あ、変わんないですね。変わんないかもしれないけど、変わるかもしれない。プリントをすれば分かるのか。
つまり、プリントとデバッグプリントということですね。そうですね、例としては。それで20行目の右側みたいな感じが出る。なるほど、ここでね。プリントの中でprint(d.debugDescription)
。これでも同じ動きだと思うんだけどな。これ、そうですよね。
バージョンで変わった。20が出てる。20の右側のやつを出すためにDebugDescription
があるって書いてますね。24は短い整数を出して、25でフルのやつをちゃんと出してくれるみたいな。2017年なので、何か変わってる可能性はありそう。そうですね。
昨日、Stack Overflowにそこの該当箇所の実装がリンクされてません。なるほど。C++使ったんですよね、上のほうに。C++っぽいスタンダードが、こういう実装だからって書いてるんですが、今ってこの実装とかって使ってるんですかね。
どうですかね。FloatingPointToString
関数の実装はこれみたいな話を言ってますけど、これ、安い人になってないってことなんですかね。どうだろう。メインにはまだあるから、どうですかね。そこを突き詰めてもあんまり意味ないんじゃないかな。使われてようと。
書いてるコードはとりあえずなくなって。なるほどね。個人的に興味深いのは、なぜ20行目です。これが、これちょっと再現方法。他の具体的なコードで再現する方法が今パッと思い浮かばないので、ちょっと探してみますけど。でも、もしかすると今色々みんなが調べてくれたみたいな、表現用のテキストよりもより細かい誤差を、誤差というかより精密な表現のデバッグ情報。
そして、Double
型はカスタムでDebugStringConvertible
も持っていた時代があるみたいな、そういったことだとすると、そういった可能性もないとは言えないですね。なるほどね。今回の場合は特にString
型の方が分かりやすかったなという気はしますけど、とりあえずこういうことね。
よりデバッグ的な情報を含めるバージョンと、インスタンスそのものの値表現という方法と、こういう風な形で。CustomStringConvertible
、CustomDebugStringConvertible
、その辺りを買い分けていくといい感じという話ぐらいですかね、今日はね。
じゃあね、時間になったので今日はこれぐらいにして、また次回ね、もうちょっとね、この辺りで細かい約束事というか、表現方法というかがあるんで、その辺りをね、また見ていこうかなと思います。はい、ではお疲れ様でした。ありがとうございました。