|
| 1 | +Users have long asked for a lighter notation to define a class with some fields |
| 2 | +and a constructor that initializes them. Sometimes, they ask for full "data |
| 3 | +classes", meaning that's basically *all* the class contains. But often they |
| 4 | +simply want a simpler syntax for declaring fields and their constructor |
| 5 | +initializers on a regular user-defined class. |
| 6 | + |
| 7 | +The challenge is that there are many many things that field declarations, |
| 8 | +constructors, and constructor parameters may need to specify: |
| 9 | + |
| 10 | +* Is the constructor `const` or not? Is it named? |
| 11 | +* Is there a `super()` initializer? Initializer list? |
| 12 | +* Is there a constructor body? |
| 13 | +* Is the field `final` or mutable? `late`? Does it `@override` a supertype |
| 14 | + getter? |
| 15 | +* Is the parameter named or positional, required or optional? Does it have a |
| 16 | + default value? |
| 17 | + |
| 18 | +## Corpus analysis |
| 19 | + |
| 20 | +It's unlikely that any syntactic sugar can cover *all* of these cases, so to |
| 21 | +make some patterns look better, we need to optimize for the most common ones. To |
| 22 | +that end, I scraped a corpus of 2,000 pub packages (7,116,622 lines in 51,038 |
| 23 | +files) to look at how they define and initialize their fields: |
| 24 | + |
| 25 | +``` |
| 26 | +-- Constructor type (69447 total) -- |
| 27 | + 39003 ( 56.162%): non-const ======================== |
| 28 | + 16254 ( 23.405%): non-const factory ========== |
| 29 | + 13341 ( 19.210%): const ======== |
| 30 | + 849 ( 1.223%): const factory = |
| 31 | +``` |
| 32 | + |
| 33 | +Most constructors are not `const`, but `const` and `factory` do show up fairly |
| 34 | +often. |
| 35 | + |
| 36 | +``` |
| 37 | +-- Constructor name (69447 total) -- |
| 38 | + 45911 ( 66.109%): unnamed ================================== |
| 39 | + 23536 ( 33.891%): named ================== |
| 40 | +``` |
| 41 | + |
| 42 | +Unnamed constructors are twice as common as named constructors. |
| 43 | + |
| 44 | +``` |
| 45 | +-- Non-factory body (52344 total) -- |
| 46 | + 44862 ( 85.706%): ; ========================================== |
| 47 | + 7064 ( 13.495%): {...} ======= |
| 48 | + 418 ( 0.799%): {} (empty) = |
| 49 | +``` |
| 50 | + |
| 51 | +Of generative constructors, more than 4/5ths do not have a body. This was |
| 52 | +pretty surprising to me. |
| 53 | + |
| 54 | +``` |
| 55 | +-- Super initializer (24633 total) -- |
| 56 | + 19489 ( 79.117%): has super() ====================================== |
| 57 | + 5144 ( 20.883%): none ========== |
| 58 | +``` |
| 59 | + |
| 60 | +4/5 of constructors do have a superclass initializer in the constructor list. |
| 61 | +Not too surprising given how many classes in modern Dart code are Flutter |
| 62 | +widgets that need to forward `key` to the base class. |
| 63 | + |
| 64 | +``` |
| 65 | +-- Constructor parameters (69447 total) -- |
| 66 | + 26920 ( 38.763%): only positional parameters ==== |
| 67 | + 25700 ( 37.007%): only named parameters === |
| 68 | + 10631 ( 15.308%): no parameters == |
| 69 | + 3810 ( 5.486%): both positional and named parameters = |
| 70 | + 1638 ( 2.359%): only optional positional parameters = |
| 71 | + 748 ( 1.077%): both positional and optional positional parameters = |
| 72 | +``` |
| 73 | + |
| 74 | +Looking at the constructor parameter lists, we see a variety of styles. Most |
| 75 | +have either all positional parameter or all named parameters with roughly equal |
| 76 | +numbers of each. The latter are typically Flutter widgets and the former are |
| 77 | +"vanilla" Dart classes. |
| 78 | + |
| 79 | +``` |
| 80 | +-- Field initialization (188115 total) -- |
| 81 | + 103520 ( 55.030%): `this.` parameter ==== |
| 82 | + 37620 ( 19.998%): not initialized == |
| 83 | + 34274 ( 18.220%): at declaration == |
| 84 | + 8710 ( 4.630%): initializer list = |
| 85 | + 3196 ( 1.699%): `this.` parameter, initializer list = |
| 86 | + 675 ( 0.359%): `this.` parameter, at declaration = |
| 87 | + 112 ( 0.060%): at declaration, initializer list = |
| 88 | + 8 ( 0.004%): `this.` parameter, at declaration, initializer list = |
| 89 | +``` |
| 90 | + |
| 91 | +This looks at the instance fields in a class and how they are initialized. It |
| 92 | +doesn't look at assignments to fields in methods or the constructor body. It's |
| 93 | +only looking to see if a field is initialized at its declaration, using a |
| 94 | +`this.`-style parameter in the constructor list, or in a constructor initializer |
| 95 | +list. A field may be initialized in multiple ways since classes can have |
| 96 | +multiple constructors. |
| 97 | + |
| 98 | +Also a little suprising to me. More than half of all fields are initialized |
| 99 | +*solely* using `this.` parameters. Initializer lists are fairly rare. I |
| 100 | +suspected that many of the field initializers in initializer lists were to |
| 101 | +initialize a private field with a named parameter without the `_`, so I looked |
| 102 | +at the initializing expressions: |
| 103 | + |
| 104 | +``` |
| 105 | +-- Fields in initializer lists (13760 total) -- |
| 106 | + 9768 ( 70.988%): other =============================== |
| 107 | + 2420 ( 17.587%): _field = field ======== |
| 108 | + 1572 ( 11.424%): field = literal ===== |
| 109 | +``` |
| 110 | + |
| 111 | +So, yes, it's the most common specific reason for initializing a field in the |
| 112 | +initializer list that I could recognize, but all sorts of expressions appear |
| 113 | +there. |
| 114 | + |
| 115 | +``` |
| 116 | +-- Field mutability (187575 total) -- |
| 117 | + 97385 ( 51.918%): final ========================= |
| 118 | + 69715 ( 37.166%): var ================== |
| 119 | + 11263 ( 6.005%): late === |
| 120 | + 9212 ( 4.911%): late final === |
| 121 | +``` |
| 122 | + |
| 123 | +A little more than half of instance fields are `final`. Not too surprising since |
| 124 | +it plays nice with Flutter's immutable reactive model and "Effective Dart" |
| 125 | +recommends it. |
| 126 | + |
| 127 | +``` |
| 128 | +-- Constructor count (70913 total) -- |
| 129 | + 22313 ( 31.465%): 0 ================== |
| 130 | + 35526 ( 50.098%): 1 ============================= |
| 131 | + 9269 ( 13.071%): 2 ======== |
| 132 | + 986 ( 1.390%): 3 = |
| 133 | + 2518 ( 3.551%): 4 == |
| 134 | + 136 ( 0.192%): 5 = |
| 135 | + 44 ( 0.062%): 6 = |
| 136 | + 33 ( 0.047%): 7 = |
| 137 | + 15 ( 0.021%): 8 = |
| 138 | + 6 ( 0.008%): 9 = |
| 139 | + 25 ( 0.035%): 10 = |
| 140 | + 4 ( 0.006%): 11 = |
| 141 | + 6 ( 0.008%): 12 = |
| 142 | + 6 ( 0.008%): 13 = |
| 143 | + 4 ( 0.006%): 14 = |
| 144 | + 4 ( 0.006%): 15 = |
| 145 | + 5 ( 0.007%): 16 = |
| 146 | + 3 ( 0.004%): 17 = |
| 147 | + 2 ( 0.003%): 18 = |
| 148 | + 2 ( 0.003%): 19 = |
| 149 | + 1 ( 0.001%): 20 = |
| 150 | + 2 ( 0.003%): 21 = |
| 151 | + 1 ( 0.001%): 28 = |
| 152 | + 1 ( 0.001%): 68 = |
| 153 | + 1 ( 0.001%): 81 = |
| 154 | +``` |
| 155 | + |
| 156 | +Most classes only define a single constructor. About a third of classes have |
| 157 | +no constructor at all. They either rely on the default constructor or are |
| 158 | +abstract classes that are either used as interfaces or static namespaces. |
| 159 | + |
| 160 | +(The class with 81 constructors is StreamSvgIcon from stream_chat_flutter, one |
| 161 | +for each of a large number of pre-defined icons with constructor parameters to |
| 162 | +color and scale the icon.) |
| 163 | + |
| 164 | +## Opinion |
| 165 | + |
| 166 | +My impression from looking at the numbers is that we could cover a fairly large |
| 167 | +number of classes if we aimed for: |
| 168 | + |
| 169 | +* Classes that define a single constructor. |
| 170 | + |
| 171 | +* Where all the fields can be initialized from `this.` parameters (or possibly |
| 172 | + at the field declaration). |
| 173 | + |
| 174 | +* But it has to support both positional and named parameters. |
| 175 | + |
| 176 | +* Supporting const constructors would be good but not necessary. |
| 177 | + |
| 178 | +* Likewise we can get far with just unnamed constructors, but named is pretty |
| 179 | + useful. |
| 180 | + |
| 181 | +* Some ability to call the superclass constructor is necessary, though I think |
| 182 | + [`super.`](https://github.com/dart-lang/language/issues/1855) would mostly |
| 183 | + solve this. |
0 commit comments