Reference semantic ranges and algorithms (and std.random)

Jonathan M Davis jmdavisProg at gmx.com
Thu Sep 20 00:26:48 PDT 2012


On Thursday, September 20, 2012 08:51:28 monarch_dodra wrote:
> On Tuesday, 18 September 2012 at 17:59:04 UTC, Jonathan M Davis
> 
> wrote:
> > On Tuesday, September 18, 2012 17:05:26 monarch_dodra wrote:
> >> This is issue #1: I'd propose that all objects in std.random be
> >> migrated to classes (or be made reference structs), sooner than
> >> later. This might break some code, so I do not know how this is
> >> usually done, but I think it is necessary. I do not, however,
> >> propose that they should all derive from a base class.
> > 
> > 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:
> 
> http://forum.dlang.org/thread/bvuquzwfykiytdwsqkky@forum.dlang.org#post-yvts
> ivozyhqzscgddbrl:40forum.dlang.org
> 
> A "used to be valid" PRNG has now become an un-initialized PRNG".
> This is extremely insidious, as the code still compiles, but will
> crash.

There's always the check that the internals have been initialized on every 
call and initialize it if it hasn't been solution. It's not pretty, but it 
won't break code. It's actually a use case that makes me wish that we had 
something like the invariant which ran before every public function call 
except that it was always there (even in -release) and let you do anything you 
want.

In any case, while it's a bit ugly, I believe that simply adding checks for 
initialization in every function call is the cleanest solution from the 
standpoint of backwards compatibility, and the ugliness is all self-contained. 
As far as performance goes, it's only an issue if you're iterating over it in 
a tight loop, but the actual random number generation is so much more 
expensive than a check for a null pointer that it probably doesn't matter.

> #2
> Change to class, but leave behind some "opCall"s for each old
> constructor, plus an extra one for default:

> Is this second solution something you think I should look into?

Since

A a;

will just blow up in your face if you switch it to a class, it's not a non-
breaking change even as a migration path, so I don't see that as really being 
viable. Even if you've found a way to minimize the immediate code breakage, 
you didn't eliminate it. If you're going to break code immediately, you might 
as well just break it all at once and get people to fix their stuff rather than 
mostly fix it but not quite, especially when you're asking them to change their 
code later anyway as part of a migration path.

Regardless, when this came up previously, I believe that the conclusion was 
that if we were going to switch to classes, we needed to do something like 
create std.random2 and schedule std.random for deprecation rather than 
changing the current structs to classes (either that or rename _every_ type in 
there and schedule them for deprecation individually, but then you have to 
come up for new names for everything, and it's more of a pain to migrate, 
since all the names changed rather than just the import). So, I believe that 
the idea of switching to classes was pretty much rejected previously unless 
entirely new types were used so that no code would be broken.

I think that we have two options at this point:

1. Switch the internals so that they're in a separate struct pointed to by the 
outer struct and check for initialization on every function call to avoid the 
problem where init was used.

2. Create a new module to replace std.random and make them final classes in 
there, scheduling the old module for deprecation.

Honestly, I'd just go with #1 at this point, because it avoids breaking code, 
and there's increasing resistance to breaking code. Even Andrei, who was 
fairly willing to break code for improvements before, is almost paranoid about 
it now, and Walter was _always_ against it. So, if we have a viable solution 
that avoids breaking code (especially if any ugliness that comes with it is 
internal to the implementation), we should probably go with that.

- Jonathan M Davis


More information about the Digitalmars-d mailing list