October 06, 2020
On Tuesday, 6 October 2020 at 12:28:55 UTC, Adam D. Ruppe wrote:
> On Tuesday, 6 October 2020 at 08:35:20 UTC, claptrap wrote:
>> Because Stefan didnt rely on external code, he showed *all* the code needed, so a counter example should have been the same. Since the point is to compare the two *language* features, not compare library calls.
>
> Language features are a means to an end.
>
> If the Phobos version compiles faster, uses less memory, and has the same result in the binary, it is a victory, even if it as significantly more code.
>
> I'm skeptical of the type functions, but interested. They might help with a real problem. But they need to actually win on the end results, not against artificial strawmen.

Im less interested in performance than I am in being able to express what I want to do in clear concise code. I find template/mixin shenanigans a bit like writing that doesnt have any vowels, ys y cn stll ndrstnd t, but it takes a lot more work and feels fundamentally unsatisfactory.


October 06, 2020
On Tuesday, 6 October 2020 at 18:00:29 UTC, Daniel K wrote:
> On Monday, 5 October 2020 at 11:44:34 UTC, Stefan Koch wrote:
>
> template ImplicitConversionTargets(T)
> {
> 	alias ImplicitConversionTargets = ImplicitConvertible!(T, basic_types);
> }
>
> pragma (msg, ImplicitConversionTargets!(long));
>
> That's 16 lines of code. Heck it even compiles in D1 if only the alias declarations are written in the old style.
>
> Personally I prefer using existing language features.
>
> /Daniel K

If someone said to you "I have this list of integers and I want to get a new list containing the ones are divisible by a specific value", would you write this...

----

int[] vals = [4,7,28,23,585,73,12];

int[] getMultiplesX(int i, int candidate, int[] candidates)
{
    int[] result;
    if ((candidate % i) == 0)
        result ~= candidate;
    if (candidates.length > 0)
        return result ~ getMultiplesX(i, candidates[0], candidates[1..$]);
    else
       return result;
}

int[]  getMultiplesOf(int i)
{
    return getMultiplesX(i, vals[0], vals[1..$]);
}

----

Or would you write it like this...

int[] vals = [4,7,28,23,585,73,12];

int[] getMultiplesOf(int i)
{
    int[] result;
    foreach(v; vals)
        if ((v % i) == 0) result ~= v;
    return result;
}

----

Its the same basic algorithm, search a list and make a new list from the entries that satisfy a certain condition. It beggars belief that anyone would argue that the second version is not better on every metric that is important in writing good code. It's clearer, more intuitive, more concise, it will almost definitely be faster and less wasteful of resources. Newbies will grep it far quicker than the other versions. That means faster to write and easier to maintain and debug.

I'm honestly losing the will to live here.
October 06, 2020
On Tuesday, 6 October 2020 at 20:57:10 UTC, claptrap wrote:
> Im less interested in performance than I am in being able to express what I want to do in clear concise code.

Give me an example of what you'd like to use type functions for.

I betcha I can adapt it to current D with very few changes.

Take a look at this for example:

---
import std.algorithm;
template largestType(T...) {
        auto get() {
                return reified!"a.sizeof".map!T
                    .sort!((a, b) => a > b).front; // or maxElement of course
        }
        alias largestType = T[get.idx];
}

pragma(msg, largestType!(long, int, real, byte)); // real
---


Or what about this, without even seeing the template keyword?

import std.algorithm;

pragma(msg,
  reified!"is(a : long)" // compile time lambda we need
    .run!(types => types.filter!(a => a)) // run the code over the fetched info
    .over!(string, int, float, Object)); // the list of types
);



Note it used std.algorithm's filter over the mapped compile time lambda! But, of course, you'll see it is a string. That is a limitation here, pity we don't have some kind of short syntax template lambda.




This is the reifiy and dereify implementations:

---

template reified(string mapCode, alias runCode = void) {
        template evaluate(alias a) { enum evaluate = mixin(mapCode); }

        template run(alias code) {
                alias run = reified!(mapCode, code);
        }

        template over(T...) {
                auto helper() {
                        static struct Result {
                                size_t idx;
                                typeof(evaluate!(T[0])) value;
                                alias value this;
                        }

                        Result[] result;
                        foreach(idx, t; T)
                                result ~= Result(idx, evaluate!t);

                        return result;
                }

                static if(is(runCode == void))
                        enum over = helper;
                else
                        mixin("alias over = " ~ dereifiy(runCode(helper())) ~ ";");
        }
}

