Well, why though ? Julia has plenty of numerical libraries going for it, and all of them can be used in a differentiable manner (see, Neural ODE etc.). While Julia was built by a mix of people doing Numerical Linear Algebra and PLs (MIT, UCSD..), Swift seems to be entirely the output of the latter group.
I wonder how important more superficial elements will be in terms of future adoption. As a Python programmer Swift looks much more familiar and if you can easily import familiar packages from Python then maybe this will help overcome the inertia required to switch, especially in industry.
Even if Swift wins over Julia it certainly won't be because of these two points (syntax and Python interop). Julia probably has the best Python interop out of any other language and the syntax is fairly simple (although a bit ugly with the 'end' blocks and the allowed unicode characters).
One of the things that people in my field (physics) love when I showcase Julia is the ability to use greek letters as variables: this makes mathematical formulae so clean to read!
Don't you dare talk shit about my Unicode characters. Any langauge written after Unicode's adoption has no excuse not to use them. They're wonderful. They're magical. They let you say exactly what you mean, and keep it condensed enought that you can read it at a glance.
If you're in a field where symbols have existing meanings, it's asinine to make your code clunky and harder to read by not using those existing meanings.
'end' is subjective (I like it). The unicode characters allow to write syntax which is very close to mathematical formulae, this can help a lot with reading/understanding in some situations.
Python is my main language, and I struggled mightily to get my head around some aspects of Swift (e.g. randomly sticking question marks in different places until it was happy). As an aside, I also found the API incredibly verbose, the documentation poor, and Xcode to be a bad IDE.
In contrast, I enjoyed Julia - while the documentation isn't great either, after a single afternoon of learning (with plenty of Googling) I was able to port code over from Python and have it run perfectly.
Julia is compiled unlike Python though (at least by default), the moment you call Zygote it will have to run through the entire program you want to differentiate and fail immediately if any type does not match (without the need of any type annotation besides in the method arguments for the multiple dispatch and structs, as Julia has type inference). As some people say, Julia will fail "Just Ahead of Time", and if you're experimenting with live code in the REPL or Jupyter (the recommended workflow) it could very well be just as good.
That said, for machine learning a static type system (as they are now) is definitely not as much of a boom as in most other areas. Most of it involves tensor operations, and embedding things like it's shape in the generics will often lead to an exponential explosion of possible monomorphizations that the compiler will be forced to create. Even JIT languages like Julia will have trouble even though it only needs to compile when it's used (for example StaticArrays will get to a point when the compiler will take so much time that it's not worth it anymore). And even then I feel like most of the issues that actually take time are deeper than stuff like shapes and not naming dimensions, like numerical instabilities, hyperparameter tuning, architecture search and other stuff that gets more benefit of a language allowing quicker exploration.
Python is also compiled to bytecode in the mainstream implementation. But that is an implementation detail and has nothing to do with the (lack of a) type system.
Julia is also "compiled" down to it's (untyped) IR before even starting (when all macros are expanded), but since it's a dynamic language like Python, it can't know types at compile-time, so this step can only know parser errors. It could only type check at this step if the type was in the definition and not in the variable inside (if it was static). And both Julia and Python are also strongly typed (there isn't really a language that lacks a type system).
The difference is that whenever a function in called in Julia, based on the type of the arguments the compiler can infer every type of every variable and arguments of every subsequent function, immediately compiling them down to machine code (unlike Python there is no such thing as a Julia Virtual Machine or even a Julia Interpreter outside of the debugger). Whenever you enter a function in Julia it becomes a static program, with all the properties of a static language (for example, you can't redefine types, you can define functions but the program can't see them since they are not part of the compiled code, you can't import libraries, and all types were already checked before running). That's why Julia is fast, and the language was entirely designed for working this way (there are lots of things you can do in Python that you can't in Julia to make this work).
> And both Julia and Python are also strongly typed (there isn't really a language that lacks a type system).
No they are not. At least not for my definition of the term "strongly typed". Of course you can use a different definition, but if, as you say, "there isn't really a language" that does not fulfill your definition you might want to reconsider its usefulness. The point of classifying languages is mood when every fan comes along and says "my favorite language also has that, if you tweak your understanding just a bit".
I used the more common definition (strong vs weak being orthogonal to static vs dynamic). Both Julia and Python are strong and dynamic (and duck typed).
This is me querying Julia for it's typed intermediate representation of the function `foo` I defined if the input is an integer. As you may be able to see, this is a statically typed program. So while Julia is a dynamic language, the interior of function bodies can be static if type inference succeeds. However, if type inference fails, Julia is also perfectly happy to just let chunks be dynamic.
If you're having trouble understanding that code_typed output and are familiar with C, maybe the output LLVM instructions are helpful:
Also, I encourage you to note that nowhere did I manually put a type annotation in my definition of foo. foo is a generic function and will compile new methods whenever it encounters a new type.
You seem to confuse type checking with shape-driven optimization. Of course the latter can borrow a lot of terminology from the former and in a typed language you would be stupid not to use your types, but they are different topics. Any decent JavaScript or lisp compiler or interpreter will do the same.
Also what Julia does is not exactly type inference. It is rather a form of specialization. Type inference just gives you the type for an expression using just the syntactical representation. Nothing more.
As an example, if I was to invoke your function f with a floating point number, would Julia not happily specialize it for that too (assuming factorial was defined for floating points or taken out of the equation)? The thing here is, that a type checker needs to know about all the specializations a-priori.
Not sure what you mean by that example. If "f" accepts a float and you give a float it will work. If "f" accepts a number (or t subclass number) it will work since float subclass number. If "f" only accepts int then it will fail since you can't convert a float to int (as in there are no defined promotion/conversion rule from float to int). The type check works just like any language.
Specialization will only occur if there are more than one "f" that handles your type (for example both "f" with a number and with float), and then the compiler will choose the most specialized (float). If you write your Julia program like you would with a static language (for example using over-restrictive types like just float and int) it will return the same error as the static language as soon as it can infer the types (when it leaves the global namespace and enters any function). Using over-restrictive types (and declaring return types for methods) is considered bad practice in Julia, but that's a cultural thing, not a limitation of the type checking.
It happens at compile time in Julia (required for the multiple dispatch to work), the compile time is just interlocked between runtime steps. Calling something with incorrect arguments will fail even if the function is never called during runtime (unless the compiler figures that out using only compile time information and decides not to compile it).
But I understand your point, it's not really like a static language checking as it can't do without running the program, so it's better than fully interpreted (a unit test would catch errors even in paths it didn't take) but worse than static checking (as it can't compile 100% of the valid program at once).
> As an example, if I was to invoke your function f with a floating point number, would Julia not happily specialize it for that too (assuming factorial was defined for floating points or taken out of the equation)?
Yes, but I could then define
bar(x::Int64) = x^2 - 1
and this would create a method which only operates on 64 bit integers.
Now I want to be clear that I'm not saying Julia is a static language, I'm just saying that once specialization occurs, things are much more like a static language than one might expect, and indeed it's quite possible add mechanisms for instance to make a function error if it can't statically infer it's return type at compile time.