#10 リファレンス/リファレントと値 (同姓同名でも別人)

値と実体

AとBのふたりの人物がいます。

Aは私です。つまりはるかです。

a = "はるか"

Bも「はるか」ですが、私ではありません。 舞台女優です。1

a = "はるか"
b = "はるか"

aとbは名前が同じですから、値は同じです。

a.inspect     # "はるか"
b.inspect     # "はるか"
a == b        # true

私はミーミルヨコハマのエンジニアです。 一方、彼女は劇団エプオート2の女優です3

mimir_yokohama_engineer = a
a_man = a
epuort_actress = b
the_woman = b

もちろん、値は同じです。

mimir_yokohama_engineer == epuort_actress   # true

しかし、私と彼女は別の人物です。 一方、ミーミルヨコハマのエンジニアと私は同一人物です。 その実体を示すIDを見てみましょう。

a = "はるか"
b = "はるか"
mimir_yokohama_engineer = a
a_man = a
epuort_actress = b
the_woman = b

p a.object_id
p b.object_id
p mimir_yokohama_engineer.object_id
p a_man.object_id
p epuort_actress.object_id
p the_woman.object_id

結果は…

47380071672020
47380071672000
47380071672020
47380071672020
47380071672000
47380071672000

一見同じに見えるような値になってしまいましたが、よく見ると下2桁が2000にわかれています。 つまり、私と彼女で別のIDを持っているということになります。

a = "はるか"
b = "はるか"
a.object_id == b.object_id   # false

けれど実体が同じならIDも同じです。

a = "はるか"
mimir_yokohama_engineer = a
a.object_id == mimir_yokohama_engineer.object_id   # true

実体の変更

彼女が名前表記をカタカナに変更したとしましょう。4

b = "はるか"
epuort_actress = b

b.sub!("はるか", "ハルカ")

変更されているのはbの値だけではありません。 実体はひとつ(彼女は分裂したりしませんから!)ですから、epuort_actressの値もちゃんと変わっています。

b.inspect                # "ハルカ"
epuort_actress.inspect   # "ハルカ"

b.object_id == epuort_actress.object_id    # true

しかし、代入した場合は別です。 この場合は計算した結果である新しい値を代入するからです。

a = "apple"
fruit = a

a = a.sub("apple", "waffle")

a.inspect        # "waffle"
fruit.inspect    # "apple"

参照と値

値は実体が何であるかではなく値が何であるかということです。 これは同じものがひとつしか持てないわけではありません。例えば日本には何人もの山田太郎さんが(多分)います。 これらはそれぞれ別の人ですが、名前という値は同じです。

しかし、山田太郎さんはそれぞれ様々な面を持っていることでしょう。「会社員」山田太郎、「夫」山田太郎、「息子」山田太郎などなど… これらは何でそれを指し示しているかは異なりますが、指し示している先はひとりの山田太郎になります。

値はそれぞれ実体を持っています。 コンピュータ的には、その実体はメモリ上にあります。

変数は値そのものを持っている場合もありますが5変数は実体を参照していることもあります。

この参照が「リファレンス」で、参照されている実体が「リファレント」になります。

これは次のようになります。

  • 即値を持つ場合、そもそも同じ実体を複数の変数で共有することができないので、a = bのようにしたときには値がコピーされる
  • リファレンス代入した場合、a = bは同じリファレントを指すリファレンスになる

a = bがどのように動作するかについては言語によって異なります。

Ruby及びJavaScriptは原則としてリファレンスを扱います。 一部即値を持つもの(例えば数値)に関してはリファレントを変更するような操作を許していないためそれを意識する必要はなくなっています。 「リファレントそのものを変更するような破壊的メソッドを呼び出した場合、全てのリファレンスから得られる結果が変わる」と統一されているのです。

例えばPerlにおける

$str =~ /apple/waffle/;

$strそのものを変更しますが、JavaScriptの

str.replace("apple", "waffle")

strを置き換えた文字列を値として返します。str自身は変更されません。6

Perlでは通常値は即値を持つ一方、変数に対するリファレンスを手動で得ることもできます。 そのような振る舞いをもつプログラミング言語は非常に稀ですが、Perlにおいて最も苦痛を招くポイントでもあります。7

値渡しと参照渡し

これを気にしなければならない言語なんてだいぶ限られているのですが、C言語のみならずPerlとPHPが該当しますから油断なりません。

リストを渡すことを考えてみましょう。 Perlで次のようなコードを考えてみます。

sub pushlist(@) {
  my @list = @_;
  push(@list, "D");
}

@l = ("A", "B", "C");
pushlist(@l);

このとき@lの値は

("A", "B", "C")

です。

では、今度はリファレンスを渡してみましょう。 Perlは\を前置することでリファレンスが得られ、$を前置することでリファレンスからリファレントが得られます。

sub pushlist($) {
  my $list = @_[0];
  push(@$list, "D");
}

@l = ("A", "B", "C");
pushlist(\@l);

今度は

("A", "B", "C", "D")

になりました。

Perl4まではサブルーチンの外側の変数を直接書き換える、ということがよく行われていたため意識することもなかったのですが、 Perl5からは変数の受け渡しをするようになりました。

値で渡した場合は、渡されているのは同じ値を持つコピーであるため、変更を加えても渡した元に対しては影響がありません。

対して参照(リファレンス)で渡した場合は同じ実体であるため、変更を加えると呼び出し元からみても変更が反映されています。


  1. たぶん、この人は今後もたびたび登場します。↩︎

  2. つまり troupe epuort ですね↩︎

  3. これは、現実を著しく脚色した架空の人物です。↩︎

  4. リテラル文字列がデフォルトでfrozenになるため、このコードはRuby3では動作しません。↩︎

  5. 即値を持つ」という言い方をします。↩︎

  6. Rubyでは文字列リファレントになっていますが、JavaScriptでは文字列即値を持ちます。↩︎

  7. なんかちょっとC言語っぽいポイントですが、さすがに新しい言語であるPerl6ではリファレンスの手動操作はしないことになりました。よかった。↩︎