Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upImprove the ergonomics of using strongly typed Lengths #271
Conversation
|
I am quite happy about the It is a breaking change, because this pattern confuses the type inference: fn foo<T: Zero, U>() -> TypedVector2D<T, U> {
TypedVector2D::new(Zero::zero(), Zero::zero()); // compile error, can't infer type of teh parameter.
}However the same code can be written as: fn foo<T: Zero, U>() -> TypedVector2D<T, U> {
TypedVector2D::new(T::zero(), T::zero());
}Which in my opinion is a better practice (easier to understand at a glance that zero returns a T). r? @kvark |
|
Sorry, I'm still not convinced about the benefits of |
| @@ -254,6 +254,11 @@ where | |||
| } | |||
| } | |||
|
|
|||
| impl<T, U> ValueOrLength<T, U> for Length<T, U> { | |||
This comment has been minimized.
This comment has been minimized.
kvark
Jan 31, 2018
Member
please no! This is a hack, and essentially could be replaced by Into<T> but better not be replace at all.
| /// // let not_good = WorldVec2::new(ScreenLength:new(1.0), ScreenLength::new(2.0)); | ||
| /// ``` | ||
| pub trait ValueOrLength<T, U> { | ||
| fn value(self) -> T; |
This comment has been minimized.
This comment has been minimized.
The problem is that the vast majority of users stick to the raw scalar access and don't use lengths, so I am not willing to compromise on the ergonomics the raw scalar access because it means that in practice we'd lose on the overall ergonomics.
I tried and Into does not work a few reasons the main one being that the existing blanket impls in the standard library create conflicts making it impossible to implement Into<Length<T, U>> for T, and something else I don't remember clearly that required the trait to have both the I am very surprised that you consider this to be a hack. This is exactly the conversion trait for parameters pattern documented in elegant APIs in rust. I would have used one of the standard conversion traits like Into if it was possible but it unfortunately isn't. Even if using Into was possible I would actually prefer ValueOrLength, because it is a lot easier to document and understand. It has a single, well defined purpose that is apparent in its name, and integrates well with the documentation. In the docs, click on it and you get an explanation that says that functions taking this can take either a length or a scalar. Compare this with clicking on "Into" in the doc and landing on a very generic piece of the standard library (you basically need to know about common rust design patterns to understand what is going on). |
|
(oops) |
We are only talking about
Your new traits go from Length -> value, not the other way around (like you tried to implement). So not sure why you needed
For one, this link recommends using the standard conversion traits, which I also suggested (
The name is anything but idiomatic. And it is the case where if naming is problematic than we are probably doing it wrong :) |
Could you elaborate on this? If you mean that an idiomatic rust trait has to be a verb or an action that the trait represents (Add, Into, AsRef, etc.), then I strongly disagree. If you look at WebRender's source code, less than half of the traits adhere to this principle. Take a popular crate like num-traits, most of the traits are named after what the thing is rather than what it does (Float, Signed, PrimInt, etc.), Iterator is probably the most commonly used trait and isn't name 'Iterate
Yes, and I challenge you to make this work :) This is what I tried first and I couldn't make either Into and From work in this specific case because of interactions between blanket impls and orphan rules.
It's not just Consistency is usually a string value for you, maybe think of it like this: this would break euclid's rule of "Simple and convenient first, extra safety opt-in (second)" which is currently consistently applied throughout the API.
Sure, I meant "raw scalars" as in "not wrapped in a Length" whether or not they are members or computed. |
I mean AorB kind of name is never pretty, and I don't recall a precedent in stdlib.
I take your word for it doesn't work :)
All of those combined are significantly less used than the fields access, so I think we can still expect the users to adopt? It's not even the "no method xxx()` found kind of breakage. It's one where the type is different, and compiler happily points to it. Although, this is countered by:
But in this case, why bother with
You know better, but for the record - I never saw |
What seduced me about this name is that it's unambiguous and self-documenting, but if you don't like it we can certainly come up with some other name.
I was the one who lobbied (stubbornly) until I convinced enough people that euclid should be rewritten to have typed coordinates (and as a result pretty much took over it). I do see euclid as rust's safe math library, but I don't want it to be the library that is safer but that nobody use because the other libs offer less friction. That's the guideline I have been trying to follow since I pretty much rewrote the crate. I am convinced that we can make the typed APIs a lot nicer without having to compromise on the default untyped ones. If it comes at the expense of a few unusual type or function names, that's fine by me as long as whole thing remains easy to understand, and ergonomic enough that people want to use it. Ideological debate aside, this PR has two sides:
If I was to compromise on something to get more value out of this renaming, it would without hesitation be the consistent naming scheme: having |
|
Let's add this item to the list of topic to discuss when you are in Toronto. It appears that all arguments have been heard, and no clear consensus reached. How about we consider me abstained from voting on the PR and have another interested party merging and/or expressing their opinion? cc @nox |
|
I made a branch that uses only let l = Length::<f64, MyUnit>::new(0.0);
let p = TypedPoint2D::new(l, l); // cannot infer type for `T`Anyway, I hope this helps the discussion. |
Yeah I ran into that too. It's not really usable that way unfortunately. |
|
I am still surprised that the compiler cannot figure out the type. Strangely it is only a problem with |
|
I tested it and For both ( So we need a way to avoid impl ValueOrLength<Length<f64, MyUnit>> for Length<f64, MyUnit>and impl From<Length<f64, MyUnit>> for Length<Length<f64, MyUnit>, MyUnit>respectively. One way to do this is to define a impl<T: Scalar, U> ValueOrLength<T, U> for Tand impl<T: Scalar, U> From<T> for Length<T, U>respectively. Since The downside is that most other |
|
|
nical commentedJan 31, 2018
•
edited by larsbergstrom
This PR renames all
foo_typedintoget_foowhich I find to be nicer, and Introduces theValueOrLengthtrait which makes it possible to for functions to take either lengths or scalar values directly. Here is the example from the doc:This change is