On Sunday, 25 August 2013 at 16:58:12 UTC, Joseph Rushton Wakeling wrote:

Nice! :-)


Thanks ;)

I think that as a general approach it's nice, but we should preferably make generic the method of wrapping a payload.

To be honest, I think that, for now, this is an "implementation detail". All our PRNG's are created with an explicit "value type", that can easily be made the "Payload", no matter what we do.

I think this is a matter of taste, but I don't think it's bad to force the user to seed things. Note that the existing RNGs go to the extent of actually checking inside popFront() etc. if the RNG has been initialized, and if not, seeding it with a default value.

The only thing I'll remark is that these default-seed ideas are quite prevalent in other RNG implementations in other languages. Some of the existing unittests may even be based on that default seeding. So, there may be some expectation of that option being available.

An "option" I had dabbled in was making the PRNG proper a "Container", which you can *slice* to obtain a high performance range. EG:

//----
auto prng = Random(); //Creates a PRNG.
auto first = prng.front; //lazilly seeds
prng.popFront(); //Checks seeded
auto second = prng.front; //Cheks seeded again...

auto fastPRNG = prng[]; //Lazilly seeds, then returns a type *guaranteed* seeded.
auto third = fastPRNG.front; //No check of isSeeded.
//----

The initial reason I played around with this, is that it was an option to "solve" the reference problem: Make the PRNG's themselves value types, and iterate using reference type *slices*. The end result (for "std.random1") has a couple *major* drawbacks though, IMO: 1. Opt-*in*: Only those aware will use it. The rest of us mortals will still get hit by the bugs.
2. Easily escapes references to locals.
3. As always, if we introduce std.random2, it'll be that much more to clean-up.

Still, I thought I'd share my ideas, even if they aren't the best solutions. It *is* an option for the (don't)auto-seed problem.

One more thing. One of the ways I see it (specifically for LF): We can add auto-seed later if we decide it was a bad idea to not have it. The opposite isn't possible.

I think the two of us agree on the general principle that RNGs should be re-done as structs that wrap a reference to a payload. Performance- and design-wise, I'd say that the following things are worth considering:

(1) Should the memory management be done via GC (new) as in your implementation, or manually managed? Bear in mind the general desirable principle that Phobos code shouldn't rely on the GC if it
      doesn't have to.

I don't know what impact the use of the GC here can be expected to have on overall performance of a piece of code, and whether it might rule out use of this design in highly performance-conscious use-cases such as
      games, etc.

My own use of RefCounted to ensure manual memory management (no GC)
      seems to have some scope-related issues, see:

http://forum.dlang.org/post/[email protected]

... and I assume that if payload was allocated manually via malloc
      instead of new, then the same might be true.

Another option I had thought of, was to make the value-types Payloads as public, with *massive* "do not use signs". *We* then provide simple and generic (and pure/nothrow) GC-based wrappers.

From there, if, for whatever reason, a user needs malloc allocation, or heck, static allocation, he still has an "underhand" access to the payload implementation, but lifecycle management remains *his* responsibility.

I think: Simple and safe by default, with the possibility for extension.

(2) Should we provide a generic payload wrapper, or require RNG creators to implement everything manually? I strongly support a generic wrapper, as there is too great a risk of implementation error if we require
      the wrapping-of-a-reference to be done manually each time.

(3) As we discussed off-list, if we do implement a generic wrapper, how
      should it work?  Should it work as per my implementation,

          alias RandomGenerator!MyEngine MyRNG;

      or instead as you suggested, as a template mixin,

          struct MyRNG
          {
              mixin RandomGenerator!MyEngine;
          }

I can see the case for the latter as it will result in much more readable type names in error messages. However, I think it has the potential to obscure implementation details in a way that isn't helpful. Consider what
      happens if we do:

          alias RandomGenerator!MtEngine19937 Mt19937_64
// ... WRONG!! we forgot to tweak the engine when we copy-pasted, // but at least we'll see in any error messages what type of internal
          // engine is being used

      ... compared to:

          struct Mt19937
          {
              mixin RandomGenerator!MtEngine19937;
          }
// ... Copy-paste failure again, but this time it's obscured and we'll // never find out unless we look at the actual source code.

Again, for now, I think this is implementation detail. That said, I don't buy much into the typo argument. In particular, this is something that gets copy pasted only once, so we should be relatively safe.

One of the arguments in favor of "internal" mixins is that of extension: For example, laggedFib has a very real reason to implement popFrontN, as it can potentially pop thousands of elements at once in o(1). "Internal" mixin makes it easy to extend a PRNG's capabilities past that of the "lowest common denominator". With a "alias Random = RandomGenerator!RandomPayload", you are really limited to whatever RandomGenerator wrapped.

(4) The devil's advocate position -- should we take the simple route to reference-type RNGs by making them final classes? It's trivial to do but to me it feels "un-Phobos-ish" and will also have the problem of requiring a lot more code rewrites on the part of std.random users who want to
      upgrade to std.random2.

That seems like a good opening summary of issues -- destroy. :-)

Best wishes,

    -- Joe

Honestly, it might just be the simplest thing to do. For one, it would elegantly solve the "must be seeded" issue (allocation is initialization). It *guarantees* Reference semantics. Finally, the (supposed) overhead should be inexistant compared to the compelxity of a PRNG.

The option of allowing "public Payload" definitions could still leave an open door for those that need PRNG's, but don't use the GC (think vidjagames).

Reply via email to