2020-01-12

3: Why Python Variables Should Be Called 'Pointers'

<The previous article in this series | The table of contents of this series | The next article in this series>

Not admitting Python (and Java "reference type") variables to be pointers is quite unwise, and is a culprit for some needlessly jumbled discourses.

Topics


About: The Python programming language
About: The Java programming language

The table of contents of this article


Starting Context



Target Context


  • The reader will understand the differences between reference (reference-as-in-C++) and pointer ('address-in-general'-pointer) and will understand that Python (and Java so-called "reference type") variables should be called 'pointers'.

Orientation


There is an article that argues that terminology should not be arbitrary.


Main Body


1: I Declare That Any Python Variable Is a Pointer


Hypothesizer 7
Actually, any Python variable is a pointer, as any Java so-called "reference type" variable is.

I know that the prevalent terminology insists otherwise, but I claim that that terminology is very bad.

Do you say "Any terminology is arbitrary, and I have every right to define any term as I like."?

Well, I am talking about having better resources for the programmers community, and do you bring up the "right" to bull an arbitrarily bad terminology here, sir? . . . How regrettable.

Anyone may have a legal right to arbitrarily define any terminology, but not every such terminology can be acknowledged to be appropriate, I must say.

The problem of the page and the documents I have seen insist on the bad terminology is that they are not explaining why their terminology is an appropriate terminology: they are like "The term is just defined so. Accept it.". . . . How despotic.

This article will explain why my terminology is more appropriate. More specifically, it will expound on what the prevalent terminology is really claiming, the 2 requisites for any terminology to be appropriate, the critical distinction between reference and pointer, and how the prevalent terminology is not satisfying the requisites.

In fact, the inappropriateness of the prevalent terminology concerning Python variables and Java so-called "reference type" variables becomes apparent when '"pass-by-vale" or "pass-by-reference"' is the issue.


2: What the Prevalent Terminology Is Really Claiming


Hypothesizer 7
The prevalent terminology concerning Python variables and Java so-called "reference type" variables is using the term, 'reference', meaning a broad category that includes references-as-in-C++ and 'address-in-general'-pointers, and is using the term, 'pointer', meaning a narrower category that includes only 'physical-memory-address'-pointers.

According to the terminology, all the non-"value type" variables are references, and any variable cannot be called 'pointer' unless it can hold exactly a physical memory address, not an address in a more general sense.

Here, I cannot help but judge that the terminology is claiming that 'address-in-general'-pointers do not need to be given their own category, because otherwise, it should have given them the category!

In other words, it is claiming that 'address-in-general'-pointers do not need to be distinguished from references-as-in-C++.

We should notice that calling Python variables "references" is equivalent to saying "You don't need to know whether they are references-as-in-C++ or 'address-in-general'-pointers". . . . Let us take the fact to heart.


3: The 2 Qualifications for Appropriate Terminologies


Hypothesizer 7
Not all the terminologies are appropriate, and for any terminology to be appropriate, it has to satisfy at least 2 qualifications.

1st, it has to demarcate all the important concepts.

You know, that is the prime objective of the terminology, is it not?

If a terminology is enthusiastic in nit-pickingly differentiating between unimportant concepts while neglecting the prime task of differentiating between consequential concepts, that terminology will have to be deemed to be inappropriate.

2nd, it has to be consistent.

I mean, if a term means one thing at an appearance and means another at another appearance, the term is not being used consistently. . . . Certainly, the definition of any term is basically arbitrary (although misnomers are very inadvisable), but once the term is defined, it has to be always used in the single meaning, or the discourse will be confusing.

Some people may claim that the context will clarify the meaning, but unfortunately, the reality is proving that such a promise is quite commonly broken. That is because the context in the head of the communicator is almost always not exactly shared in the head of the receiver, which is quite natural (you know, as 2 different persons meet with different experiences, different knowledge, and different opinions, it is too optimistic to assume that the context can be easily shared in the 2 different heads). . . . So, the communicator is like "This should be clear!", but it is often clear only for the communicator.

On the other hand, having some multiple terms for a single concept is quite undesirable. You know, each receiver tries to discern the intention of why each term is used there (I think that to receive any discourse is such an act), and if some multiple terms are whimsically used meaning the same thing, that will be a betrayal to the trust between the communicator and the receiver. . . . In other words, a new term should not be coined for a concept that has been already represented by an existing term, because such an act deceptively implies that a new concept exists there.

In short, the set of terms has to have a one-to-one map to the set of concepts.

Someone may say "But words are not like that!". . . . I know that daily words are not like that, but I am claiming that at least, technical terms have to be rigorously defined. . . . You know, in daily conversations, you can resolve an ambiguous word by asking "Huh? What does that mean?", but in reading a document, you cannot usually have any opportunity to get answered.


4: The Differences Between Reference-As-in-C++ and 'Address-in-General'-Pointer Are Crucial, and Any Python or Java Programmer Has to Understand Them


Hypothesizer 7
The differences between reference-as-in-C++ and 'address-in-general'-pointer are crucial, and any Python or Java programmer has to understand them, although the prevalent terminology refuses to make the distinction.

That is because whether a variable is one or the other significantly changes the behaviors of the program.

Here, let me see the mechanism of each concept.


3-1: The Mechanism of Reference-As-in-C++, with a Diagram


Hypothesizer 7
Although I have already discussed what reference-as-in-C++ is, here, I will show it more succinctly with a diagram.

Let me think of a piece of C++ code.

@C++ Source Code
#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;
}

Let me caricature the memory like this.


In the diagram (and in the succeeding diagrams), the memory is caricatured like a spread sheet (not exactly accurate, of course, but being accurate would make the diagrams uselessly (for the purpose of this article) complex), with the address of any cell is depicted like 'A1', and any box on or over (as I call for any reference-as-in-C++) any cell is a variable.

In the 1st phase, there are the normal variables, 'l_stringA', 'l_stringB', 'l_stringC', 'l_testClassA', and 'l_testClassB' and the reference-as-in-C++ variables, 'l_testClassA.i_string' and 'l_testClassB.i_string'.

Note that each reference-as-in-C++ variable is defined over an already occupied cell, which is the essence of reference-as-in-C++. . . . This is an aside, but each reference-as-in-C++ variable must be depicted as an independent box because any reference-as-in-C++ is not any alias of any variable, but a new variable of its own right.

In the 2nd phase, there are added the reference-as-in-C++ variables, 'a_replacingString', 'a_testClassA', and 'a_testClassB', over the 3 already occupied cells, which is really what so-called "pass-by-reference" means.

In the 3rd phase, the contents of 'a_replacingString', which are the value of the 'C1' cell, which is "C", is set into 'a_testClassA.i_string', which is over the 'A1' cell, which means that the 'A1' cell achieves the value of "C".

In the 4th phase, the assignment operator works for 'a_testClassB' with the argument of 'a_testClassA', which (the assignment operator) sets the contents of 'a_testClassA.i_string', which is over the 'A1' cell, whose value is "C", into 'a_testClassB.i_string', which is over the 'B1' cell, which means that the 'B1' cell achieves the value of "C".

The output is this.

@Output
### 'l_testClassA': C, 'l_testClassB': C

The whole process is crystal clear.


3-2: The Mechanism of 'Address-in-General'-Pointer, with a Diagram


Hypothesizer 7
I will show what 'address-in-general'-pointer is, with a diagram.

Let me think of another piece of C++ code.

@C++ Source Code
#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;
}

In fact, 'address-in-general'-pointers in C++ are also 'physical-memory-address'-pointers, but that does not matter at all.

Let me caricature the memory like this.


In the 1st phase, there are the 'address-in-general'-pointers, 'l_stringA', 'l_stringB', 'l_stringC', 'l_testClassA', 'l_testClassB', 'l_testClassA.i_string', and 'l_testClassB.i_string'.

Note that each of the cells, 'A1', 'B1', 'C1', 'A4', and 'B4', has a value, but does not have any box on it, which means that there is no variable allocated there. Also note that any 'address-in-general'-pointer cell has a value like '=A1', which means that the value is an address, which is the essence of 'address-in-general'-pointer.

In the 2nd phase, there are added the 'address-in-general'-pointers, 'a_replacingString', 'a_testClassA', and 'a_testClassB', on the 3 so-far unoccupied cells, which achieve the passed data as the values, which is really what so-called "pass-by-value" means. . . . In this case, the values happen to be some addresses because the arguments are 'address-in-general'-pointers (the value of any 'address-in-general'-pointer is an address or null), but anyway, it is exactly "pass-by-value".

In the 3rd phase, the contents of 'a_replacingString', which are the value of the 'D1' cell, which is '=C1', is set into 'a_testClassA.i_string', which is on the 'A3' cell, which means that the 'A3' cell achieves the value of '=C1'.

In the 4th phase, the contents of 'a_testClassA', which are the value of the 'D2' cell, which is '=A4', is set into 'a_testClassB', which is on the 'D3' cell, which means that the 'D3' cell achieves the value of '=A4'.

It is obvious that 'l_testClassB.i_string' or its pointed datum in the 'B1' cell does not change in the 4th phase (they are not concerned with the value change of the 'D3' cell at all).

The output is this.

@Output
### 'l_testClassA': C, 'l_testClassB': B

The whole process is, again, crystal clear.


3-3: The Mechanism of Any Python (or Java So-Called "Reference Type") Variable


Hypothesizer 7
Now, let me study the mechanism of Python variable, via this piece of Python code.

@Python Source Code
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)

The memory caricature? Please refer to the above one of 'address-in-general'-pointer, because the memory caricature of Python variable is exactly the same with that of 'address-in-general'-pointer.

The ouput? Of course, the same.

The situation is exactly the same with Java so-called "reference type" variable.


5: Validating the Prevalent Terminology for Its Appropriateness


Hypothesizer 7
Now, let me validate the prevalent terminology for its appropriateness.

Of course, I have to check whether it satisfies the qualifications for appropriate terminologies.

Well, it does not seem to be demarcating the very important concepts of reference-as-in-C++ and 'address-in-general'-pointer, does it?

The broad definition of 'reference' is really saying that Python (or Java so-called "reference type") variable is a reference-as-in-C++, an 'address-in-general'-pointer, or whatever that is not any so-called "value type" variable, but it does not know exactly which . . .

. . . Such a vague categorization does not do, I say. I mean, it is not identifying the mechanism of variable, which (the mechanism) is consequential because it determines the behaviors of programs.

Certainly, a statement like "Any Python variable is a reference" may not be incorrect per se if one adopts the broad meaning of 'reference', but the statement is not helpful in explaining the behaviors of the variable.

On the other hand, it insists that any pointer has to be something that can hold a physical memory address, not an address in a more general sense, whose reasonability I do not understand at all. . . . Why does it have to be so? . . . I cannot help but feel that it is unsolicitedly putting in such a unreasonable, unbeneficial restriction on the term, 'pointer'. I mean, such a distinction does not matter at all for at least most programmers, while what matters is the mechanism depicted above that is common for all the 'address-in-general'-pointers. . . . In fact, I have never cared at all whether any C++ pointer is a 'physical-memory-address'-pointer or not, as whether any 'address-in-general'-pointer in the above 2nd C++ program is a 'physical-memory-address'-pointer or not does not matter at all for the behaviors of the program. . . . If one has to distinguish a 'physical-memory-address'-pointer from pointers in the general sense (although I suspect that such occasions are rare if any), we can just coin a term like 'hard pointer'.

It insists also that address arithmetic has to be allowed on any pointer. . . . Why does it has to be so? . . . Whether address arithmetic is allowed or not is about . . . whether address arithmetic is allowed or not (in other words, whether the programming language has some syntax that does address arithmetic), not about the essence of variable, which is mostly what it can hold and where it is allocated, in other words, the mechanism illustrated above.

Does the usage (or non-usage) of '.', '->', '*', '+', etc. on Python (or Java so-called "reference type") variables not look the same with that for C++ pointers? . . . That is just some differences of syntax, not about the essence of variables, as is explained more elaborately in a succeeding section.

So, does not the prevalent terminology fit the description, "is enthusiastic in nit-pickingly differentiating between unimportant concepts while neglecting the prime task of differentiating between consequential concepts"? I cannot help to think that it does perfectly.

Well, as for the 2nd qualification, the problem is that 'reference' had been used meaning reference-as-in-C++, not the broad meaning some people insist to use in, since far before Python or Java was born.

Not agree? But "pass-by-reference" is a prominent example. You know, "reference" in "pass-by-reference" means exactly reference-as-in-C++, not the broad meaning that includes whatever that is not any so-called "value type" variable.

So, as a result, 'reference' is meaning a reference-as-in-C++ at some appearances and whatever that is not so-called "value type" variable at the other appearances, which is what I call inconsistency.

Note that the Python terminology lives inside the wider terminology of the software community, and it cannot say that it does not care about a term like "pass-by-reference".

In fact, the question of '"pass-by-value" or "pass-by-reference"' is naturally asked also for Python, and the prevalent answers are jumbled because of the inconsistent terminology.

In fact, the true answer is quite clear if only Python variables are recognized as pointers, as will be explained in a succeeding section.


6: On Some Python Syntax That Might Feel Like Some Refutations of the Fact That Any Python Variable Is a Pointer


Hypothesizer 7
Someone might feel that some Python syntax is refuting the fact that any Python variable is a pointer. . . . If not, it is fine: this section can be just skipped by him or her.

Let me address such syntax I can think of.

1st, why does Python use '.' instead of '->' that C++ uses?

C++ uses '->' because it has to distinguish '->' from '.', while Python does not have to distinguish '->' from anything (because it cannot be but '->'), so, it has decided to use '.' in leu of C++ '->' because it is one character shorter.

2nd, why is any Python variable not defined with '*' as any C++ pointer is?

C++ uses '*' because otherwise, the variable will become a normal variable, while Python does not because the variable cannot be but a pointer either way. In fact, Python could have mandated using '*', but then, '*' would have to be put in for all the variable definitions, which seems quite futile exercise of fingers, so all the '*'s have been omitted altogether.

3rd, why does Python not have the C++ so-called materialization operator, '*'?

C++ uses the materialization operator because the datum pointed by an address is sometimes required instead of the address, while Python does not because the address is always expected.

For example, we write like 'print (l_originalVariableA.i_stringMemberA)', not like 'print (*(l_originalVariableA.i_stringMemberA))', while 'l_originalVariableA.i_stringMemberA' signifies an address, because the 'print' function expects an address. In fact, any function expects nothing but addresses because there is no such thing as a non-pointer argument.

4th, let me think of a piece of Python code.

@Python Source Code
l_integerA: int = 1

Does that look like putting an address, '1', not the address of the datum, '1', into the pointer? . . . It may look so, but that is putting the address of the datum, '1', all right. You know, just like that any Python variable is a pointer, any Python literal is pointer-like, which means that '1' really signifies the address of the datum, '1', not the datum itself.

5th, let me think of another piece of Python code.

@Python Source Code
l_integerA: int = 1
l_integerB: int = l_integerA + 2

Does that look like adding the address of the datum, '2', to the address of the datum, '1'? . . . Intuitively. it may look so, but in fact, the '+' operator (or any operator) is a syntax sugar of a function call (most C++ programmers should know that any C++ operator is really a function). I mean, 'l_integerA + 1' means '+ (l_integerA, 1)' (regard '+' as a function name) and returns the address of the new 'int' instance that contains the sum of the data pointed by the 2 arguments, namely '3'. . . . I agree that the syntax looks odd intuitively, but Python just has adopted such intuitive odd syntax.

As a summary, such syntax is so because of some reasons, and is not something that refutes the fact that any Python variable is a pointer. Understand that the syntax and the essence of variable are matters of 2 different layers of programming language.


7: A Political Reason


Hypothesizer 7
In fact, there is no necessity to so lengthily explain why Python and Java "reference type" variables should be called 'pointers', is it?

Technically speaking, it is almost absurd to insist that they should not be called so.

Perhaps, most people know that the real reason is political: the incorrect stigmas attached to pointers: "pointer is dangerous" and "pointer is difficult".

In fact, pointer itself is not dangerous at all, but unrestricted address arithmetic is. So, why do not they prohibit address arithmetic, while allowing pointers?, which, actually, they have done, which was quite good, but very badly, they incorrectly insisted that they got rid of pointers . . .

I want to ask whether we are engineers or politicians.

Any engineer should correct a erroneous discourse like "pointer is dangerous", instead of pandering to such a prejudice.

On the other hand, is pointer difficult? . . . Well, the essence of pointer, actually, quite familiarly appears in spread sheet, as shown in the above caricatures. Who does not understand the basic mechanism of spread sheet? . . . At least, any programmer will be able to understand it quite easily.

In my opinion, the knowledge of pointer is requisite for any programmer, and hiding what is requisite is not helping any programmer at all.


8: So, Does Python (or Java) Pass a Function Argument Value by "Pass-by-Value" or by "Pass-by-Reference"?


Hypothesizer 7
So, does Python pass a function argument value by "pass-by-value" or by "pass-by-reference"?

In fact, "pass-by-value" is a misnomer: either way, the value is passed; it is a matter of whether the value is passed by copying the value into the argument that is allocated at a so-far unoccupied slot or by creating the argument as a reference over the value; so, 'pass-by-copy' will be more appropriate, although I will reluctantly use "pass-by-value" here.

Anyway, actually, the answer has already been implicitly given above, but here, it will be given definitely.

Python passes any function argument value by nothing but pure "pass-by-value".

To explain more, any Python function argument is a pointer, whose value is an address (or 'None'), and an address (or 'None') is copied into the argument: exactly "pass-by-value".

I say, any funny new term (like "pass-by-reference-value" and "pass-by-object") should never be coined in order to represent the same old concept of "pass-by-value" (although adopting a more appropriate term like 'pass-by-copy' instead of the misnomer is welcome) because such a new term deceptively implies that there is a new concept there (refer to the 2nd qualification for appropriate terminologies).

The situation is exactly the same with any Java so-called "reference type" function argument.


9: The Conclusion and Beyond


Hypothesizer 7
Java and Python have unreasonably refused to call pointers 'pointers', adopting a broader definition of 'reference' and a narrower definition of 'pointer'.

You know, it is not that a terminology can adopt any definition arbitrarily: any terminology has to demarcate all the important concepts and be consistent, which the prevalent terminology is not satisfying.

The thing is, unless Python variable or Java "reference type" variable is understood as a pointer, not merely as a "reference" in the broader meaning, the behaviors of programs will not be understood correctly.

I suspect a prevalent intention of trying to conceal the concept of pointer, but it is not something that can be concealed.

I understand that the prevalent terminology is so because it is politically motivated, but engineers should speak like engineers, should not they?


References


<The previous article in this series | The table of contents of this series | The next article in this series>