New Feature Request - Range magic(intrinsic) compiler function

New Feature Request - Range magic(intrinsic) compiler function
https://quality.embarcadero.com/browse/RSP-13804

Hey guys,

Since Delphi does not provide a step statement in for loops, a good way to work around this is by using a Python's range(https://docs.python.org/3/library/functions.html#func-range)-like compiler magic function.
The compiler is already able to generate enumerable instances magically as it does when we use foreach thus I think implementing

Range(Start: Int32; Stop: Int32; Step: Int32): IEnumerable

as a intrinsic(magic) function is almost complete and is totally possible in the current compiler state.

Range is also very good for indexing arrays as for loop's to value is inclusive (I mean the foor loop upper bound) is very common use 
for I := 0 to FItems.Length - 1 
With the addition of Range function it could get simplified to 
for I in Range(FItems.Length) thus no subtraction is need.

A Range overload as no parameters would start from 0 with step of 1.

To avoid performance loss is better it get created at compile time as already happens with types that implement GetEnumerator in for in loop.
As for in loop already creates temporary objects and eliminates them when goes out of loop bounds, thus you just need to create the range enumerator (as it uses only integers internally it is not going to leak something) at the compile time that will be returned after the Range call. I think no extra effort is need in the sense of avoiding memory leaks.

Delphi seems to be already prepared to this feature, the only thing is necessary in my humble point of view is the way to create the Range enumerator. This feature is too much valueable as you see users asking for in a lot of post in Delphi and programming blogs.

Comments

  1. I have requested a not-so-different ranges feature myself, but for enumeration types.

    ReplyDelete
  2. A record-based roll-your-own Range() for use with for..in is comparable in speed with a regular for loop, no more than 2x slower in my tests. I think that's sufficient.

    ReplyDelete
  3. Asbjørn Heid Did you mean either the way as this feature should be implemented (records are faster as enumerables) or that this feature is not necessary at all? I got lost :D

    ReplyDelete
  4. Horácio Filho Well, I like the suggestion (Range is nice!), I just think that other things should get prioritized as the roll-your-own approach is fast enough IMO.

    ReplyDelete
  5. Horácio Filho For example, the record based approach should get even faster if the compiler was better at optimizing. Currently it's doing a lot of unnecessary copying when returning the records, and it doesn't inline the MoveNext calls etc.

    ReplyDelete
  6. Horácio Filho Also, if we could do generic constraints on enumerable (RSP-12767), we could then easily feed this record-based Range into other things, like a Map() function or whatever.

    ReplyDelete
  7. I agree with Asbjørn Heid (it happens ;-). Building a custom integer enumerator for the for..in loop is simple and flexible enough. A built in version would offer limited advantage, and would be extremely complex considering all possible rules (start, end, inclusive or not, regular step, irregular step...). A different thing is having  Yield mechanism used to build a custom enumerator, that would be fun and save quite some code.

    ReplyDelete
  8. Marco Cantù Yes Yield would be nice. I've done yield using fibers but it's 100x slower than the record-based Range(), so too slow for such an application (but not for other things).

    ReplyDelete
  9. Personally thought I miss the generic enumerable constraint the most. This hinders a lot of really nice things.

    ReplyDelete
  10. Marco Cantù Asbjørn Heid Oh no, Range has only two overloads by design: Range(stop exclusive) and Range(start inclusive, stop exclusive, integer step), no more than this is necessary once Delphi already covers the other cases. Implementing Range myself would require implementing it for all projects I have, repeting this code, or even importing just one unit containing this support for every project.
    I love Yield too, I have filled a feature request entry for add its support, it would be a very great addition but it is harder to do whereas a built-in Range function is faster to implement than the fully Yield support, and when the fully Yield support get ready Range could use it internally.

    ReplyDelete
  11. This is a simple thing that helps a lot, makes things clear and could even it get implemented (a built-in version) with some compiler hack to boost performance.

    ReplyDelete
  12. The roadmap shows new language features for Godzilla 10.2, it could be one of those :D

    ReplyDelete
  13. Horácio Filho Nothing in the compiler is "simple", and any change has a huge overhead in terms of QA etc. That's why we got to pick our most desirable features with care.

    Yes I agree Range() is a great thing, but we can have it today with just a minor speed overhead, and there's no guarantee the compiler-implemented version would be faster unless additional effort was put into make it fast.

    That's why I think this is a good example of what fits perfectly in a community-based library.

    ReplyDelete
  14. Sorry guys, I am feeling like a preadolescent complainer hehehehe.

    ReplyDelete
  15. Asbjørn Heid I really understand you, thanks for your patience :D The RTL is pretty beautiful and complete, it seriously has almost all I have need during my Delphi days, so new features may get more attention now :D 
    For me, Range is a vital thing once there is real use cases for it and it makes Delphi programming more delicious.
    I would not like to import other unit to achieve that, having a built-in Range function would encourage developers to embrace its use once it doesn't require import other unit or even an entire project, small effort to use.

    ReplyDelete
  16. In any case, the idea is interesting (even if not at the compiler level). I have just written a demo with this code:

      for I in TNumbersRange.Range (5, 13, 3) do
        Show (IntToStr (I));

    Will blog Monday the full implementation, just a small extension of a demo in my language book. Of course, could use "Range()" only, but I prefer it as a class function.

    ReplyDelete
  17. I am strongly opposed to new features. First, the existing errors should be eliminated. The QP is full of errors that hurt.

    ReplyDelete
  18. Horácio Filho Don't beat yourself up over it, it's a good suggestion, and had the compiler dev team been 10x I would have been cheering for it as well :)

    ReplyDelete
  19. Marco Cantù My tastes regarding the typical pascal naming convention has changed drastically after a few years with C++ and Python... so eew :P

    ReplyDelete
  20. Marco Cantù Asbjørn Heid Sorry sorry for my inconveniences, please :D Thanks in advance.

    ReplyDelete
  21. Thanks a lot Marco Cantù I will appreciate it too much :D An implementation using global function looks better :D

    ReplyDelete
  22. Can certainly be improved, just a skeleton piece of code. Has an inclusive implementation.

    ReplyDelete
  23. Marco Cantù Your implementation includes stop while the py one does not which imo makes more sense when dealing with zero based ranges where you have a range from 0 to count that does not include count in its result - otherwise you still don't gain anything when working with arrays as you have to subtract one from the length to pass it.

    FWIW here is my implementation (probably will make it into Spring4D at some point): https://bitbucket.org/snippets/sglienke/krkrx

    ReplyDelete
  24. Stefan Glienke Yes, I prefer inclusive by default, but one could have different functions with different settings. To me when working with arrays you'd still use a direct for..in loop on the array itself, no need to worry about range items -- I know there are cases you need the index anyway, not just the value.

    ReplyDelete
  25. Marco Cantù Stefan Glienke Thanks for the implementations :D You are lovely :D

    ReplyDelete
  26. And just to complete the trifecta, here's my (non-yielding) take: http://paste.ie/view/f5595c92

    edit: which doesn't handle negative steps, I should have added that. I wanted to get as close as possible to plain "for..do" in speed.

    ReplyDelete
  27. And for completeness, my yielding Range: http://paste.ie/view/353d74a5

    Yield is implemented via Fibers.

    ReplyDelete

Post a Comment