Listing the names of local variables


#1

I would like to know the names of local variables used in a function for debugging. In some cases, misspelled variables don’t produce an error but the function returns nonsense results. This is hard for me to find the problem. What is the way to show the list? Or, are there any suggestions to avoid this kind of error (e.g. requiring to declare all variables like implicit none in Fortran)?


#2

If you use @code_warntype macro on your function you will get a list of local variables in Variables: section. In Body: section if your code refers to global variables then module name will be prepended. Here is an example for Julia 0.6.2 (under Julia 0.7 the output looks a bit different but the general functionality is similar):

julia> x = 1
1

julia> z = 1
1

julia> function f()
       y = 1
       global x = 10
       println(z)
       g() = x+y
       g()
       end

f (generic function with 1 method)

julia> @code_warntype f()
Variables:
  #self# <optimized out>
  y <optimized out>
  g <optimized out>

Body:
  begin  # line 3:
      Main.x = 10 # line 4:
      (Main.println)(Main.z)::Void # line 6:
      return (Main.x + 1)::Any
  end::Any

#3

Thank you for your quick replay. I just applied @code_warntype to my function but I found 2 issues with this macro. First, it doesn’t show variables in a while block like:

function readfile(filename::String)
   a = Dict{String,Int}()
   buf = ""
   open(filename,"r") do ios
      while(!eof(ios))
         bud = strip(readline(ios))
         items = split(buf,"")
         a[items[1]] = length(items)
      end
   end
   return a
end

where bud in while is a nonsense variable. I guess this is because of the scope of variables but I want to find this error. Second, in some functions, @code_warntype doesn’t always show local variables defined at the beginning of the function (in a pre-compiled module).

Is there any specific usage of @code_warntype? Any other macros or packages to detect misspelling or typos?


#4

bud is a valid local variable here and should be shown as such by @code_warntype. It would be possible to have a mode where all local variables need to be declared with local but it would be a non-trivial implementation effort.


#5

Because of the do construct, the while clause is buried in an anonymous function, which is hard to diagnose with introspection tools like code_warntype. Is there any prospect that this will become easier soon?


#6

It seems to me that this ls() output inside of @code_warntype is just a side effect of the real purpose of the macro, which is to debug a phrase of code for type efficiency. However, the presence of this side effect raises the hope of writing a real R-like ls() to show REPL variables - I shall look into this. You can also see local contents with whos() but it is very slow.

@yutaka, if you want to see the contents of variables in a loop, perhaps you can just print or @show them. For example, add @show bud to your while loop, or do so conditionally. An old school debugging trick.

Finally, you can also use Traceur.jl to print out little performance tips about your code, which may be easier to interpret than the @code_warntype macro’s output at first.


#7

Thank you for all your replies. It seems that there are no macros/packages to raise an error with undeclared variables like implicit none in Fortran and use strict in Perl. (By the way, Python doesn’t have such a system, neither.) The same conclusion has been derived in the old Julia-user-group in Google Groups that I just found after I posted this issue.

The above thread mentions a package Lint.jl. Although it can detect most of undeclared variables and it may be a solution, it doesn’t warn me of the bud variable in this particular case, perhaps because of the same reason as @code_warntype. I wonder how Julia users detect misspelled variable-names that are syntactically correct but definitely wrong when the tools doesn’t work well. Or, there should be a certain coding-style that avoids bugs caused by misspelling as well as that maximize the performance.

@pasha, yes, use of @show may be the simplest way. I will notice my misspelling while writing all possible @shows. I also tried Traceur.jl and it looks useful. Thank you for the information.


#8

The key difference is that in both Python and Julia you don’t declare local variables. So there’s no way to distinguish between a new variable and a typo in some cases.


#9

But, isn’t this just because there is no mode in Julia and Python for requiring that local variables be declared ? (which may, as you said, be difficult to implement in Julia). In Perl, you don’t need to declare local variables either— unless you turn the feature on with use strict. If you don’t use strict then it is harder to find mistyped identifiers.

