www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Creating a custom iota()

reply realhet <real_het hotmail.com> writes:
Hello,

I have my own DateTime struct.
It has opCmp() and opBinary(), I can do arithmetic with this 
custom DateTime and the amazing time units of the **quantities** 
package.

Now I'm about mo make iterations in a DateTime range:

     const
       st = DateTime(UTC, "22.1.1 8:30").utcDayStart,
       en = DateTime(UTC, "22.3.5 19:56").max(now).utcDayStart + 
(100.0/64)*nano(second);

     //this works
     for(auto d = cast()st; d < en; d += day) writeln(d);

     //this would be nicer, but not works
     iota(st, en, day).each!writeln;

My question is, is there a way to 'extend' the functionality of 
the std.iota() function or it is better to come up with a unique 
name for this special 'iota' functionality?

I know that if I'm going to patch iota in my module, I have to 
make ALL the iota() variants plus my special iota variant visible 
in that module scope. Is this a good practice, or is there a 
better way?

Or maybe put it inside a DateTimeRange struct, and implement the 
std range functionality?
May 12 2022
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, May 12, 2022 at 11:57:54AM +0000, realhet via Digitalmars-d-learn wrote:
[...]
 I have my own DateTime struct.
 It has opCmp() and opBinary(), I can do arithmetic with this custom
 DateTime and the amazing time units of the **quantities** package.
 
 Now I'm about mo make iterations in a DateTime range:
 
     const
       st = DateTime(UTC, "22.1.1 8:30").utcDayStart,
       en = DateTime(UTC, "22.3.5 19:56").max(now).utcDayStart +
 (100.0/64)*nano(second);
 
     //this works
     for(auto d = cast()st; d < en; d += day) writeln(d);
 
     //this would be nicer, but not works
     iota(st, en, day).each!writeln;
 
 My question is, is there a way to 'extend' the functionality of the
 std.iota() function or it is better to come up with a unique name for
 this special 'iota' functionality?
Custom user types should already be supported by iota; see: https://issues.dlang.org/show_bug.cgi?id=10762 Does your DateTime type support the `++` operator? If not, that's the most likely reason iota failed to instantiate on it. Just add `opUnary(string op : "++")` to your type, and it should work. [...]
 Or maybe put it inside a DateTimeRange struct, and implement the std
 range functionality?
[...] That would work too. But I'm curious, why didn't you use std.datetime? The DateTime type there supports inter-date arithmetic (as far as it makes sense), and can be easily made into a range using std.range.recurrence. T -- When solving a problem, take care that you do not become part of the problem.
May 12 2022
parent realhet <real_het hotmail.com> writes:
On Thursday, 12 May 2022 at 16:57:35 UTC, H. S. Teoh wrote:

 Does your DateTime type support the `++` operator?
It can't because I only want to use the quantities.si.Time type to do arithmetic with my DateTime. In my previous DateTime, it was a lot of problem that I was doing math on it's raw internal variable (which was a double where 1.0 meant 1 day.) In the new version adding int(1) does a compilation error. I will use 1*second or 1*day or 1*micro(second) instead. I will always state the measurement unit to avoid confusions.
 But I'm curious, why didn't you use std.datetime?
