-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Non-dispatchable field specialization syntax. #45687
Comments
Here is a concrete example from an actual user code I saw while giving a workshop this week: https://gist.github.com/ChrisRackauckas/2861111912ec3b3f7ec4d7cd74e9567e The stack trace is decreased from 15k characters to 7.5k characters without losing any real information. Furthermore, if anyone did dispatch on these type parameters, I'd close their issue saying they were relying on non-public API information and so there code is brittle and I want nothing to do it it. These parameters are not just something that don't tend to be dispatch on, they are never intended to be dispatched on. Today: Tomorrow: |
Duplicate of #36201 |
Since the primary issue here is stack traces being large, what about just not printing type parameters in stack traces that don't matter for dispatch of the method that's called? That would allow understanding what method was called and why without printing so much information in the stack trace and doesn't require any new language features either. |
What I think might also be interesting is to skip printing types that already appear elsewhere in the stacktrace |
I'm not sure I follow that idea. |
If you have |
Concerning readable stack traces: Often I am also interested only in functions in my current project. Edit: https://github.com/BioTurboNick/AbbreviatedStackTraces.jl seems to tackle this problem |
@vtjnash, that would seem to be more accurately described as not printing the type of values whose type has already been printed in the stack trace. The same type could be printed multiple times. Moreover, given that arguments don't generally have the same name or position all the way through the stack, that seems like it would be very confusing. I'd like to focus on the core problem here: we print entire types in stack traces that are huge. The clearest most implementable solution is to limit what aspects of a type in a stack trace we print to only the parts of the type that are relevant to dispatch. This can be computed as a combination of concrete types of the arguments and the type of the method that is called. For example, if method is |
I like this proposal for reasons beyond getting more readable stacktraces. Also, writing structs with ten type parameters gets tiring and I am sometimes tempted to leave performance on the table just to avoid writing this (as I had to do yesterday): struct Tree{
StateNodeArray,
BoolNodeArray,
Int16NodeArray,
Float32NodeArray,
BoolActionArray,
Int16ActionArray,
Float32ActionArray,
}
state::StateNodeArray
terminal::BoolNodeArray
parent::Int16NodeArray
valid_actions::BoolActionArray
prev_action::Int16NodeArray
prev_reward::Float32NodeArray
prev_switched::BoolNodeArray
policy_prior::Float32ActionArray
value_prior::Float32NodeArray
num_visits::Int16NodeArray
total_values::Float32NodeArray
children::Int16ActionArray
end |
Does it matter that some of the specialize only fields seems constrained to have the same type? This proposal doesn't offer a way to express that but it could. Eg I'm thinking of using a semicolon to separate the "real parameters" from the specialize only ones. Of course then you still have to give them a name, so maybe you could have another feature for omitting the name entirely (dare I suggest underscore?). |
In the example I posted, the ones that are the same are only the same in order to decrease the number of type parameters and improve debugging. In fact, most of them are incorrect and we could specialize more if we set some to If necessary in a first implementation, one can just use an inner constructor. |
Reopened due to more discussions with @Keno. This discussion focused around the fact that it's not just stack traces but it also would make method matching less expensive. Basically what it comes down to is the following. Those type parameters are thrown in the stack trace because they can be dispatched on. But, they weren't made to be dispatched on, they were made to specialize the type for performance. If someone dispatched on them, I would want to just slap them with an error that said "don't do that". So to me, this is pointing to two things accidentally being confused as one. Parametric typing and specialization are two separate features, they do two different things. They are just mixed here, but they do not necessarily need to be. You could specialize a type on types of fields without exposing the ability to dispatch on those field types to the user. So privacy of the types is just one aspect, and probably not the most important. But allowing specialization without requiring dispatchability has multiple uses. |
With the printing improvements I think this can be closed. |
Whilst the stack-trace was the most notable expression of the problem, I do think that a distinction of "specializeable" vs "dispatchable" field-types could be a good language feature. So maybe leave this open, or write a new issue which focuses only on that? |
I was looking again at #40138 and my more evolved understanding of this issue is the fact that fully parametric fields are treated in the same manner as other type parameters, and that just seems wrong. For example, if I have a type
What our current stacktrace assumes is that knowing
T
is "fundamental" to understandingMyType
. In reality, I only did::T
because I wanted the Julia compiler to optimize it. If it wasn't a programming optimization, then I would've done:There's nothing fundamental to understanding the type of
x
that is required for understanding howMyType
works: nothing ever dispatches on it, it's not part of the public API (so I don't intend anyone to ever dispatch on it), yet it's treated as first-class information. How can I allow program specialization here without allowing ease of dispatch? I can't. This seems to be a fundamentally missing feature in the type system.For an example of this in practice, look at none other than the infamous
ODEIntegrator
type.where one of its fields are:
Beautiful. This is the thing that generated a literally 10,000 character stack trace when Unitful numbers were put into it. But why? Let's understand this in the context of the above.
abstol
andreltol
are parametrically typed because it's possible for a user to pass different types in there. Yes it's normal tosolve(prob,Tsit5(),abstol=1e-6,reltol=1e-6)
, but you can also dosolve(prob,Tsit5(),abstol=[1e-6,1e-2],reltol=1e-6)
to have per-element tolerances. Great feature right? Well by allowing this feature, I now have to make the internal struct parametric, and so now everybody who uses the ODE solver has to seeabsType
andrelType
in every stack trace. Mind you, not a single function in the entire library dispatches on this type parameter, nor is anything ever intended to, so this type parameter is a waste of time in any stacktrace: I've just learned to ignore it.The integrator type has >100 type parameters at any given type, and the first thing we have to train a user to do is ignore all of them. What the hell? Don't we think something has gone very wrong here?
I think the issue is that we have in the type syntax conflated the different ideas of "I want this program to specialize on this field" and "I want to be able to dispatch on this field". When we write functions, we do
f(x)
and we know thatf
will (most likely, some heuristics) specialize on the type ofx
. We do not require that people writef(x::T) where T
everywhere. If we did, our function stack traces would be ginormous because every call would describe methods likef(x::T,y::T2,z::T3) where {T1.T2.T3}
and people would go "what does this mean?". But we do not treat types the same way: we force basically "where T" on every field that we want to specialize, and we print this information to the user that basically just say "hey, DifferentialEquations.jl likes to specialize, you wanted to know that right?"No, nobody cares. So what can we do? What about a new language feature for non-dispatchable field specialization syntax?
And it would then be really nice to have a macro in Base where this is equivalent to:
This better captures the essence of what the type is, makes it easier to write code without performance bugs, and will go a very long way to making the stacktraces more legible.
The text was updated successfully, but these errors were encountered: