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

forAll doesn't support explicit generator being passed #277

Closed
johanatan opened this issue Oct 8, 2018 · 9 comments
Closed

forAll doesn't support explicit generator being passed #277

johanatan opened this issue Oct 8, 2018 · 9 comments

Comments

@johanatan
Copy link

i.e., the version of forAll as depicted below and referring to in docs and tutorials doesn't seem to be included in the actual latest pod release:

forAll(someGenerator) { // the checker }

I was able to get:
forAllNoShrink(someGenerator) { // the checker }
to compile however.

When someGenerator is a fairly simple conglomeration of primitive generators, I'd image a shrinking strategy to be available and thus the former to actually compile/work. Is there something I'm missing?

@CodaFi
Copy link
Member

CodaFi commented Oct 9, 2018

In order for forall to shrink your type with your explicit generator, its underlying value type must conform to Arbitrary. If, for example, you have a generator of tuples, you currently cannot convince Swift that your generator's value type conforms to Arbitrary as it cannot expression protocol conformances for non-nominal types.

For that, and for more complex collections, you can use a Modifier Type. If you already have a shrinking function in mind, you can also use forAllShrink.

@johanatan
Copy link
Author

johanatan commented Oct 9, 2018

One of the cases where this failed for me is a generator of a custom class (DetectObject in the code below; which is just being used as a dumb struct because obj-c can't consume actual Swift structs) that I have in my project.

Here's the definition of it if that helps you diagnose:

    static func detectObjGenGen(strGen: Gen<String>) -> Gen<DetectObject> {
        let pointExtentGen =
            Gen<Double>.choose((0, 1)).flatMap {
                sequence([Gen.pure($0), Gen<Double>.choose((Double.ulpOfOne, 1.0 - $0))]).map {
                    ($0.first!, $0.last!)
                }
            }

        let rectGen = sequence([pointExtentGen, pointExtentGen]).map { ($0.first!, $0.last!) }
        return Gen<DetectObject>.zipWith(strGen, Gen<Double>.choose((0, 1)), rectGen,
                                   transform: { (lbl, score, rect) -> DetectObject in
            let pt1 = rect.0
            let pt2 = rect.1

            assert(pt1.0 + pt1.1 <= 1.0)
            assert(pt2.0 + pt2.1 <= 1.0)
            assert(pt1.1 >= 0.0 && pt1.0 >= 0.0)
            assert(pt2.1 >= 0.0 && pt2.0 >= 0.0)

            let rect = CGRect.init(x: pt1.0, y: pt2.0, width: pt1.1, height: pt2.1)
            let detCat = NearestDetections.scoreToDetectionCat(score: Float(score))
            return DetectObject.init(box: rect, label: lbl as NSString, score: Float(score),
                                          imageDims: CGSize.zero,
                                          detectionCategory:detCat)
        })
    }

    static let detectObjGen = detectObjGenGen(strGen: String.arbitrary)

Not sure what the "underlying value type" here would be but this is built from a combination of primitive generators as you can see.

@CodaFi
Copy link
Member

CodaFi commented Oct 9, 2018

Does YUMMLDetectObject conform to Arbitrary?

@CodaFi CodaFi closed this as completed Oct 9, 2018
@CodaFi CodaFi reopened this Oct 9, 2018
@johanatan
Copy link
Author

Doubtful. How can I make that so?

@CodaFi
Copy link
Member

CodaFi commented Oct 9, 2018

If you control the declaration of YUMMLDetectObject, you can pretty safely add a conformance with an extension. If you don't control the declaration, you can add an orphan instance in your module or declare a modifier type.

@johanatan
Copy link
Author

Perfect. Thank you!

@johanatan
Copy link
Author

So, this is now working for me with an explicit Arbitrary conformance specified. But it seems a bit odd that my Arbitrary definition refers to the exact generator originally passed to forAll. Why can't forAll just take that generator it was provided and assume that it will be the definition of any Arbitrary that would be defined for it?

@CodaFi
Copy link
Member

CodaFi commented Oct 9, 2018

Why can't forAll just take that generator it was provided and assume that it will be the definition of any Arbitrary that would be defined for it?

So, we could do this, and if you think about it backwards, this is kind of how the whole shebang works. Arbitrary is shorthand for "this type has a generator and a shrinker" and forAll simply shells out to forAllShrink with those values as arguments. So if you intend to use the shorthand, you have to play ball with a protocol conformance. If you intend to be explicit, you have to be explicit everywhere.

@johanatan
Copy link
Author

Ah, so then I could just call forAllShrink directly without having to define an Arbitrary?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants