Thread overview
Re: Generic structural recursion
Jan 26, 2022
H. S. Teoh
Jan 26, 2022
H. S. Teoh
January 26, 2022
On Wed, Jan 26, 2022 at 06:41:50PM +0000, John Colvin via Digitalmars-d wrote:
> On Wednesday, 26 January 2022 at 18:22:18 UTC, H. S. Teoh wrote:
> > [...]
> 
> You could define a template that introspects a struct type and gives you a tuple of getters (each taking a struct and returning one of the doubles), then your functions that do real work would just loop over those getters. I suspect using the new(-ish) alias assign feature would help in defining that template.

Excellent idea!!  Instead of trying to inject some arbitrary function with multiple arguments into the middle of a recursive traversal, do the traversal separately at compile-time exclusively on the type, generate an array of fields, and let the caller loop over them.

Here's the solution I settled on:

--------
string[] eachParam(T)(string prefix = "")
{
    string[] result;
    if (__ctfe)
    {
        foreach (field; FieldNameTuple!T)
        {
            alias F = typeof(__traits(getMember, T, field));
            static if (is(F == double))
            {
                result ~= prefix ~ "." ~ field;
            }
            else static if (is(F == struct))
            {
                result ~= eachParam!F(prefix ~ "." ~ field);
            }
            else static if (is(F == E[n], E, size_t n))
            {
                foreach (j; 0 .. __traits(getMember, T, field).length)
                {
                    result ~= eachParam!E(text(prefix, ".", field,
                                               "[", j, "]"));
                }
            }
        }
        return result;
    }
    else assert(0);
}

void interpolate(alias ipol = linearInterpolate, T)
                (ref T model, double idx, T t1, T t2)
{
    static foreach (param; eachParam!T)
    {
        mixin("model" ~ param) = ipol(idx, mixin("pose1" ~ param),
                                           mixin("pose2" ~ param));
    }
}

void nullify(ref T model)
{
    static foreach (param; eachParam!T)
    {
    	mixin("model" ~ param) = double.nan;
    }
}

// ... and so on
--------

Both parts nice and clean. Well OK, so I used a bunch of mixins. But they are little self-contained snippets rather than entire function bodies. That's a win.


T

-- 
Why ask rhetorical questions? -- JC
January 26, 2022
On Wed, Jan 26, 2022 at 02:06:18PM -0500, Steven Schveighoffer via Digitalmars-d wrote: [...]
> ```d
> auto interpolate(alias fn, X : double)(ref X val1, ref X val2)
> {
>    static if(is(typeof(fn(val1, val2)))) return fn(val1, val2);
> }
> 
> auto interpolate(alias fn, X)(ref X val1, ref X val2) if (is(X == struct))
> {
>    // loop and recurse
> }
> ...
> ```

That's what I had, but the problem is that the number of X arguments differs from function to function.  So I'd have to duplicate the recursion part for each number of arguments, which is bad because there's a chance I might change the recursion in the unary version and forget to update it in the binary/ternary/etc. version.


T

-- 
Why can't you just be a nonconformist like everyone else? -- YHL
January 27, 2022

On 1/26/22 2:57 PM, H. S. Teoh wrote:

>

On Wed, Jan 26, 2022 at 02:06:18PM -0500, Steven Schveighoffer via Digitalmars-d wrote:
[...]

>
auto interpolate(alias fn, X : double)(ref X val1, ref X val2)
{
    static if(is(typeof(fn(val1, val2)))) return fn(val1, val2);
}

auto interpolate(alias fn, X)(ref X val1, ref X val2) if (is(X == struct))
{
    // loop and recurse
}
...

That's what I had, but the problem is that the number of X arguments
differs from function to function. So I'd have to duplicate the
recursion part for each number of arguments, which is bad because
there's a chance I might change the recursion in the unary version and
forget to update it in the binary/ternary/etc. version.

Well, this is what I would do then.

auto interpolateImpl(alias fn, Vals...)(ref Vals vals) if (is(Vals[0] == double))
{
   return fn(vals);
}

// handle all the other specific types etc.

auto interpolate(alias fn, Vals...)(ref Vals vals) if (allSameType!Vals)
{
   return interpolateImpl!fn(vals);
}

Then the only complex one is the one for aggregates.

-Steve