Another possible solution to the global scope debacle

Ok, fair enough

In terms of language design, I think the current scoping rules are fine. In general, local scope should be preferred to global scope. I am strongly against making things global by default.

In terms of pedagogy: warnings (or infos) are an ideal solution for this. These user-friendly warnings can be turned off by experienced users who understand scoping rules.

In terms of ease of use: perhaps a convenient syntax to designate variables as global so users don’t have to type out a full global x.

EDIT:
Perhaps my solution is not the best. The important point I’m trying to make is that perhaps the usability concerns can be addressed without resorting to “global by default” behavior.

4 Likes

I thought (2) were coming. I thought let ... end was a sugar for (() -> begin ... end)(). It was a nice pattern to use in module-level scope to avoid leaking variables as globals. For example, if you need to do some metaprograming using @eval and while, I suppose simply surrounding the block by let would not work anymore with the choice (1)? Also, I suppose top-level for/while loop could be efficiently executed just by surrounding it by let in principle with choice (2)?

It looks like both choices have a consistency which is mutually exclusive without 1.0-like scoping behavior. If we are to lose one consistency anyway, why not choose a useful one? Or maybe there are useful cases for choice (1)?

How about creating new keywords for the proposed changes? E.g. for! affects global scope by default, while the behavior for the current (1.0) for is maintained.

To clarify…

Are you assigning a value to a variable?
   No:  this topic is irrelevant
   Yes: Are you assigning within a module?
      Yes: this topic is irrelevant
      No:  Are you assigning within a function or struct def?
         Yes:  this topic is irrelevant
         No:   Are you assigning within a let block?
            Yes: this topic is irrelevant
            No:  this topic is relevant

As I understand it, this topic applies to top-level assignment within a julia script or the REPL and to assignment within a top-level for or while loop.

As I understand it, this is relevant within modules and in lets. Modules have their globals. And whether let introduces or not a local scope remains to be settled.

The way I understand this (see Stefan’s response above), the new rules affect all “global” variables, whether defined at the REPL (module Main), or inside a nested module.

You know what? This proposal is starting to grow on me. It’s perhaps just a matter of changing my mental model of what constitutes a scope boundary.

Okay, so let’s have modules and functions as the only scope boundaries. But then I think I would prefer let blocks to be transparent (leaky) just like for (option (1) in @StefanKarpinski’s post). Additionally, I think I would prefer the binding i in for i = ... to remain local, just like in let i = .... There is a nice parallelism there, and it’s easy to remember.

There is also the nice parallelism that’s been discussed that wrapping any top-level for into a function doesn’t change the way it works, i.e.

x = 0
for i = 1:2
    x = i
end
x  # x == 2

works the same as in

function f()
    x = 0
    for i = 1:2
        x = i
    end
    x  # x == 2
end

So, is there any real shortcomings of @jeff.bezanson’s proposal (not just a matter of taste)? It does solve the soft/hard scope complexity that motivated #19324, and I cannot think of any real drawback now… :thinking:

(EDIT: … apart from the obvious “how the hell do we get this into 1.x now?”)

7 Likes

It sounds like you have come around to exactly @jeff.bezanson’s position :grin:. I was skeptical about this design initially, but after mulling it over for a while, it’s grown on me. Yes, local is a better default than global, but the more skilled user can easily either (a) do things inside functions or (b) sprinkle a local here and there. Putting the burden of knowledge on the more advanced user seems appropriate.

11 Likes

please see
my post asking about this
maybe we are talking about two different things

The problem I have with this is that functions are supposed to be self contained (and ideally small) units. Therefore it should be easy to tell what values the for block is capturing.

If a global for block automatically captures global state, a user can easily mess up global state without intending to.

To me it seems better to opt-in to dangerous behavior rather than to opt-out (this is apart from any annoying verbosity).

If working in the REPL has too much friction, I don’t see why we can’t just provide additional features to help mitigate that friction. I don’t deny that working in the REPL is an essential part of most Julia workflows. Perhaps there is a way to be both safe and ergonomic.

When there’s a choice, Julia generally has people opt into performance instead of opt out (@inbounds, @simd, @fastmath, type-unstable code just works, etc.). Requiring someone to local seems to follow that guideline. I think it’s both consistent throughout the language and is more generally just a good design principle.

4 Likes

So I don’t grok what is it if module contents be global.

Yes, this would apply inside of modules—modules are global.

“modules are global” is different from this?

module A
a = 5
end
module B
a = 55.5
end
using A
using B
Error both A and B provide a variable named `a` in global scope

I would argue that mutating global state is the advanced use case! But I digress. There has been a lot of fruitful discussion and I trust you guys to make the right choice.

1 Like

Derp - I think I got thrown by this:

Thinking that variables outside of functions would all become what @mauro3 called “global global.” But it makes sense from context, apologies for being dense.

I think interactive usage is a less advanced use case than scripting and package writing, and in interactive usage, globals are what threads a computation to the next.

1 Like

I’m convinced! I guess I was focused too much on package writing rather than interactive use.

1 Like

What we do should help and not hinder future realization of namespaces, api protocols, trait organization, …
[I’m not implying anything about the proprosal]