import std.meta;

string dereifiy(T)(T t, string localName = "T") {
        import std.conv;
        import std.range;
        static if(isInputRange!T) {
                string result = "AliasSeq!(";
                foreach(item; t)
                        result ~= localName ~ "[" ~ to!string(item.idx) ~ "],";
                result ~= ")";
                return result;
        } else
                return localName ~ "[" ~ t.idx.to!string ~ "]";
}
---
October 06, 2020
On Tuesday, 6 October 2020 at 18:30:15 UTC, Steven Schveighoffer wrote:
> It comes down to one thing -- arrays vs. tuples. In type functions, a tuple is an array, and you can do all the things you can do with a normal array: mutate, sort, shrink, grow, loop, use as a range, etc.
>
> With a Tuple, everything is immutable, and each change needs to go across a new template boundary. Even a loop is not really a loop.

It seems to me like maybe the most obvious way from point A to point B is to lift these limitations on tuples (and aliases, and manifest constants). Then we could write, for example:

template Filter(alias pred, Args...)
{
    enum pos = 0;
    foreach (Arg; Args) {
        static if (Pred!Arg) {
            Args[pos] := Arg;
            pos := pos + 1;
        }
    }
    alias Filter = Args[0 .. pos];
}

You would still pay for the performance overhead of tuple foreach and static if, so maybe it's not that big a win, but it's still an improvement over recursive templates and CTFE+mixins.
October 06, 2020
On Tue, Oct 06, 2020 at 11:16:47PM +0000, claptrap via Digitalmars-d wrote: [...]
> If someone said to you "I have this list of integers and I want to get a new list containing the ones are divisible by a specific value", would you write this...
> 
> ----
> 
> int[] vals = [4,7,28,23,585,73,12];
> 
> int[] getMultiplesX(int i, int candidate, int[] candidates)
> {
>     int[] result;
>     if ((candidate % i) == 0)
>         result ~= candidate;
>     if (candidates.length > 0)
>         return result ~ getMultiplesX(i, candidates[0], candidates[1..$]);
>     else
>        return result;
> }
> 
> int[]  getMultiplesOf(int i)
> {
>     return getMultiplesX(i, vals[0], vals[1..$]);
> }
> 
> ----
> 
> Or would you write it like this...
> 
> int[] vals = [4,7,28,23,585,73,12];
> 
> int[] getMultiplesOf(int i)
> {
>     int[] result;
>     foreach(v; vals)
>         if ((v % i) == 0) result ~= v;
>     return result;
> }

I would write it like this:

	int[] vals = [4,7,28,23,585,73,12];

	int[] getMultiplesOf(int i)
	{
	    return vals.filter!(v => (v % i) == 0).array;
	}

One line vs. 4, even more concise. ;-)

Thing is, what someone finds intuitive or not, is a pretty subjective matter, and depends on what programming style he's familiar with and/or prefers.  What a C programmer finds readable and obvious may be needlessly arcane to a Java programmer, and what an APL programmer finds totally obvious may be completely impenetrable to anyone else. :-P

If we're going to argue over merits, it would really help resolve matters if we stuck to things that are objectively measurable, since reasonable people are going to disagree on the subjective points.


T

-- 
Being able to learn is a great learning; being able to unlearn is a greater learning.
October 06, 2020
On Tuesday, 6 October 2020 at 23:36:00 UTC, Paul Backus wrote:
> On Tuesday, 6 October 2020 at 18:30:15 UTC, Steven Schveighoffer wrote:
>> It comes down to one thing -- arrays vs. tuples. In type functions, a tuple is an array, and you can do all the things you can do with a normal array: mutate, sort, shrink, grow, loop, use as a range, etc.
>>
>> With a Tuple, everything is immutable, and each change needs to go across a new template boundary. Even a loop is not really a loop.
>
> It seems to me like maybe the most obvious way from point A to point B is to lift these limitations on tuples (and aliases, and manifest constants). Then we could write, for example:
>
> template Filter(alias pred, Args...)
> {
>     enum pos = 0;
>     foreach (Arg; Args) {
>         static if (Pred!Arg) {
>             Args[pos] := Arg;
>             pos := pos + 1;
>         }
>     }
>     alias Filter = Args[0 .. pos];
> }
>
> You would still pay for the performance overhead of tuple foreach and static if, so maybe it's not that big a win, but it's still an improvement over recursive templates and CTFE+mixins.

