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

Unifying Property<unit> and Property<bool> #365

Open
ghost opened this issue Sep 21, 2021 · 5 comments
Open

Unifying Property<unit> and Property<bool> #365

ghost opened this issue Sep 21, 2021 · 5 comments

Comments

@ghost
Copy link

ghost commented Sep 21, 2021

I've been looking through Haskell Hedgehog recently, and I noticed that their Property type is not polymorphic. I'd like to get the F# version in line with this, and I have some ideas on how this might be done, though I'm very open to ideas/suggestions here.

module Property =
  /// Turns a generator into a property, verifying that the condition always holds.
  val always : ('a -> bool) -> Gen<'a> -> Property

  /// Turns a bool generator into a property, verifying that the generator always generates `true`.
  val alwaysTrue : Gen<bool> -> Property

  /// Turns a bool generator into a property, verifying that the generator always generates `false`.
  val alwaysFalse : Gen<bool> -> Property

  /// Turns a `Result` generator into a property, verifying that the generator always generates `Ok`.
  val alwaysOk : Gen<Result<'a, 'err>> -> Property

  /// Turns a `Result` generator into a property, verifying that the generator always generates `Error`.
  val alwaysError : Gen<Result<'a, 'err>> -> Property

  /// Turns a unit generator into a property, verifying that the generator always throws a specific exception.
  /// This could resolve `#128`.
  val alwaysThrowsExact<'exn :> exn> : Gen<unit> -> Property

  /// Turns a unit generator into a property, verifying that the generator always throws an exception.
  val alwaysThrows : Gen<unit> -> Property

  /// Turns a unit generator into a property, verifying that the generator never throws an exception.
  /// Alias for Property.forAll?
  val neverThrows : Gen<unit> -> Property

This API would be used like so:

gen {
  let! x = Gen.bool
  Expect.equal x x "x = x"
}
|> Property.neverThrows
|> Property.check

gen {
  let! x = Gen.bool
  return x = x
}
|> Property.alwaysTrue
|> Property.check

gen {
  raise (NotImplementedException())
}
|> Property.alwaysThrowsExact<NotImplementedException>
|> Property.check

gen {
  let! z = Gen.int32 (Range.constantBounded ())
  return z
}
|> Property.always (fun x -> x <> 0)
|> Property.check

I'm still unsure how we would support counterexample and maybe another function or two with this, mostly because I haven't dug into them yet.

@ghost
Copy link
Author

ghost commented Sep 21, 2021

Possible solution for #128.

@TysonMN
Copy link
Member

TysonMN commented Sep 22, 2021

Is this a duplicate of #310?

Edited to add:
Oh, #310 is a PR. So now this is the issue that that PR was intending to address.

@TysonMN
Copy link
Member

TysonMN commented Sep 22, 2021

I previously expressed some push back to this issue (in #310 (comment) and especially #336 (comment)).

I no longer hold either of those positions. In particular, F#'s CE builder is expressive enough to do what we want (c.f. PR #364).

I now agree with this change in principle. However, I would like PR #336 merged before this issue is addressed (to avoid conflicts).

@ghost
Copy link
Author

ghost commented Sep 22, 2021

@TysonMN I think Haskell Hedgehog "has its cake and eats it too" on this front. The publicly exposed Property type is a wrapper around the PropertyT transformer:

-- | A property test, along with some configurable limits like how many times
--   to run the test.
--
data Property =
  Property {
      propertyConfig :: !PropertyConfig
    , propertyTest :: PropertyT IO ()
    }

-- | The property monad transformer allows both the generation of test inputs
--   and the assertion of expectations.
--
newtype PropertyT m a =
  PropertyT {
      unPropertyT :: TestT (GenT m) a
    } deriving (
    -- ...
    )

I think this PropertyT may still be used polymorphically, but I'll have to do a deeper dive. Either way, the top-level Property type fixes the type parameter with (), so we can probably follow that pattern.

@TysonMN
Copy link
Member

TysonMN commented Sep 23, 2021

My ability to read Haskell is not as good as I want, so I will take your word it.

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

1 participant