JacquesCarette / Drasil

Generate all the things (focusing on research software)
https://jacquescarette.github.io/Drasil
BSD 2-Clause "Simplified" License
136 stars 25 forks source link

Local scope in Julia #3821

Open B-rando1 opened 5 days ago

B-rando1 commented 5 days ago

I've been working some more on the Julia render, and I discovered that it's stricter with local scope than other languages. A minimal example is the following:

a = 0

while a < 5
    println("a = $a")
    a += 1;
end

This throws an error, because the while loop introduces a 'soft local' scope (see here for details), and local scopes cannot refer to global variables without explicitly stating that they are. There are two ways to get the intended behaviour:

1. Using global declaration
a = 0

while a < 5
    global a
    println("a = $a")
    a += 1;
end
2. Using a function to define functions in a local scope

If variables are defined in a local scope, then nested local scopes are free to access them.

function main()
    a = 0

    while a < 5
        println("a = $a")
        a += 1;
    end
end

main()

Analysis

Both options have difficulties.

balacij commented 4 days ago

It sounds like option 1 is the 'right' choice in the sense that if we go with option 2, we wouldn't be able to generate purely executable Julia scripts. Am I understanding this correctly?

B-rando1 commented 4 days ago

It sounds like option 1 is the 'right' choice in the sense that if we go with option 2, we wouldn't be able to generate purely executable Julia scripts. Am I understanding this correctly?

I'm not quite sure what you mean. As long as we add main() to the bottom of the file, it would still run with the same behaviour, i.e. an outside user wouldn't be able to tell that the program is inside a function.

If you mean that scripts require a main function and won't run in the global scope, then you are correct. This might conflict with #3512, but at the same time it's no different than the C++ target. Let me know if you think this is something we should avoid.

My bigger concern with option 2 is that it doesn't help external modules that define global variables, for example here. That isn't a perfect example, as the Python version uses a class, and neither target seems to actually use the Constants file 🤔. It gets the point across anyway, that even with option 2 we still could have cases where we need to separate the representation of global and local variables in GOOL.

The only benefit of still doing option 2, is that it would allow us to safely generate the global declarations at the function level rather than the level of while, for, function, etc. As an example:

# Option 1
a = 1 # Needs to be global for some reason

while a < 5
    global a # Need to declare at top of while loop, because there's no function to declare at
    a += 1
    println(a)
end
# Option 2
a = 1 # Needs to be global for some reason

function main()
    global a # can safely put this at the top of the function, which might be easier to implement
    while a < 5
        a += 1
        println(a)
    end
end

main()

To be honest I'm not completely sure, but my guess is that we have more infrastructure in place to put global declarations at the top of functions than we do for the top of while-loops, etc.; so putting the main script inside a function might make the global declarations easier to implement.

I hope this clears up my reasoning.

balacij commented 4 days ago

We briefly spoke about this in person, but I think I still stand with option 1. Option 1 better captures the intent of a 'unmodular, unbundled'-style program (e.g., one-shot/use-style scripts, as in #3512), unlike Option 2, which would force some amount of modularization. The issue with the comparison to C++ is that it is not a scripting language first, unlike Python and Julia. That being said, 'unmodular, unbundled'-style programs are rather unserious for large projects, so I think it's a nice feature to support for show and tell, but not necessarily something we would expect to seriously use for any non-trivial project.

B-rando1 commented 3 days ago

Sounds good @balacij. I'm not exactly sure, but I think we'll have to add a new field for variables denoting whether they're locally or globally defined. I was hoping that we'd be able to reuse Scope or Permanence from ClassInterface.hs, but those are only associated with class attributes, not general variables.

Here are my current thoughts, then:

I think this would be worth briefly discussing in #3815.

JacquesCarette commented 2 days ago

The conclusions in the last post above (about VisibiliySym and ScopeSym make sense. We do want script-like programs with global variables as well as proper programs (and libraries) that don't. So we need to know the difference.