Skip to content

Replace distribution::Sample with Distribution + polymorphism over Rng#256

Merged
dhardy merged 14 commits into
rust-random:masterfrom
dhardy:distribution
Feb 20, 2018
Merged

Replace distribution::Sample with Distribution + polymorphism over Rng#256
dhardy merged 14 commits into
rust-random:masterfrom
dhardy:distribution

Conversation

@dhardy

@dhardy dhardy commented Feb 10, 2018

Copy link
Copy Markdown
Member

If #244 was controversial, this is even more so. There are many commits because there are many changes here, and these are bundled together to make Distribution::sample run-time polymorphic over Rng from the start without affecting Rand.

  1. Replace Sample and IndependentSample with Distribution
  2. Add a Uniform distribution and move all Rand implementations to it
  3. Switch Rng::gen() to use Uniform and deprecate Rand and rand_derive
  4. Merge SampleRng trait code (required to avoid having to type (&mut rng).gen() in all implementations)
  5. Change sample<R: Rng>(..) -> T to sample<R: Rng + ?Sized>(..) -> T

This PR is closely related to dhardy#83. We could potentially keep Rand and fix rand_derive to use Rand::rand(rng) not rng.gen(), or we could even keep Rand over Uniform but make change the method: rand<R: Rng + ?Sized>(..) -> T (breaking existing implementations, but it's still a smaller change for users than deprecating Rand like in this PR). I think I prefer approach since Uniform is a real distribution (uniformity, and maybe someday supporting currying of distributions) and avoids redundant feature overlap.

Comments please on whether you want rand to go in this direction!

@dhardy dhardy added the E-question Participation: opinions wanted label Feb 10, 2018
@dhardy

dhardy commented Feb 10, 2018

Copy link
Copy Markdown
Member Author

