> It violates the indiscernibility of identicals, which is one of the cornerstones of Western logic.
Oh, please. Python isn't violating any principles of Western logic. It's only violating your idiosyncratic insistence that there can only be one in-memory representation of any immutable value. Python does this for some types but not for others. Yet computers manage to run Python just fine, Western logic notwithstanding.
If you want to argue that Python ought to change its implementation to guarantee that, for example, any two references to the tuple (1, 2, 3) must refer to the same in-memory representation (so the 'is' operator would always return True), because that would save memory, or make the runtime faster, or whatever, that's fine. Then we can talk about the tradeoffs involved, such as increasing complexity in the interpreter code. But trying to claim that Python is violating "one of the cornerstones of Western logic" is just too much.
> It's only violating your idiosyncratic insistence that there can only be one in-memory representation of any immutable value.
I never said this! There can be multiple representations of the same value. For example, the same ordered set may be represented as two distinct red-black trees, balanced differently.
Values are different from their memory representations. Different memory representations of the same value are okay. Branching on the difference is not. Of course, to hide representation differences from users, you need abstract data types [0], and, sadly, Python doesn't have these.
> If you want to argue that Python ought to change its implementation to guarantee that, for example, any two references to the tuple (1, 2, 3) must refer to the same in-memory representation (so the 'is' operator would always return True), because that would save memory, or make the runtime faster, or whatever, that's fine.
I never said this either. I said that it should be a valid optimization, not that implementations absolutely have to do it. As things stand now, it's not a valid optimization, because it would break existing programs.
In actuality, I don't care much about the optimization itself. However, it's a good benchmark for assessing whether Python has compound values. Values can be deduplicated, because they may be represented more than once. Objects can't be deduplicated, because by definition an object exists exactly once in memory.
> But trying to claim that Python is violating "one of the cornerstones of Western logic" is just too much.
It seems appropriate to me. But, to be clear, what I was claiming violates the indiscernibility of identicals is dragonwriter's explanation, not Python itself. A simple explanation that doesn't violate the indiscernibility of identicals is to admit that Python doesn't have compound values. Which takes us back to square one [1].
As Rhett Butler said to Scarlett O'Hara, you gave a very good imitation.
> Different memory representations of the same value are okay. Branching on the difference is not.
Branching on a difference other than a difference in logical identity, if you are intending to test for logical identity, is obviously an error. The fix for that in Python is simple: if you want to test for logical identity, use the == operator.
Branching on a difference other than a difference in logical identity, if you're interested in a difference other than a difference in logical identity, is perfectly reasonable. Maybe you've never had occasion to do that in programs you write, but others might.
> I said that it should be a valid optimization
Consider my comment suitably modified. The rest of what I said still stands: there are tradeoffs involved, and reasonable people can differ on where the tradeoff ends up. You have one opinion; the Python developers have another. That doesn't mean the Python developers are violating Western logic.
> what I was claiming violates the indiscernibility of identicals is dragonwriter's explanation, not Python itself. A simple explanation that doesn't violate the indiscernibility of identicals is to admit that Python doesn't have compound values.
And to me this is a distinction without a difference. Nothing is stopping you from defining "compound values" so that a Python tuple isn't a compound value. But nothing is stopping the rest of us from not caring about your definition. Which, as you say, takes us back to square one.
What would be helpful is if you could give some reason why your definition of compound values is so important, other than talking about identity of indiscernibles and violating Western logic. What makes a language that allows compound values, by your definition, better than a language that doesn't? And if your only answer (other than preserving Western logic) is "better runtime efficiency" (less memory, faster operations), then it seems to me you would do much better to make that argument directly, instead of cloaking it with all this talk about "compound values". But maybe that's just me.
> Branching on a difference other than a difference in logical identity, if you're interested in a difference other than a difference in logical identity, is perfectly reasonable.
It's unreasonable to have a finer-grained distinction than logical identity. If two things are the same, they are the same. If they are not, well, they are not. Again, tautologies!
> That doesn't mean the Python developers are violating Western logic.
I never said Python developers are violating Western logic. I said dragonwriter's explanation of how so-called “compound values” work in Python is inconsistent with Western logic. I have an explanation of how Python works that's consistent with it, but it requires rejecting the assumption that Python has compound values.
> Nothing is stopping you from defining "compound values" so that a Python tuple isn't a compound value.
The definition of compound value is simple: a value that you can tear apart into its constituent parts, then reassemble, getting the original value. You can't get the original tuple object by creating a new tuple object with the original tuple's components. The `is` operator will brush the difference on your face.
> What makes a language that allows compound values, by your definition, better than a language that doesn't?
The easiest way to reason about programs that's completely rigorous (i.e., not just testing on a couple sample inputs) is equational reasoning, which is basically showing that two syntactically different expressions evaluate to the same value. (For example, one expression might be an obviously correct but unacceptably inefficient program, and the other expression might be the program you actually intend to deliver.) In practice, realistic problem domain have to be modeled using compound entities (values or objects), so if you want to use equational reasoning, you need compound values.
> It's unreasonable to have a finer-grained distinction than logical identity.
Maybe to you. Not to me. And not, I suspect, to most of the other programmers in this discussion.
> The definition of compound value is simple
Your definition. Why should I care? See below.
> if you want to use equational reasoning, you need compound values.
Ok, so this would mean that, according to you, it is impossible to use equational reasoning with Python programs. Whereas, according to me, it is only impossible to do this with Python programs that use mutable objects (where "mutable" here includes tuples whose slots point to mutable objects). And of course this wouldn't just apply to Python; the general distinction here could be drawn, in principle, in any language. Or even independently of choosing a language.
So I have another tradeoff here: I can use mutable objects, which can make programs easier to write, but then I can't reason rigorously about them; or I can restrict myself to immutable objects (which means that any time I would have mutated an object writing programs the other way, I instead have to construct a new immutable object with the same logical properties that the mutated object would have had), which makes programs harder to write, but allows me to reason rigorously about them.
Can you show me any real-world examples where using the latter programming style has paid dividends?
> Ok, so this would mean that, according to you, it is impossible to use equational reasoning with Python programs.
You can use equational reasoning on the values that Python actually gives you: small numbers, special constants and object references.
> Whereas, according to me, it is only impossible to do this with Python programs that use mutable objects (where "mutable" here includes tuples whose slots point to mutable objects).
You can in principle use equational reasoning on programs that manipulate mutable objects. What you can't do is treat two distinct objects as equal. But, of course, in practice, you can do whatever you want. Whether your reasoning is sound is a-whole-nother matter.
> I instead have to construct a new immutable object with the same logical properties that the mutated object would have had), which makes programs harder to write, but allows me to reason rigorously about them.
No, this would be wrong. You can't use equational reasoning on objects themselves. You can only use equational reasoning on values. The value itself might be a program that manipulates objects, but you can't equate two distinct objects.
> You can't use equational reasoning on objects themselves.
I didn't say you could. I said you could use equational reasoning on programs that only manipulate immutable objects. "Immutable" means that the object's value can never change, so you can substitute the object's value for the object itself everywhere it appears in the program. Then you can apply equational reasoning to the values so obtained.
In the particular case I described, you would end up using equational reasoning on the value transformation implemented by the code that constructs the new immutable object. You would obtain that transformation, as above, by substituting object values for objects in the syntactic statement of the code.
> You can in principle use equational reasoning on programs that manipulate mutable objects.
How do you reason using an object's value if that value can change?
> "Immutable" means that the object's value can never change, so you can substitute the object's value for the object itself everywhere it appears in the program.
No, you can't. Immutable objects still have physical identities. If you want to do equational reasoning, you need to get a hold of the value itself.
> How do you reason using an object's value if that value can change?
Objects don't have “values”, they have “states”. And I said you can reason about programs that manipulate objects, not about the states of these objects. (Though maybe in a few special cases you can do the latter too.)
Is there some kind of reference that explains the terminology and theory you're using? Because it makes no sense to me as you're explaining it. It would be nice if such a reference also gave some real world examples where the terminology and theory you're using actually pays dividends, as I asked before.
(1) “Most languages use a call by value [evaluation] strategy, in which only outermost redexes are reduced and where a redex is reduced only when its right-hand side has already been reduced to a value - a term that has finished computing and cannot be reduced any further.” (Types and Programming Languages, p. 57)
(0) “In computer science, an object can be a variable, a data structure, or a function or a method, and as such, is a location in memory having a value and possibly referenced by an identifier.” ( https://en.wikipedia.org/wiki/Object_(computer_science) ) In some languages, object states aren't values, though.
(1) “object: a cell (unless otherwise explicitly stated)” (p. 325), “cell: a number of contiguous memory fields forming a single logical structure” (Garbage Collection: Algorithms for Automatic Dynamic Memory Management, p. 322)
> It would be nice if such a reference also gave some real world examples where the terminology and theory you're using actually pays dividends, as I asked before.
Compiler authors take advantage of the notion of value all the time. For example, If two expressions are guaranteed to evaluate to the same value, a compiler may emit code that evaluates the expression once, then reuses the result twice.
---
Sorry, I can't reply to you directly because I'm “submitting too fast”, but:
These blog posts show how to do equational reasoning on Haskell programs. (Technically, the subset of Haskell that doesn't contain nonproductive infinite loops.)
Although Haskell is particularly well suited for using equational reasoning, it can also be used in other languages, provided your program primarily manipulates values, rather than objects.
Oh, please. Python isn't violating any principles of Western logic. It's only violating your idiosyncratic insistence that there can only be one in-memory representation of any immutable value. Python does this for some types but not for others. Yet computers manage to run Python just fine, Western logic notwithstanding.
If you want to argue that Python ought to change its implementation to guarantee that, for example, any two references to the tuple (1, 2, 3) must refer to the same in-memory representation (so the 'is' operator would always return True), because that would save memory, or make the runtime faster, or whatever, that's fine. Then we can talk about the tradeoffs involved, such as increasing complexity in the interpreter code. But trying to claim that Python is violating "one of the cornerstones of Western logic" is just too much.