www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Metaprog can be abstruse

reply user1234 <user1234 12.de> writes:
Example:

```d
auto genDecimalRanks()
{
     import std.conv;
     auto r = 1;
     auto result = "[";
     foreach (i; 1 .. 11)
     {
         result ~= to!string(r);
         if (i < 10)
             result ~= ", ";
         r *= 10;
     }
     result ~= "]";
     return result;
}

/// like 
[1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000]
immutable decimalRanks = mixin(genDecimalRanks);
```

It takes much more space and time to write DDOC + the generator 
than the space + time required to write the equivalent explicit 
declaration without comments, i.e "self documenting".

So metaprog is not the panacea, do you think to that before 
"meta-progrogramming", or do you "meta-prog" just because it's 
nice ?
Jul 04 2022
next sibling parent reply Bastiaan Veelo <Bastiaan Veelo.net> writes:
On Monday, 4 July 2022 at 08:24:01 UTC, user1234 wrote:
 Example:

 ```d
 auto genDecimalRanks()
 {
     import std.conv;
     auto r = 1;
     auto result = "[";
     foreach (i; 1 .. 11)
     {
         result ~= to!string(r);
         if (i < 10)
             result ~= ", ";
         r *= 10;
     }
     result ~= "]";
     return result;
 }

 /// like 
 [1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000]
 immutable decimalRanks = mixin(genDecimalRanks);
 ```

 It takes much more space and time to write DDOC + the generator 
 than the space + time required to write the equivalent explicit 
 declaration without comments, i.e "self documenting".

 So metaprog is not the panacea, do you think to that before 
 "meta-progrogramming", or do you "meta-prog" just because it's 
 nice ?
Not necessarily. ```d immutable decimalRanks = iota(10).map!(p => 10.pow(p)).array; ``` -- Bastiaan.
Jul 04 2022
next sibling parent reply user1234 <user1234 12.de> writes:
On Monday, 4 July 2022 at 09:40:45 UTC, Bastiaan Veelo wrote:
 Not necessarily.
 ```d
 immutable decimalRanks = iota(10).map!(p => 10.pow(p)).array;
 ```

 -- Bastiaan.
Hi, to this alternative I'd say that it's not self documenting. So it still has 50% of the initial problem.
Jul 04 2022
next sibling parent reply Bastiaan Veelo <Bastiaan Veelo.net> writes:
On Monday, 4 July 2022 at 09:43:11 UTC, user1234 wrote:
 On Monday, 4 July 2022 at 09:40:45 UTC, Bastiaan Veelo wrote:
 Not necessarily.
 ```d
 immutable decimalRanks = iota(10).map!(p => 10.pow(p)).array;
 ```

 -- Bastiaan.
Hi, to this alternative I'd say that it's not self documenting. So it still has 50% of the initial problem.
I can see your point, but it depends on how used you are to reading chains of range algorithms. I read this as "given the integers 0 to 9, use them as the powers of ten and put them in an array". There is a point to be made that this is more self-documenting than the hand-written alternative: Here it is obvious that the ranks cover all powers of 10 from 0 to 9 in order. In the hand-written alternative you'd either assume that is the case, or have to meticulously count all the zero's to be sure. -- Bastiaan.
Jul 04 2022
parent reply Dom Disc <dominikus scherkl.de> writes:
On Monday, 4 July 2022 at 10:19:36 UTC, Bastiaan Veelo wrote:
 On Monday, 4 July 2022 at 09:43:11 UTC, user1234 wrote:
 On Monday, 4 July 2022 at 09:40:45 UTC, Bastiaan Veelo wrote:
 Not necessarily.
 ```d
 immutable decimalRanks = iota(10).map!(p => 10.pow(p)).array;
 ```
Hi, to this alternative I'd say that it's not self documenting. So it still has 50% of the initial problem.
There is a point to be made that this is more self-documenting than the hand-written alternative: Here it is obvious that the ranks cover all powers of 10 from 0 to 9 in order.
This advantage becomes much more clear in, let's say: ```d immutable long powersOfThree = iota(27).map!(p => 3.pow(p)).array; ``` Handwritten this is a very long chain of arbitrary numbers and you have to put a very close look, that those are indeed the powers of three.
Jul 04 2022
parent reply kdevel <kdevel vogtner.de> writes:
On Monday, 4 July 2022 at 13:03:55 UTC, Dom Disc wrote:
[...]
 This advantage becomes much more clear in, let's say:
 ```d
 immutable long powersOfThree = iota(27).map!(p => 
 3.pow(p)).array;
 ```
 Handwritten this is a very long chain of arbitrary numbers and 
 you have to put a very close look, that those are indeed the 
 powers of three.
Really? ``` immutable long [] powersOfThree = [ 1, 3, 3 * 3, 3 * 3 * 3, 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3, ]; ```
Jul 05 2022
parent Dom Disc <dominikus scherkl.de> writes:
On Wednesday, 6 July 2022 at 00:11:58 UTC, kdevel wrote:
 On Monday, 4 July 2022 at 13:03:55 UTC, Dom Disc wrote:
 [...]
 This advantage becomes much more clear in, let's say:
 ```d
 immutable long powersOfThree = iota(27).map!(p => 
 3.pow(p)).array;
 ```
 Handwritten this is a very long chain of arbitrary numbers and 
 you have to put a very close look, that those are indeed the 
 powers of three.
Really? ``` immutable long [] powersOfThree = [ 1, 3, 3 * 3, 3 * 3 * 3, 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3, 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3, ]; ```
Ok, but with this, you can't see where the overflow will occur. So you have the same problem as with the one-liner (and it is indeed very much longer).
Jul 06 2022
prev sibling next sibling parent bauss <jj_1337 live.dk> writes:
On Monday, 4 July 2022 at 09:43:11 UTC, user1234 wrote:
 On Monday, 4 July 2022 at 09:40:45 UTC, Bastiaan Veelo wrote:
 Not necessarily.
 ```d
 immutable decimalRanks = iota(10).map!(p => 10.pow(p)).array;
 ```

 -- Bastiaan.
Hi, to this alternative I'd say that it's not self documenting. So it still has 50% of the initial problem.
I think that depends on your expertise. It's self-document if you understand the functionality of iota, map, pow and array. Saying it's not self documenting is the same as saying: `writeln("Hello World!")` isn't self documenting, but I do believe we can both agree on that it pretty much is. It's self documenting for the same reason, you understand the functionality behind writeln and thus knows what it is doing. The same can be said about the above function chain. It's very much straight-forward in what it's doing and it's only when one doesn't understand these "basic" functions that one don't find it self-documenting.
Jul 04 2022
prev sibling parent drug007 <drug2004 bk.ru> writes:
On 7/4/22 12:43, user1234 wrote:
 On Monday, 4 July 2022 at 09:40:45 UTC, Bastiaan Veelo wrote:
 Not necessarily.
 ```d
 immutable decimalRanks = iota(10).map!(p => 10.pow(p)).array;
 ```

 -- Bastiaan.
Hi, to this alternative I'd say that it's not self documenting. So it still has 50% of the initial problem.
As for me it is self documenting. And it is more readable than your hand written imperative version.
Jul 04 2022
prev sibling parent reply kdevel <kdevel vogtner.de> writes:
On Monday, 4 July 2022 at 09:40:45 UTC, Bastiaan Veelo wrote:
 [...]
 Not necessarily.
 ```d
 immutable decimalRanks = iota(10).map!(p => 10.pow(p)).array;
 ```
Your example requires ``` import std.range; // for iota import std.algorithm; // for map import std.math; // for pow of int ``` Then your example is prone to errors: ``` immutable decimalRanks = iota(13).map!(p => 10.pow(p)).array; // silent wrap around ``` (unit)test to the rescue: ``` auto make_decimal_rank () { import std.range; import std.algorithm; import std.math; return iota (13).map!(p => 10.pow (p)).array; } void main () { import std.stdio; immutable decimalRank = make_decimal_rank; decimalRank.writeln; typeof (decimalRank).stringof.writeln; } unittest { enum ar13 = [ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000 ]; pragma (msg, typeof (ar13)); assert (make_decimal_rank == ar13); } ``` Running: ``` dmd -checkaction=context -unittest -run decrank long[] decrank.d(35): [unittest] [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 1410065408, 1215752192, -727379968] != [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000] 1/1 modules FAILED unittests ```
Jul 04 2022
next sibling parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Monday, 4 July 2022 at 23:19:27 UTC, kdevel wrote:
 Then your example is prone to errors:

 ```
 immutable decimalRanks = iota(13).map!(p => 10.pow(p)).array; 
 // silent wrap around
 ```

 (unit)test to the rescue:
To be fair, original example also requires some testing.
Jul 05 2022
parent kdevel <kdevel vogtner.de> writes:
On Tuesday, 5 July 2022 at 07:37:35 UTC, Alexandru Ermicioi wrote:
[...]
 To be fair, original example also requires some testing.
Sure, but user1234 was never in favor of it. It is prone to the same wrap around which have been avoided by using strings in the first place: ``` auto sgenDecimalRanks() { auto r = "1"; auto result = "["; foreach (i; 1 .. 13) { result ~= r; if (i < 12) result ~= ", "; r ~= '0'; } result ~= "]"; return result; } ```
Jul 05 2022
prev sibling parent reply Dom Disc <dominikus scherkl.de> writes:
On Monday, 4 July 2022 at 23:19:27 UTC, kdevel wrote:
 ```d
 immutable decimalRanks = iota(10).map!(p => 10.pow(p)).array;
 ```
This example requires ```d import std.range; // for iota import std.algorithm; // for map import std.math; // for pow of int ``` Then your example is prone to errors: ```d immutable decimalRanks = iota(13).map!(p => 10.pow(p)).array; // silent wrap around ```
Sorry for the late reply, but I just stumbled over this old thread. I now have a function ```d ubyte maxpow(const ulong base) { } ``` That returns the maximum power of the given base that fits in a ucent (for smaller target types, shift down the result by one for each halfing of the size). With this you can create the function ```d ulong[] powersOf(byte n) { import std.range : iota; import std.algorithm : map; return iota(maxpow(n)>>1).map!(p => ulong(n)^^p).array; } ``` If this function is used to create lookup-tables, it will be called only during compile-time, so the imports are not in the resulting executable (therefore it doesn't matter that I replaced pow by the buildin ^^). ```d static immutable ulong[maxpow(17)>>1] ranksOf17 = powersOf(17); ``` I think this is much more readable as a list of obscure number literals, it is safe to use, doesn't increase linker time and doesn't blow up the binary. Hurray to the meta-programming! Hurray! Hurray!
Dec 28 2022
parent reply kdevel <kdevel vogtner.de> writes:
On Wednesday, 28 December 2022 at 13:57:13 UTC, Dom Disc wrote:
 [...]
 I now have a function

 ```d
 ubyte maxpow(const ulong base) { }
 ```
Code?
 That returns the maximum power of the given base that fits in a 
 ucent
ucent? Cannot use cent/ucent or core.int128's types here.
 (for smaller target types, shift down the result by one for 
 each halfing of the size).
What do you mean by "shift down"? "Shift right" aka "divide by two"?
 With this you can create the function

     ```d
     ulong[] powersOf(byte n)
     {
        import std.range : iota;
        import std.algorithm : map;
        return iota(maxpow(n)>>1).map!(p => ulong(n)^^p).array;
     }
     ```
This generates too few elements. ``` import std.stdio; import mod_maxpow; auto powersOf (T) (ulong n) { import std.range : iota; import std.algorithm : map; import std.array : array; return iota(maxpow!T (n) + 1).map!(p => n^^p).array; } int main (string [] args) { static immutable ranksOf17 = powersOf!ulong (17); writeln (ranksOf17); return 0; } ``` prints ``` [1, 17, 289, 4913, 83521, 1419857, 24137569, 410338673, 6975757441, 118587876497, 2015993900449, 34271896307633, 582622237229761, 9904578032905937, 168377826559400929, 2862423051509815793] ``` `maxpow!ulong(17)` is 15. Counting starts with 0 so we must add one.
 If this function is used to create lookup-tables, it will be 
 called only during compile-time, so the imports are not in the 
 resulting executable (therefore it doesn't matter that I 
 replaced pow by the buildin ^^).

 ```d
 static immutable ulong[maxpow(17)>>1] ranksOf17 = powersOf(17);
 ```
Unnecessary code duplication.
 I think this is much more readable as a list of obscure number 
 literals, it is safe to use,
Unit tests?
Jan 01 2023
parent Dom DiSc <dominikus scherkl.de> writes:
On Sunday, 1 January 2023 at 23:05:46 UTC, kdevel wrote:
 On Wednesday, 28 December 2022 at 13:57:13 UTC, Dom Disc wrote:
 [...]
 I now have a function

 ```d
 ubyte maxpow(const ulong base) { }
 ```
Code?
 That returns the maximum power of the given base that fits in 
 a ucent
ucent? Cannot use cent/ucent or core.int128's types here.
Yeah, I implemented this long time ago, when I still had hope that ucent will be available soon :-/
 (for smaller target types, shift down the result by one for 
 each halfing of the size).
What do you mean by "shift down"? "Shift right" aka "divide by two"?
Yes. Divide by two to find out what power fits in uint, divide by four for ushort and divide by 8 for ubyte.
 [...]
 This generates too few elements.
Ok, but I tend to omit n^^0 and n^^1 - so I would say it even generates too many elements :-)
 ```d
 static immutable ulong[maxpow(17)>>1] ranksOf17 = powersOf(17);
 ```
