2020年6月7日日曜日

3: なぜ、Python(およびJava「リファレンスタイプ」)変数は'ポインター'と呼ばれるべきなのか

<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>


動機


Python(およびJava「リファレンスタイプ」)変数が'ポインター'であることを認めないことは、とても不得策であり、一部の不必要に混乱した言説の元凶となっている。

話題


About: Pythonプログラミング言語
About: Javaプログラミング言語

この記事の目次


開始コンテキスト


  • 読者は、プログラミング一般に関する基本的な知識を持っている。

ターゲットコンテキスト



  • 読者は、リファレンス(C++におけるようなリファレンス)とポインター('広い意味におけるアドレス'ポインター)の違いを理解し、Python(およびJavaのいわゆる「リファレンスタイプ」)変数は'ポインター'と呼ばれるべきであることを理解する。

オリエンテーション


Hypothesizer 7
実のところ、Python変数はどれもポインターである、Javaのいわゆる「リファレンスタイプ」変数がどれもそうであるように。

お分かりのように、私は、'ポインター'という用語を、一部の人々が言い張っているよりも広い(そして、よりリーズナブルな)意味で使用している。

それらの人々は、Python変数やJavaのいわゆる「リファレンスタイプ」変数は'ポインター'ではなく、'リファレンス'と呼ばれなければならない、と言い張っているが、そのような用語体系は助けになっておらず有害である、と私は論じる。

はあ?用語の定義はどれも恣意的ではないのか?...基本的には、そうだ。それでは、ある用語体系を非難する奴は馬鹿げてるのではないか、なぜなら、誰がどんな用語体系を自分の好きなように恣意的に定義しても構わないのだから?...そうではない、実のところ、少なくとも、適切な用語体系というものを話題にしているのであれば。

どんな用語体系も、適切であるためには、少なくとも2つの要件を満たしていなければならず、それらを満たしていない用語体系は、責められてよいし、責められるべきだ。

実のところ、Python変数およびJavaのいわゆる「リファレンスタイプ」変数に関わる広く流布された用語体系の不適切さは、「値渡し」か「リファレンス渡し」かが問題となる際にあらわになる。


本体


1: Python変数およびJavaのいわゆる「リファレンスタイプ」変数に関わる広く流布された用語体系


Hypothesizer 7
Python変数およびJavaのいわゆる「リファレンスタイプ」変数に関わる広く流布された用語体系は、'リファレンス'という用語を、'C++におけるようなリファレンス'も'広い意味におけるアドレス'ポインターも含む、ある広いカテゴリを意味して使い、'ポインター'という用語を、'物理メモリアドレス'ポインター'しか含まない、ある狭いカテゴリを意味して使う。

その用語体系によれば、すべての非「バリュータイプ」変数は、リファレンスであり、厳密に物理メモリアドレスを(もっと広い意味におけるアドレスをではなく)格納できる変数でなければ、それは決して'ポインター'と呼ばれてはならない。

えーと、一部の人々は、そうした'リファレンス'および'ポインター'の定義を採用する揺るぎない権利が自分たちにはある、と 主張するかもしれないが、彼らの用語体系が全体として適切であるかどうかは、別問題だ。


2: 用語体系が適切であるための2つの要件


Hypothesizer 7
どんな用語体系も、適切であるためには、少なくとも2つの要件を満たしていなければならない。

第1に、それは、全ての重要なコンセプトを区分けしていなければならない。

だって、それが、用語体系の主要目的でしょう?

もしも、ある用語体系が、重要でないコンセプト間の不必要な区別をすることに熱中する一方で、とても重要なコンセプト間の区別をするという主要任務を怠っていれば、その用語体系は、不適切であるとみなされなければならないだろう。

第2に、それは、首尾一貫していなければならない。

というのは、もしも、ある用語がある箇所で登場してあるものを意味し、別の箇所で登場して別のものを意味するのであれば、その用語は、首尾一貫して使われていない。...確かに、どの用語も定義は基本的には恣意的であるが、その用語がひとたび定義されたら、それは、常にその単一の意味で使われなければならない、そうでなければ、話の受け手のの一部は混乱させられかねない。

コンテキストが意味を明らかにしてくれると主張する人々が一部にいるかもしれないが、そのような保証はない、なぜなら、その用語が使われるであろうあらゆるコンテキストを誰も予言できないのだから。それに、話の送り手によって意図されたコンテキストが、話の受け手によって本当に正確に共有されていることなど、ほとんどないのではなかろうか?

その一方、1つのコンセプトに対して複数の用語があるというのも、とても望ましくないことだ。話の受け手はそれぞれ、なぜ各用語がそこで使われているのかの意図を見分けようとするわけ(話を受けるというのはそういう行為だ、と私は考える)であり、いくつか複数の用語が同じものを意味して気まぐれに使われたら、それは、話の送り手と受け手の間の信頼に対する裏切りであろう。...言い換えると、既存の用語によって既に代表されているコンセプトに対して新しい用語が作られるべきではない、なぜなら、そのような行為は、新たなコンセプトがそこにある、と虚偽に示唆するから。

簡潔に言うと、用語の集合は、コンセプトの集合と1対1写像を持っていなければならない。


3: C++におけるようなリファレンスと広い意味におけるアドレスポインターの違いは極めて重要であり、それをどのPythonまたはJavaプログラマーも理解しなければならない


Hypothesizer 7
C++におけるようなリファレンスと広い意味におけるアドレスポインターの違いは極めて重要であり、それをどのPythonまたはJavaプログラマーも理解しなければならない。

その理由は、ある変数がその一方であるか他方であるかによって、そのプログラムの振る舞いが著しく変わってくることだ。

ここでは、各コンセプトのメカニズムを見てみよう。


3-1: C++におけるようなリファレンスのメカニズム、図付き


Hypothesizer 7
C++におけるようなリファレンスが何であるかを、私は既に論じたが、ここでは、それを、図を使って、もっと簡潔に示そう。

1片のC++コードを考えてみる。

@C++ ソースコード
#include <iostream>
#include <string>

class TestClass {
 public:
  ::std::string & i_string;
  TestClass (::std::string & a_string);
  virtual ~TestClass ();
  TestClass & operator= (const TestClass & a_TestClass);
};

TestClass::TestClass (::std::string & a_string): i_string (a_string) {
}

TestClass::~TestClass () {
}

TestClass & TestClass::operator= (const TestClass & a_TestClass) {
 i_string = a_TestClass.i_string;
 return *this;
}

class Test1Test {
 public:
  static int main (int const & a_argumentsNumber, char const * const a_arguments []);
};

void testFunction (TestClass & a_testClassA, TestClass & a_testClassB, ::std::string & a_replacingString) {
 // the phase 2
 a_testClassA.i_string = a_replacingString;
 // the phase 3
 a_testClassB = a_testClassA;
 // the phase 4
}
   
int Test1Test::main (int const & a_argumentsNumber, char const * const a_arguments []) {
 ::std::string l_stringA ("A");
 ::std::string l_stringB ("B");
 ::std::string l_stringC ("C");
 TestClass l_testClassA = TestClass (l_stringA);
 TestClass l_testClassB = TestClass (l_stringB);
 // the phase 1
 testFunction (l_testClassA, l_testClassB, l_stringC);
 ::std::cout << "### 'l_testClassA': " << l_testClassA.i_string << ", " << "'l_testClassB': " << l_testClassB.i_string << ::std::endl;
 return 0;
}

上記におけるメモリ状態を以下のようにカリカチュア化する。


上記の図(および以下の図たち)において、メモリ状態は、スプレッドシートのようにカリカチュア化されており(厳密に正確というわけではないが、正確にすると、図が無益(本記事の目的において)に複雑になってしまう)、セルのアドレスは'A1'のように表わされ、セル上のまたはセルに重ねられた(C++におけるようなリファレンスに対して私はそう形容する)ボックスは、変数である。

第1フェーズでは、通常変数、'l_stringA'、'l_stringB'、'l_stringC'、'l_testClassA'、'l_testClassB'、およびC++におけるようなリファレンス変数、'l_testClassA.i_string'、'l_testClassB.i_string'がある。