I think lifting limitations on tuples can't be done in general without violation of current language rules.
Type functions are actually just a shell around operations that would be illegal in the language as is.
But because the type function provides a boundary I can do these things without invalidating the language semantics.

Type functions are something which can be proven to not have influence on language semantics outside of their own function bodies.
October 07, 2020
On Tuesday, 6 October 2020 at 23:44:30 UTC, Stefan Koch wrote:
> Type functions are actually just a shell around operations that would be illegal in the language as is.
> But because the type function provides a boundary I can do these things without invalidating the language semantics.
>
> Type functions are something which can be proven to not have influence on language semantics outside of their own function bodies.

Ugh, why not do it properly and just add type variables? It is just a pointer... At runtime it could point to the type's RTTI node which contains a pointer to the constructor.

October 07, 2020
On Tuesday, 6 October 2020 at 23:44:30 UTC, Stefan Koch wrote:
> On Tuesday, 6 October 2020 at 23:36:00 UTC, Paul Backus wrote:
>>
>> It seems to me like maybe the most obvious way from point A to point B is to lift these limitations on tuples (and aliases, and manifest constants). Then we could write, for example:
>>
>> template Filter(alias pred, Args...)
>> {
>>     enum pos = 0;
>>     foreach (Arg; Args) {
>>         static if (Pred!Arg) {
>>             Args[pos] := Arg;
>>             pos := pos + 1;
>>         }
>>     }
>>     alias Filter = Args[0 .. pos];
>> }
>>
>> You would still pay for the performance overhead of tuple foreach and static if, so maybe it's not that big a win, but it's still an improvement over recursive templates and CTFE+mixins.
>
> I think lifting limitations on tuples can't be done in general without violation of current language rules.
> Type functions are actually just a shell around operations that would be illegal in the language as is.
> But because the type function provides a boundary I can do these things without invalidating the language semantics.
>
> Type functions are something which can be proven to not have influence on language semantics outside of their own function bodies.

I agree that allowing completely unrestricted tuple mutation would be problematic, but I think it's still worth asking how far we could potentially go in that direction.

For example, maybe you're only allowed to mutate tuples inside the scope that declares them. That would let you implement templates like Filter and staticMap iteratively, while still presenting an immutable "interface" to the rest of the program.
October 07, 2020
On Wednesday, 7 October 2020 at 00:03:46 UTC, Ola Fosheim Grøstad wrote:
> On Tuesday, 6 October 2020 at 23:44:30 UTC, Stefan Koch wrote:
>> Type functions are actually just a shell around operations that would be illegal in the language as is.
>> But because the type function provides a boundary I can do these things without invalidating the language semantics.
>>
>> Type functions are something which can be proven to not have influence on language semantics outside of their own function bodies.
>
> Ugh, why not do it properly and just add type variables? It is just a pointer... At runtime it could point to the type's RTTI node which contains a pointer to the constructor.

If you know a way to do that cleanly, that does not involve a redesign of the compiler,
I am very interested to hear about it.
As things stand type functions are what I can get away with, and still be reasonably confident I won't violate language invariants.
Also their syntax blends in fairly nicely with the rest of D.

October 07, 2020
On Wednesday, 7 October 2020 at 00:11:48 UTC, Stefan Koch wrote:
> If you know a way to do that cleanly, that does not involve a redesign of the compiler,
> I am very interested to hear about it.
> As things stand type functions are what I can get away with, and still be reasonably confident I won't violate language invariants.
> Also their syntax blends in fairly nicely with the rest of D.

Allright, if the syntax is such that it can be made more general later then all is good.

(Ive only modified the dmd lexer/parser, so I don't know where the limitations are bqeyond ast level...)