https://youtu.be/XS6FGvkQz7M
本日はThe Basics
の 定数と変数の出力
についての節から 文字列補完構文
についてみていきます。以前にも着目したことのある構文ですけれど、そのときからそれなりに時間が経っているので見え方が違ってきているかもしれないですし、その時に参加していなかった人もいると思いますので、改めてじっくり眺めてみようと思ってます。その日の話をはっきり覚えている人がいらしたら、その日と比べて何か受け取り方が変わってないか、そんな気持ちで参加してみると良いかもしれないです。どうぞよろしくお願いしますね。
——————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #106
00:00 開始 01:55 文字列補完構文の基本 06:25 複数の埋め込みが可能 07:03 評価式も埋め込み可能 09:32 整数のビットに関するあれこれ 10:01 ランレングス圧縮 10:56 ビット情報の使い道を探してみる 16:03 Swift らしいコードに寄せた書き方 18:22 BinaryInteger プロトコルとの兼ね合い 21:29 大小比較で活用されて⋯なさそう 22:33 複雑な評価式を埋め込み可能 26:14 文字列補完構文内のコード補完 30:09 ログに表示したいだけなら print が便利 32:07 挿入されるテキスト表現 35:46 独自の文字列表現を埋め込む 41:27 インスタンスをテキスト表現する方法が多数存在 42:06 文字列表現の所感とクロージング ———————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #106
はい、じゃあ今日は「The Basics」ですね。「The Swift Programming Language」というApple公式のドキュメントに沿って、この勉強会を進めているわけですが、その基本セクションの中で、今日は「文字列保管項目(String Interpolation)」について見ていこうかと思います。この話は、長く参加されている方には以前「Welcome to Swift」あたりでも見たので、記憶に残っている方もいるかもしれません。覚えている方は、その当時と比べて、今どういうふうに感じ方が違っているのかなとか、そういった着目点で見ると面白いと思います。また、初めて見る人にとってはおなじみの機能になるはずなので、Swiftではこのあたりを押さえておくことで、いろいろな場面で柔軟に対応できるようになります。良い機会なので、じっくり見ていったらいいかなと思います。
では、基本的なところからスライドに沿って見ていこうと思いますが、例えば、print
文で使っている文字列保管項目というのは、文字列リテラルの中に変数やインスタンスの内容を埋め込むことができる機能です。これが文字列保管項目です。専門用語としては「String Interpolation」と呼ばれています。この言葉は前々回にも何気なく出てきているので、聞き覚えのある方もいると思います。
具体的な説明を読むと、長い文字列の中にプレースホルダーとして変数や定数の名前を埋め込むという項目です。文字列保管項目に埋め込んだ名前は、その時点での値に置き換えられます。値という言葉に語弊があるかもしれませんが、インスタンスの値で置き換えられます。書き方は、丸括弧で変数名を括り、その開き括弧の手前にバックスラッシュを記載することで、文字列保管項目になるのです。
じゃあまず、このあたりをプレイグラウンドで見てみましょう。他に何か話すことがあるか、ちょっと待ってくださいね。まず見ていけばOKかな。では、プレイグラウンドで軽く見ていきます。
例えば、変数、いや定数ですね。定数があって、その値をプリントするときに、次のように書きます。
let a = 10
print("Aの値は \\(a) です")
このようにすると、簡単に値を埋め込んだ文字列がプリントできます。今はprint
の例を紹介しましたが、これは文字列として扱うこともできます。たとえば、次のように書くと、
let s = "Aの値は \\(a) です"
定数s
に文字列として保管項目で作った文字列が入ります。プリントしたらどちらも一緒ですけど、こうして変数として扱えるのです。変数s
の型はString
型なので、普通の文字列として扱えるということが分かります。
このように、関数として文字列を取るといった場合にも、この文字列保管項目で作ったインスタンスを取ることができます。こういった言語の仕様ですね。文字列保管項目の基本はこれに尽きるので、すぐに使えると思います。
複数の変数も紹介しておくと、問題なくいくつでも使えます。次のように書けば、
let b = 20
print("Aの値は \\(a) で、Bの値は \\(b) です")
ちゃんとB
も埋め込めます。改めて説明すると非常に簡単です。この機能は便利で単純に使えるからいいですね。けれど、特別な説明をするとすれば、もう少し複雑なことができるという点です。
文字列保管項目の中には、変数の値だけでなく、式も書けます。たとえば、絶対値を表示する場合に次のように書けます。
let a = -10
print("Aの絶対値は \\(abs(a)) です")
すると、a
の絶対値が表示されます。他にも、次のようなビットパターンを表示できます。
let a = -10
print("Aのビットパターンは \\(String(a, radix: 2)) です")
このように、文字列保管項目でさまざまな情報を簡単に埋め込むことができます。デバッグプリント、つまり現在どんな値になっているのかを実行時に確認するためにプリント文を埋め込んだりするときにとても便利です。
ちなみに、リーディングゼロビットを調べる機能があります。次のように書けば、
let a = 20
print("Aのリーディングゼロビット数は \\(a.leadingZeroBitCount) です")
リーディングゼロビット数を取得できます。でも、これがどこで使うかというと、自分はちょっと思いつかないんですけど、ビット操作で複雑なことをするときには役立つのかもしれません。ランレングス圧縮などで使う場合もありますが、普段使うことは少ないかもしれませんね。 同じものが何個続くというのは、ランレングス圧縮でもほとんど使わないですね。例えば「59個のゼロです」というデータがあったとしても、それは簡単にデータにできるからです。この関数は使わないというか、Swiftにはそういったプロパティがないんですかね。確かにそのアイデアも一つありますが、他の使い道は何でしょうか。昔は使われていたのかな。
アセンブラをイメージすると、ビット操作は結構深いものがありますね。そんなことをよくやっていました。たとえば、ビットシフトやキャリーフラグを用いた条件分岐などの演算です。それに比べて、「何ビット0が続くか」みたいな計算はあまり記憶にないですね。他にもビットカウントの方法がありますね。
例えば、bitWidth
プロパティです。これは64ビット整数なら64ビット分の情報を取れます。現代のコンピューティングでは、整数値の表現が有限なので、このビット幅は役立つかもしれません。また、リーディングゼロの数によって有効桁数を計算することができます。
具体的には、例えば次のようなコードでリーディングビットのゼロの数を使って有効桁数を計算できます。
let bitWidth = 64
let leadingZeroBitCount = 42
let significantBits = bitWidth - leadingZeroBitCount
print("Significant bits count: \\(significantBits)")
この結果、significantBits
が22ビットであることがわかります。このように、16ビットの変数に無理やり入れようとするとオーバーフローすることがわかります。
次に、オーバーフローの例を見てみましょう。
let number: Int = 1 << 21
do {
let convertedNumber = try Int16(exactly: number).unwrap()
print("Converted number: \\(convertedNumber)")
} catch {
print("Conversion failed due to overflow.")
}
このように、型変換を試みますが、オーバーフローするため変換は失敗します。これを防ぐために、安全にチェックを入れておくことが大切です。
手続き型のコードでオーバーフローを防ぐ方法と比べ、Swiftではより安全に型変換を行う方法があります。特に便利なのが、オプショナルバインディングを使った方法です。
guard let value16 = Int16(exactly: number) else {
print("Failed due to overflow.")
return
}
print("Converted number: \\(value16)")
このように、Swiftらしいコードを書くことで、安全に型変換を行い、エラーを防ぐことができます。従来の手続き型と比べても非常に直感的で、わかりやすいですね。
Swiftは手続き言語というよりも、マルチパラダイム言語としての特徴を持っています。型推論も積極的に利用することで、非常に表現力豊かにコーディングができます。 もし「twist」についての詳細を知りたいのであれば、こちらの方法をお勧めします。このように、ビットパターンを使う機会はあまり多くないかもしれませんが、ビットリーディングやゼロビットカウントといったものもあるので、必要なときに思い出すと役に立つかもしれません。しかし、なぜ標準にあるのかは不思議ですね。誰が持っているのでしょうか?これを考えると、たぶんInt
型が持っていて、BinaryInteger
プロトコルなどにもありますね。FixedWidthInteger
が持っていそうな気がします。このあたりの定義をたどる方法は何があるでしょう?例えば、この定義をチェックしてみると、ざっくり確認できますが、この中にはたくさんあって探すのも大変です。
まず、BinaryInteger
を見てみましょう。このプロトコルには、データ型のビット数に制限があることを説明するためのものです。例えば、将来的にビット数が無限に取れるPCが出てきた場合、FixedWidthInteger
ではなくBinaryInteger
で対応できるようになります。こういった可能性を秘めたプロトコルですかね。
次に、LeadingZeroBitCount
というプロパティが具体的にどこで使われるのかについてですが、これはビット表現をする整数型を規定するプロトコルであり、おそらくビット判定が必要な場面で使われるのでしょう。値が一致するかどうかを判定するときや、そのときにビット判定を先に行うことで効率化が図れるかもしれません。
続いて、文字列補完構文の話に戻ります。さっき複雑な式を書きましたが、例えば以下のように書けます:
let value = "A + B is \\(A + B)"
このように計算もできるし、A != 0
みたいな評価式も使えます。また、Int
型にあるadvance
という関数を使って、
let advancedValue = "Value is \\(advance(by: 40))"
のようなものもできます。この中に様々な計算や処理を埋め込むことができ、便利ですが、あまりに複雑になるとコードが読みにくくなるので、適度に分けることが大切です。
また、配列をmap
で変換してからreduce
で合計を計算する例もこのように書けます:
let result = [1, 2, 3, 4].map { $0 * 4 }.reduce(0, +)
こうやって、非常に強力な構文解析力を活用することができるので、Swiftでの開発は非常に楽になりますね。 なので、他の言語にもありますよね。例えば、Perl言語とか。任意の言語でもいいのですが、こうやって補完するときに、例えばPerlスクリプトでもこういうふうな書き方ができる言語とかって結構あります。ただ、こういった言語でなかなか複雑なのを埋め込むことができる言語もあるんでしょうけど、制限があって結局、外で value = 計算
といったようにしてから表示することが多いと思います。そういう場合には気遣い無用って感じですね。
ただ、ちょっとこの文字列補完構文を使っていて気になるところがあって、自動補完を使いたいときに、例えば b.advanced(by:)
を呼びたいとします。このとき、コード補完が出てこないですよね。これ、不便じゃないですか? 単純なツールの制限だと思うんですけど、これを補完したいために一回丸括弧を閉じてからドットを打つということを自分はやるんですけど、これに対して良い補完方法や気をつけていることがある方、いませんか?
とにかく、なんか微妙なところですが、ここでドットを打ってすぐに補完が出てほしいというのが少し不満なんです。自分としては、最初に全ての括弧を先に書いておく癖があるので、特に不便には感じないかな。
なるほど、その癖がついてしまえば確かにスムーズにいくかもしれないですね。どうでもいい煩わしさを感じているなら、この癖をつけることで効率が上がりますね。いいですね。
括弧の閉じを忘れないようにするためにも、この方法はいいかもしれない。確かに、最初から閉じ括弧を書いてから中身を書くようにすると便利ですし、各行をまとめたいときには効果的です。
また、関数の括弧や並括弧も同様に最初に閉じ括弧を書いてから中身を書きます。特にVisual Studio Codeなどのエディタでは自動で括弧を補完してくれる機能もありますが、自分で書いておくと確実です。
余談が入りましたが、文字列補完構文に戻ります。この機能を使うとき、各インデントやスペーシングなども整えやすくなって便利ですね。 デバッグのためにちょこちょこといくつかの数字を足したい場合には、print
文で簡単に処理してしまえばよいのですが、こうしたケースでは、通常は文字列補完を使うよりも、可変長引数を使うことが増えてきたように感じます。
しかし、文字列として値を取りたい場合には、文字列補完が非常に便利です。例えば、テキストラベルのようなものにテキストとして値を設定する際には、文字列補完がとても役に立ちます。
この文字列補完ですが、例えばクラスを作成した場合に、そのクラスのインスタンスを文字列補完に渡すことができます。その際にどうなるかという点については、前回の勉強会で説明した内容もあるので、難しい話ではないと思います。
クラスのインスタンスを文字列補完に渡すと、Swiftではすべてのインスタンスが文字列で表現可能になっています。具体的には、インスタンスのテキスト表現が特に指定されていなければ、組み込みの表現でその型名などが表示されます。
例えば、デバッグ用のカスタム表現がプロトコルで規定されていれば、そのカスタム文字列が表示されます。また、「このインスタンスはこのテキストで表現可能だよ」といった規定があれば、その文字列に変換されます。このように、あらかじめ規定されているインスタンスのテキスト表現に置き換えられる仕組みになっています。
また、文字列に対して演算する場合、例えばdescription
プロパティに文字を足すようなコードを書くことが可能です。こうすると、最終的にこの文字列がテキスト表現として出力されます。例えば、以下のようなコードです。
var description = "\\(instance) + Some additional text"
print(description)
このようにして、ストリング型とストリング型を足し終わった最後のテキスト表現を出力します。
さらに、文字列補完にはStringInterpolationProtocol
を使った表現もあります。これはあまり使わないかもしれませんが、文字列リテラルのキャパシティやインターポレーションカウントなどの詳細な設定が可能です。この勉強会でも以前見た記憶がありますが、具体的な内容については忘れてしまった部分も多いです。
いずれにせよ、文字列補完には基本的な使い方から、カスタムの表現方法まで様々なアプローチがあり、それぞれの用途に応じて利用することができます。 ストリングインターポレーション系って他に何があるんだろう?要は独自のインターポレーションを表現するとか思ったけど、自分が勘違いしてそうかな。今読んでいるエクステンションストリングについてですが、エクステンション String.StringInterpolation
に mutating func appendInterpolation
といったものがありますね。
うんうん、そうだそうだ。アプローチが違うんですね。この受け取る母体の話をしているんですね、きっと。だからもう String
型に既に受け入れる母体ができているから、このプロトコルではなくて String
型母体の方に、母体の StringInterpolation
の方に mutating func appendInterpolation
を使ってパラメーターを取る、という話だったんですね。これがさっき説明されていた通りですね。
appendLiteral
タイプを取ってたのですね。これはリテラル。また全然違う話になりました。StringInterpolation
について見てみると、何でも Generics で取れそうな雰囲気を醸し出していますね。
DefaultStringInterpolation
がありますが、これは StringInterpolationProtocol
だから、DefaultStringInterpolation
がになっているんですね。具体的にどういう作りになっているのかを見てみると、さっき言っていたプロトコルがあって、それで appendLiteral
, appendInterpolation
があるんですね。これは Generics になっていて任意の型が取れるようになっています。
何も返す必要はないんですね。普通に value: T
型で、CustomStringConvertible
になっているクラスや OutputStreamable
になっているものしか渡せなくなっています。この部分が少し不思議ですね。なぜ Any
じゃだめなんだろう。ここ本当、Any
はもう規定されているからかな。勝手に自分の頭の中で話しているので、ちょっと分かりにくいかもしれませんが、軽く流してもらえれば大丈夫です。
これで、value
としてオブジェクトを実装する方法として、例えば
value.append(...
という形で足してあげることができます。mutating
だから self
に加工するんですね。なので self += ...
のようにしてあげると、オブジェクトの self.appendLiteral
になります。なるほど、これは賢いですね。
しかし、Playground がちょっと動かなくなっていますね。エラーのせいかな?これで動かすと StringInterpolation
が独自の結果を返してくれるというふうな動きになるわけですね。こうすると、この表現がいろんな方法でカスタマイズできるようになって、独自のインターポレーションの変換方法が備わっています。
面白いですよね。独自のデバッグ表現の方法や独自の値表現の方法など、前回お話しした LosslessStringConvertible
もそうですが、こういったふうにインスタンスを文字列で表現する幅広い方法があるのですね。なぜこんなに手厚いんだろう。
ちょっとこの辺りが特徴的で、余計複雑にならないか心配ですが、こんなにたくさんあるけれど結局 CustomStringConvertible
ばかりを使っているなと感じます。そのバランス的な違和感をなんとなく覚えます。
まあまあ、こういったところを見ていくと面白そうですね。ただ今日は時間になってしまったので、ここで終わりにしましょう。インターポレーションのカスタマイズがちょっと動かなかったので、この後自分でチェックして、ちゃんと動くかどうかまた見てからお話しします。
では、少し半端な感じですが、今日の勉強会はこれで終わりにしようと思います。お疲れ様でした。ありがとうございました。ご視聴ありがとうございました。