注目すべきは、C++におけるようなリファレンス変数はそれぞれ、既に占有されたセルに重ねられて定義されているということで、それがC++におけるようなリファレンスのエッセンスだ。...これは余談だが、C++におけるようなリファレンス変数はそれぞれ、独立したボックスとして表わされなければならない、なぜなら、C++におけるようなリファレンスはどれも、変数の別名などではなく、それ自体として、れっきとした新たな変数なのだから

第2フェーズでは、C++におけるようなリファレンス変数、'a_replacingString'、'a_testClassA'、'a_testClassB'が、既に占有された3つのセルに重ね られる形で追加されているが、それが、いわゆる「リファレンス渡し」が本当に意味することなのだ。

第3フェーズでは、'a_replacingString'のコンテンツ('C1'セルの値である)である"C"が、'a_testClassA.i_string'('A1'セルに重ねられている)にセットされているが、それは、'A1'セルが"C"という値を獲得するということだ。

第4フェーズでは、代入オペレーターが、'a_testClassB'に対して'a_testClassA'という引数で働き、それ(その代入オペレーター)が、'a_testClassA.i_string'('A1'セルに重ねられており、値が"C"である)のコンテンツを'a_testClassB.i_string'('B1'セルに重ねられている)にセットするが、それは、'B1'セルが"C"という値を獲得するということだ。

アウトプットは以下のとおりだ。

@出力
### 'l_testClassA': C, 'l_testClassB': C

プロセス全体が水晶のように透明だ。


3-2: 広い意味におけるアドレスポインターのメカニズム、図付き


Hypothesizer 7
広い意味におけるアドレスポインターとは何かを、図付きで示そう。

また別の1片のC++コードを考えてみる。

@C++ ソースコード
#include <iostream>
#include <string>

class TestClass {
 public:
  ::std::string * i_string;
  TestClass (::std::string * a_string);
  virtual ~TestClass ();
  TestClass & operator= (const TestClass & a_TestClass);
};

TestClass::TestClass (::std::string * a_string): i_string (a_string) {
}

TestClass::~TestClass () {
}

TestClass & TestClass::operator= (const TestClass & a_TestClass) {
 i_string = a_TestClass.i_string;
 return *this;
}

class Test1Test {
 public:
  static int main (int const & a_argumentsNumber, char const * const a_arguments []);
};

void testFunction (TestClass * a_testClassA, TestClass * a_testClassB, ::std::string * a_replacingString) {
 // the phase 2
 a_testClassA->i_string = a_replacingString;
 // the phase 3
 a_testClassB = a_testClassA;
 // the phase 4
}

int Test1Test::main (int const & a_argumentsNumber, char const * const a_arguments []) {
 ::std::string * l_stringA = new ::std::string ("A");
 ::std::string * l_stringB = new ::std::string ("B");
 ::std::string * l_stringC = new ::std::string ("C");
 TestClass * l_testClassA = new TestClass (l_stringA);
 TestClass * l_testClassB = new TestClass (l_stringB);
 // the phase 1
 testFunction (l_testClassA, l_testClassB, l_stringC);
 ::std::cout << "### 'l_testClassA': " << *(l_testClassA->i_string) << ", " << "'l_testClassB': " << *(l_testClassB->i_string) << ::std::endl;
 delete l_testClassA;
 delete l_testClassB;
 delete l_stringA;
 delete l_stringB;
 delete l_stringC;
 return 0;
}

実のところ、C++における広い意味におけるアドレスポインターは、物理メモリアドレスポインターでもあるのだが、それは全然どうでもいいことだ。

上記におけるメモリ状態を以下のようにカリカチュア化する。


第1フェーズでは、広い意味におけるアドレスポインター、'l_stringA'、'l_stringB'、'l_stringC'、'l_testClassA'、'l_testClassB'、'l_testClassA.i_string'、'l_testClassB.i_string'がある。

注目すべきは、セル、'A1'、'B1'、'C1'、'A4'、'B4'のそれぞれが値を持っているが、その上にボックスはないということで、それは、そこには何の変数も割り当てられていないことを意味している。もう1つ注目すべきは、広い意味におけるアドレスポインターはそれぞれ、'=A1'のような値を持っていることで、それは、その値がアドレスであることを意味しており、それが、広い意味におけるアドレスポインターのエッセンスだ。

第2フェーズでは、広い意味におけるアドレスポインター、'a_replacingString'、'a_testClassA'、'a_testClassB'が、それまで占有されていなかった3つのセル上に追加されて、それらが、渡されたデータを値として獲得するが、それが、いわゆる「値渡し」が本当に意味することだ。...本ケースでは、引数たちが広い意味におけるアドレスポインターだから、それらの値がたまたまアドレスである(広い意味におけるアドレスポインターの値はアドレスまたはnullである)のだが、そういうことに関係なく、それは、まさしく「値渡し」である。

第3フェーズでは、'a_replacingString'のコンテンツ('D1'セルの値であり、'=C1'である)が、'a_testClassA.i_string'('A3'セル上にある)にセットされるが、それは、'A3'セルが'=C1'という値を獲得することを意味する。

第4フェーズでは、'a_testClassA'のコンテンツ('D2'セルの値であり、'=A4'である)が、'a_testClassB'('D3'セル上にある)にセットされるが、それは、'D3'セルが'=A4'という値を獲得することを意味する。

'l_testClassB.i_string'も、それに指される'B1'セル内のデータも、第4フェーズにおいて変化しない(それらは、'D3'セルの値の変化などに全く関知しない)ことは、明白に分かる。

アウトプットは以下のとおりだ。

@出力
### 'l_testClassA': C, 'l_testClassB': B

プロセス全体が、ここでも、水晶のように透明だ。


3-3: Python(または、Javaのいわゆる"リファレンスタイプ")変数のメカニズム


Hypothesizer 7
それでは、Pyhon変数のメカニズムを吟味してみよう、以下の1片のPythonコードを通して。

@Python ソースコード
import sys
from typing import List

class TestClass:
 def __init__ (a_this: "TestClass", a_string: str) -> None:
  a_this.i_string: str
  
  a_this.i_string = a_string

class Test1Test:
 @staticmethod
 def main (a_arguments: List [str]) -> None:
  l_stringA: str = "A"
  l_stringB: str = "B"
  l_stringC: str = "C"
  l_testClassA: "TestClass" = TestClass (l_stringA)
  l_testClassB: "TestClass" = TestClass (l_stringB)
  # the phase 1
  testFunction (l_testClassA, l_testClassB, l_stringC)
  sys.stdout.write ("### 'l_testClassA': " + l_testClassA.i_string + ", " + "'l_testClassB': " + l_testClassB.i_string + "\n")

def testFunction (a_testClassA: "TestClass", a_testClassB: "TestClass", a_replacingString: str) -> None:
 # the phase 2
 a_testClassA.i_string = a_replacingString
 # the phase 3
 a_testClassB = a_testClassA
 # the phase 4

if __name__ == "__main__":
 Test1Test.main (sys.argv)

メモリ状態のカリカチュア?上掲の、広い意味におけるアドレスポインターのものを参照してください、なぜなら、Python変数のメモリ状態カリカチュアは、広い意味におけるアドレスポインターのそれと全く同じだから 。

アウトプット?勿論、同じです。

事情は、Javaのいわゆる「リファレンスタイプ」変数においても全く同じだ。


4: 広く流布している用語体系の適切さを検証する


Hypothesizer 7
それでは、広く流布している用語体系の適切さを検証しよう。

勿論、私は、それが、適切な用語体系の資格を満たしているか否かチェックしなければならない。

えーと、それは、C++におけるようなリファレンスと広い意味におけるポインターというとても重要なコンセプトを区分けしているようには思われない。

'リファレンス'のその広い定義が本当に言っているのは、Pythonの(またはJavaのいわゆる「リファレンスタイプ」の)変数は、C++におけるようなリファレンスか、もしくは広い意味におけるアドレスポインターか、もしくはいわゆる「バリュータイプ」変数ではない何らかのものであるが、実際にその内のどれであるかは知らない...、ということだ。