Unnecessary code duplication.
Yes, I hate that too. But ```d ulong[] ranksOf17 = powersOf(17); ``` will generate a dynamic array, and unfortunately an inherited length for static arrays (e.g. something like uint[$] x = [1,2,3]; or whatever syntax we could agree upon) is still not available :-(
 Unit tests?
Of course. Nothing that you can do will ever make those obsolete. Mine are rather exhaustive: ```d /// exponentiation without overflow /// return invalid if overflow would occur. T safePow(T)(const(T) base, const int exp) if(isIntegral!T) { if(exp < 2) return !exp ? 1 : (exp < 0 && base.abs != 1) ? 0 : odd(exp) ? base : 1; static if(isUnsigned!T) { if(base < 2) return base; if(base >= 1<<(T.sizeof<<2)) return invalid!T; // base too large to fit any powers of it into the same type if(exp > (maxpow(base)>>(5-bitlen(T.sizeof)))) return invalid!T; return T(base^^exp); // calc only if no overflow for sure (very efficient) } else { auto r = safePow(abs(base), exp); // max power may not fit in the signed type if(r > T.max) return invalid!T; // but at least we can detect it return (base < 0 && odd(exp)) ? -T(r) : T(r); } } unittest { import std.conv; void test(T)() { T r; foreach(T base; (-128 * isSigned!T)..128) { foreach(uint exp; 0..256) { r = safePow(base, exp); if(exp==1) assert(r == base); else if(r == invalid!T) { assert(T.max / safePow(base, exp-1).abs < base.abs, "max wrong: ",base,"^^",exp," <= "~T.stringof~".max"); break; } assert(r == base^^exp, "calc wrong: ",base,"^^",exp," != ",r); } } } test!byte; test!ubyte; test!short; test!ushort; test!int; test!uint; test!long; test!ulong; } ```
Jan 02 2023
prev sibling next sibling parent reply bauss <jj_1337 live.dk> writes:
On Monday, 4 July 2022 at 08:24:01 UTC, user1234 wrote:
 Example:

 ```d
 auto genDecimalRanks()
 {
     import std.conv;
     auto r = 1;
     auto result = "[";
     foreach (i; 1 .. 11)
     {
         result ~= to!string(r);
         if (i < 10)
             result ~= ", ";
         r *= 10;
     }
     result ~= "]";
     return result;
 }

 /// like 
 [1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000]
 immutable decimalRanks = mixin(genDecimalRanks);
 ```

 It takes much more space and time to write DDOC + the generator 
 than the space + time required to write the equivalent explicit 
 declaration without comments, i.e "self documenting".

 So metaprog is not the panacea, do you think to that before 
 "meta-progrogramming", or do you "meta-prog" just because it's 
 nice ?
If you don't like the iota version then you can just do this: (Modified version of yours.) ```d auto genDecimalRanks() { import std.conv; auto r = 1; int[] result = []; foreach (i; 1 .. 11) { result ~= r; r *= 10; } return result; } /// like [1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000] static immutable decimalRanks = genDecimalRanks(); ``` Everything at compile-time doesn't have to be strings and mixin :) static and enum will trigger ctfe.
Jul 04 2022
parent reply user1234 <user1234 12.de> writes:
On Monday, 4 July 2022 at 10:29:24 UTC, bauss wrote:
 On Monday, 4 July 2022 at 08:24:01 UTC, user1234 wrote:
 [...]
If you don't like the iota version then you can just do this: (Modified version of yours.) ```d auto genDecimalRanks() { import std.conv; auto r = 1; int[] result = []; foreach (i; 1 .. 11) { result ~= r; r *= 10; } return result; } /// like [1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000] static immutable decimalRanks = genDecimalRanks(); ``` Everything at compile-time doesn't have to be strings and mixin :) static and enum will trigger ctfe.
still the same problems as the initial version (even if yes I know it's absurd code)
Jul 04 2022
parent bauss <jj_1337 live.dk> writes:
On Monday, 4 July 2022 at 10:37:14 UTC, user1234 wrote:
 still the same problems as the initial version (even if yes I 
 know it's absurd code)
I'm actually not sure what you're even trying to achieve or what problem you're trying to solve tbh. Could you perhaps give an example of what your expectations are and how exactly this differs from your expectations of runtime? Because my modified version of yours works both at ctfe and runtime, so it has nothing to do with ctfe really. The only thing ctfe about is that it's statically initialized, but you can use it for runtime arrays too, where the function will be executed at runtime. So there's really no metaprogramming involved in that.
Jul 04 2022
prev sibling parent ryuukk_ <ryuukk.dev gmail.com> writes:
I personally only use metaprogramming for tasks i will know will 
become repetitive

For your example it is not worth it, you only would write that 
code once anyways

For my case here, it perfectly worth it, adding new packet 
handler is a matter of an attribute

The complexity of the code added should be relative to its 
benefits, no need to over do it

![screenshot](https://i.imgur.com/gLvENTY.png)
Jul 04 2022