-
Notifications
You must be signed in to change notification settings - Fork 205
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. |
I was talking about the feature that allows to say (in whatever syntactic form) "whenever the context type is AlignmentGeometry, you can use the shortcuts from the class Alignment". Your (hypothetical) example does exactly that! static extension FromAlignment on AlignmentGeometry {
static export Alignment;
} I proposed a similar form before but didn't earn any points. I'm glad the idea finally got another supporter :-) Do you agree that if we allow "static export" statement in the extension on AlignmentGeometry, we should also allow it in the class AlignmentGeometry itself, thus making the extension unnecessary in this specific case? Yes, you can "static export" any number of classes, but in some common scenarios, it might be prone to misunderstanding. My question to you is: how did you arrive at the conclusion that "export" keyword is preferable to "import" in your example? (NOTE: The syntax of disambiguation is unclear. You can't say AlignmentGeometry.lerp because it will be ambiguous again). |
For processing of shortcut syntax like
We could also consider execution ("Is this a compile-time only mechanism, or could it have a dynamic semantics which is independent of the static typing?"), but I won't go there because nobody (as far as I can see) has proposed that it should be anything other than a compile-time mechanism. For (1), the most common proposal is to support situations where the context type is associated with a declaration that supports declarations of static members, and may support declarations of constructors (at least some kinds). We will then use the static namespace of that context type. My proposal about default parameter scopes does this by default, but also allows an We may encounter We may of course have multiple syntactically nested expressions, and we could use a nested-scopes approach. For example, I don't think we have any further proposals about (1). For (2), the simplest approach is that the shortcut namespace is populated by actual syntax: When we have determined that the namespace I've mentioned static extensions many times as an important mechanism that would allow developers to populate an namespace @tatumizer proposed another mechanism that would populate a given static namespace: We could have There were some ideas around including subtypes/subclasses as a way to automatically populate the namespace, e.g., for The relevant design questions here would be at least the following:
I think we have a bunch of jigsaw puzzle pieces here, and it's useful to try to organize them into a bigger picture. |
I think there's a slight misunderstanding about what my concerns are. Cf:
With the current Given my If a package author decided to, they could ban the abbreviated syntax from being used with their package.
That seems very valuable. Out of all the exchanges we've had, I'd say that's probably the first step in the right direction IMO :) |
I will try to make an argument in favor of the latter - that is "only for shortcuts". So after injecting static members of Alignment into AlignmentGeometry, the constant class Animal {
//...
}
class Horse extends Animal {
static final bolivar=Horse(name: 'Bolivar',...);
} Would it be a good idea to copy the definition of class Animal {
static final bolivar=Horse.bolivar;
} I don't think so. Citing O Henry, "Bolivar cannot carry double". We still can define a shortcut to Bolivar to be used with the context type Animal, but there's no reason to copy the reference to Bolivar to Animal class for that. The QN for Bolivar still remains So my interpretation of As to whether the feature specifically dedicated to shortcuts makes sense, my answer is yes. It's a first-class, central feature of the design. If you open any example of Swift UI, you will see the shortcuts everywhere. Same would happen to Flutter apps. @lrhn wrote:
There's no way to redesign Flutter with this specific feature in mind. The design of AlignmentGeometry with two subclasses is correct. (The Horse-Animal example shows that). (We might want to discuss a better name for the concept. "Contextual shortcuts"? "Contextual aliases"? "Contexttual something"? |
@rrousselGit wrote:
That's certainly true. But note that you cannot write any abbreviated forms today, and every single actual argument which can be specified today can also be specified with the same syntax if we get this feature. In other words, there's nothing stopping you if you want to pass an actual argument that doesn't have an abbreviated form, and it isn't even going to get worse than what we have today. So, "possible values usable with the abbreviated syntax" is not the same thing as "possible values".
The proposal that uses But I suppose it means something like "let us recognize In general, the idea would be that you are primarily using the default (that is, the context type) to provide the namespace of static/constructor declarations which are available as shortcuts. This is probably going to work just fine in a lot of cases. However, if the default won't work for you then you can use This might occur because you want to support a very large number of shortcut names, and you don't want to pollute the static namespace of the target type itself; or you want to support different sets of shortcut names for different parameters (e.g., because the target type is used for many different purposes like You can use mechanisms like static extensions to populate any of these namespaces (the default one in the target type itself, or any Hence, it makes sense that you could use an If you actually declare void foo(Never x) {
... // Lots of code that took days to write.
} I just don't understand, what's the point you are trying to make by showing that it is possible to write a declaration which is useless, and perhaps even actively inconvenient? Of course you can do that!
Again, every actual argument which can be passed today can also be passed, using exactly the same syntax, if we add this feature. There's nothing non-allowed about an actual argument which isn't a shortcut. If you want to enable shortcuts then change
Perhaps that package would then just be cloned, and the clone could be maintained by people who have better intentions?
It wouldn't be hard, either. However, it might be an improvement, and it might not. It certainly eliminates a little bit of expressive power. For example: enum Day {monday, tuesday, wednesday, thursday, friday, saturday, sunday}
abstract final class WeekendDays { // Just a namespace.
static const Day saturday = Day.saturday, sunday = Day.sunday;
}
void reportWeekendWork(int hours, Day day in WeekendDays) {
... // Report to my employer that I've been working during the weekend.
}
void main() {
reportWeekendWork(2, .saturday); // OK.
reportWeekendWork(5, .friday); // Compile-time error.
reportWeekendWork(5, Day.friday); // OK, and this form is a hint that "I know what I'm doing".
} Saturday and Sunday are the "front row" values, the typical values, for this particular purpose. But there could be special situations where work on other days is counted as weekend work as well. For instance, the weekend could be considered to start Friday afternoon, or you might want to report work after midnight on a Sunday specifying The point is that completion in an IDE will help you write It makes sense to me that you might want to have subsets of an enumerated type as the available shortcuts, but this can't be expressed if we insist that all the static members of a class must be included as shortcuts. |
I think there are several reasons why we might want more control over namespaces, for example in terms of an @tatumizer wrote:
This would indeed eliminate the form void foo(AlignmentGeometry alignment in AlignmentShortcuts) {...}
abstract final class AlignmentShortcuts {
static export Alignment;
static export AlignmentGeometry hide topRight; // Assume a name clash. We can resolve it.
} The However, the introduction of a constructor into a different namespace could gives rise to some confusion: class A {
const A.named();
import shortcuts from B; // Imports constructors, too?
}
class B extends A {
const B() : super.named() {...}
factory B.otherName() => B();
}
void main() {
A a1 = .named(); // OK, no problems.
A a2 = .new(); // It may be confusing that it creates a `B()`.
A a3 = .otherName(); // Probably OK.
} So perhaps we shouldn't try to add constructors using these namespace management mechanisms? For example, adding the One way out would be to say that void foo({required EdgeInsetsGeometry padding in EdgeInsets}) {...}
void main() {
foo(padding: .all(16.0)); // Means `EdgeInsets.all(16.0)`.
foo(padding: .lerp(someEdgeInset, anotherEdgeInset, 0.1); // OK.
foo(padding: EdgeInsetsGeometry.lerp(...)); // No shortcuts for this one.
} Another approach would be to equip the superclass ( void foo({required EdgeInsetsGeometry padding}) {...}
static extension on EdgeInsetsGeometry {
const factory EdgeInsetsGeometry.all(double d) = EdgeInsets.all;
const factory EdgeInsetsGeometry.directionalAll(double d) = EdgeInsetsDirectional.all;
... // More constructors as needed.
} I know, this is considered verbose and inconvenient, but it is possible to do this using only static extensions, and it allows us to provide constructors from several subclasses, with renaming as needed. |
If there have to be two subclasses, then having to say which one you use seems reasonable. Not having a shortcut is working as intended? But if, say, |
@eernstg I am fully aware that the option to use I want to evaluate the impacts of You said:
As thought experiment, consider the following code: void fn({Color? color in *}); (In that scenario, we could have The idea of that thought experiment is, the meaning of Then, we are trying to compile this Dart expression: From here, we could have the following algorithm:
Then, we change Afaik, this would be a fully working implementation of your Where am I going with this? Added to that, that filter mechanism So, in the context of the dot syntax,
The main appeal it has for me was, it reframed what It also means that adding But looking back, the value is fairly low once we consider static extensions. We can safely ignore it. To be a bit more productive here: Do we need to implement that We are in agreement that |
@lrhn wrote:
True. Unless you account for probabilities. BTW, I shared your view for a while, and still share it to some extent, but to a lesser extent than before. Again, there's a notion of a "typical use case". We see this phenomenon in the notion of "default values". What is the point of them if you can always pass the parameter explicitly? And sometimes you must! But if there's a good default for a typical use case (which sometimes exists, sometimes doesn't), then it's a very convenient convention. The same happens here: in Now it's a question of probabilities. It seems that in a large majority of cases, people use Alignment rather than AlignmentDirectional.. For many users who don't care about rtl alphabets, Alignment is all there is, and they will be very disappointed to find out that their assumed shortcuts are not really the shortcuts. The same happens with all classes whose name ends in Geometry (there's a number of them).
They could do it, but they didn't. And I can understand (or, rather, speculate) why. The option "all" is just one of the possibilities. It's a part of a whole family of other possibilities, and they didn't want to promote one value, separating it from the rest of the family. (This makes sense to me). But sometimes they DO promote. I'm aware of just a single example: |
@eernstg : I agree that your example with Speaking of "in" clause: I do believe that there's a good argument for it, in addition to the main mechanism of automatically exposed shortcuts from the namespace, plus with "export/import". If we agree on static extension on AlignmentGeometry {
with shortcuts in Alignment;
} so there's no confusion around import/export. My greater concern is that Flutter team may not approve this whole thing, and then all our discussions will be in vain :( |
Another observation: Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(color: Color(0xFFFFFFFF)),
left: BorderSide(color: Color(0xFFFFFFFF)),
right: BorderSide(),
bottom: BorderSide(),
),
),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 2.0),
decoration: const BoxDecoration(
border: Border(
top: BorderSide(color: Color(0xFFDFDFDF)),
left: BorderSide(color: Color(0xFFDFDFDF)),
right: BorderSide(color: Color(0xFF7F7F7F)),
bottom: BorderSide(color: Color(0xFF7F7F7F)),
),
color: Color(0xFFBFBFBF),
),
child: const Text(
'OK',
textAlign: TextAlign.center,
style: TextStyle(color: Color(0xFF000000))
),
),
) With Container(
decoration: const .new(
border: .new(
top: .new(color: .new(0xFFFFFFFF)),
left: .new(color: .new(0xFFFFFFFF)),
right: .new(),
bottom: .new(),
),
),
child: Container(
padding: const .symmetric(horizontal: 20.0, vertical: 2.0),
decoration: const .new(
border: .(
top: .new(color: .new(0xFFDFDFDF)),
left: .new(color: .new(0xFFDFDFDF)),
right: .new(color: .new(0xFF7F7F7F)),
bottom: .new(color: .new(0xFF7F7F7F)),
),
color: .new(0xFFBFBFBF),
),
child: const Text(
'OK',
textAlign: .center,
style: .new(color: .new(0xFF000000))
),
),
)
Whereas with Container(
decoration: const .(
border: .(
top: .(color: .(0xFFFFFFFF)),
left: .(color: .(0xFFFFFFFF)),
right: .(),
bottom: .(),
),
),
child: Container(
padding: const .symmetric(horizontal: 20.0, vertical: 2.0),
decoration: const .(
border: .(
top: .(color: .(0xFFDFDFDF)),
left: .(color: .(0xFFDFDFDF)),
right: .(color: .(0xFF7F7F7F)),
bottom: .(color: .(0xFF7F7F7F)),
),
color: .(0xFFBFBFBF),
),
child: const Text(
'OK',
textAlign: .center,
style: .(color: .(0xFF000000))
),
),
) Another possibility is to disallow shortcuts to default constructors. Or to all constructors. Or to all methods except constants. EDIT: For completeness, I include a variant based on Container(
decoration: const <>(
border: <>(
top: <>(color: <>(0xFFFFFFFF)),
left: <>(color: <>(0xFFFFFFFF)),
right: <>(),
bottom: <>(),
),
),
child: Container(
padding: const .symmetric(horizontal: 20.0, vertical: 2.0),
decoration: const <>(
border: .(
top: <>(color: <>(0xFFDFDFDF)),
left: <>(color: <>(0xFFDFDFDF)),
right: <>(color: <>(0xFF7F7F7F)),
bottom: <>(color: <>(0xFF7F7F7F)),
),
color: <>(0xFFBFBFBF),
),
child: const Text(
'OK',
textAlign: .center,
style: <>(color: <>(0xFF000000))
),
),
)
|
That sounds like a lint. If you don't like how the feature looks in a particular case, don't use it. I'd let people use this as much as they want in their own code. We can't stop people from writing code with bad readability today, but if we don't allow an optional feature over hypothetical readability concerns, we may be preventing them from reading the best code in done cases. (I won't support a lint about always or never using shorthands in the recommended lint set. That's a readability choice that depends on the actual code.) |
@lrhn: maybe we should look more closely at
Indeed! (Further down the thread, I even tried to develop a theory of block expressions based on this syntax). Why At first, EDIT: I fixed my last post by adding a variant with |
@rrousselGit wrote:
OK! I don't think it's going to take us any further to discuss whether an So let's agree to disagree on the claim that ' However, I think it's important to note that the ability to have an Someone might say that we don't need this flexibility, we can just edit the class of the parameter type. However, nobody has the actual editing rights for every class in the universe, so that is generally not an option. Next, a mechanism like static extensions could be used to add some static/constructor members to the parameter type (or indeed to any static namespace). That's a very important kind of abstraction (in particular, because it's modular, and it will allow us to eliminate certain kinds of unwanted dependencies). However, it can't be used to specify different sets of shortcuts for different parameters with the same parameter type. So static extensions are certainly useful and relevant, but they can't replace an In short, the support for Of course, this expressive power includes the ability to specify that the set of shortcuts is empty (
But this means that every shortcut Perhaps you didn't mean "if we changed the But we can't have a language mechanism that relies on traversing all declarations that have a static namespace, and then selecting the "relevant" ones. There's nothing technical about
That's certainly true. If we introduce a mechanism that relies on the parameter type (only) then it is a non-breaking change to introduce support for a mechanism like |
This. |
@tatumizer wrote:
Yes, I tend to think that it is useful to ensure (by direct editing, or using static extensions, or in some other way) that the namespace which is being used has the declarations which are needed as shortcuts. void foo({required A a}) {/*...*/}
class A {
final int i;
const A(this.i);
static const a1 = A(1);
static const a2 = A(2);
}
class B extends A {
const B(super.i);
void bar() {}
}
static extension AExtension on A {
static const a3 = A(3);
const factory A.asB(int i) = B;
}
void main() {
// All shortcuts have the same expansion: Add `A` at the front.
foo(a: .a1);
foo(a: .a2);
foo(a: .new(42));
foo(a: .a3);
foo(a: .asB(-10));
// Non-shortcut expressions are of course still supported.
foo(a: const B(14));
} So we're introducing a constructor named This means that there's no magic, there is nothing extra to explain, we just know that the relevant namespace is This is manual work (we need to declare the constructor However, I think the extra manual work is acceptable, given that we avoid having an extra mechanism that somehow implicitly "imports" the constructor (OK, you can do Returning to the original comment:
.. I'd say that we should just make sure that there is no need for implicit imports of subclass constructors, we should simply make sure that there is a constructor which will do the job, even if that takes a little bit of manual work to create it.
It would certainly be a very easy change to use the syntax I'm not so sure about listing the specific shortcuts. What if we want to provide a couple of thousand values? I'm also not quite sure where we'd go and look for a declaration named
Right, this is yet another example where it is very tempting to ask for namespace management features (import/export, hide/show, quantification). I still prefer to focus on management of general static namespaces rather than management of namespaces specifically for shortcuts. The point is that they are more general, and we can essentially express the latter using the former: Just declare a normal namespace
Surely we'll have something, perhaps just a plain "turn |
What's a typing layer according to you? In the former scenario, And the purpose of To top it all, it is a mechanism that is applied on pretty much every variable declaration, alongside its type.
We can make a wrapper class or use an extension types with anything. I don't think that specific problem is a concern of the dot syntax, but rather the typing system as a whole. And the solution doesn't have to be specific to the dot syntax. The problem you're describing already exists today without this feature.
That's what I'm saying, yes. IMO, if a function takes in a The only thing that matters to me is "What is necessary to get that .primaryColor to work?"
There can be some language limitation around it. IMO what you're suggesting is no different from a function saying it can only work with multiline |
The method I am against injecting stuff into the namespace where it doesn't belong. It doesn't feel right. You can argue that the notation I suggested (*) If we choose a special notation for |
I think we can get away with pure "leading dot" notation by using a bit of linguistic equilibristics. The convention may allow a dot without an id to serve as an alias, too, assuming it points to a default constructor of some class. We can also define an alias A note about parameter-specific shortcuts: I tried to find the potential use cases in Flutter - they all deal with the parameters of type double; the constants of type double such that there's no other constants of type double are defined. In other words, if we have class X {
foo(double beauty) {...}
//...
static const highBeauty = 1.0;
static const lowBeauty= 0.0;
static const mediumBeauty = 0.5;
} then there's no other "double" constants there. |
@rrousselGit wrote:
I'll fold the response because I think it might not be of general interest.I don't have any particular agenda with respect to the phrase 'typing layer'. I'd be perfectly happy if we'd just never use it. Anyway, I'm trying to understand your intentions when using this phrase. My working hypothesis is that it means something like "a language mechanism that plays a role which is similar to the role played by a declared type". "Similar" is a wonderfully flexible word, which means that almost anything can now be a 'typing layer'. However, if we try to narrow it down to something that makes sense we could say that the mechanism must establish a guarantee. For example, if this 'typing layer' is used with a formal parameter then the body can make additional assumptions about the possible values of that parameter, because it's guaranteed that the value of the parameter will satisfy those assumptions. With That's the main reason why I don't think it's reasonable to characterize The clause In other words, it enlarges the set of possible expressions that we can pass as actual arguments by a (small) set of terms of the form
Yes, it informs us about the fact that certain shortcuts are supported.
I don't agree that this is similar to a type constraint, because knowing that a particular actual argument is also the value of a static member of It sounds like you want to talk about the shortcuts as if we had a constraint like "thou must never pass any actual arguments that aren't shortcuts". I have no idea why anyone would want to enforce such a constraint.
The purpose of If you wish to reduce user mistakes it might be helpful to specify a set of shortcuts which is narrow, consisting only of values that are definitely typical for this parameter. It might very well be a user mistake to pass a typical value as the actual argument in any given situation. Perhaps a particular call site requires an exceptionally rare value, because that's the correct thing to do. However, the big picture might still be that there will be fewer bugs in the invocations of the given method/function if the shortcuts are typically an appropriate argument value. Types are there because it is helpful for overall program correctness to enforce that certain variables or parameters can only have values from a specific set (and expressions in general have similar guarantees). This means that we know the given value can be used in a specific manner (say, a I don't think it's a useful perspective on shortcuts that they may help avoiding some errors because of their "typicality", and I do think it's useful to think about them as distinguished argument values that deserve a particularly convenient syntax;
That would be extremely surprising to me.
Certainly,
But you would never do that: A simple
I was just saying that you can't actually add a new static member to, say,
You could also say that they are kind of opposite to each other: The functions with the assertions at the start of the body are (dynamically) enforcing some stronger constraints on the actual argument values than the declared type; a function using
Indeed, that could be useful! This is a request for the kind of extensibility that I've proposed we should use static extensions to express: // In 'some_lib.dart'.
void fn({required Color color}) {...}
// In 'main.dart'.
import 'some_lib.dart';
static extension on Color {
static const primaryColor = MyTheme.primaryColor;
... // Other theme colors.
}
void main() {
fn(color: .primaryColor); // OK.
} If It would be highly convenient to have a kind of quantification (like There could of course be name clashes. You could then use a different name in the static extension. Other than that, I think it works.
Use a static extension to inject the desired shortcuts into the static namespace which is already chosen by
You can use a shortcut, you don't have to edit
Please. The author of
You don't get to decide which parameters a given method of a class accepts, or their types, or the return type, or the name of the method. Similarly for getters, setters, variables. Similarly for lots of other properties that are part of a declaration. That's all fine, apparently. There is one particular way to prevent the addition of extra shortcuts, namely: The package author made the choice to use |
Experiment: replacing Look insideContainer(
decoration: const $(
border: $(
top: $(color: $(0xFFFFFFFF)),
left: $(color: $(0xFFFFFFFF)),
right: $(),
bottom: $(),
),
),
child: Container(
padding: const $.symmetric(horizontal: 20.0, vertical: 2.0),
decoration: const $(
border: $(
top: $(color: $(0xFFDFDFDF)),
left: $(color: $(0xFFDFDFDF)),
right: $(color: $(0xFF7F7F7F)),
bottom: $(color: $(0xFF7F7F7F)),
),
color: $(0xFFBFBFBF),
),
child: const Text(
'OK',
textAlign: $.center,
style: $(color: $(0xFF000000))
),
),
) The syntax abstract class AlignmentGeometry default Alignment {
//...
} This might cover many problematic cases that were not addressed by @lrhn's proposal. |
Re: eernstg I'll just state again that it'd be worth looking into not including |
@tatumizer wrote:
I'm considering the situation where we want to provide shortcuts for multiple constructors declared by multiple classes (presumably, a set of classes that are all subtypes of the parameter type). The typical example could be So how would we do that? A couple of obvious ideas are the following:
I'm a little worried about the first one because it is quite "magic", that is, it will probably need to take a lot of decisions about which static members/constructors to include, from which classes, and it isn't obvious that the developer can do anything if those choices aren't as desired. Alternatively, we'd need to introduce machinery such that the developer can make the choices as needed. The second alternative would be rather straightforward. We probably still don't want to have a huge amount of machinery, at least not unless we introduce a separate declaration such that it doesn't swamp the parameter declaration entirely: shortcut MyShortcut = {
EdgeInsetsGeometry hide new,
EdgeInsets, // A plain `.new(...)` means `EdgeInsets(...)`, and we get `.all` etc as well.
EdgeInsetsDirectional rename all as directionalAll,
};
void foo({required EdgeInsetsGeometry padding in MyShortcut}) {...} In any case, we would probably have a mechanism which is too magic, or we'd introduce a lot of machinery in order to be able to control all that magic as needed. Now, the motivation for introducing declarations like
We could then use If we don't want to use a new, specialized mechanism which will allow us to inject an // --- In Flutter libraries.
abstract class EdgeInsetsGeometry {
const EdgeInsetsGeometry();
}
class EdgeInsets extends EdgeInsetsGeometry {
...
}
class EdgeInsetsDirectional extends EdgeInsetsGeometry {
...
}
// --- 'my_lib.dart'.
static extension EdgeInsetsGeometryExtension on EdgeInsetsGeometry {
factory EdgeInsetsGeometry.all(double _) = EdgeInsets.all;
factory EdgeInsetsGeometry.directionalAll(double _) = EdgeInsetsDirectional.all;
... // Other constructors and static members that we want to use as shortcuts.
}
void foo({required EdgeInsetsGeometry padding}) {...}
// --- 'main.dart'.
void main() {
foo(padding: .all(16.0));
foo(padding: .directionalAll(16.0));
...
} The trade-off is that (a) there's no need for new, elaborate mechanisms in order to populate
Agreed, but I don't think it applies here. It is a perfectly well-known and legitimate design that a given (perhaps abstract) class From this point of view it is also not a (big?) problem that It all depends. If |
@eernstg wrote:
They all have different interfaces. I am thinking of another idea.
The idea of "another namespace" hinges on the assumption that all (or most) problematic cases for shorthand notation are exactly those that come from the inheritance; in Flutter, hierarchies are shallow, with the (abstract) base class A and its direct subclasses B, C, ... (in most cases there are only two), and it would be possible to choose one of them to serve as a kind of "default implementation", or "most commonly used implementation" of A. Then the |
Interesting! I like the consistency part of this idea: " We have two mechanisms here: (1) The incompleteness is visible: A reader would know that On the other hand, if Anyway, it's great to have so many ideas on the table! |
Yes, I am also of two minds about abbreviating The cases of calling default constructors are very common in Flutter - this is visible to the naked eye, so (My working hypothesis is that from UX standpoint |
I don't see how |
@mateusfccp : if we ever get the constructor invocation like |
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: