Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing Arbitrary, what to name Strategy? #98

Closed
zmitchell opened this issue Oct 27, 2018 · 4 comments
Closed

Implementing Arbitrary, what to name Strategy? #98

zmitchell opened this issue Oct 27, 2018 · 4 comments
Labels
help-request This issue is asking for advice/help on using proptest

Comments

@zmitchell
Copy link

I have an enum that I want to select an arbitrary variant of during my tests:

pub enum Polarization {
    Linear(Angle),
    Circular(Handedness),
    Elliptical(Complex<f64>, Complex<f64>),
}

where Angle and Handedness are

pub enum Angle {
    Degrees(f64),
    Radians(f64),
}

pub enum Handedness {
    Left,
    Right,
}

and Complex comes from num_complex.

I also want to be able to select arbitrary values of particular variants i.e. Polarization::Linear(Angle) with an arbitrary Angle. To do that, I just defined a new enum with the variants:

pub enum PolarizationKind {
    Linear,
    Circular,
    Elliptical,
    Any,
}

I started implementing Arbitrary for Polarization, but I'm kind of stuck on the type Strategy = ??? piece. I'm not exactly sure what the type of my Strategy is. Here is my implementation so far:

impl Arbitrary for Polarization {
    type Parameters = PolarizationKind;
    type Strategy = I_DONT_KNOW;

    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
        match args {
            PolarizationKind::Linear => any_linear_polarization(),
            PolarizationKind::Circular => any_circular_polarization(),
            PolarizationKind::Elliptical => any_elliptical_polarization(),
            PolarizationKind::Any => {
                prop_oneof![
                    any_linear_polarization(),
                    any_circular_polarization(),
                    any_elliptical_polarization(),
                ]
            }
        }
    }
}

pub fn any_linear_polarization() -> impl Strategy<Value = Polarization> {
    any::<Angle>().prop_map(|angle| {
        Polarization::Linear(angle)
    })
}

pub fn any_circular_polarization() -> impl Strategy<Value = Polarization> {
    any::<Handedness>().prop_map(|h| {
        Polarization::Circular(h)
    })
}

pub fn any_elliptical_polarization() -> impl Strategy<Value = Polarization> {
    (any::<f64>(), any::<f64>(), any::<f64>(), any::<f64>()).prop_map(|(xr, xi, yr, yi)| {
        let x = Complex::new(xr, xi);
        let y = Complex::new(yr, yi);
        Polarization::Elliptical(x, y)
    })
}
@AltSysrq AltSysrq added the help-request This issue is asking for advice/help on using proptest label Oct 27, 2018
@AltSysrq
Copy link
Collaborator

If you use .boxed(), the type is just BoxedStrategy<Self>. For example,

impl Arbitrary for Polarization {
    type Parameters = PolarizationKind;
    type Strategy = BoxedStrategy<Self>;

    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
        match args {
            // ...
        }.boxed() // Erase the full type to just BoxedStrategy<Value>
    }
}

Right now, that's generally the best choice on stable Rust (in some cases it could be desirable to avoid boxing for performance, e.g. if your test needs to generate tens of millions of values), and here you don't have a choice since the lambdas in prop_map make the full type unnameable.

If you're using unstable, I think named extential types would be another option here, but I'm not familiar enough with the current state of the feature to provide an example off hand.

@Centril
Copy link
Collaborator

Centril commented Oct 27, 2018

So using existential type Foo: Strategy<Value = Polarization> on nightly will not be enough.
The type checker won't let you unify the three functions and the prop_oneof![..] because the three different impl Strategy<Value = Polarization>s returned by these function and the TupleUnion<(...)> you get from prop_oneof![...] are all of them different types; so you are working with 4 separate types here.

If you want to avoid boxing and write out the full type in type Strategy = ???; you'll need to inline all the calls to the function. Another way to do it if you use existential type is to create an enum that holds all the 4 different possible types and then implement Strategy for that... (@AltSysrq: we could add such an enum to proptest perhaps by using the frunk crate by providing impl<..> Strategy for Coproduct<..>).

I'm not familiar enough with the current state of the feature to provide an example off hand.

As for the current state of the existential type feature from RFC 2071, it is currently implemented, but using a temporary syntax that we'll change before stabilization. I'm working on building consensus in the language team and elsewhere for the type Foo = impl Bar; syntax.

Meanwhile, @AltSysrq's suggestion to use .boxed() is the pragmatic solution for now which I'd also use in this case.

and here you don't have a choice since the lambdas in prop_map make the full type unnameable.

nit: not exactly, these closures don't capture any state from outside so they can be coerced to function pointers.

@AltSysrq
Copy link
Collaborator

The type checker won't let you unify the three functions and the prop_oneof![..] because the three different impl Strategy<Value = Polarization>s returned by these function and the TupleUnion<(...)> you get from prop_oneof![...] are all of them different types; so you are working with 4 separate types here.

Good catch, there's that too. The simplest way to make my example work then is to put .boxed() on each individual match case.

        match args {
            PolarizationKind::Linear => any_linear_polarization().boxed(),
            PolarizationKind::Circular => any_circular_polarization().boxed(),
            PolarizationKind::Elliptical => any_elliptical_polarization().boxed(),
            PolarizationKind::Any => {
                prop_oneof![
                    any_linear_polarization(),
                    any_circular_polarization(),
                    any_elliptical_polarization(),
                ].boxed()
            }
        }

@AltSysrq
Copy link
Collaborator

AltSysrq commented Feb 4, 2019

Closing due to inactivity.

@AltSysrq AltSysrq closed this as completed Feb 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help-request This issue is asking for advice/help on using proptest
Projects
None yet
Development

No branches or pull requests

3 participants