-
Notifications
You must be signed in to change notification settings - Fork 201
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
Allow for shorter dot syntax to access enum values #357
Comments
This would be especially nice in collections: const supportedDirections = <CompassPoint>{.north, .east, .west};
bool isSupported = supportedDirections.containsAll({.north, .east}); It's worth noting too that we would only allow it in places where we can infer the enum type. So final north = .north; // Invalid.
final CompassPoint north = .north; // Valid.
final north = CompassPoint.north; // Valid. |
In Swift this feature works not only for enums but also for static properties of classes. See also: class Fruit {
static var apple = Fruit(name: "apple");
static var banana = Fruit(name: "banana");
var name: String;
init(name: String) {
self.name = name;
}
}
func printFruit(fruit: Fruit) {
print(fruit.name);
}
// .banana is here inferred as Fruit.banana
printFruit(fruit: .banana); |
How would the resolution work? If I write If we have a context type, we can use that as a conflict resolution: Alternatively, we could only allow the short syntax when there is a useful context type. I guess we can do that for the non-context type version too, effectively treating any self-typed static constant variable as a potential target for (Even more alternatively, we can omit the |
One approach that could be used to avoid writing |
@lrhn You may want to study how it works in Swift. I think their implementation is fine. |
If we're taking votes, I vote this ☝️ Regarding the case with void _handleCompassPoint(CompassPoint myValue) {
if (myValue == .north) {
// do something
}
}
I don't know enough about this, but I don't see why this would need to be the case if we're going with the "useful context type" only route? Right now we can do: final direction = CompassPoint.north;
print(direction == CompassPoint.south); // False.
print(direction == CompassPoint.north); // True.
print("foo" == CompassPoint.north); // False. If we know that
I don't personally prefer this approach because we risk collisions with existing in scope variable names. If someone has |
The problem with context types is that We'd have to special case equality with an enum type, so if one operand has an enum type and the other is a shorthand, the shorthand is for an enum value of the other operand's type. That's quite possible, it just doesn't follow from using context types. We have to do something extra for that. |
We can generalize the concept of "enum value" to any value or factory. If you use It still only works when there is a context type. Otherwise, you have to write the name to give context. |
To omit the FromText(
'some text',
style: FontStyle(
fontWeight: FontWeight.bold
),
), ToText(
'some text',
style: ( // [FontStyle] omitted
fontWeight: .bold // [FontWeight] omitted
),
), For enums and widgets without a constructor the FontWeight.bold -> .bold // class without a constructor
Overflow.visible -> .visible // enum
color: Color(0xFF000000) -> color: (0xFF000000) // class with constructor From issue #417_Some pints may have been presented already Not include subclasses of typeInvalidpadding: .all(10) This wont work because the type ValidtextAlign: .cener This will work because The
|
Omitting the period for constructors would lead to a whole slew of ambiguous situations simply because parentheses by themselves are meant to signify a grouping of expressions. Ignoring that, though, I think removing the period will make the intent of the code far less clear. (I'm not even sure I'd agree that this concise syntax should be available for default constructors, only for named constructors and factories.) And about the
Notice how there is no space between the
And now that there's no space between the |
A solution could be to introduce a identifyer. *.bold // example symbol But then again, that might just bloat the code/ language. |
I'd like to see something along these lines final example = MyButton("Press Me!", onTap: () => print("foo"));
final example2 = MyButton("Press Me!",
size: .small, theme: .subtle(), onTap: () => print("foo"));
class MyButton {
MyButton(
this.text, {
@required this.onTap,
this.icon,
this.size = .medium,
this.theme = .standard(),
});
final VoidCallback onTap;
final String text;
final MyButtonSize size;
final MyButtonTheme theme;
final IconData icon;
}
enum MyButtonSize { small, medium, large }
class MyButtonTheme {
MyButtonTheme.primary()
: borderColor = Colors.transparent,
fillColor = Colors.purple,
textColor = Colors.white,
iconColor = Colors.white;
MyButtonTheme.standard()
: borderColor = Colors.transparent,
fillColor = Colors.grey,
textColor = Colors.white,
iconColor = Colors.white;
MyButtonTheme.subtle()
: borderColor = Colors.purple,
fillColor = Colors.transparent,
textColor = Colors.purple,
iconColor = Colors.purple;
final Color borderColor;
final Color fillColor;
final Color textColor;
final Color iconColor;
} |
Exhaustive variants and default values are both concepts applicable in a lot of scenarios, and this feature would help in all of them to make the code more readable. I'd love to be able to use this in Flutter! return Column(
mainAxisSize: .max,
mainAxisAlignment: .end,
crossAxisAlignment: .start,
children: <Widget>[
Text('Hello', textAlign: .justify),
Row(
crossAxisAlignment: .baseline,
textBaseline: .alphabetic,
children: <Widget>[
Container(color: Colors.red),
Align(
alignment: .bottomCenter,
child: Container(color: Colors.green),
),
],
),
],
); |
Replying to @mraleph's comment #1077 (comment) on this issue since this is the canonical one for enum shorthands:
I agree that it's delightful when it works. Unfortunately, I don't think it's entirely simple to implement. At least two challenges are I know are: How does it interact with generics and type inference?You need a top-down inference context to know what f<T>(T t) {}
f(.foo) We don't know what What does it mean for enum-like classes?In large part because enums are underpowered in Dart, it's pretty common to turn an enum into an enum-like class so that you can add other members. If this shorthand only works with actual enums, that breaks any existing code that was using the shorthand syntax to access an enum member. I think that would be really painful. We could try to extend the shorthand to work with enum-like members, but that could get weird. Do we allow it at access any static member defined on the context type? Only static getters whose return type is the surrounding class's type? What if the return type is a subtype? Or we could make enum types more full-featured so that this transformation isn't needed as often. That's great, but it means the shorthand is tied to a larger feature. How does it interact with subtyping?If we extend the shorthand to work with enum-like classes, or make enums more powerful, there's a very good chance you'll have enum or enum-like types that have interesting super- and subtypes. How does the shorthand play with those? Currently, if I have a function: foo(int n) {} I can change the parameter type to accept a wider type: foo(num n) {} That's usually not a breaking change, and is a pretty minor, safe thing to do. But if that original parameter was an enum type and people were calling All of this does not mean that I think a shorthand is intractable or a bad idea. Just that it's more complex than it seems and we'll have to put some real thought into doing it right. |
If changing the interface breaks the context to the point that name inference breaks, then that is probably a good thing in the same way that making a breaking change in a package should be statically caught by the compiler. It means that the developer needs to update their code to address the breaking change. To your last example in particular foo(int n) {}
// to
foo(num n) {}
Enums don't have a superclass type, so I don't really see how an inheritance issue could arise when dealing with enums. With enum-like classes, maybe, but if you have a function that takes an enum-like value of a specific type, changing the type to a wider superclass type seems like it would be an anti-pattern anyway, and regardless would also fall into what I said earlier about implementing breaking changes resulting in errors in the static analysis of your code being a good thing. |
FWIW you list design challenges, not implementation challenges. The feature as I have described it (treat I concede that there might be some design challenges here, but I don't think resolving them should be a blocker for releasing "MVP" version of this feature. Obviously things like grammar ambiguities would need to be ironed out first: but I am not very ambitions here either, I would be totally fine shipping something that only works in parameter positions, lists and on the right hand side of comparisons - which just side steps known ambiguities.
Sometimes putting too much thought into things does not pay off because you are entering the area of diminishing returns (e.g. your design challenges are the great example of things which I think is not worth even thinking about in the context of this language feature) or worse you are entering analysis paralysis which prevents you from moving ahead and actually making the language more delightful to use with simple changes to it.
You break anybody doing this:
Does it mean we should maybe unship static tear-offs? Probably not. Same applies to the shorthand syntax being discussed here. |
I'm not a computer scientist but aren't the majority of these issues solved by making it only work with constructors / static fields that share return a type that matches the host class & enum values? That's my only expectation for it anyway, and none of those come through generic types to begin with. If the type is explicit, it seems like the dart tooling would be able to to know what type you're referring to. I don't think the value of this sugar can be understated. In the context of Flutter it would offer a ton of positive developer experience.
In the context of Flutter the missing piece that I find first is how to handle |
Yes, good point. I mispoke there. :)
That feature has caused some problems around inference, too, though, for many of the same reasons. Any time you use the surrounding context to know what an expression means while also using the expression to infer the surrounding context, you risk circularity and ambiguity problems. If we ever try to add overloading, this will be painful.
We have been intensely burned on Dart repeatedly by shipping minimum viable features:
I get what you're saying. I'm not arguing that the language team needs to go meditate on a mountain for ten years before we add a single production to the grammar. But I'm pretty certain we have historically been calibrated to underthink language designs to our detriment. I'm not proposing that we ship a complex feature, I'm suggesting that we think deeply so that we can ship a good simple feature. There are good complex features (null safety) and bad simple ones (non-shorting It's entirely OK if we think through something and decide "We're OK with the feature simply not supporting this case." That's fine. What I want to avoid is shipping it and then realizing "Oh shit, we didn't think about that interaction at all." which has historically happened more than I would like.
That's why I said "usually". :) I don't think we should unship that, no. But it does factor into the trade-offs of static tear-offs and it is something API maintainers have to think about. The only reason we have been able to change the signature of constructors in the core libraries, which we have done, is because constructors currently can't be torn off. |
This is also an excellent point. Consider the following: class Beauty {
static const low = 0.0;
static const medium = 0.5;
static const high = 1.0;
}
class Math {
static const pi = 3.14;
static const tau = 6.28;
static const e = 2.72;
}
double foo({ double beauty in Beauty, double math in Math }); The fact that The only thing parameter default scopes would accomplish that static extension methods wouldn't is that it adds identifiers to a parameter without adding it to the type's own namespace. The benefits of that are few and highly subjective, so you would find it hard to convince anyone that that alone is worth the long list of downsides, complexities, and very probable sources of confusion. |
I like when I can copy and paste the code anywhere and it still works. This wouldn't allow it, so I don't like it. |
OverviewI have another place where this feature is extremely useful that may not be readily apparent: RamblingsThe Drift ORM (which I co-maintain) uses generated code for it's Manager API. This generated code includes pre-built filters for composing complex queries easily. The code would have looked horrible: users.filter(UsersTableFilter.name("Bob") & UsersTableFilter.age(10)); Instead we decided to use a callback which provides the class users.filter((f) => f.name("Bob") & f.age(10)); In pretty much all packages that generate code (like freezed, auto_route, etc.) the generated classes have with long, badly names. This means that the developer limits what classes are used externally Also, being that the developer hasn't the generated code, he has no idea what the name is. This feature would allow for packages that generate code to be used in a much more user friendly way users.filter(.name("Bob") & .age(10)); |
I think it's unlikely that Even if we introduce receiver context inference, we'd have to guess that a bobcat operator will have operands of the same type as the result. |
@Abion47 wrote:
That's an interesting conclusion, and quite surprising to me! I'd arrive at the opposite conclusion, looking at exactly the same code: The author of You can obviously ignore the default scope and pass any The parameter default scope mechanism is designed to make the well-aligned invocations concise, and require the mis-aligned ones to be written out explicitly. If the mechanism is used as intended then the mis-aligned ones are indeed going to be likely bugs, so it's good that they stand out. For that reason, I think it's a feature rather than a bug that If you insist then you can write Finally, if the void main() {
double d = Math.tau + Beauty.medium;
foo(beauty: Math.tau, math: Beauty.medium); // This is perfectly appropriate!
} In this case, Finally, you could also use the namespace of the class static extension Beauty on double { // We could omit the name `Beauty`, if preferred.
static const low = 0.0;
static const medium = 0.5;
static const high = 1.0;
}
static extension Math on double { // Ditto, could omit `Math`.
static const pi = 3.14;
static const tau = 6.28;
static const e = 2.72;
}
void main() {
double d = .pi;
d = .low;
} You can do it, but I think it's very unlikely to be a good idea. If we put a large number of named values of a particular type into the namespace of that type itself then those named values will be available in every situation where the context type is that type. I just don't think we're going to be happy about a 500 element long list of completions whenever we type |
@dickermoshe wrote about users.filter(UsersTableFilter.name("Bob") & UsersTableFilter.age(10)); I couldn't immediately find I assume that class UsersTableFilter {
UsersTableFilter.name(String _);
UsersTableFilter.age(int _);
UsersTableFilter operator &(UsersTableFilter _) => this;
}
class Users {
void filter(UsersTableFilter _) {}
}
void main() {
var users = Users();
users.filter(.name("Bob")); // OK.
users.filter(UsersTableFilter.name("Bob") & .age(10)); // OK.
users.filter(.name("Bob") & .age(10)); // Compile-time error.
} The reason why we can abbreviate In the next invocation we can abbreviate However, we cannot abbreviate both operands of There is ongoing work to allow receivers to have a non-trivial context type, but it seems unlikely that it would cover this kind of situation. So we can do some of these things using parameter default scopes, but not all. |
A bit random my comment, but I just really wish I could type |
I think every proposal will do that. |
The only time an author can possibly know what all arguments to a parameter will be significant is if the parameter itself were of a sealed enum type. Otherwise, the author cannot know if the user has either extended the type or has specified some constant values of their own that they wish to pass as an argument. This goes back to the problem that I laid out in the other issue that parameter default scopes aren't extensible - they require the author to either enforce a rigid contract or see the future. Also:
I agree that there is code smell in that example, but I disagree where it's ultimately coming from. If there is a scenario where the source of the value matters so much as to actually specify the source of the extended type(s) to pull from, then that would imply that there is significant correlation of said type with the value itself. Say in the above example, the The issue with the PDS approach is that it promotes validation by convention. The contract between type and value is virtually non-existent, so there is no language function that actually checks if the passed value is an appropriate one. In this case, I would not consider PDS to be a feature, I would consider it an anti-pattern because it is promoting the use of an approach that isn't the best one for the task. If the association between the namespace of the scoped type(s) and the passed value is so strong that it's worth specifying a type, then the parameter shouldn't be a enum Beauty {
low(0.0),
medium(0.5),
high(1.0);
final double value;
const Beauty(this.value);
}
enum Math {
pi(3.14),
tau(6.28),
e(2.72);
final double value;
const Math(this.value);
}
double foo(Beauty beauty, Math math); Now both
Except the mechanism doesn't facilitate that at all. Well-named types, variables, and functions do. On its own, the PDS mechanism doesn't provide any more protection against misused invocations than a style guide.
The problem is that the number of scenarios in which it makes sense to use PDS is getting narrower and narrower. As it stands, the only time it would be potentially a good application would be if:
This is a hyper-specific set of circumstances, and most examples of these circumstances I have seen have other problems with them that would prompt the question of whether or not it should just be refactored. And beyond that, point 1 and 4 are directly at odds with each other. If a function can take any arbitrary parameter beyond what is pre-defined, then it would not at all be unusual for the user to want to be able to define their own pre-defined types, at which point they will get upset that they can't use the dot syntax to reference them. At that point, either they will have to either put up with the limitations of PDS or they will end up extending the root type with their values anyway.
This is the same concern about putting stuff in the global namespace in languages like JS, and the solution there should be the solution here - don't do that. There will almost always be code smell involved with extending a built-in type, particularly a primitive type like Also, keep in mind that values added to |
This is known as the type of the parameter. Any value of a type which is assignable to the type of the parameter can be passed. That is hardly a rare exception. Actually, every single formal parameter in Dart has that property. Not much narrowing so far.
In some situations there may be such a single collection. That's fine. In other situations there's a need for extensibility. I've several times mentioned the example where some namespace (for example, That's definitely not limited to a single collection, that's a collection which is created by clients according to their needs and preferences.
Your choice, you can add them to a separate namespace or to the root type. Please don't pretend that anything will stop you if you make the choice to add them to the root type.
You really, seriously do not read what I'm writing, right? I haven't tried to count how many times I've explained that extensibility is an integrated and important part of this proposal, and it is achieved by means of static extensions that are used to populate the chosen default scopes, be it the parameter type itself or some other namespace.
None of your points have been valid so far, you're just inventing limitations that do not exist.
It is you, not I, who has maintained that it is unacceptable to have any other mechanism than the context type to determine the set of named, distinguished values of the form If we must put every such value into the same bucket, and that's a widely used type (like The ability to use an @Abion47, I'm sorry I had to turn up the volume a bit at this time. I will not continue to explain the same things again and again, only to read arguments, again and again, that are obviously based on assumptions that you should know are not true. |
You misunderstand. "Any arbitrary value" means any value assignable to a type that a user might provide, as opposed to there being only a small subset of available values that the author restricts the input to. For instance, this: void foo(int bar) // accepts any integer And not this: enum Numbers { one, two, three }
void foo(A bar) // The only valid arguments are Numbers.one, Numbers.two, or Numbers.three
This subjective choice is now going back to another major problem listed in the other issue - it's an opt-in feature. If a package author didn't agree with you that PDS is beneficial, they won't design their package using it. Now everyone using their package is locked into their opinion with no way to work around it.
And as I have repeatedly brought up, your idea of extensibility for your feature is to piggy back on another feature that already does 99% of what your proposal will accomplish with the remaining 1% being extremely limited and subjective in its benefits. And on top of this, you have yet to address any of the other REAL concerns I brought up in the other issue thread:
Lest you say these are all just my opinions, note that I am not the only one to bring them up. So until you can adequately address these four things, your proposal is not going to get any real traction.
Not any more inconvenient than having to navigate those same namespaces without the dot syntax. If I wanted to use a member of I think you are greatly overestimating the real-world impact of static extensions on namespace pollution. Namespace pollution is only a problem when values are added to the namespace that don't belong in that namespace, and when that happens, 999999 times out of a million, it's an implementation problem, not a language problem. The overwhelming majority of the time, when someone finds themselves in this situation, it's their own fault. And again, if your worry is that people will start to add hundreds of extended members onto common Dart types, that's already a discouraged practice in any language that supports a global namespace or these kinds of extensions.
I never said that was unacceptable. What I said was it should be the target for this issue to be implemented as it keeps the baseline functionality simple and easy to understand.
The reason I repeat these arguments (even on this thread where it is likely off-topic, but it was brought up and now here we are) is that you aren't adequately addressing them. You have yet to defend why your proposed feature needs to exist beyond an extremely subjective viewpoint regarding a handful of extremely specific circumstances. You have yet to explain why your feature's few benefits outweigh the staggeringly long list of downsides and complexities that exist in its current design. So I'm sorry if you're getting irritated by these arguments, but every time I bring them up, your response boils down to either "if you don't like the feature, don't use it" or "avoiding polluting the namespace is worth introducing all these other problems". Because I'm sorry, but that's not good enough. |
This comment was marked as resolved.
This comment was marked as resolved.
I suggest you try I bet that after your semicolon rant, you'll see this feature as really the game changer it is in a modern declarative UI framework
|
@eernstg Going back to the Could the concern be instead solved through extension types? Not static extensions, but the feature that was added not too long ago. Given: fn({num beauty in Beauty}); We could alternatively express it as: extension type const Beauty(num value) {
static const Beauty high = Beauty(1);
}
fn({Beauty beauty }); I think this would be more sensical, as extension types have an actual impact on type safety. One thing I dislike with the It would be a bit breaking to make such a change for |
Main concerns comes to me from original case. Consider: enum CompassPoint {
north,
south,
east,
west,
}
enum Direction {
north,
south,
east,
west,
}
if (myValue == .north) {
// do something
} The same applies to import. If e.g. external library would define enum with |
The way If it works with If there is every an ambiguity, it's safer to not allow the program to compile than it is to guess, but even if we are very permissive in where we'd look for candidates, I can't see you ever breaking a And we probably won't be that permissive. Checking for static members and constructors on the context type/static type of other |
As far as I understand, your point seems to be OK for me. How about sth like this: late dynamic myValue; // not initialized, initialization goes later based of some runtime condition.
// Variable can be assigned to CompassPoint / Direction or sth else
if (myValue == .north) {
// do something
} If compiler would reject such dynamic cases, I am OK with proposition |
This feature wouldn't work with |
Ok then. Sorry for fuss. I am +1 |
@rrousselGit wrote:
That's a very interesting idea! It does differ from the default scope idea, of course, and some things do get harder. For example: In order to invoke We could make the invocation of the constructor implicit if we get support for implicit constructors. However, this introduces an extra (and an implicit) step to the situation, which is probably not very good for the overall readability of the code. The point about type safety is that only expressions of type It does make a difference when it comes to distinguished values: We can do In other words, this idea seems to be highly optimized for passing distinguished values, and not so user-friendly for passing any other value. If we actually, seriously, want to enforce that only some distinguished values can be passed then we do have another option: enum Beauty {
low(0),
medium(0.5),
high(1);
final num value;
const Beauty(this.value);
}
void fn({Beauty beauty = Beauty.medium}) {
var beautyValue = beauty.value;
// ...
}
void main() {
fn(beauty: .high);
} This is not only optimized for distinguished values, it enforces that we can't have anything other than one of the distinguished values.
That's again a good point! However, I have a slightly different starting point: I'm thinking of this mechanism as a way to provide convenient and concise access to specific (that's what I call "distinguished") values of the required type. I haven't considered it to be an important goal to avoid any values of the correct type to be passed. So if you wish to call However, you do get a hint: |
I don't think there's anything wrong with having to write I personally really dislike the inconsistency between Overall, I just feel like that It almost feels like fn({@In(Beauty) num beauty}); We could very well have that
And if a package author is looking for something stricter, they can instead use any of the mentioned alternatives (enums, classes, extension types, whatever...) |
Well, the purpose of the I don't really understand the part about hints and autocompletion. You can write Surely, an IDE with support for completion could transform
Dart doesn't otherwise provide language mechanisms shaped as metadata. Of course, any mechanism could be given any syntactic form that provides the required information, so it doesn't matter, technically. But we're definitely talking about a language mechanism because it changes the binding of some name applications to the corresponding declarations.
Sounds like you're thinking about a different mechanism: We would receive help from the IDE to transform I'm proposing a mechanism that allows the program to contain the short form, not an IDE feature that allows us to obtain the long form that we have today with less manual work.
Good, I'd want that from any proposal in this topic area. We can pass any expression whose static type is assignable to a type
Any It would definitely be possible to do those things. But it wouldn't abbreviate anything, it would just allow us to write the same code with fewer keystrokes. I'm proposing a language mechanism that allows programs to contain abbreviated expressions whose meaning is defined in terms of the relevant namespace. The most common case will surely be that there is no My assumption is that it's a reasonable style choice to have programs where the abbreviated expressions exist. That is, I'm assuming that Otherwise, if you insist that actual code must always use the long form YMMV. |
I think there's a big misunderstanding here. I think the core of the misunderstanding stems from what On the flip side, I go by the assumption that there are other mechanisms possible that do not involve
In both cases, the discussed In that scenario, the void fn({num beauty});
fn(beauty: .pi); // sugar for either fn(beauty: num.pi) or fn(beauty: Math.pi) based on the implementation used
void fn({@In(Beauty) num beauty});
fn(beauty: .pi); // The code still runs, but analyzer now produces a warning, because num.pi/Math.pi aren't from Beauty Does that make my view clearer? I think that distinction matters, because one of the reason for pushback against "context + static extensions" is the namespace pollution and lack of restrictions on things like But those issues are mainly autocompletion/hint issues ; which could be covered by the linter rather than the language. My apologies, I should've mentioned this from the get-go :) |
It's always good to fix those, thanks for the clarifications!
OK! But this implies that you do assume a language mechanism (such that
I was assuming my proposal, I don't think anyone else has proposed anything that introduces a clause which is added to a formal parameter declaration. With this proposal, an actual argument passed to a formal parameter declared as If you declare a parameter with no This means that
Certainly.
Indeed, this is the default (and probably vastly most common) case for parameter default scopes, too.
Indeed again, they are crucial to my proposal because they allow for a useful kind of separation of concerns: The provider of a service can say "I'll search for the
That looks like a quite different mechanism, and I haven't thought about the implications. One question does arise, though: How would we know when we encounter
This is definitely a property that we can achieve. With my proposal, if you don't have an I just happen to think that it's too inflexible to use the context type (or any other fixed choice of namespace) unconditionally: This implies that every parameter with the same declared type must use the same set of distinguished values (the I think you're saying pretty much the same thing here:
It is possible to use " // Provider's library.
void foo(String s in FooNamespace) {}
class FooNamespace {
static final String id = 'Hello, world!'; // Universally useful!
}
// Client's library, or imported into client's library.
class SomeOtherNamespace {
static String get id2 => 'Some other useful thing';
// More useful static members.
}
static extension FooProvider on FooNamespace {
static export SomeOtherNamespace; // Add all the members to `FooNamespace`.
} |
It sounds like I misunderstood quite a lot of things! If you're talking about a cherry on top, that eases my mind quite a bit. Thank for clarifying :) To avoid misunderstanding more, how does your Consider: class A {
static int distinguished = 1;
}
class B {
static int distinguished = 2;
}
void fn({int? value in A}) => print(value);
void fn2({int? value in B}) => print(value); Then, we used as: fn(value: .distinguished);
fn2(value: .distinguished); My gut feeling is that with your proposal, this would print:
That's something I'm really worried about personally. Instead, what I'd want is a compilation error, warning be about a conflict between two static extensions. extension A on Object {
int get distinguished => 1;
}
extension B on Object {
int get distinguished => 2;
}
Object value = ...;
print(value.distinguished); // Conflict error |
Exactly.
That's a good point! I do think it's a conflict which is more or less a built-in property of this feature (in any variant that I can think of): If the point is that we want to write We can specify where to look locally ( With this, it seems unavoidable that two different syntactic contexts can provide distinct specifications about the place to look, and hence also the meaning of a term like It might not be so bad in practice, though: Most names in public APIs carry a useful amount of information about the meaning of the named entity, even in the case where it is somewhat ambiguous. So we are more likely to have something like this: void main() {
chooseTShirt(size: .medium);
fryTheSteak(degree: .medium);
// If we wish to resolve the ambiguity for improved
// readability, nothing prevents us from doing this:
chooseTShirt(size: TShirtSize.medium);
} (Being a vegetarian, I can't promise that the frying part is done correctly. ;-) |
I disagree that the issue is unavoidable. That's the main reason why I keep pushing back :) In my world where we use static extensions + context type, using the same example we'd have: static extension A on int {
static int distinguished = 1;
}
static extension B on int {
static int distinguished = 2;
}
void fn({int? value}) => print(value);
void fn2({int? value}) => print(value); Then, used as: fn(value: .distinguished);
fn2(value: .distinguished); The key is that the language interprets would interpret it as: fn(value: int.distinguished);
fn2(value: int.distinguished); Note how we're not interpreting it as |
This example is already directly supported by the default parameter scopes proposal, assuming that we get support for extension types as well: static extension A on int {
static int distinguished = 1;
}
static extension B on int {
static int distinguished = 2;
}
void fn({int? value}) => print(value);
void fn2({int? value}) => print(value);
void main() {
fn(value: .distinguished); // Error, ambiguous.
fn2(value: .distinguished); // Error, ambiguous.
} So that's a software design choice, not a language mechanism distinction. [Edit: In the first version of this comment I had an extra paragraph about handling The important point I'd like to make is that I do not think it's a good choice to put all the distinguished values of type |
For the feature to be useful, it is not necessary to cover all theoretically possible options (which are hard to formalize). |
IMO constructors and static functions are really important. Padding(
padding: .all(42),
)
Border(
border: .all(.elliptical(10, 20)),
)
... |
When using enums in Dart, it can become tedious to have to specify the full enum name every time. Since Dart has the ability to infer the type, it would be nice to allow the use of shorter dot syntax in a similar manner to Swift
The current way to use enums:
The proposed alternative:
The text was updated successfully, but these errors were encountered: