https://www.youtube.com/watch?v=lmE_sdgv0Ik
今回は A Swift Tour
の中から、Swift でコードを書いているとお馴染みの「文字列補完構文」についてみていきます。いわゆる "Title: \(titleLabel.text)"
みたいな書式のお話ですけれど、その基本的なところをおさらいしつつ、もう少しだけ踏み込んでみたときの様子とかを眺めていけたらいいなと思ってます。よろしくお願いしますね。
———————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #29
00:00 開始 00:30 文字列補完構文 00:46 どこまで日本語に訳す必要があるのか 01:38 文字列リテラルに値を埋め込む方法 02:55 統一感のある書き方 05:13 文字列補完を使ってみる 08:20 書式化文字列 11:38 文字列同士の足し算 12:24 書式化文字列の難点 15:20 文字列補完の順番を変える方法はなさそう 16:48 高度な書式を扱うための機能 18:34 文字列補完で展開できるもの 22:28 改行を含む文字列リテラルでの文字列補完 26:47 両側を二重引用符で括ったテキストを文字列として扱いたいとき 28:46 Objective-C リテラル 31:01 雑談 32:36 全てのインスタンスは文字列化可能 35:15 標準の文字列変換と独自の文字列変換 36:23 文字列への変換のされ方 41:01 CustomDebugStringConvertible 41:31 CustomStringConvertible 41:55 CustomPlaygroundDisplayConvertible 42:53 オプショナル型が内包する値の文字列化 43:39 debugPrint 45:08 DefaultStringInterpolation 47:32 LosslessStringConvertible 48:09 文字列変換の使い分け 49:14 文字列補完構文で引数を受け取る 51:13 クロージング ————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #29
今日はストリングインターポレーションについてお話しします。Swiftをいじっている人にとっては結構おなじみの機能だと思いますが、それを振り返る回になります。
さて、無理やり日本語に訳すると「文字列補完構文」、つまりストリングインターポレーションというやつです。大げさに言うと、ちゃんとした名前で言うとこういう風に呼ばれます。こういうのを扱うたびに思うんですが、どこまで日本語に訳すのが本来適切なんだろうと、いつも疑問に思います。ただ、個人的には日本語に訳す方がとりあえずの入口として悪くないのかなという気がします。そんなに日本語訳があったとしても、そこまで語弊を含まない印象を持つので、自分は結構積極的に日本語に訳していきます。どちらが良いのかは、その都度考えながらやっています。
とりあえず「文字列補完構文」について説明します。この機能は文字列内に値を埋め込む便利な方法です。Swiftプログラミングランゲージに紹介されていましたが、もう少し端的かつ適切に言うと、「文字列リテラルに値を埋め込む便利な方法」です。
文字列リテラルに対して値を埋め込むと、その文字列リテラルは文字列補完構文(ストリングインターポレーション)という形のリテラルになります。若干存在感が違ってきますが、要はコンパイルしたときの動きが変わってきます。埋め込みがあるかないかは、パフォーマンスを意識したときには少し気にするポイントになります。言語によっては、ダブルクオートのときには埋め込めてシングルクオートのときには埋め込めないといった振る舞いをしてリテラルそのものとして捉えたりしますが、Swiftの場合はダブルクオーテーションで統一している感じです。
これの便利なところは、ストリングインターポレーションにしたいからといってダブルクオートに変えなきゃいけないという余計な心配が不要な点です。また、Swiftのこういう文字列リテラル的な表現はダブルクオートで書こうという統一感があり、個人的には非常に好きです。前回の型変換の話でも感じましたが、変換するときに「こういう場合はこういう書き方」という風に、いちいち考える必要が少ないのが特徴です。そういったところがSwiftの見どころの一つとも言えます。
具体的な書き方としては、普通の文字列リテラルの中に \\()
の形で変数や値を埋め込むとその値が展開されます。これを少し具体的に見ていきましょう。
例えば、次のようにある値を文字列の中に含めたいとします。
let title = "Swift Programming Language"
let summary = "This book is titled: \\(title)"
print(summary)
こうすることで、「This book is titled: Swift Programming Language」という形で文字列が出力されます。
他にも、もっとオーソドックスなやり方としては次のような方法があります。
let title = "Swift Programming Language"
let summary = "This book is titled: " + title
print(summary)
これも同じ結果を得ますが、ストリングインターポレーションを使えばもっとシンプルに書けるというわけです。こうした違いが、プログラミングの可読性や効率性を向上させるために役立ちます。
このように、ストリングインターポレーションを使うことでコードをシンプルに、美しく保つことができます。次回もお楽しみに。 他にもSwiftには様々なやり方があって、ちょうどコメントにも出ているところですね。まず、オーソドックスな埋め込みスタイルです。昔からよく使われる方法として、文字列をフォーマットする方法があります。これは「フォーマットストリング」と呼ばれるものです。ただ、これには一部制約があるかもしれませんね。例えば、String(format: ...)
は標準ライブラリにあるかもしれませんが、確認してみましょう。ちょっとコンパイルを通してみると、String(format: ...)
は使えるようです。ただし、Foundationフレームワークをインポートしないとダメかもしれません。
import Foundation
let bookTitle = "Swift Programming"
let formattedString = String(format: "Book Title: %@", bookTitle)
print(formattedString)
このコードでは、"%@"
で文字列の挿入が成功しました。ただし、C言語スタイルのフォーマット文字列(%s
など)を使いたい場合、文字列をC文字列に変換する必要があるかもしれません。
let cString = bookTitle.cString(using: .utf8)
if let cString = cString {
let formattedString = String(format: "Book Title: %s", cString)
print(formattedString)
}
この方法でも動作しますが、若干の手間がかかるかもしれませんね。要注意なのは、C言語の書式文字列では型の厳密さが求められることです。間違った型を使うと、バッファオーバーランなどのセキュリティ問題が発生することがあります。
Swiftでは、こういった問題を解消するために高レベルな文字列操作が可能です。たとえば、文字列の連結や埋め込みが非常に簡単に行えます。
let interpolatedString = "Book Title: \\(bookTitle)"
print(interpolatedString)
この方法はC言語のsprintf
よりも安全で、エラーを減らすことができます。また、C言語ではこれを解決するためにsnprintf
が登場し、バッファオーバーランを防ぐようになりました。
Swiftは安全性を重視しているため、こうした古い方法を使わなくても、容易に安全なコードが書けるようになっています。これはSwiftが持つ大きな利点の一つです。書式文字列を使う場合も、Swiftならではの安全性のコンセプトがよく見られます。
さらに、C言語の書式文字列では特定の型(%d
, %ld
, %f
など)を指定しなければならず、間違えるとバグの原因になります。しかし、Swiftではこのような問題を避けるために、型安全な方法が提供されています。これにより、安心してプログラミングができますね。 ただ、これで気を使わないといけないところは特にフォーマット文字列の部分です。文字列変数で取ったりすると、そのあたりで苦労することがあります。書き方を忘れちゃいましたが、フォーマット文字列では順番を指定できるんですよね。2番目のパラメーターとして指定する方法とかがあったと思いますが、忘れてしまいました。設計者の工夫が感じられるポイントが多く、個人的にこういったマニアックなところが好きなので、どんな表現ができるのか試してみるのが面白いです。
多言語対応などでフォーマットの順番が重要な場面がありますので、こういった機能は必要ですよね。そういった場合、ストリングインターポレーションの順番替えができそうにないときは、文字列保管構文ではなく、書式化文字列を使用する形になります。また、独自の方法を用いたりもします。
具体的な方法は忘れてしまいましたが、例えば英語では1個だと単数形、2個だと複数形になるなどを制御するクラスがあるという話を聞いたことがあります。また、通貨表記のフォーマットなども同様で、デートフォーマッターのようなクラスもあります。文字列変換を高度に行うためには、さらに高度な方法が求められるので、そこまで確認する必要がでてきます。
一方で、文字列保管構文 (string interpolation
) は、基本的な文字列リテラルを便利にするためのツールで、ライトな用途で使用するのに最適です。積極的に使って問題ないもので、簡単に展開できる点が魅力です。例えば、デバッグで文字列の長さが必要なときに評価式として書くことができます。
let str = "Hello, world!"
print("String length: \\(str.count)")
このように評価式を簡単に書くことができるのです。また、他の関数も呼び出せます。例えば、アッパーケースの変換やマップ関数などです。
let uppercaseStr = str.uppercased()
print("Uppercase: \\(uppercaseStr)")
通常の文字列操作以外に、メソッドチェーンも可能です。以下のように、文字列を操作してジョインドすることもできます。
let characters = str.map { String($0) }
.joined(separator: ",")
print("Characters: \\(characters)")
文字列保管構文では、即興でデバッグプリントを書くときにも便利に使えます。また、パース処理がしっかりしていて、丸カッコを扱うときもスムーズに進めることができます。エスケープ処理が不要で使いやすいのも魅力の一つです。 なので、ガンガンと積極的にパワフルに使っていっても、自分が読むところならとりあえずオッケーかなっていう気がします。
あと、何かあるかな。
文字列補間構文で面白いところとして、文字列に改行を含むものを作りたいときにダブルクオーテーションを3つで書いていくという書式がSwiftにはあります。これを使うと全体的に改行されたテキストができるのかな。できていますよね、これね。
ここでも文字列補間構文がちゃんと使えます。例えば、何の脈絡もないですが、ここで丸カッコで細く添えるようなイメージにしてみます。全然関係ない話ですが、こうやって2行目のところにカッコで添えて変数を展開したりといったことも普通にできます。
あとは、文字列の中にダブルクオーテーションを含みたいときに、#""#
のようにシャープとダブルクオーテーションでテキストを囲むと、ダブルクオーテーションを含むことができます。こういう書き方もできて、ここでもちゃんとプリントしてみましょうかね。
let text = #"テキストに "ダブルクオーテーション" を含む"#
print(text)
これで、ダブルクオーテーションが入っている形で動いています。問題なく動きましたね。
私も少し記憶が曖昧でしたが、シャープを含める書き方も対応しています。以下のように書けます。
let text = ##"テキストに #"ダブルクオーテーション"# を含む"##
print(text)
このようにトリプルシャープを使えば、さらに複雑な文字列を扱うことができます。全然問題なく使えましたね。
これで、文字列リテラルの紹介も兼ねて、カスタマイズしていける場面もありますが、ストリングインターポレーション全然対応していますので、安心して使っていけます。この書き方を覚えて積極的に使っていきましょう。
リテラルに対して埋め込んでいくことが大事で、リテラルはもともとサクッと使うものですが、その使いやすさがしっかりと備わっているところがすごく良いなと思います。
余談ですが、シャープダブルクオーテーションの書き方はとても便利ですが、例えばテキストを引用符で囲むようなシチュエーションでは使えないのが悲しいところです。しかし、複数のシャープで囲うことで解決できます。
let text = ###"テキストに ##"ダブルクオーテーション##" を含む"###
print(text)
JavaScriptなどでは、正規表現の先頭に好きな文字を設定することができますが、Swiftでも似た発想でシャープを追加することで対応できます。個人的にObjective-Cの文字列リテラルの発想も好きです。C言語の完全互換性を保つためのアプローチは芸術の域に達していると感じます。
Objective-C++もC++と完全互換性がありますが、これも非常に美しい設計です。この美しさは語り継いでいきたいポイントです。ストリングインターポレーションについては、これで一通り見てきましたが、他に面白い使い方があれば教えてください。 そうですね、熱量が変わるとトーンが変わるという話は重要なポイントです。全然関係ない話ではないのですが、海外のラジオとかを受信してスクープを拾う、というエピソードを聞いたことがあります。例えば、大スクープをいち早く報道するアナウンサーが興奮気味に話すと、リスナーは「これは大事だ」と感じ取ることができます。こうやって大事なポイントはトーンを聞いていると伝わってきて、印象に残りやすいですよね。これは音声でのやり取りの一つの良さで、ブログにはない魅力だと思います。
さて、次にSwiftの「ストリングインターポレーション」について話してみましょう。ストリングインターポレーションの魅力を感じている人はいませんか? 自分ばかりしゃべっているかもしれませんが、せっかくなので、このストリングインターポレーションがなぜ成り立っているかを紹介します。
例えば、適当な構造体(struct)を突っ込んでも表示できるのって不思議だと思いませんか? これがSwiftの重要な根幹の一つなんです。Swiftの特徴として、全てのものは文字列として表現できるという点があります。この特徴によって、print
関数が成り立っています。
たとえば、print("self")
のようにメタデータを表示できるし、数字も表示できます。print(123)
のように。また、関数も表示できるんです。例えばstride(from: 2, through: 10, by: 2)
という関数を使って、指定したストライドで歩むことができますが、関数自体をprint
で表示できるんです。ただし、ジェネリクスの部分は少し複雑なので置いておきましょう。
普通の関数、たとえば文字列のアッパーケース変換関数"abcdef".uppercased()
も表示できます。このように、何でも文字列化してコンソールに表示できるのがポイントです。print
に渡して表示できないものって基本的にないんですよ。これがなかなか面白い特徴ですね。
他の言語だと、StringConvertible
のような特定のプロトコルを実装したものでないと文字列に変換できませんが、Swiftは言語仕様としてそれが標準で組み込まれています。それにより、特定の変換方法を持つカスタムストリングが扱えるんです。これがCustomStringConvertible
プロトコルです。
さらに、言語構文で標準化された文字列変換書式をサポートしています。たとえば、ストリング型に備わっているdescription
イニシャライザーです。このイニシャライザーはAny
型を引数に取ります。これによって、任意の型の値を渡しても文字列として表現することができるのです。 ストリングディスクリビングというのが具体的に何かという点ですが、「any」と書いていないものの、制約されていないジェネリクスというのは「any」と同等です。ここが面白いところで、「any」を取って渡したものが文字列化されて手に入るのです。つまり、ストリング型として得られるということです。これが、ストリングインターポレーションの中で標準的に行われているコードです。
例えば、この book
のタイトルの中にサマリーを埋め込んだケースを見てみます。特に配慮しない場合、文字列型は多少の配慮があるかもしれませんが、String(describing:)
というメソッドを使ったのと同等の動きをするのです。このメソッドが内部でいろいろと処理を行ってくれます。
そして、このストリングディスクリビングが面白い点は、Swift のコンパイラに組み込まれているところです。例えばカスタムストリングコンバーティブルがあれば、それを使うことができるのです。バリュー型を作って試してみましょう。
例えば、以下のように struct Value
というものを作成し、それを表示させてみます。
struct Value {
var property: Int
}
このインスタンスを作成し、ストリングディスクリビングに渡してやるとします。たとえば、以下のようにします。
let instance = Value(property: 42)
print(String(describing: instance))
これが一番基本的な動きであり、Swift の根本的な部分に組み込まれているものです。外部から介入することはできません。例えば、クラスにすると表示が異なる場合があります。
構造体とクラスの異なる表示方法が面白いのは、Swift がこれらをどう捉えているかを垣間見ることができる点です。今回の時間の都合で詳しくは省きますが、なぜこのような違いがあるのかを考察することで、構造体とクラスの役割の違いを理解する手がかりとなります。
エクステンションとして、例えば Value
にカスタムデバッグコンバーティブルを追加してみましょう。
extension Value: CustomDebugStringConvertible {
var debugDescription: String {
return "Debug Value"
}
}
これを行うと、String(describing:)
は Debug Value
を返すようになります。さらにカスタムストリングコンバーティブルを追加すると、
extension Value: CustomStringConvertible {
var description: String {
return "String Value"
}
}
この場合、表示が "String Value" に変わります。プレイグラウンドでの表示がカスタムデバッグストリングコンバーティブルの方を優先することもありますが、各場面に応じて表現が変わるのも興味深い点です。
オプショナル型に変換した Value?
をストリングディスクリビングに渡すと、デフォルトではデバッグストリングコンバーティブルの方が優先されます。
プリントを使った際も同様で、一般的な print()
の他に debugPrint()
も使用できます。debugPrint()
を通した場合、カスタムデバッグストリングコンバーティブルが優先されて表示されます。このように、プリントの動作や表示をカスタマイズすることができます。 インスタンスを渡すと、カスタムデバッグストリングコンバーティブルが優先されるという動きになりますね。ここにはまとめて書かずに、別の場所に整理して書くべきでした。このような動きがある他に、コメントにもある通り、エクステンションでデフォルトストリングインターポレーションを拡張して、mutating func appendInterpolation
のようにしてバリュー型を渡すことでセルフに対してappendLiteral
を適用し、インターポレーションのように書くことができます。
この方法で、ストリングインターポレーションがカスタマイズできることを示します。私は勉強会の準備を通じて、この方法を知りました。現時点では、ストリングインターポレーションの扱いが変わることがあり、今後も手直しする場合があるので気を付けるべきです。ただ、この機能は非常に便利で、ストリングインターポレーションに独自の機能を埋め込みたい場合には重宝します。
ただし、派手なことをすると安定性に欠けるため、カスタムストリングコンバーティブルやカスタムデバッグストリングコンバーティブルで対応できるものなら、そちらを優先したほうが良いと感じます。カスタムストリングコンバーティブルは、型のインスタンスが型の意味を保持しつつ文字列化できる機能です。逆変換が求められる場合には、ロスレスストリングコンバーティブルを適用し、イニシャライザーとしてディスクリプションを取る必要があります。
要するに、受け取るディスクリプションが同じであることが期待され、相互変換が可能な場合はカスタムストリングコンバーティブルを使います。一方、デバッグ情報として表現する程度であれば、カスタムデバッグストリングコンバーティブルを使います。このように、適用する方法を使い分けることが重要です。
ストリングインターポレーションに対するコメントもありますが、Genericsなども利用できるため、引数を増やしてより強力なカスタマイズが可能です。例えば、instance x:1
のようにして、特定の状況に応じた適切な変換が可能です。これは非常に興味深く、表現力が高まります。今後もSwiftは賢く進化していくため、この書き方がすぐに使えなくなることはないと思いますが、改変されることはあり得ます。
オーバーロードやオプショナルの付与など、他の多様な書き方もできるでしょう。このようにカスタムストリングコンバーティブルの機能について検討していくことが重要です。
以上で今回のストリングインターポレーションについてのお話を終わります。皆様お疲れ様でした。ありがとうございました。