On Thursday, 20 September 2012 at 09:58:41 UTC, Johannes Pfau wrote:
Am Thu, 20 Sep 2012 08:51:28 +0200
schrieb "monarch_dodra" <[email protected]>:

> Moving to classes would definitely break code, but it should > be possible to > make them reference types simply by making it so that their > internal state is
> in a separate object held by a pointer.

I was thinking of doing that. The problem with this (as I've run into and stated in another thread), is a problem of initialization: The simpler PRNGs are init'ed seeded, and are ready for use immediately. Changing to this approach would break the initialization, as shown in this post:

How would the internal state be allocated? malloc + refcounting or GC?

I'm not really happy about that as I'd like to avoid allocation
(especially GC) whenever possible. Using a PRNG only locally in a function seems like a valid use case to me and it currently doesn't
require allocation. As the similar problem with
std.digest/std.algorithm.copy has showed value type ranges seem not
very well supported in phobos.

I wonder why we don't pass all struct/value type ranges by ref? Is
there a reason this wouldn't work?

Or we could leave the PRNGs as is and provide reference wrappers. This would allow to place the PRNG on the stack and still make it work with
all range functions. But it's also cumbersome and error prone.

Best solution is probably to have wrappers which allocate by default, but also allow to pass a reference to a stack allocated value. Then make the wrappers default, so the default's safe and easy to use.

Pseudo-code:

struct RNG_Impl
{
    uint front();
    void popFront();
    bool empty();
}

struct RNG
{
    RNG_Impl* impl;

    this(ref RNG_Impl impl)
        impl = cast(RNG_Impl*) impl;

    void initialize()
    {
        assert(!impl); //Either initialize or constructor
        impl = new RNG_Impl();
    }
}

RNG_Impl impl;
RNG(impl).take(5); //No allocation (but must not leak references...)

Regarding the initialization check: I'd avoid the check in release mode. Not initializing a struct is a developer mistake and should be found in debug mode. I think it's unlikely that error handling code can
handle such a situation anyway. But you could check how
std.typecons.RefCounted handles this, as it also need explicit
initialization.

That is a good point, I'd also make the "default" heap allocated, but give a way to access a stack allocated payload.

Regarding the "developer mistake", the problem is that currently:
"Misnstdrand a;"
Will create a valid and seeded PRNG that is ready for use, so we can't break that.

Arguably though, the argument holds for Mersenne twister, which needs a function call to be (default) seeded.

HOWEVER I find that:
auto a = MersenneTwister();  //Not seeded and invalid
auto b = MersenneTwister(5); //Seeded and valid
Is a confusing, especially since MersenneTwister provides "seed()" to default seed.

I'd rather have:
auto a = MersenneTwister(); //Not *yet* seeded: It will be done on the fly...
auto b = MersenneTwister(5); //Seeded and valid

But AGAIN, on the other hand, if you change back again to your proposed stack allocated MersenneTwisterImpl:
auto a = MersenneTwister();  //Not Seeded, but not assertable
auto b = MersenneTwister(5); //Seeded and valid

It really feels like there is no perfect solution.

********
The truth is that I would have rather ALL prngs not have ANY constructors, and that they ALL required an explicit seed: This would be uniform, and not have any surprises.

OR

That creating a prng would seed it with the default seed if nothing is specified, meaning that a prng is ALWAYS valid.

It feels like the current behavior is a bastardly hybrid...

Reply via email to