やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

「リテラル」を読む

 覚えることがたくさんある。

成果物

情報源

リテラル

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

数値リテラル

コード例 種別
123,0d123 整数
-123 符号つき整数
123.45 浮動小数点数。 .1 など "." で始まる浮動小数点数は許されなくなりました。0.1 と書く必要があります。
1.2e-3 浮動小数点数
0xffff 16進整数
0b1011 2進整数
0377,0o377 8進整数
42r,3.14r 有理数。ただし、誤解を招く恐れがあるため、6.022e+23r のような指数部に有理数リテラルを含む形式は指定できません。
42i,3.14i 複素数
42ri,3.14ri 虚数部が有理数複素数

数値リテラルには、_' を含めることができます。 ruby インタプリタは' を単に無視し、特別な解釈は何もしません。これは、大きな数値の桁数がひと目でわかるように記述するのに便利です。リテラルの最初と、最後には _ を書くことはできません。(リテラルの前(符号(+,-)の直後を含む)に を置くとローカル変数やメソッド呼び出しと解釈されます)

 Pythonも同じことができたはず。

_ は、0x などの prefix の直後に書くことはできません。また、_ を連続して書いてもエラーになります。他、細かい部分でこのあたりの規則は見直され統一されました。

1_000_000_000  => 1000000000
0xffff_ffff  => 0xffffffff

文字列リテラル

"this is a string expression\n"
'this is a string expression\n'
%q!I said, "You said, 'She said it.'"!
%!I said, "You said, 'She said it.'"!
%Q('This is it.'\n)
"this is multi line
string"

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

シングル ダブル バック
リテラル 式展開 シェル展開

複数行にわたって書くこともできます。この場合含まれる改行文字は常に\nになります。実際のソースファイルの改行コードとは無関係です。

"A
B"
=> "A\nB"

空白を間に挟んだ文字列リテラルは、コンパイル時に1つの文字列リテラルと見倣されます。

p "foo" "bar"
=> "foobar"

 +で結合しなくてもいいってことか。これはあくまでリテラルのときのみ。変数のときはエラーになる。

a = 'foo'
b = 'bar'
p a b #=> undefined method `a' for main:Object (NoMethodError)

 なので変数のときは以下のようにする必要がある。

p a + b
p "#{a}#{b}"

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

文字列式は評価されるたびに毎回新しい文字列オブジェクトを生成します。

バックスラッシュ記法

文字列中でバックスラッシュ(環境によっては¥記号で表示されます)の後に記述する文字によっては特別な意味を持たせる事ができます。

 C言語でいうエスケープシーケンスのこと。ASCII文字コードにおける制御コードを表現する記法。それに加えてユニコードや一部キーボードも表現できるようだ。

コード 意味
\t タブ(0x09)
\v 垂直タブ(0x0b)
\n 改行(0x0a)
\r キャリッジリターン(0x0d)
\f 改ページ(0x0c)
\b バックスペース (0x08)
\a ベル (0x07)
\e エスケープ (0x1b)
\s 空白 (0x20)
\nnn 8 進数表記 (n は 0-7)
\xnn 16 進数表記 (n は 0-9,a-f)
\cx,\C-x コントロール文字 (x は ASCII 文字)
\M-x メタ x (c | 0x80)
\M-\C-x メタ コントロール x
\x 文字 x そのもの
\unnnn Unicode 文字(n は 0-9,a-f,A-F、16進数4桁で指定)。
\u{nnnn} Unicode 文字列(n は 0-9,a-f,A-F)。nnnnは16進数で1桁から6桁まで指定可能。スペースかタブ区切りで複数の Unicode 文字を指定できる。例: "\u{30eb 30d3 30fc a}" # => "ルビー\n"
\改行 文字列中に改行を含めずに改行

 よく使うのは\n\tくらいだと思う。

式展開

$ruby = "RUBY"
"my name is #{$ruby}" #=> "my name is RUBY"
'my name is #{$ruby}' #=> "my name is #{$ruby}"

ダブルクォート(")で囲まれた文字列式、コマンド文字列および正規表現の中では#{式}という形式で式の内容(を文字列化したもの)を埋め込むことができます。式が変数記号($,@)で始まる変数の場合には {}を省略して、#変数名という形式でも展開できます。文字#に続く文字が {,$,@でなければ、そのまま文字#として解釈されます。明示的に式展開を止めるには#の前にバックスラッシュを置きます。

 え、#{}って#に省略できたんだ? でも微妙だな。#を常に省略できるほうが嬉しいんだが。

$global = 'GLOBAL'
p "$global = #$global" #=> "$global = GLOBAL"
class C
  def initialize
    @iv = 'iv-value'
    p "@iv = #@iv" #=> "@iv = iv-value"
  end
end
C.new

式展開中の式は、ダブルクォートなども含めて Ruby の式をそのまま書くことができます。コメントも許されます。

p "#{ "string" # comment }"   # => "string"

 上記はエラーになる。なぜなら↓だから。

式展開中のコメントは、# から } まででなく改行までです。上記の例は

p "#{ "string" # comment
  }"                          # => "string"

と書く必要があります。

 紛らわしい。ようするに以下の2パターンのみ成功する。

p "#{""}"
p "#{"" # コメント
}"

文字リテラル

?の後に文字を1字指定した場合はその文字を表す文字列を返します。文字のエンコーディングソースコードエンコーディングと同じに設定されます。

コード 結果
?a a
?\s 半角スペース
?\t タブ
?あ
?\u3042
?\C-a \u0001(コントロール a を表す String)
?\M-a \xE1(メタ a を表す String)
?\M-\C-a \x81(メタ-コントロール a を表す String)
  • コントロール\CCTRLキーのことだと思われる
  • メタ\MというのはALTキーのことだと思われる

 おそらくテキストエディタEMACS界における常識。私は触ったことがないので知らないが、よく見かける。

コマンド出力

`date`
%x{ date }

バッククォート(`)で囲まれた文字列は、ダブルクォートで囲まれた文字列と同様にバックスラッシュ記法 の解釈と式展開 が行なわれた後、コマンドとして実行され、その標準出力が文字列として与えられます。コマンドは評価されるたびに実行されます。コマンドの終了ステータスを得るには、$? を参照します。

%記法による別形式のコマンド出力もあります。

 すばらしい。シェルなら一発でできることがRubyでもできる。

ヒアドキュメント (行指向文字列リテラル)

文法:

<<[(-|~)]["'`]識別子["'`]
   ...
識別子

 これはbashとほぼ同じ構文!

普通の文字列リテラルはデリミタ(", ', など)で囲まれた文字列ですが、ヒアドキュメントは<<識別子' を含む行の次の行から `識別子' だけの行の直前までを文字列とする行指向のリテラルです。例えば、

print <<EOS      # 識別子 EOS までがリテラルになる
  the string
  next line
EOS

これは以下と同じです。

print "  the string\n  next line\n"

ヒアドキュメントでは、開始ラベル `<<識別子' が文法要素としての式にあたります。これは、開始ラベルを使ってヒアドキュメント全体を引数に渡したりレシーバにしたりできるということを意味します。

# 式の中に開始ラベルを書く
# method の第二引数には "    ヒアドキュメント\n" が渡される
method(arg1, <<LABEL, arg2)
    ヒアドキュメント
LABEL

# ヒアドキュメントをレシーバにメソッドを呼ぶ
p  <<LABEL.upcase
the lower case string
LABEL

# => "THE LOWER CASE STRING"

 いいね!

開始ラベルの次の行は常にヒアドキュメントとなります。例えば、以下のような記述は文法エラーになります

printf('%s%d',
       <<EOS,
       3055 * 2 / 5)   # <- この行はヒアドキュメントに含まれてしまう
This line is a here document.
EOS

 これは罠。

開始ラベルを <<-識別子' のように-' を付けて書くことで終端行をインデントすることができます。これ以外では、終端行に、余分な空白やコメントさえも書くことはできません。

if need_define_foo
  eval <<-EOS   # '<<-' を使うと……
    def foo
      print "foo\n"
    end
  EOS
  #↑終端行をインデントできます。
end

 シェルと同じ。いいね。ちゃんとインデントしたコードが書ける。

開始ラベルを <<~識別子 のように ~ を付けて書くことで、以下のようなヒアドキュメントを書くことができます。

    expected_result = <<~SQUIGGLY_HEREDOC
      This would contain specially formatted text.

      That might span many lines
    SQUIGGLY_HEREDOC

最もインデントが少ない行を基準にして、全ての行の先頭から空白を取り除きます。インデントの深さを決定するために主にタブやスペースで構成された行は無視されるので、注意してください。しかし、エスケープされたタブやスペースは、通常の文字と同じように扱われます。

 シェルにもないすばらしい機能。これが欲しかったんや!

一行に複数のヒアドキュメントを書くこともできます。

print <<FIRST, <<SECOND
   これは一つめのヒアドキュメントです。
   まだ一つめです。
FIRST
   この行からは二つめのヒアドキュメントです。
   この行で終わります。
SECOND

 いいねいいね。

開始ラベル <<識別子' の識別子' を(""、''、`)のいずれかで囲むことで、ヒアドキュメントとなる文字列リテラルの性質は対応する文字列リテラルと同じ扱いになります。ただし、文字列中に " や ' はバックスラッシュエスケープせずにそのまま書けます(エスケープする必要がありません)。シングルクォートで囲ったヒアドキュメントの場合、' をエスケープする必要がないということは、\の特別扱いも必要ないということになります。つまり、シングルクォートで囲ったヒアドキュメントは完全に書いたままの文字列になります。以下の例を参照してください。識別子' がクォートで囲まれていないときはダブルクォートでくくられているのと同じです。

 展開まで制御できる。すばらしい。シェルでは展開するためにわざわざevalしていたはず。それがいらない。

# バックスラッシュ記法、式展開が有効
print <<"EOS"
The price is #{$price}.
EOS

# 上のものと同じ結果
print <<EOS
The price is #{$price}.
EOS

# 式展開はできない
print <<'EOS'
The price is #{$price}.
EOS

# コマンドを実行
print <<`EOC`
date
diff test.c.org test.c
EOC

文字列リテラルのそれぞれの性質に関しては 文字列リテラル、 式展開、 バックスラッシュ記法、 コマンド出力 を参照してください。

 ヒアドキュメントは今までのプログラミング言語中最高。こんなすばらしいのはじめて見た。

正規表現リテラル

/^Ruby the OOPL/
/Ruby/i
/my name is #{myname}/o
%r|Ruby|

/で囲まれた文字列は正規表現です。正規表現として解釈されるメタ文字については正規表現を参照してください。

終りの/の直後の文字は正規表現に対するオプションになります。オプションの機能は以下の通りです。

オプション 意味
i 正規表現はマッチ時に大文字小文字の区別を行わない
o 一番最初に正規表現の評価が行われた時に一度だけ式展開を行う
x 正規表現中の空白(改行も含む)を無視する。また、バックスラッシュでエスケープしない`#' から改行までをコメントとみなして無視する(ただし、コメント中に / を含めると構文解析に失敗するので注意)
m 複数行モード。正規表現 "." が改行にもマッチするようになる

 以下は/foobar/と同じ。空白を含めるには \ のようにエスケープします。

/foo        # コメント
bar/x

正規表現中の文字は特に指定がない場合、スクリプトエンコーディングの値に従って処理されます。

 はい。

オプションとして n, e, s, u のいずれかを指定することで正規表現文字コードスクリプトエンコーディングに関係なく個々の正規表現リテラルに指定することもできます。

 それぞれがどのエンコードなのか説明求む。

%記法 による別形式の正規表現も指定できます。

 また%記法かよ。なんか難しそうだから嫌。

正規表現の中では文字列と同じバックスラッシュ記法や 式展開も有効です。

 マジか。oオプションは一度だけ式展開するとあるが、それがなければ何度でも式展開するってことか? 一度だけ式展開したいときってどんなとき?

正規表現リテラルはその中に式展開を含まなければ、何度評価されても同一の正規表現オブジェクトを返します。 式展開を含む場合は評価のたびに(式展開の結果を元に)正規表現コンパイルされ正規表現オブジェクトが生成されます(ただし上記の o オプションを指定すれば、同一の正規表現オブジェクトを返します)

 ええと、つまりoオプションは初回ビルド時に式展開して、それをオブジェクトコード化しちゃうってこと? それに対してoオプションがないときは実行すると毎回評価するということか?

配列式

[1, 2, 3]
%w(a b c)
%W(a b c)

文法:

`[' 式`,' ... `]'

それぞれの式を評価した結果を含む配列を返します。配列はArrayクラスのインスタンスです。

要素が文字列リテラルかシンボルリテラルの場合に限り、%記法 による別形式の配列表現も指定できます。

 %w%Wのことね。これリテラルで囲わなくても書けるから超便利。まあPythonみたく"a b c".split(' ')でもいい気がするが。それより短く書ける。

配列式は評価されるたびに毎回新しい配列オブジェクトを生成します。

ハッシュ式

{ 1 => 2, 2 => 4, 3 => 6}
{ :a => "A", :b => "B", :c => "C" }
{ a:"A", b:"B", c:"C" } # 一つ上の例と同じ。キーがシンボルの場合はこのように書ける。
{ "a":"A", 'b':"B", "c":"C" } # 一つ上の例と同じ。
                              # キーにシンボルを使う文法ではこのように
                              # シンボル名をシングルクオートやダブルクオートで
                              # 囲うことができる。これによって空白を含むような
                              # シンボルなどをキーにできる

文法:

`{' 式 `=>' 式 `,' ... `}'

それぞれの式を評価した結果をキーと値とするハッシュオブジェクトを返します。ハッシュとは任意のオブジェクトをキー(添字)として持つ配列で、Hashクラスのインスタンスです。

メソッドの引数、もしくは配列式の末尾に要素が1つ以上のハッシュを渡す場合は、{, }を省略することができます。

例:

method(1,2,3=>4)      # method(1,2,{3=>4})
obj[1=>2,3=>4]        # obj[{1=>2,3=>4}]
[1=>2,3=>4]           # [{1=>2, 3=>4}]

ハッシュ式は評価されるたびに毎回新しいハッシュオブジェクトを生成します。

 なんかググったら記法は色々あるっぽい。

hash = {}
hash = {'key': 'value'}
hash = {'key' => 'value'}
hash = {key: 'value'}
hash = {:key => 'value'}

 {key: 'value'}が一番見慣れた形。=>は冗長だし見づらい。

範囲オブジェクト

演算子式/範囲式を参照

範囲式はその両端が数値リテラルであれば、何度評価されても同じオブジェクトを返します。そうでなければ評価されるたびに新しい範囲オブジェクトを生成します。

 以下みたいなやつ。

1 .. 5
(1..)
(1...)
Range.new(1,5)

シンボル

例:

(シンボルの例)

      :class
      :lvar
      :method!
      :andthisis?
      :$gvar
      :@ivar
      :@@cvar
      :+

 おそらくクラス名(モジュール名)、ローカル変数名、メソッド名(末尾に!,?があるやつも含めて)、グローバル変数名、インスタンス変数名、クラス変数名のことだろう。+ってなに? :が1つ以上ってこと?

 でもたしか前の章ではクラス名って定数なんじゃなかったっけ? なのにここでは定数がシンボルじゃないっぽい。どゆこと? いや、定数もシンボルに含まれるのかな? 上記の例には明記されていないけども。

文法:

`:' 識別子
`:' 変数名
`:' 演算子

Symbolクラスのインスタンス。ある文字列とSymbolオブジェクトは一対一に対応します。

Symbol リテラルに指定できる演算子はメソッドとして再定義できる演算子だけです。演算子式 を参照して下さい。

以下の記法も使えます。

p :'foo-bar' #=> :"foo-bar"
p :"foo-bar" #=> :"foo-bar"
p %s{foo-bar} #=> :"foo-bar"

 -などの記号を使いたいときはクォートすればいいのか。

この記法では、任意のシンボルを定義することができます。

:"..." の形式は、バックスラッシュ記法や 式展開が有効です。

 それを用いれば式や変数の値をシンボルにすることもできると。

シンボルは常に一意のオブジェクトで、(式展開を含んでいてもその結果が同じ文字列であれば)何度評価されても同じオブジェクトを返します。

ほとんどのシンボルはGC可能です。

@see https://bugs.ruby-lang.org/issues/9634

%記法

文字列リテラル、コマンド出力、正規表現リテラル、配列式、シンボルでは、 %で始まる形式の記法を用いることができます。文字列や正規表現では、"',/' など(通常のリテラルの区切り文字)を要素に含めたい場合にバックスラッシュの数をコードから減らす効果があります。また配列式では文字列の配列やシンボルの配列を簡単に表現できます。それぞれ以下のように対応します。

%!STRING! : ダブルクォート文字列
%Q!STRING! : 同上
%q!STRING! : シングルクォート文字列
%x!STRING! : コマンド出力
%r!STRING! : 正規表現
%w!STRING! : 要素が文字列の配列(空白区切り)
%W!STRING! : 要素が文字列の配列(空白区切り)。式展開、バックスラッシュ記法が有効
%s!STRING! : シンボル。式展開、バックスラッシュ記法は無効
%i!STRING! : 要素がシンボルの配列(空白区切り)
%I!STRING! : 要素がシンボルの配列(空白区切り)。式展開、バックスラッシュ記法が有効

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

%(()) => "()"

 え、!でなくてもいいの?

p %?some? #=> "some"
p %$some$ #=> "some"
p %%some% #=> "some"
p %&some& #=> "some"
p %-some- #=> "some"

 おお、ほんとだ。まあ!でいいや。

 変数のときでも使えるのかな? たとえば変数nameの値をダブルクォートしたいときとか。

name = 'ytyaru'
p %!#{name}! #=> "ytyaru"

 できた。

文字列の配列の%記法はシングルクォートで囲んだ文字列を空白文字で分割したのと同じです。たとえば、

%w(foo bar baz)

は['foo', 'bar', 'baz']と等価です。

バックスラッシュを使って空白を要素に含むこともできます。

%w(foo\ bar baz)

=> ["foo bar", "baz"]

 ようするに%wは展開せず文字列リテラルにするってこと。

%W は、%w と同様ですが、ダブルクォートで囲んだ文字列のように、式展開、バックスラッシュ記法が使用できます。空白による分割は式展開を評価する前に行われます。

v = "c d"
%W(a\ b #{v}e\sf #{})

=> ["a b", "c de f", ""]

 はい。仕様を分からせるためにわざと難しく書いてあって見づらいけども。

シンボルの配列の場合も文字列の配列の場合と同様です。

%i(foo\ bar baz)

=> [:"foo bar", :baz]


v = "c d"
%I(a\ b #{v}e\sf #{})

=> [:"a b", :"c de f", :""]

 はい。使いどころがわからんけども。

所感

 結構たいへんだった。

対象環境

$ uname -a
Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux