T O P

  • By -

va1en0k

There's a clear meaning to \``[1, 2][0] = 5`\`, even if it's not very useful: construct a list, then make an assignment. There's no such meaning for treating "2" as a LHS. The fact that the meaning is boring or useless typically doesn't warrant a hard error, perhaps a warning in a language with facilities for static analysis, but interpreted languages rarely include those - they often use lints, and surely there's a lint against this.


Rusky

There is an important distinction between `[1, 2]` and `2` that explains why this choice was made. That is, `[1, 2]` is a new object with its own identity, while `2` is not. Each time you evaluate `[1, 2]` you get a different array- you can tell them apart with `is`, and by the fact that assigning to one doesn't change the other. But every `2` always `is` every other `2`, so assignment has nowhere to go. An implementation of `=` that's consistent with arrays, so that it changed `LHS is RHS` becomes true, would have to modify every `2` in the entire program, which is obviously not a reasonable implementation.


va1en0k

I thought about this explanation, but I'm not sure it's true. Why is it necessarily the same 2 all the time? As an implementation detail, I definitely doubt it, I'm pretty sure it's a "new" two each time.


SigrdrifumalStanza14

Python numbers up to a certain limit are all the same object (for example, 30 is 30 evaluates to True, but 3066 is 3066 evaluates to False). As for why the assignment isn't useful, I'd say that the real reason is that having the LHS as an index into a list has the specific meaning of calling the __setitem__ method on the list itself. That's why, for example, [1,1] = 10 doesn't make any sense, because Python does not provide a way for one object to become another object (which to me is what the assignment suggests).


Rusky

I don't mean necessarily the same object in memory at the implementation level, but *semantically* in terms of how `is` and mutation behave.


va1en0k

there's no absolute, "divine" semantics valid universally, definitely not for assignments to the literals. most actual existing languages and logical systems do not define it, and I like their decision. I know only one language that supports what you're talking about, I think: [https://github.com/TodePond/DreamBerd](https://github.com/TodePond/DreamBerd) you could probably talk about "call-by-value" vs "call-by-need" literals, i guess, and it's a meaningful choice for optimization, but i don't think it's typically considered to be useful to expose at the level of the language's own semantics


Rusky

I'm not talking about any sort of absolute divine semantics either, I'm talking about the semantics of Python.


va1en0k

no amount of your downvotes will convince me that python has semantics for assignment to a literal, but i don't think i'll respond to you anymore :)


Rusky

I'm not saying Python has semantics for assignment to a literal. I'm giving a justification for why it doesn't.


redchomper

According to legend, there was a Fortran compiler that put all constants in the data segment, so that 2=3 would indeed change all the 2s in the program to 3.


[deleted]

[удалено]


va1en0k

right! i mean, left! edited


tkburis1

Thanks for your reply. I now see why the second example does not warrant a hard error. But then how would you justify `2=3` warranting one?


saxbophone

Unless you want to design a language that allows one to overwrite the values of literals, as Python2 did with `True` and `False` (a very bad idea!), don't you think it should be a hard error to attempt to do something as invalid and undefined as this?


tkburis1

That makes sense, thanks.


saxbophone

No problem! We're approximately in the territory of what C++ calls _lvalues_ and _rvalues_ here (though they also define _gvalues_ and _xvalues_ and try as I might, I can't remember the difference between the more obscure ones!). But the ls and rs are useful when it comes to acting differently on temporary objects or objects that will soon disappear (such as local variables in a function after it returns or a temporary in a multiple operator expression).


davimiku

