リテラル

消えたダブルクォーテーション

前回学習したことから、

print("Hello, world")

という文は「"Hello, world"を表示してください」という命令だということがわかりました。


ところが、このプログラムの実行結果はたしか

Hello, world

でした。
大体よさそうなのですが、「Hello, world」を囲む""(ダブルクォート:二重引用符)が消えてなくなってます。
これはいったいどういうことなのでしょうか。

文字列リテラル

実は、この""で囲まれたものは、“文字列リテラル”という存在になります。
Rubyリファレンスマニュアルの該当部分を確認してみましょう。
リテラル
文字列リテラル

リテラルとは

数字の1や文字列"hello world"のようにRubyのプログラムの中に直接記述できる値の事をリテラルといいます。

ふむ。
一応、元々の言葉の意味も調べてみます。
literal

literal
【名-1】 誤植{ごしょく}、誤字{ごじ}
【名-2】 《コ》直定数{ちょく ていすう}
【形】 文字{もじ}どおりの(意味{いみ}の)、逐語的{ちくごてき}な、文字{もじ}の、融通{ゆうずう}の利かない、事実{じじつ}に忠実{ちゅうじつ}な、想像力{そうぞうりょく}に欠けた、味気{あじけ}ない、四角四面{しかく しめん}の

「文字通りの意味の」というのがそれらしい感じですね。


これはつまりどういうことなのでしょう。実例で考えてみます。
たとえば、今までの「Hello, world」ではなくて「print」と出力してみたい場合の正解は、

print("print")

こうなります。
もし、これを

print(print)

と書いてしまったらどうなるでしょうか。
書いた当人としては「print」と出力したくて書いたのですが、考えてみるとprintというのは関数(厳密にはメソッドとやら)です。
ですから、これだと「print関数(の戻り値)」を出力します、という風に読めてしまうわけですね。
それでは困るので、これは「関数の名前としてのprint」ではなく、「ただの文字の並びとしてのprint」です、ということが明示できなければいけません。それを実現するのが、“文字列リテラル”という存在なのです。

文字列リテラルのつくりかた

それでは文字列リテラルについて引用してみます。

文字列はダブルクォートまたはシングルクォートで囲まれています。ダブルクォートで囲まれた文字列ではバックスラッシュ記法 と式展開(後述)が有効になります。シングルクォートで囲まれた文字列では、\\(バックスラッシュそのもの)と \'(シングルクォート)、行末の\(改行を無視します) を除いて文字列の中身の解釈は行われません。

%記法 による別形式の文字列表現もあります。

……だ、そうです。
つまり。

  • ダブルクォートまたはシングルクォートで囲まれる
  • ダブルクォートで囲まれた場合
    • バックスラッシュ記法が有効
    • 式展開が有効
  • シングルクォートで囲まれた場合
    • 基本的に文字列の中身の解釈は行われない
    • ただし、\\・\'・行末の\は例外
  • ダブルクォート、シングルクォートを使わない%記法というものもある

ということで、最初の定義を最後の最後で覆してくれました。
%記法についてもそれなりに見ておきましょう。⇒%記法
いくつか並んでますが、今回見るべきは以下の3つです。

  • %!STRING! : ダブルクォート文字列
  • %Q!STRING! : 同上
  • %q!STRING! : シングルクォート文字列

さらに、

!の部分には改行を含めた任意の非英数字を使うことができます。始まりの区切り文字が括弧(`(',`[',`{',`<')である時には、終りの区切り文字は対応する括弧になります。括弧を区切り文字にした場合、対応が取れていれば区切り文字と同じ括弧を要素に含めることができます。

ということらしいです。
結局のところ、文字列リテラルを表現する方法は5通り*1あるのですね。*2
ためしに全部使ってみます。

print("北海道")
print('日本ハム')
print(%!ファイターズ!)
print(%Q+日本一+)
print(%q(おめでとう!))

↓実行結果

北海道日本ハムファイターズ日本一おめでとう!

見事な時事ネタです。


さてさて、このように書き方は5通りですが、その解釈のされかたは結局ダブルクォート系とシングルクォート系の2つです。
ではこの2つの解釈の違いを確認してみましょう。
先ほど文字列リテラルについて引用した際に確認しましたが、この2つの違いは

  • バックスラッシュ記法
  • 式展開

の2点が有効であるかどうか、という話に尽きるようです。
このうち、式展開については諸々の都合により後回しに……。
今回はバックスラッシュ記法についてのみ確認してみましょう。

バックスラッシュ記法

=>バックスラッシュ記法
\t、\n、\r……と、頭に\が付いた文字が並んでいます。
私の環境で見る限り、バックスラッシュには見えないのですが、実はこれにはワケがあります。

バックスラッシュは約物の一つで、\のような形をしている。バックスラッシュとはスラッシュ ( / ) の逆という意味である。/に比べれば、自然言語ではあまり使われることのない記号である。

ASCIIのバックスラッシュ (0x5C, 5/12) はJIS X 0201では円記号であるため、日本のコンピュータや日本語のフォント・OS環境ではバックスラッシュが円記号として表示されるものが多い。

ということで、元来バックスラッシュで使われていた文字コード番号が、日本の環境では円記号として使われている場合が多いのです。
そんなわけなので、ひとまずは『バックスラッシュ ⇒ 円記号』と脳内置き換えしておいてもなんとかなるような気がします。


表示の問題は良いとして、そもそもこれらはどのような役割を持つものなのでしょうか。
実はこれら(\tや\nなど)は『バックスラッシュ(円記号)+文字』で1文字分のデータを表します。そして何故頭にバックスラッシュが付いているのかというと、これによって実際の文字では表現しきれない特殊な文字を表すことができるのです。
具体的にはリファレンスマニュアルにあるとおりで、例えば、

\t
タブ(Tab)
\n
改行(New line)
\r
キャリッジリターン(復帰:carriage Return)

などなどです。
このようなものを文字列リテラル内に含めることで、特殊な文字の出力を表現できるのです。
試しにこんな風に書いてみると、

print("シ\n")
print("\tン\n")
print("\t\tジ\n")
print("\t\t\tラ\n")
print("\t\t\t\tレ\n")
print("\t\t\t\t\tナ\n")
print("\t\t\t\t\t\t〜\n")
print("\t\t\t\t\t\t\tイ")

↓実行

シ
	ン
		ジ
			ラ
				レ
					ナ
						〜
							イ

こうなります。
全部を一つの文字列にしてしまっても大丈夫。

print("シ\n\tン\n\t\tジ\n\t\t\tラ\n\t\t\t\tレ\n\t\t\t\t\tナ\n\t\t\t\t\t\t〜\n\t\t\t\t\t\t\tイ")

もちろん、%記法を使ってこんな風にしてもOKです。

print(%!シ\n!)
print(%Q!\tン\n!)
print(%!\t\tジ\n!)
print(%Q!\t\t\tラ\n!)
print(%!\t\t\t\tレ\n!)
print(%Q!\t\t\t\t\tナ\n!)
print(%!\t\t\t\t\t\t〜\n!)
print(%Q!\t\t\t\t\t\t\tイ!)


このようなバックスラッシュ記法はダブルクォート系の文字列リテラルでないと有効になりません。
ですので、

print('シ\n')
print(%q{\tン\n})
print('\t\tジ\n')
print(%q(\t\t\tラ\n))
print('\t\t\t\tレ\n')
print(%q[\t\t\t\t\tナ\n])
print('\t\t\t\t\t\t〜\n')
print(%q@\t\t\t\t\t\t\tイ@)

このようなシングルクォート系を利用したコードだと……

シ\n\tン\n\t\tジ\n\t\t\tラ\n\t\t\t\tレ\n\t\t\t\t\tナ\n\t\t\t\t\t\t〜\n\t\t\t\t\t\t\tイ

バックスラッシュもなにも、そのまま表示されます。


それにしても、ヒルマン監督おおはしゃぎですね。


バックスラッシュ記法はいくつかありますが、特殊文字については最初は\n・\t・\r・\bくらい知っておけば問題ないように思えます。
使われる文字も大体元の単語から取ったものだったりしますので、結びつけて覚えておくと良いでしょう。
もう一つ知っておかなければいけないのは、\xとして紹介されている「文字そのもの」を表す書き方です。これはいったいどのような場面で使われるのでしょうか?
たとえば次のような出力結果を得たいと考えます。

This site is 'Hatena Diary'.

このとき、

print("This site is 'Hatena Diary'.")
print('This site is 'Hatena Diary'.')

上記のようなコードを書いてしまうと……

parse error, unexpected tCONSTANT, expecting ')'
print('This site is 'Hatena Diary'.')
                           ^
warning: parenthesize argument(s) for future version
parse error, unexpected ')', expecting $
print('This site is 'Hatena Diary'.')
                                     ^

こんな感じのメッセージが出て、正しく実行できません。問題は、2行目のシングルクォーテーション版にあります。
''で囲まれた部分が文字列リテラルになるので、このコードの場合だと

print('This site is 'Hatena Diary'.')

赤字で示した部分のみが文字列リテラルとして解釈され、その間が不正な文章になってしまいます。
同じように、

print("This site is "Hatena Diary".")

というように""が入れ子になってしまっている状況でも同様の問題がおきます。

print("This site is "Hatena Diary"'.")

このような状況に対応するために、\xを利用します。
つまり、シングルクォートやダブルクォートを『文字列を定義するための特別な記号』として扱わずに、単なる文字として認識して欲しい、と明示するわけです。

print("This site is \"Hatena Diary\".")
print('This site is \'Hatena Diary\'.')

このように「ただの文字だよ」と示したい文字の前に\を付加することで、特別な意味を打ち消すことができます。


あるいはバックスラッシュを使わずに、

print(%Q!This site is "Hatena Diary".!)
print(%q!This site is 'Hatena Diary'.!)

などと%記法を使っても書けます。
引用符が入り混じるような文字列の場合は、こちらの方が便利かもしれません。

文字列リテラルの使いわけ

以上のような特徴を踏まえたうえで、実際の場面ではどの方法で文字列リテラルを定義すれば良いのでしょうか。
現在の段階では、下図のような判断基準を持ち出すことができそうです。

実のところ、取り敢えずダブルクォーテーション使ってれば何とかなる気はします。
でも、たまにはちょっと意識しておかないと忘れてしまいそうなので、気をつけましょう。

まとめと練習問題

今回は、こんなことを学びました。

  • プログラム中に直接記述する「そのままの値」をリテラルと呼ぶ
  • 文字列リテラルの定義方法は5つある
    • ""ダブルクォーテーション
    • ''シングルクォーテーション
    • %!!
    • %Q!!
    • %q!!
  • 解釈の違いにより、2つの種類が存在する
    • 式展開、バックスラッシュ記法が利用できるか否かで違いが現れる
  • 特殊文字などを表現するためにはバックスラッシュ記法を使う
問題

次の出力を行うプログラムを作成してください。
なお、各行の間には1つ空行があり、2行目の先頭にはタブが1つ含まれています。

リーグ優勝、'日本一'

	そして……

次は"アジアの頂点"へ!
記述例

たぶん、簡単なのはこんな感じだと思います。

print(%Q-リーグ優勝、'日本一'\n\n\tそして……\n\n次は"アジアの頂点"へ!-)


もちろん、あえてこんな面倒な書き方をしても大丈夫です。

print('リーグ優勝、\'日本一\'')
print(%Q!\n\n\tそして……\n\n!)
print("次は\"アジアの頂点\"へ!")


紹介していないやり方ですが、こういうのもアリです。

print(%Q-リーグ優勝、'日本一'

\tそして……

次は"アジアの頂点"へ!-)

次回

今回は文字列リテラルについて触れましたので、次回は数値リテラルと仲良くなろうと思います。

*1:もちろん、%記法の場合は任意の非英数字が使えるのでバリエーションはいくつかありますが

*2:ヒアドキュメントはひとまず置いておきます