Reference semantic ranges and algorithms (and std.random)

monarch_dodra monarchdodra at gmail.com
Thu Sep 20 03:23:12 PDT 2012


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" <monarchdodra at gmail.com>:
>
>> > 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...


More information about the Digitalmars-d mailing list