Julia doesn't catch undefined variables at compile time?


#1
function foo()
    x = 1
    if x < 1
        println("less than 1")
        println(bar)
    else
        println("greater than or equal to 1")
    end
end

foo()

the result is

greater than or equal to 1

unless of course you set x=0 then you get

less than 1
ERROR: LoadError: UndefVarError: bar not defined
in foo() at /home/nfs4/briand/src/julia/jcap/test.jl:5
in include_from_node1(::String) at ./loading.jl:488
in process_options(::Base.JLOptions) at ./client.jl:262
in _start() at ./client.jl:318
while loading /home/nfs4/briand/src/julia/jcap/test.jl, in expression starting on line 11

is there a magic switch to make the compiler try harder?
you can’t write robust code if you actually have to wait for that if clause to actually execute before catching that error.


#2

This is not possible, since there could be a global variable bar that is defined after you define foo, but before you call it:

julia v0.5> function foo()
                x = 0
                if x < 1
                    println("less than 1")
                    println(bar)
                else
                    println("greater than or equal to 1")
                end
            end
foo (generic function with 1 method)

julia v0.5> bar = 3
3

julia v0.5> foo()
less than 1
3

EDIT: However, just use Lint.jl or install the linter-julia package in Atom and you will immediately be told that you are using an undeclared symbol.


#3

You are of course right, but a warning could be useful. I think that mistakenly using a variable one forgot to initialize (or mistyping a variable name) is much more common in practice.


#4

ouch.

Would it be too bold to request a global variable declaration of some sort??

The compiler proper should really be able to catch this sort of thing.

allowing unfettered/unidentified use of global is really problematic.


#5

There is sort of a global variable declaration:

global a

means that a inside the function refers to a global variable of that name.

Maybe it could be enforced that it would be an error to not do this, but equally likely there are problems with that that I’m not aware of.

As I added as an edit to my original post, this is the kind of thing that a linter finds easily.
Try the linter-julia atom package – it’s very neat.


#6

This will turn the original function into this:

function foo()
    global println
    x = 0
    if x < 1
        println("less than 1")
        println(bar)
    else
        println("greater than or equal to 1")
    end
end

#7

Isn’t there a way that only variables and not functions requires a global declaration ? In my experience Julia’s implicit assumption that everything is global can lead to some hard-to-find bugs and performance issues.


#8

I think @yuyichao’s point is that there is no way to distinguish function names from other variables. E.g. bar could just as easily be the name of a function defined elsewhere.


#9

I guess you could get function calls at parse time but if you use a function in map or such, it wouldn’t work, so you would still need to declare those as global.


#10

No, for the same reason as I gave in my first post: the function bar may be defined after foo.


#11

In my understanding OP’s point is that it shouldn’t be allowed and that it should throw an error at parse time if you don’t declare bar as global.


#12

Right, but @yuyichao’s point was that then you would have to declare as global any function that you used inside a given function.


#13

But since function calls are parsed as such you might not need to for normal function calls, so

function foo()
    println("less than 1")
end

would be valid, but not

function foo()
    map(println,["less than 1"])
end

Which would need to be written as

function foo()
    global println
    map(println,["less than 1"])
end

It would still be bad, but not as bad.


#14

There’s not really anything special about function calls. There’s also (constant) globals (used a lot in basically every packages that uses ccall) and many functions are used as variables now that we support broadcast syntax.

A better version of this would be requiring pre-declaration of globals in the global scope so at least they only needs to be declared once and only for recursive once that can’t be taken care of by reordering.

There’re still corner cases though. There’s eval and generated function where you basically can’t do this at all (you can’t know at parse time what symbols they are using). And using also complicate things a bit so someone wanting this feature would need to come up with a semantic that’s compatible with all these. In the mean time, I believe a linter can easily warn about this (and I’m pretty sure Lint.jl can do that already).


#15

For what it’s worth, Clojure solves this issue with a declare macro that does forward declaration of identifiers. I think Clojure has much of the same issues to deal with as Julia (i.e. eval, macros, symbols imported from other namespaces), so I see no reason why it isn’t doable.

I think it’s more an issue of taste. As has been pointed out, doing late resolution of identifiers enables one to reference globals, or even functions, that will be declared later. This is particularly useful when working at the REPL. It also allows one to organize code in a file more freely.

Because of Clojure’s eager resolution, and because declare is seen as a tool only to break issues of mutual reference, what ends up happening is that Clojure source files are peppered with simple helper methods toward the top, while the “meat” of a namespace is usually located near the bottom of the file.

The flip side, of course, is being more secure that you haven’t made a typo somewhere, but if that’s really all you need then I agree that a linter is more useful than changing such a fundamental bit of the language.


#16

Julia will happily compile all kinds of meaningless or nonsensical code like 6+“a” – not just undefined variables – without an error message at compile time. Only when the code executes will the error be issued. For this reason, I suggest that you develop a suite of test cases and use them in conjunction with a code-coverage tool for any Julia project of significance. This is not just Julia advice, however. Even in a language like C++ in which all variables have to be declared and all function calls have to make sense at compile time, it is still a good idea to check code coverage on a project of significance.


#17

I’m wondering if the global could be required just for non-const globals? It seems to me that these are the ones that cause the majority of the problems (they often come from typo’s, they cause type stability issues, etc).

A warning emitted at specialization time would seem reasonable if an unmarked, non-const global is to be searched for at run time.


#18

Specialization/compile time would be the worst time for such a warning. It’s indeterministic and won’t help static checking.


#19

maybe the whole thing is best addressed by a reasonably smart syntax highlighter in the editor? if there would be a slightly different color to local symbols vs outer symbols, you would immediately notice that it is not what you wanted. and it is not a problem if functions are colored as outer, since they actually are.


#20

Related