...そんなぼんやりしたカテゴリー分けでは済まないんですよ、と私は言う。だって、それは、変数のメカニズムを特定していないが、そのメカニズムが重要なわけ、それがプログラムの振る舞いを決定するのだから。

確かに、「Pythonの変数はどれもリファレンスだ」のような言明は、'リファレンス'のその広い意味を採用する限りは、それ自体として間違っているわけではないかもしれないが、その言明は、変数の振る舞いを説明する役に立っていない。

他方で、その定義は、ポインターはすべからく物理メモリアドレス(もっと広い意味におけるアドレスではなく)を保持できるものでなければならない、と言い張っているが、その合理性を私は全然理解しない。...なぜ、そうでなければならないのか?...'ポインター'という用語に、そのように非合理で無益な制限を、おせっかいにかけているように感じざるをえない。だって、そのような区別は、少なくともほとんどのプログラマーにとって、全然どうでもよいことであり、重要なのは、広い意味におけるアドレスポインター全てに共通な上記のメカニズムだ。...実際、C++ポインターが物理メモリアドレスポインターであるか否かなど、私にとって重要だったことは決してない、だって、上記2番目のプログラムにおける広い意味におけるアドレスポインターが物理メモリアドレスポインターか否かなど、そのプログラムの振る舞いに全然関係ないのだから。...もしも、ある物理メモリアドレスポインターを広い意味におけるポインターから区別しなければならないことがあるのであれば(そのような機会は、あったとしても稀だろうと私は推測するが)、'ハードポインター'のような用語を作ればよいだけだ。

その定義は、ポインターにはすべからくアドレス操作が許されなければならない、とも言い張っている。...なぜ、そうでなければならないのでしょうか?...アドレス操作が許されるか否かは、...アドレス操作が許されるか否か(言い換えると、そのプログラミング言語が、アドレス操作を行なうシンタックスを持っているか)についての話であって、変数のエッセンス(おおまかに言って、それが何を保持できるかおよびそれがどこにアロケートされるか、言い換えると、上記に図解されたメカニズム)についての話ではない。

Python(またはJavaのいわゆる「リファレンスタイプ」)変数に対する'.'、'->'、'*'、'+'等の用法(または不用法)が、C++ポインターに対するそれと同じに見えない?...それは、シンタックスの違いにすぎないのであって、変数のエッセンスについての話ではない(後続のセクションででもっと詳細に説明されるように)。.

それでは、広く流布している用語体系は、「重要でないコンセプト間の不必要な区別をすることに熱中する一方で、とても重要なコンセプト間の区別をするという主要任務を怠ってい」るという形容に当てはまっているだろうか?完全に当てはまっていると私は考えざるをえない。

えーと、第2の資格について言えば、問題は、「リファレンス」は、C++におけるようなリファレンスという意味で使われてきた、一部の人々が使うべきだと言い張っている広い意味にではなく、PythonやJavaが誕生するずっと以前から、ということだ。

同意しませんか?しかし、「リファレンス渡し」が顕著な例だ。ご存知のように、「リファレンス渡し」における「リファレンス」は、まさに、C++におけるようなリファレンス意味している、いわゆる「バリュータイプ」変数ではない何ものをも包含する広い意味ではなく。

したがって、結果として、'リファレンス'は、ある出現では、C++におけるようなリファレンス、を意味し、他の出現では、いわゆる「バリュータイプ」ではない何ものか、を意味するということになってしまっており、それが、私が、首尾一貫性の欠如と呼ぶものだ。

注意すべきは、Pythonの用語体系は、ソフトウェアコミュニティ全体のより広範囲の用語体系の中に生きているのであって、「リファレンス渡し」のような用語には我関せずとは言えないということだ。

実際、「値渡し」か「リファレンス渡し」かという疑問は、Pythonに対しても当然発せられるのであり、広く流布している答えがぐちゃぐちゃなのは、首尾一貫していない用語体系のせいである。

実は、正しい答えは、極めて明快である、Python変数がポインターとして認識されてさえいれば、後続のあるセクションで説明されるとおり。


5: Python変数はどれもポインターだという事実に対する反証だと感じられるかもしれない一部のPythonシンタックスについて


Hypothesizer 7
一部のPythonシンタックスが、Python変数はどれもポインターであるという事実の反証になっている、と感じる人がいるかもしれない。...もしも、そうでなければ、それは結構だ: 本セクションは、ただ読みとばせばよい。

私が考えつく限りのそうしたシンタックスを論じてみよう。

第1に、なぜ、Pythonは、C++が使う'->'でなく、'.'を使うのか?

C++が'->'を使う理由は、'->'を'.'から区別しなければならないからだ、その一方、Pythonは、'->'を何からも区別する必要がなく(なぜなら、'->'しか指定できないから)、したがって、'.'をC++の'->'の代わりに使うことに決めた、なぜなら、そのほうが1文字分短いから。

第2に、なぜ、Python変数は、'*' を用いて定義されないのか、C++のポインターとは違って?

C++が'*'を使うのは、さもなければ、その変数が通常変数になってしまうからであり、その一方、Pythonがそうしないのは、その変数は、どのみちポインターでしかありえないからだ。実際、Pythonは、'*'を使うことを義務付けることもできたが、そうしていたら、'*'を全ての変数定義に付けないといけないことになっていただろうが、それは、とても無駄な指の運動に思えるので、全ての'*'が全部省略されたわけ。

第3に、なぜ、Pythonは、C++のいわゆる実体化オペレーターである'*'を持たないのか?

C++が実体化オペレーターを用いる理由は、アドレス自体ではなくアドレスによって指されるデータが場合により要求されるからであり、その一方、Pythonがそうしない理由は、アドレス自体が常に求められるからだ。

例えば、我々は、'print (l_originalVariableA.i_stringMemberA)'のように書き、'print (*(l_originalVariableA.i_stringMemberA))'のようには書かない('l_originalVariableA.i_stringMemberA'はアドレスを表わしている)が、それは、'print'ファンクションがアドレスを求めるからだ。実際には、どのファンクションも、アドレスしか求めない、なぜなら、ポインターでない引数などというものは存在しないから。

第4に、ある1片のPythonコードを考えてみよう。

@Python ソースコード
l_integerA: int = 1

それは、そのポインターへ'1'というアドレスを入れているように見えるだろうか、'1'というデータのアドレスをではなく?...そのように見えるかもしれないが、それは、問題なく、'1'というデータのアドレスを入れているのだ。お分かりのように、Python変数がどれもポインターであるように、Pythonリテラルのどれもポインターライクなのだ('1'は、本当は、'1'というデータのアドレスを表わしている、そのデータそのものではなく、ということ)。

第5に、別の1片のPythonコードを考えてみよう。

@Python ソースコード
l_integerA: int = 1
l_integerB: int = l_integerA + 2

それは、'2'というデータのアドレスを'1'というデータのアドレスに加えているように見えるだろうか?...直感的に言えば、そう見えるかもしれないが、実際には、'+'オペレータは(またはどのオペレータも)、あるファンクションコールのシンタックスシュガーなのだ(ほとんどのC++プログラマーは、C++オペレータは実際にはどれもファンクションであることを知っているだろう)。つまり、'l_integerA + 1'は、'+ (l_integerA, 1)'を意味しており('+'は、ファンクション名だとみなしてください)、2つの引数によって指されるデータの和(つまり、'3')を保持する新たな'int'インスタンスのアドレスをリターンするのだ。...そのシンタックスは直感的に変に見えるということには賛成だが、Pythonがそういう直感的に変なシンタックスを選択したというだけのことだ。

要約すると、それらのシンタックスがそのようになっているのは、いくつかの理由があってのことであり、Python変数がどれもポインターであるという事実の反証となるものではない。シンタックスと変数のエッセンスは、プログラミング言語の異なる2つのレイヤーに属する事項であることを理解する必要がある。


6: ある政治的な理由


Hypothesizer 7
実のところ、こんなに長々と、なぜ、PythonおよびJava「リファレンスタイプ」変数たちがポインターと呼ばれるべきであるかを説明する必要などないのではないだろうか?

