First, julia has it's own IR (comparable to swift IR as mentioned by Chris), which is used to great effect in the autodiff packages to allow for compile time zero overhead gradients. The flexibility is actually the other way around, Julia's compiler allows these sort of compile time initiatives to be built in Julia rather than hacked into C++ like swift.
Regarding the duck typing vs type checking bit, that's a matter of tooling. It's much harder to replicate Julia's combination of speed and dynamism (for example to do flexible multistage programming) than to capitalize on Julia's the compile time information to build type checking (which are planned)
The compiler lag is going to be gone soon from two ends: more dynamic (interpreting code that doesn't need to be compiled, this is pretty much there already) and more static: Compiling and caching entire Julia programs. There are already fantastic improvements to this situation in 1.2
That leaves the error messages and stack traces, which ...well what's the last version you have tried? I find them to be ok, but they can and will be improved.
Regarding general purpose packages in Julia: check out http://genieframework.com/ as an example.
Julia's multimethods are awesome and a clear advantage over swift's methods. A protocol abstraction is similiar to julia's facility for traits, will at some point will be likely baked into the language (but even now allows for compile time resolution of methods just through multi dispatch abstraction , which again, is only one step away from building type check abstractions!).
If you want to know more about Julia's plans and philosophy in the regards, see: https://github.com/FluxML/Flux.jl/issues/614
Swift has clearly an advantage in that domain, as
has C++, C#. In an enterprise setting such as Google no one
actually wants speed and dynamism out of a language (C++
programmers are not allowed to use rtti for example), rather
consistently boring, predictable, statically guaranteed results
with as minimal fuzz as possible (Go, Java, C++) and enough
competent programmers in that language.
I agree that it is neat that you can hack the Julia
compiler in a library to do compile time
automatic differentiation, but my point was that if you
build it into the language like the Swift for Tensorflow
authors are doing this will result in a more conservative,
if inflexible, but potentially more performant solution.
In other words precisely because you need a team of people
to implement and integrate this feature in C++ you will
get a stable "single source of truth" implementation
in the language, not several (Flux, Zygote)
constantly changing libraries.
Well I've tried DiffEqFlux.jl with Julia 1.1, it works
well enough, but the stack traces are ridiculously long
if something goes wrong, a ~200 LOC program takes seconds
to compile and the library and package management story seems
immature compared to rust/swift/python/c++.
That's a pretty weird statement, considering how hard it is to write a complex C++ program error free. I suppose Swift is much better in this regard - But compared to C++, Julia is a bliss and one can easily write huge applications error free, while I had my absolute worst debugging experiences in C++, where it's very easy to create obscure and hard to find bugs.
If you really need to get predictable results and actually know those results - you won't get away with a static type checker anyways, but will have to write & run tests, which then pretty much make it irrelevant whether your language is dynamic or not.
I just run my code in small batches while developing it, so at every point I already know that it works. This helps writing code that nicely separates into small chunks of functionality, and you basically automatically write tests for it while at it.
This is especially nice when refactoring a large code base. I can already run many small and usable tests in the middle of a refactor (while the program wouldn't even compile in a static language).
In my experience, this greatly helps to improve the quality of the refactor, and makes it much easier to see problems in your refactoring approach very early on.
In a static language, you'd only get these crucial insights after you're done with fixing all compilation errors - which is usually when you're already done with the whole refactor... and after that, you might still have lots of errors in it that you can only find with real tests ;)