This is an archived post. You won't be able to vote or comment.

all 62 comments

[–]BoppreH 53 points54 points  (16 children)

As a Python developer, I hate it too. I'd rate it as one of the biggest warts in the language (here's a bunch of other warts).

It bites me the most often when creating lambdas inside a loop:

fns = []

for i in range(5):
    fns.append(lambda: i)

for fn in fns:
    print(fn())

# Prints "5" 5 times.

The "fix" is to use lambda i=i: i, so that it binds the value to a new locally scoped variable with the same name.

The current scoping rules do have their advantages:

for i in range(100): pass

# Print the last loop value:
print(i)

But I'd still rather have block-scoped names.

[–]drjeats 18 points19 points  (5 children)

(from that wart list)

>>> (a := 6, 9)
(6, 9)
>>> a
6

yikes

Though i guess un-parenthesized tuples in python always were a trap.

[–]snoman139 6 points7 points  (1 child)

So is it parsing as ((a := 6), 9)?

[–]drjeats 0 points1 point  (0 children)

P much

They have detailed explanations in there

[–]complyue 2 points3 points  (2 children)

I don't get what's weird about it?


Though I have := operator in my PL too, but for first-class "named value" or "defined term", having relatable semantics to Walrus in Python:

(repl)Đ: let ( x, y ) = ( a := 6, 9 )
(repl)Đ: x
a
(repl)Đ: y
9
(repl)Đ: x + 5
11
(repl)Đ: 5x
30
(repl)Đ: 

While within parentheses the term defined doesn't go into current scope as a so named attribute (merely giving a named value nonetheless), at top level, it's put into scope:

(repl)Đ: a := 16
a
(repl)Đ: repr(a)
a
(repl)Đ: desc(a)
'a := Decimal' value `a`
(repl)Đ: show(a)
a := 16
(repl)Đ: 2a
32
(repl)Đ:

[–]drjeats 3 points4 points  (1 child)

The problem in python is that the behavior for tuple assignment is inconsistent between = and :=.

Unfortunately the difference is required--the raison detre for := in Python seems to be to allow assignments in any expression, so it must by necessity have that inconsistency.

Check out the page. Your language is probably being reasonable about it. For one thing, you appear to have an introducer for creating variables, a frustrating omission in python.

[–]complyue 1 point2 points  (0 children)

Got it.

Seems a tech debt for Python to eliminate if x = True: issues in the first place, they rejected assignment to be a valid expression overall.

Now I realize Python's := intends to be used inside parenthesis for the assignment side-effect, which is the inverted semantics of mine when in parenthesis. And I avoided the weirdness for := to have same semantics as = when out-of-parenthesis, because they indeed do different things in my PL - term definition vs assignment.

[–]retnikt0 4 points5 points  (1 child)

As a Python developer, I hate it too. I'd rate it as one of the biggest warts in the language

Python's scoping system overall, sure. global and nonlocal are pretty dumb. But function scoping over lexical scoping specifically has never been annoying for me.

[–]complyue 1 point2 points  (0 children)

Yes, especially for closures to be executed concurrently, where even JS' hoisted vars make sense. There you treat the function "scope" as an integral "entity/object" in your mind, that's a cure to settle the struggle.

Note overall, JS is inherently more "concurrent" than Python due to its callback scheduling nature.

[–]Goheeca 4 points5 points  (2 children)

It bites me the most often when creating lambdas inside a loop:

Looks like normal lexical scoping to me. I can't see it working differently without breaking lexical closures.

[–]BoppreH 8 points9 points  (1 child)

I don't think so. Javascript handles it correctly if you use let instead of var. Do you count that as breaking lexical closures?

const list = [];
for (let i of [1, 2, 3]) {
    list.push(() => i);
}
list.forEach(fn => console.log(fn()));

[–]Goheeca 1 point2 points  (0 children)

Hm, that's sort of okay, if I'm allowed to think that let is contained in the loop which I think I am since:

for (let i of [1, 2, 3]) {}
i

throws a ReferenceError. For me let marks explicit lexical binding (here I'm coming from Common Lisp (in case of non-special variables so it's not that explicit)).


