AdaCore / ada-spark-rfcs

Platform to submit RFCs for the Ada & SPARK languages
63 stars 28 forks source link

[RFC] Lightweight iterators #37

Open reznikmm opened 4 years ago

reznikmm commented 4 years ago

Link to text: https://github.com/reznikmm/ada-spark-rfcs/blob/lightweight-iterators2/considered/rfc-lightweight-iterators.rst

jere-software commented 4 years ago

I like that the suggestion is to decouple iterators from cursors as I feel coupling them together was a huge mistake in Ada's original design.

I am curious how this works with limited types and large types as it looks like you are simply returning the object by value from a function which is either illegal (limited types) or expensive (large types).

Did I miss something in the text on how that is handled?

Also in your code where you assign iterators to Magic_String, only the first one mentions it is a type and a name. Is that true for all of the iterators or just the first one?

reznikmm commented 4 years ago

@jeremiahbreeden You can return a limited type from a function since Ada 2005. We do this only once to construct an iterator variable "in place" before enter the iteration loop. I don't expect an iterator to be a large type. Any way we don't do worse then current implementation does (like Iterate and First functions in the container library).

In Magic_String my idea was that you can use an identifier or a selected_component in Iterators aspect. If you use an identifier then it designate both type of the iterator object and iterator to use in the loop statement. If you use a selected_component, then selected_component refers to the type of the iterator object (possible in the other package), but in the loop statement you use just selector_name. So if you have

limited with Magic_Strings.Word_Iterators;
limited with Magic_Strings.Line_Iterators;

package Magic_Strings is

   type Magic_String is private
     with Iterators =>
       (Each_Character,
        Word_Iterators.Each_Word,
        Line_Iterators.Each_Line);

Then last two iterators located in child packages like:

package Magic_Strings.Word_Iterators is
   type Each_Word is limited private;

   function First (Self : Magic_String) return Each_Word;
   function Has_Element (Self : Each_Word) return Boolean;
   procedure Next (Self : in out Each_Word);

   function Word (Self : Each_Word) return Magic_String;
   function From (Self : Each_Word) return Positive;
   function To (Self : Each_Word) return Positive;

private

And usage is

for Cursor in Text.Each_Word loop
   Ada.Text_IO.Put_Line
     (Cursor.Word.To_String & " starts from " & Cursor.From'Img);
end loop;
jere-software commented 4 years ago

@reznikmm I'm familiar with returns for limited types, but that normally assumes either you are constructing them or you have some means of copying them. That isn't always the case. For example, I am given a library that provides a limited type with no copy operation (this is a real world example for me that actually happens very often). I want to make a collection of them so I can easily parse through 1000 of them to find a particular one (say with a custom Ordered_Map). With the current iterator scheme, I can do that. I can make a custom ordered map (searchable by an enumeration or integer type) for my limited types and use a reference_type to access them. Additionally I can use the "for of" syntax with them.

I'm asking if that is still possible with this method. It looks like it could be but I saw nothing in the examples for that, I was just checking because I don't want to lose that functionality. I just wasn't sure if returning by value instead of reference is a requirement for this method as all the examples were by value.

I'm probably the only person who cares about this, I make a lot of use out of custom containers for limited types (mainly because that's what all the libraries I am provided with seem to give me).

mosteo commented 4 years ago

I'm probably the only person who cares about this, I make a lot of use out of custom containers for limited types (mainly because that's what all the libraries I am provided with seem to give me).

Unrelated and just curious (sorry for the noise), and I may be missing something obvious, but how are these limited types put into containers in the first place?

jere-software commented 4 years ago

I'm probably the only person who cares about this, I make a lot of use out of custom containers for limited types (mainly because that's what all the libraries I am provided with seem to give me).

Unrelated and just curious (sorry for the noise), and I may be missing something obvious, but how are these limited types put into containers in the first place?

Most all of the libraries I was given were written in the ada95 days (before support for returning limited types in functions). A general idiom I have to use for it is:

package Lists is new My_Limited_Lists(Some_Limited_Type);
A_List : Lists.List;

-- other stuff

A_List.Append;  -- notice no parameters here, just an append call to add a default element
Some_Initializing_Procedure(  A_List.Reference(  A_List.Last )  );

Since the reference function returns by reference, I can modify it after creating it. If I add a bunch at a time, I just do a quick "for of" loop to give them all a default values

Not super clean, but limited types are fairly limited (hah!)

Side note, for limited types that do have a constructing function, I add a profile of that function as a formal parameter to my generic and update the Append/Prepend operations to use it. Same for copying. I have generics that take a copying procedure as a parameter and they provide operations that require copying. Those are even more niche than my normal use case though.

clairedross commented 4 years ago

I like this idea a lot. I think having several iterators with different values may come in handy, for maps for example. If we want to allow in place modification, we could consider passing a Reference function, returning an access-to-variable type instead of Element. Giving a way to provide a Constant_Reference function may also be interesting for efficiency.

reznikmm commented 4 years ago

@jeremiahbreeden I don't see how we can lose the functionality you mentioned. The returning by value method is used for iterators only. In current edition the RFC hasn't propose a way to access Element from iterator yet. To be discussed. But even with current version you can provide a function, that accepts an iterator value and return user reference type from Ada 2012, so you can access elements by reference.

jere-software commented 4 years ago

@reznikmm Ok that works then. I just wanted to make sure. I wasn't seeing it from the description in the file, so I didn't want it to slip under the radar without at least addressing the topic. Sorry for the trouble.