-
Notifications
You must be signed in to change notification settings - Fork 213
Implicit Constructor proposal #108
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
Comments
I think static conversion operators might work better (similar to C#). Example here: #105 (comment) class Uri {
...
// Add implicit conversions from String <-> Uri in both directions.
static operator String(Uri uri) => uri.ToString();
static operator Uri(String str) => Uri.parse(str);
} The benefit is you can go in both directions. So the Uri class could define conversions to and from String. Makes it easier to support implicit conversions when you don't control both classes (e.g. similar to how extension methods let you add methods to a class you don't control). |
Yup, that's a great analog. I don't know why, but I'm drawn to a strict model here: "The only thing that can define implicit String -> Widget conversion is the Widget class". It eliminate a lots of weirdness about which conversion is picked in a given context, etc. ...but certainly open to discuss! |
Defining implicit conversion operations only on the source makes sense if you can use static extension methods to add them to any type. Ditto for only on the target type. The difference is that on the source type, they can be treated as instance methods (like all other operators), but on the target type, they are constructors. Static extension methods would only work for instance methods. Do we need an explicit way to invoke the implicit conversions? Consider the option of declaring normal instance members as |
I wonder how resolution will work for implicit conversion functions? If I want to call a method that takes Or even worse, with subtyping there could be multiple suitable conversions: class TA {
implicit TA.fromSA(SA sa);
}
class TB extends TA {
implicit TB.fromSB(SB sb);
}
class SA {}
class SB extends SA {} now if you have Now consider
|
Here is a comment based on the proposal in this PR about implicit construction. In this proposal, an implicit constructor will only be enabled if it is imported and explicitly This basically means that there is a per-library choice to make: "Which implicit constructors do we want to enable in this library?" and the chosen ones can be found easily by searching the list of imports for the word
True. The assumption is that it is possible to read code that uses implicit construction if it involves a very well-known set of enabled implicit constructors. In contrast, it is assumed that the code would be unreadable if implicit constructors could easily and implicitly be enabled (say, if there was no need to So if you're going to use a given implicit constructor just a few times in a library then you'd import it and call it explicitly: In that situation this is probably a better trade-off than enabling that constructor for implicit invocations, thus enlarging the set of implicit constructors that every reader of the library must keep in mind. About specificity: When there are multiple enabled implicit constructors in a situation where an expression has type In the class TA {}
class TB extends TA {}
static extension ImplicitTA on TA {
implicit factory TA.fromSA(SA sa) = TA;
}
static extension ImplicitTB on TB {
implicit factory TB.fromSB(SB sb) = TB;
}
class SA {}
class SB extends SA {}
void f(SB sb) {
TA ta = sb; // Implicitly invokes `TB.fromSB(sb)`.
} The conceptual motivation for choosing the
True again, this is one more construct in Dart where static analysis information will give rise to non-trivial run-time behavior choices. However, the overall motivation for this mechanism is that it should be used in situations where the conversion from one type to another one is considered to be a tedious and trivial detail. The fact that implicit constructors are likely to be few and well-known in any given library is an important contributor to the realization of this goal. Yet, the fact that it is an implicit mechanism at all is the most common argument against the feature. Finally, I think considerations similar to these are more or less applicable to other proposals about implicit constructors, not just the proposal in #3040. |
Thanks Erik for the detailed explanation and a link to #3040! Having to explicitly enable implicit conversions seems like a good trade off.
I guess what worries me here, is not the static analysis influencing run-time behavior, but specifically addition/removal of imports (which is often done by some tooling automatically) influencing run-time behavior. But again, this is already true for Dart with normal extension methods, so this proposal doesn't make things worse. |
Alas, who can hope for more! 😁 |
I believe this feature could solve the With implicit constructors, we could define something like this: class CopyValue<T> {
const CopyValue(this.value);
final T value;
}
static extension CopyWithExtension<T> on CopyValue<T> {
implicit const factory CopyValue.from(T value) = CopyValue;
} This basically allows any value to be turned into a CopyValue. Using this, we can then theoretically write: class MyModel {
const MyModel(
required this.a,
this.b,
)
final String a;
final String? b;
static MyModel copyWith(
CopyValue<String>? a,
CopyValue<String?>? b,
) {
return MyModel(
a: a?.value ?? this.a,
b: b != null ? b.value : this.b,
);
}
} which when used like this: void main() {
final x = MyModel(a: 'a', b: 'b');
final y = x.copyWith(b: null);
print(y.b == null); // true
} which would then finally solve the problem of being unable to tell whether null was passed explicitly. Because our However, with implicit constructors, this wrapping process can be made completely implicit, making the experience for users outside essentially seamless. In my opinion, such a solution would be extremely elegant, because it bypasses all the other problems raised in other proposals for "how do I tell whether null was passed explicitly or not". if the dart language provides an object like |
The value of union types seems pretty clear: at the time of writing, it's the 6th-most upvoted language issue, and since Dart is used for Flutter apps, allowing for improvements to Flutter APIs is highly valuable as well. Based on conversations with a few folks, I've learned that the syntax proposed here allows for exactly the same benefits: // union typedef
typedef ColorOrInt = Color | int;
// union class declaration
sealed class ColorOrInt {
implicit const factory ColorOrInt.fromColor(Color color) = FromColor;
implicit const factory ColorOrInt.fromInt(int i) = FromInt;
} Implicit constructors could also apply to the extension_type_unions package, offering the same functionality with better performance. typedef ColorOrInt = Union2<Color, int>; |
Solution for #107, Related docs
This issue is for discussing the pros and cons of this particular proposal.
General discussion about the issue should go in #107 where everybody can see it.
I propose to add a new keyword –
implicit
that can be added to constructors and factories.The text was updated successfully, but these errors were encountered: