静かなる名辞

pythonとプログラミングのこと


共有渡しと参照の値渡しと

はじめに

 関数やメソッドに引数を渡す方法は、一般的には

  • 値渡し
  • 参照渡し

 の2通りがあると認知されている。

 ところで、『参照の値渡し』という言葉も(ほぼ日本語Web圏限定で)存在する。これは「いわゆる『参照渡し』は参照自体を書き換えるんじゃなくて、参照する対象を変えるだけだから、そう呼んだ方が適当だよ!」という思想に基づくもの、だと思う。

 このページを見るとわかりやすい。

キミの言語は参照渡しできる? - Qiita

 つまり、こういうことができたら『参照渡し』で

a = "hoge"
b = "fuga"
swap(a, b)
print(a, b)  # => fuga, hoge

 できなかったら「キミの言語は『参照渡し』できないよ、キミが『参照渡し』だと思っているのは『参照の値渡し』だよ!」ということか。言いたいことはわかる。

 上のリンクにはC言語で「参照渡し」をやる方法としてこういう例が載っている。

#include <stdio.h>

void swap(char *a, char *b){
  char tmp;
  tmp = *a;
  *a = *b;
  *b = tmp;
}

int main(void){
  char x = 'A', y = 'B';
  swap(&x, &y);
  printf("%c\n", x);
  printf("%c\n", y);
  return 0;
}

 ちなみに、こっちの「参照渡し」は歴史が古く、少なくともFORTRANからあるっぽい。というか、FORTRANはデフォルトですべて参照渡し。

subroutine f(x)
  integer x
  x = 42
end subroutine

program main
  integer a
  a = 3
  print *, a
  call f(a)
  print *, a
end program

!           3
!          42

 C言語とかに慣れた目には奇異に映るけど、よくよく考えてみるとメモリ番地だけ渡せばいいので効率的だし(まあ実際にどういう実装なのかまでは確認していないけど)、多少注意していればプログラムも書きやすいので、これはこれで合理的だと思う。

 話が逸れた。参照の値渡しでは、こういう「参照渡し」チックな動作はできない。pythonの例。

def swap(a, b):
    a, b = b, a

a = 10
b = 20
swap(a, b)

 pythonの変数はすべてJavaなどでいうところの参照型ではあるのだけど、swapの中のa,bは単なるswap関数のローカル変数であって、呼び出し元のa, bの参照するものを書き換えたりはしない。受け取っているのは「参照の値」であって、「参照の値を格納している変数への参照」ではない。

 参照型の「真の」参照渡しについては、以下のCの例を考えてみるとわかりやすい。

#include <stdio.h>

void swap(char **a, char **b){
  char *tmp;
  tmp = *a;
  *a = *b;
  *b = tmp;
}

int main(void){
  char x = 'A', y = 'B';
  char *xptr, *yptr;
  xptr = &x;
  yptr = &y;
  swap(&xptr, &yptr);
  printf("%c\n", *xptr);
  printf("%c\n", *yptr);
  return 0;
}

 xptr, yptrのアドレスを渡す訳ですね。pythonではこれはできない(というか、そもそも変数の概念そのものが違うけど)。で、こういうものを称して参照の値渡し、とする。

共有渡しについて考える

 さて、『参照の値渡し』とよく似た概念として、『共有渡し』がある。

 ちなみに、日本語wikipediaの「引数」ページには『共有渡し』は存在しない代わり参照の値渡しがあり、英語版wikipediaの「Evaluation strategy」ページには『Call by sharing』*1はあって参照の値渡しはない。なんだかなぁ。

引数 - Wikipedia
Evaluation strategy - Wikipedia

 『共有渡し』が何者かというと、これは英語版wikipediaのページを読むのがわかりやすいのだが、一行引用してくると