技術的に言えば、それらがそう呼ばれるべきでないと主張するなど、ほとんど馬鹿げている。

多分、ほとんどの人々は、真の理由は政治的なものだと知っているだろう: ポインターに付けられた正しくない汚名: 「ポインターは危険」および「ポインターは難しい」だ。

実のところ、ポインターそのものは全然危険ではなく、制限されないアドレス操作が危険なのだ。それでは、なぜ、アドレス操作を禁止しないのか、ポインターを許可したまま?それを、実際に、彼らは行なったのであり、それは、とてもよ良かったのだが、とてもまずいことに、彼らは、ポインターを駆逐したと不正確に主張してしまったのだ...

我々は技術者なのか政治家なのか、と私は問いたい。

技術者であるならば、「ポインターは危険だ」のような誤った言説を訂正すべきだ、そのような偏見におもねるのではなく。

他方で、ポインターは難しいのか?...えーと、ポインターのエッセンスは、実際には、スプレッドシートにとても馴染み深く出現するものであり、それは、上記カリカチュアで示したとおりだ。スプレッドシートの基本メカニズムを誰が理解できないのか?...少なくとも、プログラマーであれば、誰でもそれを極めて容易に理解できるだろう。

私の意見では、ポインターの知識は、どのプログラマーにも必須であり、必須なものを隠そうとすることは、なんらのプログラマーの助けにも全然なっていない。


7: それでは、Python(またはJava)は、ファンクション引数値を、"値渡し"で渡すのか、それとも、"リファレンス渡し"で渡すのか?


Hypothesizer 7
それでは、Pythonは、ファンクション引数値を、「値渡し」で渡すのか、それとも、「リファレンス渡し」で渡すのか?

実際には、「値渡し」は実体とかけ離れた名前だ: どちらにしろ、値が渡される。本当は、値が、その値を、それまで占有されていなかったスロットにアロケートされた引数にコピーすることで渡されるのか、それとも、引数を、その値の上にかぶせたリファレンスとして作成することで渡されるのか、という問題だ。したがって、'コピー渡し'がより適切だろうが、ここでは、「値渡し」をしぶしぶ使うことにする。

どちらにせよ、実際には、答えは、既に上記で暗黙に与えられているが、ここでは、それを明確に与えよう。

Pythonがファンクション引数値を渡すのは、純粋な「値渡し」以外の何ものでもない。

もっと説明すると、Pythonのファンクション引数はどれもポインターであり、その値はアドレス(または'None')であり、アドレス(または'None')がその引数にコピーされる: 「値渡し」そのものだ。

「値渡し」という古くからある変わらぬコンセプトを表わすために、おかしな新規な用語(「リファレンス値渡し」や「オブジェクト渡し」のような)は決して作られるべきでない(実体とかけ離れた名前の代わりに'コピー渡し'といったより適切な用語を採用するのは歓迎するが)、なぜなら、そのような新たな用語は、そこに新たなコンセプトがあるかのように誤って示唆するから(適切な用語体系の第2の資格を参照のこと) と私は言う。

事情は、Javaのいわゆる「リファレンスタイプ」ファンクション引数についても、まったく同じだ。


8: 結びとその先


Hypothesizer 7
JavaおよびPythonは、不合理に、ポインターを'ポインター'と呼ぶことを拒否してきた。'リファレンス'のより広い定義および'ポインター'より狭い定義採用して。

ある用語体系はどんな定義でも恣意的に採用できるというわけではないんですよ: どんな用語体系であろうと、全ての重要なコンセプトを区別し、首尾一貫していることは求められるわけであり、それを広く流布している用語体系は満たしていない。

問題は、Python変数やJavaの「リファレンスタイプ」変数が、単により広い意味における「リファレンス」としてではなく、ポインターとして理解されなければ、諸プログラムの振る舞いは正しく理解されないということだ。

ポインターのコンセプトを隠そうとする意図が偏在しているように私は推測しているが、それは、隠しうるものではない。

広く流布している用語体系が今のようであるのは、政治的な動機によるものだと私は理解しているが、技術者は技術者のように話すべきではないだろうか?


参考資料


<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>