Thread overview
How to export a deduced template type to the enclosing scope?
Sep 23, 2014
Ali Çehreli
Sep 23, 2014
monarch_dodra
Sep 23, 2014
Ali Çehreli
Sep 25, 2014
Ali Çehreli
September 23, 2014
I think similar questions were asked by others in different contexts before.

I played with core.thread.Fibre a little bit. As others have done a number of times before, I tried to make the following syntax possible inside fiber code:

    yield(42);

I wonder whether there is a clever trick to pull out a deduced type from a template. I don't think it is possible, because there may be many instantiations of the template and it would not be clear which one to pull out. (I don't think there is any way of getting all of the instantiations of a template because the compiler has only a partial view of the program at a time.)

However, what if there is exactly one instantiation allowed?

struct S(alias Func)
{
    /* This template mixin will instantiate the yield(T) template. */
    mixin Func!();

    void yield(T)(T)
    {
        /* As expected and demonstrated by the following pragma, in
         * this case T happens to be 'double'. Assuming that Func is
         * not allowed to call yield() with more than one type, can we
         * pull the actual type of T out into S's scope? */

        alias YieldedT = T;
        pragma(msg, YieldedT);    /* Prints 'double'. */
    }

    /* QUESTION: Can we know the type for the single instantiation of
     *           yield(T) here? */

    YieldedT data;    /* What is YieldedT? */
}

mixin template MyFunc()
{
    void foo()
    {
        double d;
        yield(d);    /* <-- The single instantiation */
    }
}

void main()
{
    auto s = S!MyFunc();
    s.foo();
}

So near and yet so far... :)

Ali

September 23, 2014
On Tuesday, 23 September 2014 at 22:09:11 UTC, Ali Çehreli wrote:
> So near and yet so far... :)
>
> Ali

You explain "how" you want to achieve your goal, but I'm not sure I understand "what" the goal is. Perhaps if you explain that in more detail, I'd have a better understanding of the problem.

I've never used core.thread.Fibre, so I don't know what "yield(42);" would do, so apologies if the question is stupid.
September 23, 2014
On 09/23/2014 03:28 PM, monarch_dodra wrote:
> On Tuesday, 23 September 2014 at 22:09:11 UTC, Ali Çehreli wrote:
>> So near and yet so far... :)
>>
>> Ali
>
> You explain "how" you want to achieve your goal, but I'm not sure I
> understand "what" the goal is. Perhaps if you explain that in more
> detail, I'd have a better understanding of the problem.
>
> I've never used core.thread.Fibre, so I don't know what "yield(42);"
> would do, so apologies if the question is stupid.

Sorry. You are right. :) I almost deleted all of the specifics to avoid any concrete example as I think the question is still valid.

Very briefly, fibers provide faster concurrency by executing both the owner and the fiber on the same thread. They are D's coroutines, without the much-missed 'yield' keyword. There is some language (runtime?) support for fibers though as the 'yield()' and 'call()' calls do involve context switching behind the scenes (much faster than thread context switching).

I am not experienced with them either but from the examples on the core.thread documentation it looks like a fiber cannot yield a value in D; it must cause side-effects first, and then yield(), so that the owner can observe the side-effect.

Fibers give the convenience of iteration a.la. opCall(), and InputRange interface comes naturally. (Example below.)

I played with the idea of enabling the yield(42) syntax from the fiber code just because it does not involve side-effects and is more intuitive. There are blog posts, thread discussions, and enhancement reports about the same.

So, my goal was the above: The client would make a yield(42) call, the data would be saved in a local variable automatically (the type of which is the question at hand), and then the result would be provided on an InputRange interface.

Additionally, although I am not aware of other examples of it, again as an experiment, I tried to see whether injecting user code by a template mixin would make any sense. Since template mixins are expanded where they are mixed-in, then I thought perhaps I could deduce a type from inside that code. I failed, you will succeed. :o)

Here is a working example with the minor inconvenience of having to specify ResultT from the outside, which seems redundant. The goal is to remove the need for that parameter.

Note that I am aware of the inheritance-based kind of fibers and the following may not make sense at all. I was just experimenting.

import std.stdio;
import core.thread;
import std.algorithm;

/* This is what the user provides. We will mix this in. */
mixin template Foo()
{
    void run()
    {
        /* Note how naturally the elements are produced in imperative
         * style: */
        foreach (i; 0 .. 10) {
            yield(i);    /* The element type is obviously 'int'. */
        }
    }
}

/* This is an InputRange that exposes the yielded values as
 * elements. PROBLEM: Admittedly minor, ResultT seems redundant. */

class FiberRange(alias Func, ResultT)
{
    mixin Func!();     // User code mixed-in
    ResultT result;    // The front element
    Fiber fiber;       // The fiber that does the work

    /* Supports the more natural yield(42) syntax. */
    final void yield(T : ResultT)(T result)
    {
        this.result = result;
        Fiber.yield;
    }

public:

    this()
    {
        /* He fiber is started by the user's 'run' function. */
        this.fiber = new Fiber(&run);
        this.fiber.call();
    }

    /* Trivial InputRange interface: */

    final
    @property
    bool empty() const
    {
        return fiber.state == Fiber.State.TERM;
    }

    final
    @property
    ResultT front() const
    {
        return result;
    }

    final
    void popFront()
    {
        this.fiber.call;
    }
}

/* Convenience function: */
auto fiberRange(alias Func, ResultT)()
{
    return new FiberRange!(Func, ResultT);
}

void main()
{
    /* Works like a charm! :p */
    auto result = fiberRange!(Foo, int)
                  .filter!(a => a % 2);

    assert(result.equal([1, 3, 5, 7, 9]));
}

Can we get rid of the ResultT template parameter? Tricks with variadic templates, the with statement, etc. come to mind but I could not manage it.

Ali

September 25, 2014
On 9/23/14 7:12 PM, Ali Çehreli wrote:

> Can we get rid of the ResultT template parameter? Tricks with variadic
> templates, the with statement, etc. come to mind but I could not manage it.

I don't think so.

yield just returns control back to another fiber. It's not an entry point.

What you *could* do is match an "expectation" function with the yield result. In other words, at the place where you expect the fiber to yield you a result, pass in a pointer to a value for the yield to fill in:

expectYield(&someint); -> fiber runs, yields an int, and then when this yields the result, someint is filled in.

I'm not super familiar with how D fibers work, or fibers in general. But perhaps it's the 'call' function that could be made to take parameters.

-Steve
September 25, 2014
On 09/25/2014 05:40 AM, Steven Schveighoffer wrote:

> On 9/23/14 7:12 PM, Ali Çehreli wrote:
>
>> Can we get rid of the ResultT template parameter? Tricks with variadic
>> templates, the with statement, etc. come to mind but I could not
>> manage it.
>
> I don't think so.

Me neither. :) This question made me aware of a distinction in template use cases.

- Templates are mostly for generic algorithms as in "This code will work with any type, value, etc."

- The yield() template in my example was not about genericity: I wanted to instantiate that template with only one type (which currently has to be provided by the user). So, I wanted to take advantage of the template type deduction to detect a type from inside the user's code.

Not a big deal at all.

Ali