Thread overview
Creating a custom iota()
6 days ago
realhet
6 days ago
H. S. Teoh
6 days ago
realhet
6 days ago
Ali Çehreli
6 days ago
ag0aep6g
6 days ago
Ali Çehreli
6 days ago
realhet
6 days ago
realhet
6 days ago

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?

6 days ago
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.
6 days ago
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

6 days ago
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.
6 days ago
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!



6 days ago
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

6 days ago
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; ... }
6 days ago
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