There are many weird reasons: 5 years ago I moved to DLang from Delphi. Since 20 years I always used the old Borland date system whist stards in 1899, is an ieee double, 1 = 1 day. I only did stuff with local times, it was enough. Recently I had to work with Python as well, and there I started to have problems with time synchronizations between the 2 systems. I ended up sending the exact Borland time to the Python program and make my little routines to work with them exactly like I do 20 years ago in Delphi, 5 years ago in DLang. But I know it is not nice. So a week ago I discovered the quantities package and I decided to rewrite my DateTime to use the Time type in that. It solves all my measurement unit madness in a very elegant way. So I also want those measurement units working in all my systems. (I have lengths, frequencies too) For me std.datetime was not an option because that felt so big I scared of it at the first look. It's 1.5MB, the same size as my complete program I'm working on :D I also hade a second based time measurement 'system', it used QueryPerformanceCounter. I want do discontinue that as well. So these are the features of the new DateTime: - small size: 64 uint is the internal format. (Half of std.systemtime, but it lacks timezone, it's only UTC internally). - precise: 100.0/64 nanosecond is the smallest unit. It covers 913 years. - It was built around the new WinAPI GetSystemTimePreciseAsFileTime() It gives me 100ns resolution UTC time in less than 50ns. So that's why I don't need QueryPerformanceCounter anymore. - The extra 6 bits under the 100ns ticks will be used for time based unique ID generation. It's better for me than having 60000 years at my hand. - The integration with quantities.si makes my work much easier. I can't wait to use it in my program, as I knows physics things better than me, haha.
 std.range.recurrence.
Indeed, that's an option as well, I forgot about. Thank You!
May 12 2022
prev sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 5/12/22 04:57, realhet wrote:

     //this would be nicer, but not works
     iota(st, en, day).each!writeln;
For others, the problem is, iota does have a version that works with user types but not one that parameterizes 'step'. An oversight?
 My question is, is there a way to 'extend' the functionality of the
 std.iota() function or it is better to come up with a unique name for
 this special 'iota' functionality?
I think ioat can be improved to work with user 'step' types as I did below.
 Is this a good practice
I don't care whether it is good practice or not. :) The following is what you meant anyway and seems to work. I added 6 comments: import std.stdio; import std.range; import std.algorithm; struct Duration { ulong value; } struct DateTime { Duration sinceEpoch; // Quickest implementation for this post auto opOpAssign(string op)(Duration dur) if (op == "+") { return DateTime(Duration(sinceEpoch.value += dur.value)); } } void main() { const st = DateTime(Duration(0)); const en = DateTime(Duration(10)); const step = Duration(1); // (0) I think D should not insist on 'const' // when copying types that have no indirections. // We shouldn't need the cast() below in this case. // // Alternatively, the implementation of iota() // could use Unqual after detecting B has no // indirections. That would be better for the // user but again, the language should copy // to non-const by-default. But then, I am // sure there would be cases where an unexpected // function overload might be selected in some // cases. iota(cast()st, en, step).each!writeln; } // I adapted the following template from my // /usr/include/dlang/dmd/std/range/package.d // and then: // (1) Added 'S step' auto iota(B, E, S)(B begin, E end, S step) // (2) Removed for now // if (!isIntegral!(CommonType!(B, E)) && // !isFloatingPoint!(CommonType!(B, E)) && // !isPointer!(CommonType!(B, E)) && // is(typeof((ref B b) { ++b; })) && // (is(typeof(B.init < E.init)) || is(typeof(B.init == E.init))) ) { static struct Result { B current; E end; S step; // (3) Added property bool empty() { static if (is(typeof(B.init < E.init))) return !(current < end); else static if (is(typeof(B.init != E.init))) return current == end; else static assert(0); } property auto front() { return current; } void popFront() { assert(!empty); // (4) Used += instead of ++current // This can be improved to use the other // method a.l.a. "design by introspection". current += step; } } // (5) Added step return Result(begin, end, step); } Ali
May 12 2022
next sibling parent reply ag0aep6g <anonymous example.com> writes:
On Thursday, 12 May 2022 at 17:06:39 UTC, Ali Çehreli wrote:
 void main() {
   const st = DateTime(Duration(0));
[...]
   // (0) I think D should not insist on 'const'
   // when copying types that have no indirections.
   // We shouldn't need the cast() below in this case.
[...]
   iota(cast()st, en, step).each!writeln;
 }
[...]
 auto iota(B, E, S)(B begin, E end, S step)
[...]
 {
     static struct Result
     {
         B current;
[...]
         void popFront()
         {
[...]
             current += step;
         }
     }
[...]
 }
Mark iota's `begin` parameter as const. Then you don't need the cast, because `B` will be mutable.
May 12 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 5/12/22 12:51, ag0aep6g wrote:

 auto iota(B, E, S)(B begin, E end, S step)
[...]
 {
     static struct Result
     {
         B current;
[...]
         void popFront()
         {
[...]
             current += step;
         }
     }
[...]
 }
Mark iota's `begin` parameter as const. Then you don't need the cast, because `B` will be mutable.
Cool trick! Like this: auto iota(B, E, S)(const(B) begin, E end, S step) { // ... } It works with non-const values as well. So apparently it makes the function parameter const(B) and deduces B to be the non-const version of that, which always produces the non-const version. And I've been thinking 'iota' may not be as suitable as I thought at first. I like the following even more: auto r0 = st .by(Duration(2)) .take(5); auto r1 = st .by(Duration(2)) .until(en); A family of 'by' ovenloads can be defined or it can be templatized to be used as 'by!Duration(2)' etc.: auto by(DateTime dt, Duration dur) { struct Result { DateTime front; Duration dur; enum empty = false; void popFront() { front += dur; } } return Result(dt, dur); } Just works. But one may want to provide an accessor for front(). Ali
May 12 2022
parent realhet <real_het hotmail.com> writes:
On Thursday, 12 May 2022 at 20:12:19 UTC, Ali Çehreli wrote:
 And I've been thinking 'iota' may not be as suitable as I 
 thought at first. I like the following even more:

   auto r0 = st
             .by(Duration(2))
             .take(5);
So I wrote this by() for my DateTime and then: import quantities.si; auto by(in DateTime begin, in Frequency f){ return begin.by(1/f); } //This let me do: now.by(60*hertz) .until!"a>b"(now+1*second) .each!writeln; My mind is blowing! :D
May 12 2022
prev sibling parent realhet <real_het hotmail.com> writes:
On Thursday, 12 May 2022 at 17:06:39 UTC, Ali Çehreli wrote:
 I don't care whether it is good practice or not. :) The 
 following is what you meant anyway and seems to work.
I restricted the parameter types to the ones I wanted to use. And for the standard iota behavior I used a public import. public import std.range : iota; auto iota(in DateTime begin, in DateTime end, in Time step){ //https://forum.dlang.org/post/ivskeghrhbuhpiytesas forum.dlang.org -> Ali's solution static struct Result{ DateTime current, end; Time step; property bool empty(){ return current >= end; } property auto front(){ return current; } void popFront(){ assert(!empty); current += step; } } return Result(begin, end, step); } ... iota(st, en, day).each!writeln; //works iota(1, 10, 0.5).each!writeln; //also works It works perfectly, Thank You very much! Although that general implementation of iota is a bit complex for me, this specialized one is simple. note(0): no cast() was needed here, worked with a const DateTime{ ulong ticks; ... }
May 12 2022