!standard A.20(0) 18-01-26 AI12-0255-1/01 !class Amendment 18-01-26 !status work item 18-01-26 !status received 18-01-26 !priority Very_Low !difficulty Hard !subject Component iteration/reflection !summary ** TBD. !problem Today, one of the common reasons we hear from customers to use code generation is to generate specific serializers/unserializers, or more generally, ways to make Ada types communicate with the outside world. Examples of that: - JSON/XML serialization - General data types pretty-printing (like 'Image but for everything) - Interfacing to SQL data bases - Interfacing to distributed objects systems/store (CORBA for example). For the moment, for those kind of features, either the support has to be built-in into the language, either a code generator based on a technology such as ASIS has to be The shortcomings of this approach corresponds to the general shortcomings of code generation: - More complicated build processes - Debugging happens on the generated code, which is not always desirable - Maintenance of the code generator So ideally we would like to solve this at the language level. !proposal ## Runtime reflection A common answer to this problem in Java is reflection. Reflection allows the user to inspect an object at runtime, getting information about it, such as its type, its fields, etc. There are however several problems with reflection, in general, and in adapting it to the Ada language. General problems: - Since it is not typed, it is much more susceptible to defects than the typed parts of the program. Everything using runtime reflection might fail at runtime. - Since it happens at runtime, all the work has to be done again. It means that the program might run significantly slower than if you used compile time code generation. - Since it happens at runtime, it also means than any source of data that might be needed to generate code needs to be available when the program runs (for example, an SQL schema) Problems for Ada in particular: - Runtime reflection rests on the ability to be able, at a minimum, to inspect the type of an object at runtime. This means that either in Ada it will only work for tagged types, or we have to dramatically change the representation of objects. - Runtime reflection is impossible to use if you don't have an `Any` type (a root type every type can be converted to). The two most common things that you might want to do with reflection are: * Dynamically call a method on an object, using a handle for the method * Dynamically get the value of a field, using a handle for the field Both those things are impossible to do if you don't have a root type that the operation can return. ## Compile time reflection There is another, hybrid approach, that is often called "compile time reflection". It goes together with compile-time code generation (called generics in Ada). The ten thousand feets description of the feature is that you would be able to introspect, for example, the fields of a type, but statically, inside of a generic. There are a lot of problems/missing features with how Ada is conceived to graft that on top of the existing generics system. What I did here is an attempt to show at a very high level how it would look like, highlighting at every stage the needed features to implement the finally desired functionality. As you will see, the number of missing features is high. generic -- New kind of generic formal, and the only one on which you can use the -- "generic for" loop. type T is record; function Record_To_JSON (Self : T) return Unbounded_String; function Record_To_JSON (Self : T) return Unbounded_String is Result : Unbounded_String; begin Append (Result, "{"); generic for Field of T do -- Inside of the "generic for" block, Field is an entity of an opaque -- anonymous type that possesses a few attributes: -- Field'Type is a statically resolve type, that is the type of the field -- Field'Field_Value is the value of the field if Field'Type'Is_Discrete then Append (Result, Field'Field_Value'Image); elsif Field'Type'Is_Array then -- Here we presume we have implicit instantiation of generic -- functions, but this is not necessary to make this work. Array_To_JSON (Field'Field_Value); elsif T'Element_Type'Is_Record then Record_To_JSON (Field'Field_Value); else pragma Error ("Unhandled type") end if; -- Dumb attribute: We need to know if it's the last component, so we -- need a 'Is_Last attribute on the Field entity. if not Field'Is_Last then Append (Result, ","); end if; end do; Append (Result, "}"); return Result; end Record_To_JSON; generic type T is array; -- We propose that a generic formal array type be able to carry its index -- and element type along. It would simplify a lot of code outside of this -- discussion too. -- You could access the Element type via 'Element_Type, and the index type -- via 'Index_Type function Array_To_JSON (Self : T) return Unbounded_String; function Array_To_JSON (Self : T) return Unbounded_String is Result : Unbounded_String; begin Append (Result, "["); for El of Self loop if T'Element_Type'Is_Discrete then Append (El'Image); elsif T'Element_Type'Is_Array then -- Here we presume we have implicit instantiation of generic -- functions, but this is not necessary to make this work. Array_To_JSON (El); elsif T'Element_Type'Is_Record then Record_To_JSON (El); else pragma Error ("Unhandled type") end if; -- TODO: Missing the Is_Last condition, but at least we know how to do -- that on a regular array. Append (","); end loop; Append (Result, "]"); return Result; end Array_To_JSON; Problems: This works in this pretty simple case but is wildly incomplete. What if, for example, you need declarations for each field ? Inside of the generic for, the type of Field'Type changes for each component, which is what is expected, but could be confusing when compared to regular for loops. The number of improvements to Ada's generics/compilers is huge. For example, you probably want every sub-instantiation to be shared in terms of code emitted, else you're going to get an explosion in code size. !wording ** TBD. !discussion The work to do is way too big for Ada 202X in my opinion. It's possible that it's not even the good solution in the long run, and that alternatives solutions are better: - Good old fashioned code generation might be enough. - If it isn't, maybe specifying an hygienic macro system for Ada would be a better idea (see here: https://en.wikipedia.org/wiki/Hygienic_macro) This can be specified/standardized outside the scope of the ARG, which is probably an added benefit. !ASIS [Not sure. It seems like some new capabilities might be needed, but I didn't check - Editor.] !ACATS test ACATS B- and C-Tests are needed to check that the new capabilities are supported. !appendix From: Raphaël Amiard Sent: Friday, January 26, 2018 8:32 AM Here is my work on reflection/general component iteration. It is not really an AI as what I propose to do is nothing :) [Still, this is version /01 of the AI - ED.] However I recommend to read it as I think it will avoid revisiting the topic every time, I guess. I tried to draft how it would look to make a JSON seralizer with such a capability. If somebody thinks it's worth the effort, now is the time to make yourself heard :) **************************************************************** From: Randy Brukardt Sent: Friday, January 26, 2018 10:39 PM A few thoughts: --- There were a couple of places where the text ends in mid-sentence. I tried to finish the thought in those cases. Hope I didn't butcher the meaning. --- > - Runtime reflection is impossible to use if you don't have an `Any` > type (a root type every type can be converted to). The two most > common things that you might want to do with reflection are: > > * Dynamically call a method on an object, using a handle for the method > * Dynamically get the value of a field, using a handle for the field > > Both those things are impossible to do if you don't have a root type > that the operation can return. We've used the tag of an object as a stand-in for such a type in a few cases (see Generic_Dispatching_Constructor). One could imagine a solution along those lines. The main problem using such a generic would be describing the subprogram (method) or component. One can reduce this to a single problem by imagining that reading a component is a function returning the type of the object, and the writing the component is a procedure call taking a second parameter of the type of the object. (Thanks to Dmitry Kazakov for the basic idea.) So one would need a way to describe the profile of a general subprogram -- in practice, I've used Unchecked_Conversion for this (in CLAW for instance) but that does abandon type checking. One could use a set of generics with common profiles but of course that can't cover all possibilities. One could imagine some sort of generic formal that matches any access-to-subprogram type, but that sounds weird (and again becomes typeless inside the generic). Anyway, I think this problem is solvable. (Whether it should be solved is an altogether different question!) --- The generic approach pretty much has to abandon the contract model. Moreover, if someone (cough, cough) has a compiler that generates shared generics, the effect is essentially the same as the runtime approach. So I don't see that this buys much beyond a runtime approach (and as you say, it is too large for Ada 2050, much less Ada 2020 :-). I would be (somewhat) interested in the same example using the runtime approach (and assuming limited to tagged types, which seems like a fine limitation for something intended to copy a Java feature). Or we can just decide to vote it No Action in 10 seconds of discussion. ;-) ****************************************************************