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
Collector.register's signature doesn't make sense #431
Comments
What you propose would be a breaking change for all existing users. It promises to return a T which is a subclass of Collector, and does just that by returning itself. This way of doing things ensures that the resultant type is not Collector and thus the user doesn't have to cast it. |
Hi @brian-brazil, thanks for responding.
I realize that it can not be changed in a bugfix release.
No, that is not what is promises. It promises to return an arbitrary subtype of Collector that the caller gets to choose.
I'm afraid that's not how this works. Something like this won't compile: Counter.build().name("foo").help("bar").create().register().inc(); The compiler has no reason to infer Counter.build().name("foo").help("bar").create().<Counter>register().inc() I don't see how that's any better than a cast. In fact it's much worse, because when there is a context to infer the type parameter from, the Java compiler will do just that. So this compiles and crashes at run-time: Gauge g = Counter.build().name("foo").help("bar").create().register(); // no cast needed If you want to offer a convenient, fluent interface, you need to do what I proposed above and declare the type parameter on the class rather than the method. |
The current logic was added in #86 for the purposes of working with Scala.
This is not sane usage, a counter should be assigned to a field. |
Well, what can I say? It shouldn't have been.
That is besides the point. Please take another look at my last example: Gauge g = Counter.build().name("foo").help("bar").create().register(); // no cast needed This looks innocuous at first but will crash at run-time. Don't you agree that that's a problem? |
It'll crash immediately at startup with expected usage, not during serving. Do you have a suggestion that's not a breaking change? |
No, there is no way to fix this without changing the signature. It's the signature that's wrong, not the implementation. |
By the way, the current signature of |
Note that most users would not be affected if a type parameter is added to |
By the way, this is actually not true when you declare the variable using a type parameter (because of erasure). class Foo<T extends Collector> {
public T t = Counter.build().name("foo").help("bar").create().register();
}
new Foo<Gauge>(); This will compile and not crash, until somebody tries to use |
Custom collectors are reasonably common, all exporters have them for example.
This is not a sane usage. |
I disagree with the notion that every usage you don't like is “not sane”. The only thing I see here that isn't sane is the signature of the method. By the way, why is the API break in #86 acceptable? Because it changes the API in the exact same way that I'm proposing here. |
@brian-brazil pls correct me if I am wrong but one of the main purposes of type system is to prevent accidental bugs caused by unintentional "not sane usage". For example, passing a Boolean into a method that accepts strings is obviously not a sane usage of that method but compiler helps us out there emitting an error and not letting this break at runtime.
When things pass compilation - they get into run time. There is no such a notion of "just before the real run time". @brian-brazil do you agree that this signature can be improved in future releases to protect inexperienced programmers from accidental bugs? |
There's initialisation, and then there's serving. This is one of a few errors that'd only get caught on initialisation.
I'm open to non-breaking suggestions. |
Yeah, and then there's lazy initialization, so this can lead to bugs at any time.
This is a fancy way of saying that you're not open to suggestions since, as I pointed out before, there is no backward-compatible way to fix this. I can only ask you to reconsider, I don't think this kind of bug is acceptable in a well-behaved Java library. @willfleury, you were involved in #86, what's your opinion about this? |
Oh, I would like to point out that adding a type parameter to the Collector class would not affect binary compatibility since generics are erased. So this would only affect source compatibility, not binary compatibility. |
I care about source compatibility. Is this actually something you've run into, or more a theoretical concern? |
It causes issues for me because it causes dead code warnings in the Scala compiler. The Scala compiler understands that there is no sound way to implement a signature like that. Since we compile with warnings as errors on CI, this does cause problems for me. |
So I take it nothing is going to happen here? |
instead of "nice looking"
This ugly workaround does the trick and doesn't trigger "dead-code after |
Sounds like there's nothing we can do here, and there is a workaround. |
Oh, you very much can do something, and you did in #86. The issue is you don't want to, please be honest about that. |
maybe we could add second set of API, with better types and users would be able to pick any of APIs to use? |
Hi,
the current signature of Collector.register is
This doesn't make sense, there is no correct implementation of such a signature. It is impossible to return something of type T for every subtype T of Collector, yet this is what the signature promises. Among others, this leads to dead code warnings when using this library with the Scala programming language.
I can see two possible solutions to this problem: simplify the method's return type to
Collector
orvoid
, or makeT
a type parameter of the Collector class itself like so:The text was updated successfully, but these errors were encountered: