Compile time: we can't find any impls for this multi dispatch fn?

  1. Setup a HTTP handler.

  2. Do something stupid in the HTTP handler with HTTP.setheader(…) involving multiple dispatch.

  3. Notice: this error is not caught when we run julia server.jl, it happens only when we run curl localhost:8080

@Mason : Thank you for the suggestion of JET.jl .

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.

4 Likes

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.

1 Like

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.

I’m mentioning Revise to answer “This would significantly speed up my Julia coding cycle.”

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”

I’m really confused now. This sounds alot like a JIT, but instead, it’s called “just AOT” ?

Nope, it’s called JIT. Julia is JIT compiled.

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)

I don’t understand this statement. Suppose we have:

f(x, y, z):
  a = f_a(x, y);
  b = f_b(a, x, y, z);
  c = f_c(a, b, x, y, z);
  return c

and now we have to compile function f. Does it then compile f_a, f_b, f_c (and what those functions depend on)?

If so, it seems like we’re going to compile the entire program at once, on the first execution of main().

If not, it is not clear what “aggressive optimizations” it can do.

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.

depends on what you need. People have pointed you towards JET.jl for static analysis that understands typing system. And I have suggested Revise.jl so you don’t have to keep re-launching your program.

How do you kill the existing webserver / restart a new webserver on file change with Revise.jl ?

I think @jling is proposing that you don’t have to: unlike Scala, Julia is capable of updating code in the middle of a running session. See Developer reference · Revise.jl and consider checking out Genie.jl, which I believe already implements all this for you.

1 Like