「値渡しか参照渡しか」という二分論は混乱の元

先日のプログラミングシンポジウムでは「今の時代となってはむしろ有害な教育って何だろう」という話題が出た。その時には思いつかなかったけども、今日「Pythonはすべてが参照だっていうけど、boolとか参照渡しじゃないよね」という趣旨の発言を見て思いついた。

Pythonではすべてが参照」ってのは「Pythonの変数はCで言うと全部PyObject*」って言ってるだけのことなので、関数呼び出しが参照渡しかどうかとは独立な話。そしてPythonの関数呼び出しは値渡しです。渡される値がポインタなのでたぐれるだけ。
参照渡しと値渡しの2通りに分類できるかのような教え方をする教科書は現代ではむしろ有害なんじゃないの。今一番メジャーな渡し方は「参照の値渡し」なんだし。古い用語を使い続けることで混乱が増すだけのような。

そうつぶやいていたら@kuenishiから

今思えばあり得ない教育: Javaで値渡しと参照渡しを説明された講義
ポインタとか参照とか分からないのでちゃんとアドレス渡しとかコピー渡しと説明してください
ポインタっていうのは仕様を決めるために作った抽象的な言葉であって、初学者には邪魔というか有害な概念ですらある

という指摘があったので、まあそれもそうだなー誰かそういう説明を書けばいいのに、と思うなどした。

気が向いたら説明を書く。


追記。まず世の中には変数の代入のイメージが良くわからない人もいるようなので、変数が生まれる前から順番に考えてみよう。コンピュータの中には値をいくつも記録しておくことのできる装置が入っていて、コンピュータに例えば42っていう値を覚えておいてもらおうと思ったら、適当に空いている例えば102番目の記憶領域に42って書きこむってことになる。

でもこれでプログラムを書こうと思ったら「102番目の値を引数にして598番目から書かれてる関数を呼び出して、結果を104番目に入れて…」なんて覚えにくい数字がたくさん出てきて頭が混乱する。だから102番のことをxって呼べるような言語を作ろう、ってわけで変数ができた。「xって名前を使いたい」って書いたら、内部で空いている記憶領域を探してくれて、それが102番目だったら「xは102番目だ」って情報をシステムが覚えていてくれて、その後「x」って名前を使うと自動的に「102番目」に変換してくれる。

それで、じゃあある関数int f1(int y){...}をf1(x);と呼び出したら何が起きるべきか?

(この括弧は読まなくてもいい: ここで言っているのはyを仮引数とする関数f1を、実引数をxとして呼び出したらどうなるかという話)

ここで設計上の判断がわかれる。一つの方法は「102番目」という場所の情報を渡して、yも102番目を指すようにするやりかた。もうひとつの方法は、「42」という値の情報を渡して、新しい空いている領域104番目にそれをコピーして、104番をyと呼ぶことにするやり方。

前者だと関数f1の中でyを書き換えたら、呼び出し元のxの値も変わる。後者だとコピーした104番の値が変わるだけなので、呼び出し元のx(102番)の値は変わらない。

(この括弧は読まなくてもいい: 要するに前者は左辺値を渡していて、いわゆる参照渡し。後者は右辺値を渡していていわゆる値渡し。歴史的にはFortranが前者でCが後者を選択した。他にも名前渡しなどのいろいろな方法が考案されたが割愛)

で、いま42とか書いていた場所に「202」って書いて「これは202番目って意味ね」という使い方をするプログラミングテクニックがあるんだ。こういう「場所」のデータが書かれた記憶領域を、さっきの渡し方の後者、コピーして新しい変数を作るタイプで渡したとしよう。そうすると、コピーされたとしても「202」を指しているって点では変わらない。だからy(104番目)を書き換えてもxに影響はないが、yの内容を1回たどってからその先(202番目)を書き換えたら、当然xからたどった先(202番目)も影響を受ける。

(ここは読まなくてもいい: 要するにポインタだとか参照だとか呼ばれるもののことね。C言語で表現するなら「y = 84」ってやったら、104番目の値が変わるだけなので、呼び出し元のx(102番目)は影響を受けない。1段階たどって「*y = 84」ってやったら、202番目の値が変わるので、呼び出し元xから1段階たどった「*x」の値は84になる。)