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.
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.
I have requested a not-so-different ranges feature myself, but for enumeration types.
ReplyDelete/sub
ReplyDeleteA 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.
ReplyDeleteAsbjø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
ReplyDeleteHorá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.
ReplyDeleteHorá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.
ReplyDeleteHorá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.
ReplyDeleteI 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.
ReplyDeleteMarco 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).
ReplyDeletePersonally thought I miss the generic enumerable constraint the most. This hinders a lot of really nice things.
ReplyDeleteMarco 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.
ReplyDeleteI 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.
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.
ReplyDeleteThe roadmap shows new language features for Godzilla 10.2, it could be one of those :D
ReplyDeleteHorá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.
ReplyDeleteYes 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.
Sorry guys, I am feeling like a preadolescent complainer hehehehe.
ReplyDeleteAsbjø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
ReplyDeleteFor 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.
In any case, the idea is interesting (even if not at the compiler level). I have just written a demo with this code:
ReplyDeletefor 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.
I am strongly opposed to new features. First, the existing errors should be eliminated. The QP is full of errors that hurt.
ReplyDeleteHorá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 :)
ReplyDeleteMarco Cantù My tastes regarding the typical pascal naming convention has changed drastically after a few years with C++ and Python... so eew :P
ReplyDeleteMarco Cantù Asbjørn Heid Sorry sorry for my inconveniences, please :D Thanks in advance.
ReplyDeleteThanks a lot Marco Cantù I will appreciate it too much :D An implementation using global function looks better :D
ReplyDeleteBlog post "Stepping through Values in a Delphi for Loop" at http://blog.marcocantu.com/blog/2016-february-stepping-values-for-loop.html
ReplyDeleteCan certainly be improved, just a skeleton piece of code. Has an inclusive implementation.
ReplyDeleteMarco 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.
ReplyDeleteFWIW here is my implementation (probably will make it into Spring4D at some point): https://bitbucket.org/snippets/sglienke/krkrx
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.
ReplyDeleteMarco Cantù Stefan Glienke Thanks for the implementations :D You are lovely :D
ReplyDeleteAnd just to complete the trifecta, here's my (non-yielding) take: http://paste.ie/view/f5595c92
ReplyDeleteedit: 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.
And for completeness, my yielding Range: http://paste.ie/view/353d74a5
ReplyDeleteYield is implemented via Fibers.