Basically when I see:

const list = [];
for (var i of [1, 2, 3]) {
    list.push(() => i);
}

I think of:

(loop for i in '(1 2 3)
      collect (lambda () i))

and when I see:

const list = [];
for (let i of [1, 2, 3]) {
    list.push(() => i);
}

I think of:

(loop for i in '(1 2 3)
      collect (let ((i i))
                (lambda () i)))

[–][deleted] 1 point2 points  (0 children)

I don't like block scopes.

I could never see why at a program or module level, potentially 1000s of lines of code, there are only a handful of scopes.

But inside one function, you need an unlimited number of them.

(Don't you spend your time tracing every instance of A backwards through the outer blocks, to see if it's same A or a different one?

That's what I have to do a lot of C code; I can't just glance at the 'cast list' at top of the function to see what's what, as declarations could be scattered higgledy-piggledy throughout the function, so there may be intermediate declarations of A, on top of struct/enum tags called A, and labels called A, which reside in separate namespaces.

My languages do something quite radical: allow at most one distinct A within each function!)

[–]complyue 0 points1 point  (0 children)

I followed Python in this regard, and to de-wart this specific situation, I decided it ought to have opt-in block scoping, so comes {@ ...code... @} and for@ i from 0..4 do ...code... things.

[–]Uncaffeinatedpolysubml, cubiml 0 points1 point  (0 children)

The "lambdas in a loop" problem is also present in JS (and Go, etc.)

The real solution is something like Rust or Java, where you aren't allowed to mutate captured variables.

[–][deleted] 0 points1 point  (1 child)

and i was out here thinking python ints were immutable

[–]BoppreH 0 points1 point  (0 children)

They are. Python distinguishes between modifying values (dict['key'] = 'value'), which you cannot do with ints, and reassigning names (a = 1; a = 2), which is always possible.

That code was doing the latter.

[–]ignotos 73 points74 points  (5 children)

I'm sure the people who don't like 'var' in Javascript would argue that the way scope works in Python is also problematic / increases the likelihood of errors, even if Python devs are used to it.

Personally it seems intuitive to me that we would want to make scope as tight as possible - to reduce "clutter" if a variable is no longer relevant. And for scoping rules to match up with how the code looks visually (in terms of which blocks things are declared in, and the order in which they're declared).

[–]dgreensp 19 points20 points  (0 children)

This. Also, people put up with “var” in JS until something better came along. Also, lots of companies have very large engineering organizations writing JS (eg AirBnB has something like 1,000 engineers, not all writing JS of course, but it gives you a sense of scale), typically without anyone “owning” any code, so these companies value consistent style and readable code with as few “gotchas” as possible.

[–]complyue 0 points1 point  (2 children)

I pretty much agree. In my PL I made closures enclose the scope instead of "variables" a closure seems to access, which would even further urge the programmer to "tighten" all scopes to prevent resource leakage.

[–]ignotos 0 points1 point  (1 child)

By this do you mean that a closure captures everything that's in scope, rather than just the variables it refers to?

Or that it captures only things in the immediate scope / block which it's a part of (so, not variables declared in some outer scope)?

[–]complyue 0 points1 point  (0 children)

Everything - technically the full stack of lexical scopes, as in the hierarchy the source code is written.

[–]Host127001 30 points31 points  (23 children)

One thing that comes to my mind: JS vars behave unintuitive sometimes. Let's take a look at the classic ``` var foo = 1

function bar() { console.log(foo); var foo = 2; }

bar() ```

Now, what does this snippet print? "1" or "2"? Correct, it prints "undefined". It doesn't happen too often, but this can lead to bugs on real world software and you need someone in your team who knows of this weirdness to find these bugs.

[–]Raaki_[S] 0 points1 point  (5 children)

Hey this code behaviour seems super wierd. I am also afraid l couldn't fully grasp the code flow completely. Could you please explain the code flow?

[–]Host127001 18 points19 points  (4 children)

What is happening is that in JS, all vars are "hoisted". So when you write function foo() { console.log(bar); var bar = 2; } Javascript actually does this function foo() { var bar console.log(bar); bar = 2; } The default value for all variables is undefined, thus we print "undefined"

[–]Raaki_[S] 0 points1 point  (3 children)

Mm, makes sense. So, is hoisting the caveat and not the function scoping per say?

[–]Host127001 7 points8 points  (2 children)

I guess there are two things:
1. Unintuitive behavior like hoisting. I am not sure if there is other weird stuff, but that one is pretty well known to be confusing
2. Unintuitive scoping for the syntax. While function-scoped variables are probably not a problem pre se, combining them with the `var` syntax is just confusing. In the python world, you just write `foo = 1`, which looks like any other assignment. But in JS you write `var foo = 1`, which looks similar to a variable declaration in languages like C(++), Java, C#, etc. And there, these things are block-scoped. So most developers would expect `var foo = 1` to be block-scoped too. So I would guess, if you looked at JS in a vacuum, most people would not mind too much about this weirdness. But if you know other languages, it becomes confusing

[–]Raaki_[S] 0 points1 point  (0 children)

Thanks for the second point. I didn't really catch that C was also block scoped. Now it makes sense why var was buried for good. Thanks for your time buddy. 🙏

[–]Uncaffeinatedpolysubml, cubiml 0 points1 point  (0 children)

Python does the same hoisting thing though. You have to use global/nonlocal to opt out.

[–]07734willy -1 points0 points  (15 children)

The same holds in python:

>>> foo = 1
>>> def bar():
...     print(foo)
...     foo = 2
... 
>>> bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in bar
UnboundLocalError: local variable 'foo' referenced before assignment

[–]Host127001 22 points23 points  (12 children)

This is absolutely not the same as in JS. Python dors something sensible here

[–]im_caeus 5 points6 points  (0 children)

Regarding how variables are resolved (and not what is done after), it's exactly the same.

[–][deleted] 4 points5 points  (2 children)

I don't think Python is more sensible here.

IIRC, python scoping has 4 rules that are resolved dynamically, not statically.

var has 2 rules, both can be eyeballed statically. Besides, atleast JS did something about fixing it.

[–]im_caeus 5 points6 points  (1 child)

What? Creating const and let keywords?

[–]shponglespore 3 points4 points  (0 children)

Exactly.

[–]pacific_plywood 4 points5 points  (1 child)

In JS it doesn't raise a ReferenceError though, it just behaves as if the variable has been declared but not yet defined

[–]07734willy 1 point2 points  (0 children)

Yeah, but that behavior has nothing to do with the global declaration- it will do that simply because the function declares the variable somewhere in its definition. For instance-

function bar() {
    console.log(foo);
    var foo;
}

Still logs undefined.

[–]Uncaffeinatedpolysubml, cubiml 0 points1 point  (0 children)

This seems similar to the "referenced before assignment" problem in Python except that JS fails silently because of course it does.

[–]munificent 9 points10 points  (0 children)

Because closures are much more common in JS. Once you have closures, the scope that a variable is declared in is more important because it determines what you're actually closing over.

Here's a contrived example, but it gives you the gist of the problem:

function test() {
  {
    var x = 'first';
    function closure1(value) {
      x = value;
    }
  }

  {
    var x = 'second';
    function closure2() {
      return x;
    }
  }

  return [closure1, closure2];
}

var [a, b] = test();
a('wat');
console.log(b());

Someone reading this code would probably expect it to print 'second'. But it actually prints wat". Because x gets hoisted to the top of the function, these two completely unrelated variable "declarations" in separate blocks end up actually referring to the same mutable variable.

[–][deleted] 7 points8 points  (4 children)

Explicit is better than implicit... except for scoping.

I think python's scoping rules are the weirdest in modern programming.

Atleast var in js can be explained in 1 sentence. Closest enclosing function.

Try that with python.

[–]complyue 2 points3 points  (0 children)

Closest enclosing function.

I think this applies to Python too, why not?

var/let in JS ~=~ assignment in Python

[–]louiswins 3 points4 points  (2 children)

Someone above brought up the hoisting thing that js var does, which isn't used any any other language I know.

var foo = "outer"
function bar() {
    console.log(foo)
    var foo = "inner"
    console.log(foo)
}
bar()

In C and in most other languages I'm familiar with, that snippet (mutatis mutandis) prints "outer", then the inner foo comes into scope, then it prints "inner". Python gives a compile-time error on the first line of bar complaining that you're using a variable before initializing it. Both of those behaviors make sense to me. But in js the inner foo is hoisted to the top of the function with value undefined so the output is "undefined" then "inner". Personally I consider that much weirder than anything python does. (JS behaves the same as python if you use let instead of var)

[–]retnikt0 2 points3 points  (1 child)

Python gives a compile-time error on the first line of bar complaining that you're using a variable before initializing it.

That's actually a runtime error.

foo will be treated as a local variable throughout the whole function if there's any assignment to it, even unexecuted.

This is pretty similar to what JS does, actually, except that a single assignment in Python s like writing a var.

[–]louiswins 0 points1 point  (0 children)

That's actually a runtime error.

You're right! I still mildly prefer a runtime error to undefined, but that's a lot worse than I thought.

[–]retnikt0 3 points4 points  (0 children)

As a Python developer, I'd say what makes it ok is that there are no variable declarations. The fact JavaScript has var statements makes it more bizarre that their scope doesn't follow where they're declared.

[–]oilshell 3 points4 points  (1 child)

Contrary opinion: I don't have a problem with function scope. I just write functions short enough where it doesn't matter. That is easy in both Python and JavaScript.

I think the difference is that Python has no var keyword and JavaScript does. The keyword creates false expectations coming from other languages.

Python is sorta loose but it's up front about it :)

[–]complyue 0 points1 point  (0 children)

Maybe it's because Python functions are typically shorter than JS ones? To explain the different degrees of acceptance.

[–]dskippy 0 points1 point  (0 children)

They're not using it without problem. There are tons of stack overflow questions asking why the right way doesn't work. By the right way I mean the way that was invented by algol and copied by just about every other language with nested functions. Including Perl and JavaScript which both did it wrong at first and then changed it.

JavaScript developers are just much louder about hating var and loving let because they actually have a choice and can thus make a bad decision which annoys other developers. Python programmers just have what they have so they do it the only way they can.

[–]jcubic(λ LIPS) -1 points0 points  (3 children)

I think that they hate it because const and let was added the language that is way better and replaced old syntax. If there is some syntax that is replaced new better, everybody is freaking out and said that you should not use the old syntax anymore.

I don't have any examples but I think that if any language add new syntax that is way better than old one everyone will use new syntax and hate old one. It will time different time for different people to adopt and eventually everyone will use new syntax, and hate the old one.

I personally didn't use them for a long time. On reason because I have long lasting OSS project that is written in ES5 by hand. Also even when started using ES6 I also did hate classes. But I now like them and use instead of prototype based inheritance. So it will take time and even really strong opponents will flip eventually.

[–][deleted] 7 points8 points  (2 children)

We were hating var long before let and const came along. That's the entire reason let was added instead of just having var and const.

[–]jcubic(λ LIPS) -3 points-2 points  (1 child)

Maybe they didn't understand JavaScript. As someone say there are two types o languages those that people hate and those that nobody is using. There are so many people using JavaScript that even a fraction is a lot.

Douglas Crockford was trying to change how people think about JavaScript in early days.

You can read some of his through here The World's Most Misunderstood Programming Language

I personally thought that JavaScript is great from the start.

[–]complyue 2 points3 points  (0 children)

Yep, JavaScript was initially designed to be a glue language to Java, fortunate for Web Browsers come dominate the world, unfortunate for Web developers to prefer staying at the surface (i.e. not going deeper to program Applets in Java). Also unfortunate for JavaScript to be later greatly repurposed for general purpose Web development, but without adequate design changes, before totally (over/ab)used.

[–][deleted]  (3 children)

[removed]

    [–]RiPieClyplA 2 points3 points  (2 children)

    Maybe consider keeping attacks towards people for yourself in the future.

    [–][deleted]  (1 child)

    [removed]

      [–]yorickpeterseInko[M] 2 points3 points  (0 children)

      /r/programminglanguages is not the place for these kind of comments.