|
| 1 | +Improvements to generic instantiations |
| 2 | +====================================== |
| 3 | + |
| 4 | +Summary |
| 5 | +------- |
| 6 | + |
| 7 | +This RFC proposes to introduce a series of enhancements to Ada generics, |
| 8 | +specifically in terms of how those are instantiated, which purpose is to make |
| 9 | +the design of high level APIs based on generics much easier than before. |
| 10 | + |
| 11 | +Motivation |
| 12 | +---------- |
| 13 | + |
| 14 | +The generic programming system of Ada is pretty powerful, and is essential to |
| 15 | +Ada programming. |
| 16 | + |
| 17 | +However, in light of other modern programming languages, it's pretty obvious |
| 18 | +that certain features are missing from it to make it truly usable in the modern |
| 19 | +programming world. |
| 20 | + |
| 21 | +The general aim is to simplify the use of generics in user code, so that there |
| 22 | +is no friction when using generic subprograms and types. While there are |
| 23 | +improvements to generics from the formal point of view that could (and should) |
| 24 | +be considered for Ada, this is not the topic of that meta RFC. A few key design |
| 25 | +goals are: |
| 26 | + |
| 27 | +1. The main purpose of those enhancements is a way to express implicit |
| 28 | + instantiation of generics. |
| 29 | + |
| 30 | +2. Another key design goal is for users to be able to refer to a "unique" |
| 31 | + instance of a generic, by it's name. For example there will be a unique |
| 32 | + instance of `Ada.Containers.Vectors (Positive, Positive)`, and all people |
| 33 | + refering to it will refer to the same instance, which solves a long standing |
| 34 | + problem in generics, which is the ability to structurally reference unique |
| 35 | + entities. |
| 36 | + |
| 37 | + This is akin to structural typing for types declared inside of generics, and |
| 38 | + is a key feature that is currently missing from Ada generics, to provide |
| 39 | + interoperability between separately evolving pieces of code. (TODO: Fill |
| 40 | + example) |
| 41 | + |
| 42 | +3. We also want to simplify the way generic types (so, in other words, types |
| 43 | + which are the main interest of a generic package, like for example |
| 44 | + `Ada.Containers.Vectors.Vector`) are referred to by users, to syntactically |
| 45 | + acknowledge that they're the main entity of the package. |
| 46 | + |
| 47 | +Given the following supporting generic, |
| 48 | + |
| 49 | +```ada |
| 50 | +-- Supporting code |
| 51 | +generic |
| 52 | + type Index_Type is (<>); |
| 53 | + type El_Type is private; |
| 54 | + type Array_Type is array (Index_Type range <>) of El_Type; |
| 55 | +
|
| 56 | + type Accum is private; |
| 57 | + with function Fn (Current : Accum; El : El_Type) return Accum; |
| 58 | +function Reduce (Init : Accum; Arr : Array_Type) return Accum; |
| 59 | +``` |
| 60 | + |
| 61 | +This would ultimately allow people to write the following example: |
| 62 | + |
| 63 | +```ada |
| 64 | +function Sum (X: Float_Array) return Float is |
| 65 | + function Red is new Reduce (Positive, Float, Float_Array, Float, "+"); |
| 66 | +begin |
| 67 | + return Red (0.0, X); |
| 68 | +end Sin; |
| 69 | +``` |
| 70 | + |
| 71 | +This way: |
| 72 | + |
| 73 | +```ada |
| 74 | +function Sum (X: Float_Array) return Float is (Reduce (Fn => "+") (X)) |
| 75 | +``` |
| 76 | + |
| 77 | +Also, regarding generic packages & types, we would like the following: |
| 78 | + |
| 79 | +```ada |
| 80 | +package Float_Vectors |
| 81 | +is new Ada.Containers.Vectors (Positive, Positive); |
| 82 | +
|
| 83 | +F : Float_Vectors.Vector; |
| 84 | +F2 : Float_Vectors.Vector; |
| 85 | +``` |
| 86 | + |
| 87 | +To be expressible this way: |
| 88 | + |
| 89 | +```ada |
| 90 | +generic type Vector is Ada.Containers.Vectors (Index_Type => Positive).Vector; |
| 91 | +
|
| 92 | +F : Vector (Positive) |
| 93 | +F2 : Vector (Positive) |
| 94 | +``` |
| 95 | + |
| 96 | +### Expected high level benefits |
| 97 | + |
| 98 | +More generally, we expect the following benefits from implementation of those |
| 99 | +improvements: |
| 100 | + |
| 101 | +* Better code sharing/reduced code size: Due to the paradigm of explicit |
| 102 | + instantiations, it often happens because programmers cannot find duplications |
| 103 | + easily/because it's hard to share instances because of the structure of the |
| 104 | + project, that you have several instantiations with the same parameters. |
| 105 | + |
| 106 | +* Better modularity: |
| 107 | +* Paradigm shift in terms of expressivity: |
| 108 | + |
| 109 | +Explanation |
| 110 | +----------- |
| 111 | + |
| 112 | +We will now walk through the set of proposed features that will allow us |
| 113 | +to write the above, starting from the current set of Ada's generic |
| 114 | +features. |
| 115 | + |
| 116 | +### First step: Inference of dependent types in generic instantiations |
| 117 | + |
| 118 | +Using the feature described in [this rfc (TODO)](https://todoaddlink), |
| 119 | +we could then simplify the above code's Reduce instantiation: |
| 120 | + |
| 121 | +```ada |
| 122 | +function Sum (X: Float_Array) return Float is |
| 123 | + -- Index and element types are automatically deduced |
| 124 | + function Red is new Array_Reduce (<>, <>, Float_Array, Float, "+"); |
| 125 | +begin |
| 126 | + return Red (0.0, X); |
| 127 | +end Sin; |
| 128 | +``` |
| 129 | + |
| 130 | +Here, we're allowed to not specify generic actual parameters for parameters |
| 131 | +that can be deduced from other parameters, according to the rules described in |
| 132 | +the RFC. |
| 133 | + |
| 134 | +This simplifies the instantiation of the `Array_Reduce` generic function a |
| 135 | +little, but is not a big step up from the last version. We will understand the |
| 136 | +true edge this feature gives us in the last step. Let's go to the next |
| 137 | +iteration |
| 138 | + |
| 139 | +### Second step: Implicit instantiation of generics |
| 140 | + |
| 141 | +This one is the big step up, that will allow us to get one step closer to the |
| 142 | +initial intent. Using implicit instantiation of generic functions [(see RFC |
| 143 | +here (TODO))](https://TODO), we would be able to write the following: |
| 144 | + |
| 145 | +```ada |
| 146 | +function Sum (X: Float_Array) return Float is |
| 147 | +begin |
| 148 | + return Array_Reduce (<>, <>, Float_Array, Float, 0.0, "+") (0.0, X); |
| 149 | +end Sum; |
| 150 | +``` |
| 151 | + |
| 152 | +The last step we would like to get rid of is the repetitive |
| 153 | +instantiation parameters. |
| 154 | + |
| 155 | + |
| 156 | +> **Note** |
| 157 | +> Above we only show the syntax for functions, but packages can also be |
| 158 | +> structurally instantiatedtypes |
| 159 | +
|
| 160 | +### Third step: inference of generic actual parameters from function call params |
| 161 | + |
| 162 | +Using inference of actual generic actuals using call actuals |
| 163 | +[(see RFC here (TODO))](https://TODO), we can express the above as: |
| 164 | + |
| 165 | +```ada |
| 166 | +function Sum (X: Float_Array) return Float is |
| 167 | +begin |
| 168 | + return Array_Reduce (Fn => "+") (0.0, X); |
| 169 | +end Sum; |
| 170 | +``` |
| 171 | + |
| 172 | +Here, the only generic actual we have to specify is \`Fn\`, because: |
| 173 | + |
| 174 | +- All array type parameters are infered from the `Self` actual |
| 175 | + parameter. `Self` allows us to deduce the type of the `Array_Type` |
| 176 | + generic formal, and from this we can deduce the `Index_Type` and |
| 177 | + `Element_Type`. |
| 178 | + |
| 179 | +- The `Accum` type can be deduced either from the value of `Init`, or |
| 180 | + from the expected target type of the function call. In this case, |
| 181 | + since `0.0` is an universal real, we deduce `Accum` from the |
| 182 | + expected type of the function call, which is the return type of the |
| 183 | + `Sin` function. |
| 184 | + |
| 185 | +> **Note** |
| 186 | +> In terms of how implicit instantiations work, we can wonder whether each |
| 187 | +> reference to an instantiation with the same parameters refers to **the same |
| 188 | +> instance**, or to **a different one each time**. |
| 189 | +> |
| 190 | +> Following from the high level design point number 2 in the motivation, we |
| 191 | +> clearly want each instantiation with similar structural parameters to refer to |
| 192 | +> the same instantiation. |
| 193 | +> |
| 194 | +> In terms of name resolution, it means that, given two implicit instantiation |
| 195 | +> references, they'll reference the same instantiation if their parameters are |
| 196 | +> the same. |
| 197 | +
|
| 198 | +> **Note** |
| 199 | +> There is a big question here in my opinion in how we refer to that feature. |
| 200 | +> Talking about implicit instantiation is maybe not the best terminology. Some |
| 201 | +> other ideas: |
| 202 | +> |
| 203 | +> * "Structural instantiations" might be better in how it coins the nature of the |
| 204 | +> feature. |
| 205 | +> |
| 206 | +> * A natural one for Ada would be "Anonymous instantiations". For me the only |
| 207 | +> problem with this name is the potential PTSD from coming from anonymous |
| 208 | +> accesses/array declarations. |
| 209 | +
|
| 210 | + |
| 211 | +### Fourth step: Generic types |
| 212 | + |
| 213 | +With the current discussed features, the `Vector` example in the motivation |
| 214 | +section can be expressed as: |
| 215 | + |
| 216 | +```ada |
| 217 | +F : Ada.Containers.Vectors (Positive, Positive).Vector; |
| 218 | +F2 : Ada.Containers.Vectors (Positive, Positive).Vector; |
| 219 | +``` |
| 220 | + |
| 221 | +Which is pretty verbose. One could imagine that, in that case, the user can |
| 222 | +just make an explicit instantiation if they need a shorter name. The problem |
| 223 | +with that scheme is that, one of the features that we want to derive from |
| 224 | +implicit instantiations is structural typing of generics, so **conciseness is |
| 225 | +not the only reason to use generics**. |
| 226 | + |
| 227 | +For that use, we propose the "generic types" feature (TODO link to RFC), that |
| 228 | +will in effect add a new kind of generic entity to Ada. However, at least for |
| 229 | +the moment, the way this will be done is still hugely relying on generic |
| 230 | +packages: |
| 231 | + |
| 232 | +```ada |
| 233 | +generic type Vector is Ada.Containers.Vectors.Vector; |
| 234 | +-- This declares that uses of ``Vector`` are really uses of |
| 235 | +-- Ada.Containers.Vectors. |
| 236 | +
|
| 237 | +type Float_Vector is new Vector (Positive, Float); |
| 238 | +-- Declare a new instantiation explicitly |
| 239 | +
|
| 240 | +Inst : Float_Vector; |
| 241 | +
|
| 242 | +F : Vector (Positive, Positive) |
| 243 | +F2 : Vector (Positive, Positive) |
| 244 | +-- Refer to the structural instantiation |
| 245 | +``` |
| 246 | + |
| 247 | + |
| 248 | +### Fifth step: Partial instantiation of generics |
| 249 | + |
| 250 | +A feature that has often been asked in Ada is partial instantiations of |
| 251 | +generics, in fact, it has been asked on ada-spark-rfcs by an external user |
| 252 | +already, see https://github.com/AdaCore/ada-spark-rfcs/pull/41. |
| 253 | + |
| 254 | +This feature has great consequences on generic's usability beyond the scope of |
| 255 | +instantiations, but in the scope of this document, which are detailed in the |
| 256 | +RFC itself, but in the context of instantiations, and of the example of |
| 257 | +`Vectors`, it would allow the following: |
| 258 | + |
| 259 | +```ada |
| 260 | +generic type Vector is Ada.Containers.Vectors (Index_Type => Positive).Vector; |
| 261 | +
|
| 262 | +F : Vector (Positive) |
| 263 | +F2 : Vector (Positive) |
| 264 | +``` |
| 265 | + |
| 266 | +Prior art |
| 267 | +--------- |
| 268 | + |
| 269 | +Many (most) languages with generics have implicit/structural instantiations of |
| 270 | +generics. It's on the other hand hard to find languages with explicit |
| 271 | +instantiations like Ada. All mainstream languages today use implicit/structural |
| 272 | +generics (Java/C++/C#/Rust/Go/etc). |
0 commit comments