Version 1.0 released of Nim Programming Language

A variation on this problem

if not error
  println(answer)
else
  println(err_mse)
end

oops i mispelled, “err_msg”, but I don’t find out about it until i actually hit that else statement. I HATE THAT. This was my #1 source of bugs in python code. Seldom executed else clauses would throw errors after the program had run for 1/2 hour. It made python really sketchy for large scale projects (I have no idea how people can write reliable 10,000 line python programs).

And no, I don’t think I should run a test case for this because the variable doesn’t exist and the compiler should tell me that. How can Julia possibly do all this work on method matching and type dispatching and when it gets all said and done, and that variable is not defined ANYWHERE, comes back and says : yeah- ur gud to go. Yes I know there are probably very good reasons for it, but it sure is unintuitive.

Now it’s a substantial source of julia bugs. Yes, it does makes me wonder what happens in large scale julia programs. “My program has been running for 3 days, but just crashed because of a typo in a variable name in an else clause”.

Advantage Nim ?

2 Likes

TDD? Unit tests?

2 Likes

Just to spell it out but the reason is global variables

julia> f() = print(x)
f (generic function with 1 method)

julia> f()
ERROR: UndefVarError: x not defined
Stacktrace:
 [1] f() at ./REPL[1]:1
 [2] top-level scope at REPL[2]:1

julia> x = "hello"
"hello"

julia> f()
hello

The VSCode linter is (was) pretty good at finding these.

2 Likes

The problem is that if I define err_mse = "boo!" in an enclosing scope then the variable can exist even if it wasn’t defined in the function body itself and there is no way to statically guarantee that it won’t exist until you actually run the program and try to evaluate println(err_mse). You can restrict the language so that such a thing is not possible, but you are left with a strictly less expressive language. For some that’s an advantage for others, it’s a pain in the neck.

Julia so far has been concerned with offering as much dynamism as it can possibly offer without sacrificing performance. There hasn’t yet been a serious emphasis in julia’s development on restricting dynamism for the purposes of catching bugs. Instead, the community so far seems to mostly agree that robust test suites are the answer since actual runtime tests can catch strictly more bugs than compile time analysis (you have access to types and values instead of just types).

That said, I think julia is in a really cool position because it has such powerful reflection and introspection capabilities, I think there’s definitely room for a system of packages built around making compile time restrictions on julia code so that you can tape off a section of your code base and demand it satisfies a list of static guarantees, such as having a frozen, untouchable method table, no runtime eval, no dynamic dispatch, etc.


I played around with a macro and found a (brittle and unpolished) way of warning you at macro expansion time if you’re defining a function with unassigned symbols in it.

using MacroTools: splitdef, prewalk, @capture 

strip_args(ex::Expr) = (ex.head == :(::) || ex.head == :(=)) ? ex.args[1] : ex
strip_args(s) = s

macro check_names(fdef)
    d = splitdef(fdef)
    assigned_names = Vector{Symbol}(strip_args.(d[:args]))
    prewalk(d[:body]) do x
        @capture(x, name_ = val_) && push!(assigned_names, name)
        if x isa Symbol && !isdefined(__module__, x) && !(x ∈ assigned_names)
            @warn "Symbol $x not defined at function definition time." 
        end
        x
    end
    fdef        
end

Now if we apply this macro to a function definition with your problematic piece of code, we get a warning:

julia> @check_names function foo(error::Bool)
           answer = 10
           error_msg = "hi"
           if !error
               println(answer)
           else
               println(err_mse)
           end
       end
┌ Warning: Symbol err_mse not defined at function definition time.
└ @ Main REPL[4]:8
foo (generic function with 1 method)

In general though, this is just something a linter should be doing on your whole code base. It doesn’t really need to be enforced by a compiler.

7 Likes

Petr, of the community, agrees with you, :slight_smile:

yes well, that’s a longer conversation about when I need to write tests and depends on what kind of code i’m writing (i’m referring to the extent of testing - i’m always testing whether i like it or not :wink: . I think we can agree that the compiler catching a typo is preferable to devising a run-time test to catch a typo, or can we ?

So, trade-offs, trade-offs. Ever the problem…
Looking at a C program, if the variable is not declared, ERROR. Therefore,

global varname :: T

as a requirement means a global has to be declared and problem solved, right ? However, by the theorem of “simple and wrong”, it’s not that easy, or it would have been done. :thinking:

I believe i’m taking the off-topic thread, off-topic. I’ll stop now.

A compiler that doesn’t catch typos aside, I love Julia, and I think Nim is pretty neat too. I thought CLOS was the greatest thing ever, and I was always a fan of macros, so discovering Julia (back at 0.48) and seeing that it incorporated those kind of things sure was exciting :smiley: I’ve been using it ever since. in my little corner of the world it’s a bit of a tough sell, because Matlab. But that’s a different off-topic thread.

6 Likes