also referred to as call by object or call by object-sharing

 ということであり、つまりは関数(メソッド)の呼び出し元と呼び出し先で同じオブジェクトを「共有」する方法である。

 僕のようなpython使いにとっては「それ普通じゃね?」なのだが、確かに値渡しとも『参照渡し』とも(何を示す言葉かは不問として)異なる概念と言われればそんな気はする。この言葉は、1974年、CLUという初期のオブジェクト指向言語とともに生み出された言葉だそうな。

 これに関連して、こんな議論もある。comp.lang.python *2の昔の議論らしい。pythonの呼び出しモデルは『Call by sharing』だと結論が着いている感じ。

Call By Object

 なんか、同じものを呼ぶ名前がいっぱいある。

The most accurate description is CLU’s “call by object” or “call by sharing“. Or, if you prefer, “call by object reference“.

 こんなに呼び方が多いのはちょっと酷いんだが、“call by object reference“あたりだと言いたいことはよく伝わってくる。

どっちが良いのか

 べつに『参照の値渡し』≒『共有渡し』とみなしても良いのだが、言葉のニュアンスは違うし、他にも考えるべきことがあって「どっちを使うか、あるいはどっちでも良いのか」という問題には答えを出しづらいと思うのだ・・・。

  • 使える言語と使えない言語がそれぞれ違う

  『参照の値渡し』は『参照の値』が定義されない言語では使うべきではないと思う。また逆に、C言語に対して『共有渡し』を使うのにはなんとなく躊躇する。メモリ領域を共有しているには違いないけど、なんかもう少し低レイヤな感じなので。

  • あくまでも値渡し+参照渡し的な世界観で説明しようとする『参照の値渡し』と、オブジェクトが呼び出し元・先で「共有」されるという現象を重視する『共有渡し』

 同じ現象でも見方が違うのだと思う。

 この違いは意外と効いてきて、前者の立場を取るとJavaは「基本的に値渡し。プリミティブ型はそのまま値で渡るが、それ以外はアドレスの値が渡る」と値渡し的な世界観で説明できるが、後者の立場を取ると「プリミティブ型の値渡し、配列の『参照の値渡し』、オブジェクトの『共有渡し』の折衷」という苦しい説明にせざるを得ないと思う(配列に対して『共有渡し』の言葉を使うことを許すなら、真ん中は削れるけど)。Java使いの人たちが「Javaは値渡し!」と主張したがるのは、つまるところそういうことだろう*3

 逆にpythonやrubyのような「すべてがオブジェクト」な言語では、プリミティブ型やら何やらのことは考える必要はなく、また言語仕様の表面で参照の値(要するにアドレス)が見えてくる訳でもないので、『共有渡し』の方がすっきりすると思う。

  • 認知度とか言葉としての良し悪し

 なんか、どっちもどっちという気がする。
 単純な好みの問題だと思うけど強いて選ぶとすれば、国際的に(一応は)通用するであろうこと、言葉の良し悪しについて議論の余地が少ない*4ことから、共有渡しの方が筋は良さそう

 まあ、個人的には『共有渡し』の方がスッキリするし、好きです。でも、『参照の値渡し』が絶対に駄目かというと・・・難しいです。呼び方の問題は厄介。

まとめ

 ややこしくない(動作としてはよくわかる)けど、ややこしい(名前がうまく決められない)話だよね。

続き

 某所でこの問題が再燃していたので、続きを書いた。

www.haya-programming.com

*1:念のため書いておくと、「渡し」に対応するのは「Pass by」、「Call by」は「呼び出し」に対応するのだが、どちらにせよ意味は大して変わらないのでどちらで訳しても問題はない。この記事では日本語圏で一般的な「渡し」で統一している

*2:筆者はこれが何なのかはよくわからないが

*3:個人的には「Cみたいに明示的にアドレス渡す訳でもないのに値渡しって呼ぶのは逆説的でわかりづらいよ」と思うのだが、そのコストに勝るメリットがあるとする考えなのだろう

*4:というか議論になっていない程度に流行っていないだけかも・・・