9 ms·
Why is it not syntactic sugar? It looks like convenience. You could argue everything above machine language is syntactic sugar.
by Aardwolf 8y ago
Why is it not syntactic sugar? It looks like convenience. You could argue everything above machine language is syntactic sugar.
- akvadrako 8y agoNot so. Many things change the semantics, otherwise high level languages would just be glorified macros. For example, import mod is NOT defined as mod = eval(open("mod.py").read()) but involves abstract load module operation, which is dependant on the environment. That's why := is just syntactic sugar; there are no new semantics.
- junke 8y agoHonestly curious, how do you expand a "x := y" expression into an expression that returns y and affects x as a side-effect?
- akvadrako 8y agoI'm not sure exactly, but my point is that a program transformation can do it. However, no transformation can change an import to other operations while perserving semantics. Just for fun, this seems to work: (locals().pop('x', None), locals().setdefault('x', y))[1]
- yorwba 8y agoThat only works at top-level scope, where locals() == globals(). Inside a function, changing the dict returned by locals() doesn't actually change the variable.
- saltcured 8y agoDoes syntactic sugar have to be that strictly limited? If we are allowed to manipulate the statement block containing the sugared expression, we could define a trivial helper function which performs the 'nonlocal' side-effect and returns the value. Then, replace the "x := y" with "helper(y)".
- chriswarbo 8y ago> there are no new semantics I don't think that's right; what expression/statement is `x := y` equivalent to? I'm thinking in particular about using mutable collections to emulate assignment in a lambda, e.g. >>> counter = (lambda c: lambda: (c.append(c.pop() + 1), c[0])[1])([0]) >>> counter() 1 >>> counter() 2 >>> counter() 3 It looks like this could now be done as: >>> counter = (lambda c: lambda: (c := c + 1))(0) Yet the semantics here are very different: one is pushing and popping the contents of a list, without changing any variable bindings (`c` always points to the same list, but that list's contents changes); the other has no list, no pushing/popping, and does change the variable bindings (`c` keeps pointing to different integers). Maybe it's equivalent to using a `=` statement, but statements are forbidden inside lambdas. Maybe the lambdas are equivalent to `def ...` functions, but what would their names be? Even if we made the outer one `def counter(c)...` the resulting value would have a different `func_name` (`counter` versus `<lambda>`). Even the `if` examples that are scattered around this page don't seem to have an equivalent. For example: if (x := foo() is not None): do_something() We can't "desugar" this, e.g. to something like the following: x = foo() if x is not None: do_something The reason is that we're changing the point at which the binding takes place. For example, Python guarantees to evaluate the elements of a tuple in left to right order (which we exploited in the above push/pop example). That means we could write: if (sys.stdout.write(x), x := foo() is not None)[1]: do_something This will print the current value of `x`, then update `x` to the return value of `foo()`. I can't think of a way to desugar this which preserves the semantics. For example, using the incorrect method from above: x = foo() if (sys.stdout.write(x), x is not None)[1]: do_something This isn't equivalent, since it will print the new value of `x`. Maybe we could float the `write` call out of the condition too, but what about something like: if foo(x) and (x := bar()): do_something We would have to perform `foo(x)` with the old value of `x`, store the result somewhere (a fresh temporary variable?), perform the `x = bar()` assignment, reconstruct the condition using the temporary variable and the new value of `x`, then `del` the temporary variable (in case `do_something` makes use of `locals()`). PS: I think this `:=` is a good thing, and writing the above examples just reminded me how infuriating it is when high-level languages distinguish between statements and expressions, rather than having everything be an expression!
- 8y ago
- dragonwriter 8y ago> That's why := is just syntactic sugar; there are no new semantics. There seem to be new semantics in the interaction with comprehensions, which is one of the main sources of controversy in the discussion these linked as the OP.
- arketyp 8y ago>You could argue everything above machine language is syntactic sugar. The sugar is sprinkled on top of syntax, the stuff the parser deals with. Typing a += 1 instead of a = a + 1 is sugar because it parses the same. This assignment syntax seems different. IMHO.
- blattimwind 8y ago> because it parses the same Does not. One is addition, the other is in-place addition; they're different things and can behave differently. E.g. in "a += b" and "a = a + b", the former might not construct an intermediate object, but mutate the existing a.
- ben-schaaf 8y agoTo be more specific for python: "a += b" -> "a = a.__iadd__(b)" "a = a + b" -> "a = a.__add__(b)" "__iadd__" and "__add__" can do whatever they want.
- amelius 8y ago> You could argue everything above machine language is syntactic sugar. Type inference is not.
- cup-of-tea 8y agoSynactic sugar means the same thing can be achieved without the sugar. How could you achieve this without the new expression?
- est 8y agoby writing extra lines? I'd prefer more lines for readability reasons.
- cup-of-tea 8y agoHow would you do this? if (match := re.match(r1, s)): o = match.group(1) elif (match := re.match(r2, s)): o = match.group(1)
- PurpleRamen 8y agomatch = re.match(r1, s) if match: o = match.group(1) else: match = re.match(r2, s) if match: o = match.group(1) or a bit shorter: match = re.match(r1, s) if not match: match = re.match(r2, s) if match: o = match.group(1) You could also just loop: for pattern in (r1, r2, ...): match = re.match(pattern, s) if match: o = match.group(1) break else: do_failure_handling() But this goes a bit beyond the original question.
- cup-of-tea 8y agoI made a mistake in the question, it should have been: if (match := re.match(r1, s)): o = match.group(1) # plus some code here elif (match := re.match(r2, s)): o = match.group(2) # plus some other code here In this case only your first solution works, I think. Leaving aside that having those deeply nested ifs is incredibly ugly, I find it hard to accept that something which completely changes the possible structure of the code is just "syntactic sugar".
- 8y ago
- chriswarbo 8y ago> You could argue everything above machine language is syntactic sugar. You could, but that would turn "syntactic sugar" into a useless phrase with arbitrary meaning. The phrase "syntactic sugar" is usually reserved for language constructs which can always be rewritten, in-place, to some other construct in the same language, such that the semantics is identical (i.e. we can't tell which construct was used, unless we parse the contents of the file). Python has examples like `foo += bar` being sugar for `foo = foo + bar`. As an aside, your mention of "machine language" implies the use of operational semantics. That's where we say the "meaning" of a program depends on what it does to the machine when executed. That's fine, but it's not the only approach to semantics. In particular denotational semantics defines the meaning of a program by giving a meaning to each syntactic element of the language and their combinations, usually by rewriting them into some other, well-defined language (e.g. set theory). I much prefer denotational semantics, since it lets me 'think in the language', rather than making me 'think like the machine'.
- uryga 8y ago> Python has examples like `foo += bar` being sugar for `foo = foo + bar`. Nitpick, but I don't think that's true – AFAIK they translate into different method calls. `foo + bar` →`foo.__add__(bar)` `foo += bar` → `foo.__iadd__(bar)` (note the `i` in the second one)
- chriswarbo 8y agoHmm, good catch. Interestingly, I considered using `a + b` as sugar for `a.__add__(b)`, but rejected that because they're not quite equivalent. For example if `__add__` is defined using a magic method: >>> class A(object): ... def __getattribute__(self, attr): ... if attr == "__add__": ... return lambda *_: "hello world" ... return None ... >>> a = A() >>> a.__add__(A()) 'hello world' >>> a + A() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'A' and 'A'
- bilboa 8y ago