Hi good folks!
One of my pet peeves with Julia is the need to define types in the code above the lines where they are used. I have a few questions regarding this.
1, Is this a technical limitation or a design choice?
2, Is it likely to stay in Julia for the foreseeable future?
3, Is it a sign of bad code organization on my part that I sometimes find that it limits me in structuring my code in the way I would like?
Could you give an example of what “define types in the code above the lines where they are used” looks like? E.g. I can do this:
julia> x = 1
julia> y = 2.0
julia> z = x + y
without ever specifying types anywhere?
I understand the frustration. Especially when I have a circular reference between types, it can be difficult to reason about what should go where.
I don’t think this is going away because the type information in the signature of each function needs to be resolved for multiple dispatch to work, but I’m no expert here.
Re: code design - the approach I generally take to reduce friction is
use parametric typing in my structs so it doesn’t matter what order they come in.
Keep all my types in one or more separate files along with their accessors and any overloaded functions I want to define for those types. Then
include those at the top of my core logic file. This has the added benefit that I can split my screen between whatever function I’m working on and the the type(s) I need to reference.
I could be wrong, but I think OP means something like
struct MyType end
Where you might want to have
main at the top of your file to serve as a clear entry point to the code (so keep it at the top).
Thanks for the answer. I’ve used a similar strategy with separate files with all type definitions before but I’ve noticed that most “professional” Julia modules don’t use this strategy.
Regarding the technical need… I mean, functions can be defined below their use so I’m not sure why the same pass of the code that picks up that can’t pick up type definitions below their use.
The symbols inside a function aren’t resolved until compile time, but the type of the function itself is registered upon creation and the new method (identified by the types on which it is dispatched) is also added to the method table.
f(x) = "fall back"
f(x::MyType) = "MyType"
These two methods both need to be added to the method table. The way they are distinguished is the actual identity of the types.
And since multiple packages could define and export a type with the same name, the methods need to resolve the whole type identity to avoid collision.
As far as “professional” Julia code… Eh…
I don’t know why many packages have 1000 line source files. It’s not my preference. I maintain ExpandNestedData.jl and have even wrapped subsystems in modules because I like to explicitly import functions into the current context. I find it makes the code more readable.
Sadly it flys a bit above my head why this leads to the need to define types but not functions above when they are used. I think it is more my limited head than your explanation though.