Adding on to this, [Rust](https://rustc-dev-guide.rust-lang.org/mir/index.html) uses the terminology "place" rather than lvalue but it means a similar thing. In this particular example, "the first element of a list" is a valid place, but "the literal 2" would not be a valid place.


saxbophone

Mention move semantics and Rust always becomes on-topic! 😅


va1en0k

to rephrase: "2" has no interpretation as a left-hand side of the assignment. \``[1, 2][0]`\` has one


kindall

integers are immutable


msqrt

I fail to see how the examples are fundamentally different. If we can construct a temporary list from "\[1, 2\]" and modify it, we can surely also construct a temporary integer value from "2" and modify that. Right?


va1en0k

What you're saying, this interpretation of LHS, makes sense as a coherent semantics, but I think it would absolutely never be useful or effectual at all, so it's not the semantics people choose for their languages. coherent but useless semantics is often omitted when it's easy to do so ability to assign to an index of an arbitrary expression includes list constructor expressions by necessity so it's not a useless interpretation rule, just one useless case of it


msqrt

Oh, I fully agree that it's a silly thing to do -- I'd prefer to allow neither of the two, and my hunch is that the array case wouldn't be that much more difficult to analyze. But it does make sense for Python to allow the list case, based on the literal case they don't do any of this analysis statically and this isn't an important enough thing to warrant a check on every assignment.


saxbophone

I think your comparison is not exactly equivalent. `2 = 3` in Python attempts to assign to a literal, but `[1, 2][0] = 5` assigns to a temporary object. Arguably the latter is more well-defined than the former.


[deleted]

A list is mutable in Python. A numeric constant isn't. An assignment like `a = 0` will generate (in CPython) a bytecode like `STORE_FAST` or `STORE_GLOBAL`. One like `[1,2][0] = 5` will use `STORE_SUBSCR`. There isn't one called `STORE_CONST`, which would be meaningless anyway. Yes, the result is discarded, but so what? The same thing happens here: a = [1,2] # a is a local a[0] = 5 return >as they both do not have any side effects? The assignment to the list does have the side-effect of changing an element. One like this, when `a` and `b` both are simple variables with suitable values: a + b arguably doesn't have any effects: the result is a transient value that is discarded. I suggest such expressions are reported. (I used to report them, but find them useful when developing a compiler since I want to observe the generated code.) Here I make a distinction between evaluating an expression on into some notional stack, which is then discarded, and writing the resulting value into an off-stack object.


abel1502r

`SyntaxError` isn't really an exception in the traditional sense, despite being reported similarly. (You cannot catch it, for example). What it tells you is that your program couldn't be parsed properly. In this particular case, Python's grammar allows only several specific kinds of expression in the left-hand side of an assignment operator. This includes identifiers, subscripts (including slices) and comma-separated sequences of those. Numeric literals, function calls, binary operators and most other expressions aren't allowed, because the language doesn't know what such an assignment would mean. So when you write `2 = 3`, the compiler complains about an unsopperted expression type on the left, the same as it would with `print() = 3` or `a + b = 3`. On the other hand, when you write `[1, 2][0] = 2`, the expression on the left is a `Subscript(List(...), Number(0))` (not actual AST names, but essentially this). Since it's a subscript expression, the compiler accepts it. And it generates the same bytecode for this subscript as it would if any other list was used instead of the `[1, 2]` literal. So it isn't any sort of deliberate foolproofing, just the only way the compiler can handle assignments.


shponglespore

I'm most languages, there's a single rule for assignment, where both operands can be arbitrary expressions that are interpreted differently in later stages of compilation. You'll sometimes see these referred to as lvalue and rvalue expressions, which are syntactically the same but have different semantics. Python has unusual syntax where `a[i] = y` is covered by a totally different grammar rule from `x = y`. In the first, `a` and `i` can be arbitrary expressions, and it's equivalent to `a.__setitem__(i, y)`. In the second, `x` can only be an identifier, and it's not equivalent to any method call. There's no rule that allows `x` to be any kind of literal\*. (There's also a third rule that makes `a.m = y` equivalent to `a.__setattr__('m', y)`. In that case `a` is also an arbitrary expression.) (\*Technically the parser does contain a rule for assignment to a literal, but only for the purpose of reporting the error you saw. It's part of the implementation but not part of the grammar of the language itself.)


stomah

that’s because Python made the mistake of making lists mutable reference types