When I wrote a lot of Perl code, I found use strict indispensable. I could not understand how the Python world could survive with only some kind of linting tool. But, now I don’t miss it in Python. And I can only remember one, maybe two times, when the equivalent of use strict would have saved me time finding a typo in Julia code. There must be some reasons. One is that, in Perl, an unbound identifier will act like it has the value "" if a string is expected, and 0 if a number is expected. In Julia, these would throw an error.


#10

Yes, exactly.

One is that, in Perl, an unbound identifier will act like it has the value "" if a string is expected, and 0 if a number is expected. In Julia, these would throw an error.

Having programmed in Perl quite a bit (once upon a time), I think this is really the main value of use strict. It bundles this with requiring the declaration of each variable, which helps catching typos, but the real thing it provides is less dangerous auto-initialization. Since Julia and Python don’t do auto-initialization of variables in the first place, the benefit of something like use strict is limited to catching typos.

I could imagine some way of telling Julia to require declaring all local variables, but that’s a fairly significant feature and there’s probably not sufficient motivation for having it in Base. Personally, I would find the annoyance of always having to declare all my locals to far outweigh the occasional inconvenience of hunting for a typo bug.

One thing that I’ve thought about a few times is having a way to let a macro pre-process all code in a module. This would be really useful for Compat, for example. It would also allow this kind of feature to be implemented in a package by letting it see each expression in a module and check if it assigns any locals without declaring them first.


#11

It’s even worse than that in Perl because the variables are not really autoinitialized.

print 'Is $i an empty string: ',$i eq "","\n";
print 'Is $i equal to zero: ',$i == 0,"\n";
print 'Is $i not defined: ', !defined($i),"\n";

gives

Is $i an empty string: 1
Is $i equal to zero: 1
Is $i not defined: 1

Personally, I would find the annoyance of always having to declare all my locals to far outweigh the occasional inconvenience of hunting for a typo bug.

I definitely agree. I doubt you’ll find many (any?) people who would want such a feature, or encourage its use. But, yeah, there may come a day when a macro that checks for assigning to undeclared locals would be useful.


#12

There are definitely occasionally people who really want this. Of course they have invariably just spent an hour looking for a bug that turned out to be a typo :grin:


#13

There’s a difference between declaring a local variable versus finding a local variable that is bound but never used. In both cases it enforces that a variable is found in two places hence catching the typo.

I would be good with either functionalities. Could a good IDE warn the developer? Eg. Eclipse for Java can do that.


#14

Sure, this is something a good linter should handle.


#15

With all the changes in master, I haven’t used 'Lint.jl` in a while, do you know if it is working lately?


#16

That’s basically 90% of my bugs :grin:, the others tend to be issues with ambiguity in Julia, so it would be nice to have an easy way of checking for this quickly.


#17

No clue, I’ve never really used it (mostly because it’s rather bad imho).
I’m much more excited about a CSTParser.jl based linter similar to what the Julia plugin for VSCode uses right now.


#18

I have a module level print_workspace function here:

Maybe you can go somewhere with that?


FWIW In the past, Ive just found the workspace before and after some action and then a setdiff on the two arrays gives you a list of new local variables.

I.e. by calling the following func before and after loading a file you can see the number of new methods:


#19

FWIW, this kind of functionality is often easier to implement than it looks. With MacroTools:

using MacroTools: prewalk
using MacroTools

# Your example:
expr = quote
    function readfile(filename::String)
   a = Dict{String,Int}()
   buf = ""
   open(filename,"r") do ios
      while(!eof(ios))
         bud = strip(readline(ios))
         items = split(buf,"")
         a[items[1]] = length(items)
      end
   end
   return a
end
end

# Gather all local variables
vars = []
prewalk(expr) do x
    @match x begin
        (lhs_Symbol = b_) => push!(vars, lhs)
        for var_Symbol in _ __ end => push!(vars, var)
        ((args__,) -> _) => push!(vars, [splitarg(a)[1] for a in args]...)
    end
    x
end
vars

returns [:a, :buf, :ios, :bud, :items]. It’s by no means a complete solution, but I don’t think you’d need that many more lines to find all local variables in every function in a file. Then if you gather all variable uses with a similar strategy, you can detect that :bud is never used, and emit a warning…