A bit of n00b question. Coming from a Scala / Rust background, I really really prefer compile time errors to runtime errors.
Right now, my biggest frustration with Julia is that issues of “we can’t find an impl for this multi dispatch fn” happen at runtime, not at compile time.
I am wondering, is there a way to detect these at compile time? This would significantly speed up my Julia coding cycle.
Errors are not thrown at that time, they’re thrown at runtime even when it’s statically known at compile time that they’ll throw.
Revise has nothing to do with any of this (though it’s a great tool for what it does do)
julia> function f()
@noinline sleep(10)
throw("Aw crap, I wasted a bunch of time.")
end
f (generic function with 1 method)
julia> code_typed(f, ())
CodeInfo(
1 ─ invoke Main.sleep(10::Int64)::Nothing
│ Main.throw("Aw crap, I wasted a bunch of time.")::Union{}
└── unreachable
) => Union{}
julia> f() # 10 seconds later
ERROR: "Aw crap, I wasted a bunch of time."
Stacktrace:
[1] f()
@ Main ./REPL[14]:3
[2] top-level scope
@ REPL[15]:1
We can use JET.jl to address this:
julia> JET.report_call(f, ()) # No waiting 10 seconds
═════ 1 possible error found ═════
┌ @ REPL[14]:1 f()
│ may throw: throw("Aw crap, I wasted a bunch of time.")
└──────────────
@zeroexcuses Here’s an example of JET discovering a missing method error ahead of time:
julia> f(x) = g(x);
julia> g(::Int) = 1;
julia> JET.report_call(f, (Int,))
No errors detected
julia> JET.report_call(f, (Float64,))
═════ 1 possible error found ═════
┌ @ REPL[20]:1 g(x)
│ no matching method found `g(::Float64)`: g(x::Float64)
└──────────────
I have a dumb question: if multiple dispatch is resolved at compile time (say when I run julia server.jl) for optimizations[1], it should be known then that “err, we don’t have an impl that matches this call”; and an error / warning could be thrown then.
Instead it waits until runtime. Mechanically, what is happening to cause this behaviour ?
[1] This is the justification many use for Julia’s (potential) slow startup, and the recommendations for Revise.jl and DaemonMode.jl
As far as I understand, the reason is that semantically julia is supposed to behave as an interpreted language. Our type inference is only meant to be used as an optimization to speed up the execution of the language. Throwing known errors early at compile time could potentially break these semantics.
For instance, consider this code:
const store = []
function foo()
push!(store, 1)
throw("an error")
end
foo()
In an interpreted mode that doesn’t do any inference or optimizations, after running this code you’d get that store == [1], but if you threw the error at the start of the function you’d get store == [].
Now, I suppose if the compiler knew that a function did not have side effects, it’d be semantically okay for us to hoist the error path to the start of the runtime, but either it was decided that was also a bad idea, or it may be nobody thought it was a good enough of an idea to put the work into implementing it.
I realize this is a bit untypical, but I’m running a webserver as follows:
sigint_handler() {
kill $PID
exit
}
trap sigint_handler SIGINT
while true; do
sleep 0.1s
clear
echo "==============================="
julia www/server.jl &
PID=$!
inotifywait -e close_write www/server.jl
kill $PID
done
I’m wondering if there is a way to inject the JET.jl as a static check before the julia server.jl; so it starts the server if and only if JET.jl finds no obvious errors.
I’d recommend instead that you do that loop in julia itself instead of constantly launching and closing julia every 0.1 seconds.
But to answer your question, yeah you could just right before the loop run a quick check with JET.jl, though JET is really best used as a development aid than a automatic checker.
This is not the behavior. The inotifywait causes it to only trigger when www/server.jl is modified. The 0.1 second is a safety in case the IDE does weird things to www/server.jl .
Can you enlighten me on this? How would you do this in Julia? You would somehow need a way to kill the old HTTP.jl server and start a new one based on www/server.jl . It is not obvious to me how to do this besides calling out to shell commands.
yeah because that code is only compiled when you “run into” the piece of code that contains stupid code.
You can technically say every language has “compile time” because somewhere there’s time spent on translating human-readable code into assembly binary, but the point is in Julia you only compile “just ahead of time”, loosely you can even say “compile is just an optimization”
if you understand JIT and knows it’s a JIT then you know what I mean by “no compile time” – because the compiler doesn’t see the function until you hit the function during runtime.
But one thing that’s different from other things people call “JIT” is that Julia always eagerly and very extensively compiles the code you are about to execute. (which is very different than, say, Java)
if recursively every function call can be inferred, yes, we can probe this behavior by using @generated so we know when the function is getting compiled:
julia> @generated function g(i)
@show "compiling $i"
return 1
end
julia> function main()
sleep(10)
if 0.5 > rand()
g(1)
else
g(1.0)
end
end
julia> main()
"compiling $(i)" = "compiling Int64" # instant
"compiling $(i)" = "compiling Float64" # instant
# 10 seconds wait here
1
# you need to re-start Julia here, or re-define g()
julia> function main2()
sleep(10)
a = rand((1, 1.0, "1", pi)) # this is unstable enough Julia won't try to union split
g(a)
end
julia> main2()
# 10 seconds wait here
"compiling $(i)" = "compiling Irrational{:π}"
1
but usually it’s not the case, and some function calls are bound to be dynamic, thus you get to complain error not occurring earlier
I can see how it looks like I’m complaining about a minor issue. I have a webserver currently written in OCaml. I’m considering rewriting it in Julia, as I now need it to serve tensor related requests.
During the rewrite process, I’m running into issues I’m surprised are not reported at compile time. I don’t have a mathematical proof of this, but I suspect ML code, wiritten in Julia style, tend to be statically type-infer-able? [Not 100% sure].
I don’t think that’s a safe assumption, compiler can easily give up inference for many reasons. I think your complain is perfectly reasonable, and I’m aware the powerfulness of OCaml (for example, it has typed function (in/out put) that Julia doesn’t have, so that’s simply a large chunk of information inaccessible to Julia compiler).
I suspect you should focus on making development workflow better by using Revise, instead of trying to force Julia to infer every “business” logic function at compile time (may not be possible)
It does feel like I’m fighting an uphill battle. Do you have a recommendation of a Youtube video of someone showing off “idiomatic” Julia development workflow for non-numerical code ?
For numerical code, everything is a tensor, and there is not much to show off on the type side. I’m really interested in seeing what “idiomatic” Julia development for using types looks like.