To be clear, there are alternatives to this PR:

  1. Not make Rng::gen() polymorphic over Rng (i.e. rng.gen() will not work with rng: &mut Rng, although (&mut rng).gen() appears to work — I don't understand why the latter works)
  2. Keep Rand but change its method to fn rand<R: Rng + ?Sized>(..). This breaks every implementation of Rand so users have to do a similar amount of fixing as with this PR.
  3. Add Uniform and keep Rand. It's not difficult (Rand is only deprecated in this PR), but leaves two different solutions to the same problem.

Also to be clear, Uniform has exactly the same functionality as Rand with slightly different syntax, e.g. see the Option<T> implementation:

impl<T> Distribution<Option<T>> for Uniform where Uniform: Distribution<T> {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<T> {
        // UFCS is needed here: https://github.com/rust-lang/rust/issues/24066
        if rng.gen::<bool>() {
            Some(rng.gen())
        } else {
            None
        }
    }
}

@dhardy dhardy requested a review from KodrAus February 10, 2018 16:46
@KodrAus

KodrAus commented Feb 12, 2018

Copy link
Copy Markdown

@dhardy Just a ping to let you know I'll dig through this, but it might take a few days :)

@dhardy

dhardy commented Feb 12, 2018

Copy link
Copy Markdown
Member Author

@sbarral commented on Reddit that it may be worth keeping a distribution trait allowing mutable state since some genuine distribution sampling algorithms use it (e.g. Box-Muller generates two Gaussian samples simultaneously). Since this usage is the exception we agree that the primary distribution trait should not allow mutable state. I'm not sure what the best approach is here but see no reason why we can't leave this to later (i.e. another PR adding DistributionMut or StatefulDistribution or something) — none of the distributions implemented here use mutable state.

@KodrAus

KodrAus commented Feb 15, 2018

Copy link
Copy Markdown

You could also treat DistributionMut like a special case of Distribution:

pub trait DistributionMut<T> {
    fn sample<R: Rng + ?Sized>(&mut self, rng: &mut R) -> T;
}

impl<T, D: Distribution<T>> DistributionMut<T> for D {
    fn sample_mut<R: Rng + ?Sized>(&mut self, rng: &mut R) -> T {
        self.sample(rng)
    }
}

Sorry I've been out of the loop on rand for a while, but this looks like an improvement to me 👍

Something I'm not so sure on are the virtues of the SampleRng extension trait over provided methods on Rng. Is keeping those methods out of scope for backends worth the cognitive load on users? Are there other benefits to keeping Rng itself constrained to that single method? If there's been conversation on this previously I'd be happy to dig through it.

@dhardy

dhardy commented Feb 15, 2018

Copy link
Copy Markdown
Member Author

Thanks @KodrAus. There's some discussion in #244 (this PR just merges that one). As noted in the first post, it seems rng.gen() doesn't work if rng: &mut Rng (i.e. using dynamic dispatch) without adding the extension trait, though apparently (&mut rng).gen() does work — why? We could use that instead I guess; presumably it optimises out when using an rng of known type.


Regarding mutable (state-full) distributions, my conclusion from the discussion on reddit is that they won't be needed in the vast majority of cases, and since there's no reason the trait can't be defined externally, I'd rather leave it out of rand.

@KodrAus

KodrAus commented Feb 15, 2018

Copy link
Copy Markdown

Ahh I see, the Rng trait is object safe, but the extensions aren't. serde uses separate object-safe traits that rely on being able to forward object-safe variants of its methods to the non-object-safe originals. If we were going to follow something similar here then Rng would not be object-safe, and there'd be a separate trait like ObjectSafeRng you could use. That doesn't necessarily seem better than the Rng/SampleRng split now, it just slices the traits differently. So what we're doing here seems like a good approach to me.

@vks

vks commented Feb 16, 2018

Copy link
Copy Markdown
Contributor

Regarding mutable (state-full) distributions, my conclusion from the discussion on reddit is that they won't be needed in the vast majority of cases, and since there's no reason the trait can't be defined externally, I'd rather leave it out of rand.

Also, if in the future there is some need for mutable distributions, this can still be implemented in rand without a breaking change.

@dhardy

dhardy commented Feb 19, 2018

Copy link
Copy Markdown
Member Author

Rebased after merge of #265.

@vks

vks commented Feb 19, 2018

Copy link
Copy Markdown
Contributor

Shouldn't random be deprecated as well?

@dhardy

dhardy commented Feb 19, 2018

Copy link
Copy Markdown
Member Author

Why? It doesn't need Rand.

@vks

vks commented Feb 19, 2018

Copy link
Copy Markdown
Contributor

Yes, but it has the same problems that Rand has. It doesn't communicate the distribution.

@dhardy

dhardy commented Feb 19, 2018

Copy link
Copy Markdown
Member Author

That is true. Why don't you open a new PR removing it so that it can be discussed there? It's independent of this PR.

random() is no different from rng.gen() in that respect though.

@vks

vks commented Feb 19, 2018

Copy link
Copy Markdown
Contributor

Yeah, I'm not so happy with rng.gen() either. It is similar to fill though, except that it works for floats and char, where it is maybe better if the user has to specify the distribution. That it works with tuples is fine. I think uniform or sample_uniform would be a better name. The same applies to random.

Why don't you open a new PR removing it so that it can be discussed there?

Will do.

It's independent of this PR.

The deprecations are related, but it works as a follow-up I guess.

@dhardy

dhardy commented Feb 19, 2018

Copy link
Copy Markdown
Member Author

I get what you mean about gen not being ideal, but sample_uniform (or rng.sample(Uniform)) is a bit long really. I don't think gen is too bad.

@KodrAus

KodrAus commented Feb 19, 2018

Copy link
Copy Markdown

I like the idea of random being a low-barrier entry point for rand. Does it really need to communicate the distribution in the method name as well as the signature?

@pitdicker pitdicker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you already merged this PR.

It looks really good, and I only have two questions.

for _ in 0..1000 {
norm.sample(&mut rng);
norm.ind_sample(&mut rng);
norm.sample(&mut rng);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still a useful part of the test (here and all the similar tests that used ind_sample)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duplication is not useful. I noticed this when merging these changes into dhardy/master but didn't fix yet.

As for the tests themselves — good question. I just opened #267.


macro_rules! float_impls {
($mod_name:ident, $ty:ty, $mantissa_bits:expr, $method_name:ident) => {
mod $mod_name {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why create modules here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simply moved this code from rand_impls.rs. But I think it does have a purpose — SCALE is a local constant.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good reason. Then I feel free to remove them 😄

@dhardy dhardy mentioned this pull request Feb 24, 2018
33 tasks
pitdicker pushed a commit that referenced this pull request Apr 4, 2018
Replace distribution::Sample with Distribution + polymorphism over Rng
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

E-question Participation: opinions wanted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants