{"id":16313980,"url":"https://github.com/lysxia/quickcheck-higherorder","last_synced_at":"2026-03-09T05:31:52.952Z","repository":{"id":29399971,"uuid":"121580888","full_name":"Lysxia/quickcheck-higherorder","owner":"Lysxia","description":"QuickCheck extension for higher-order properties","archived":false,"fork":false,"pushed_at":"2022-02-14T07:45:32.000Z","size":82,"stargazers_count":16,"open_issues_count":2,"forks_count":1,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-04-25T21:46:00.042Z","etag":null,"topics":["haskell","higher-order","quickcheck","testing"],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Lysxia.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-02-15T01:30:02.000Z","updated_at":"2022-09-05T19:21:03.000Z","dependencies_parsed_at":"2022-08-07T14:16:20.972Z","dependency_job_id":null,"html_url":"https://github.com/Lysxia/quickcheck-higherorder","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lysxia%2Fquickcheck-higherorder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lysxia%2Fquickcheck-higherorder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lysxia%2Fquickcheck-higherorder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lysxia%2Fquickcheck-higherorder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Lysxia","download_url":"https://codeload.github.com/Lysxia/quickcheck-higherorder/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221809086,"owners_count":16883851,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["haskell","higher-order","quickcheck","testing"],"created_at":"2024-10-10T21:52:51.828Z","updated_at":"2026-03-09T05:31:52.913Z","avatar_url":"https://github.com/Lysxia.png","language":"Haskell","readme":"# Higher-order QuickCheck [![Hackage](https://img.shields.io/hackage/v/quickcheck-higherorder.svg)](https://hackage.haskell.org/package/quickcheck-higherorder) ![haskell-ci](https://github.com/Lysxia/quickcheck-higherorder/actions/workflows/haskell-ci.yml/badge.svg)\n\nA QuickCheck extension for properties of higher-order values.\n\n## Examples\n\nHigher-order properties are properties which may:\n\n1. quantify over functions;\n2. state equalities between functions.\n\nSome examples:\n\n```haskell\nfmap_dot :: forall a b c. (b -\u003e c) -\u003e (a -\u003e b) -\u003e Equation (Maybe a -\u003e Maybe c)\nfmap_dot g f = (fmap g . fmap f) :=: fmap (g . f)\n\ncallCC_bind :: forall r a. Cont r a -\u003e Equation (Cont r a)\ncallCC_bind m = callCC ((\u003e\u003e=) m) :=: m\n```\n\n*quickcheck-higherorder* makes it easy to define and test such properties.\n\n```haskell\nmain :: IO ()\nmain = do\n  quickCheck' (fmap_dot @Int @Int @Int)\n  quickCheck' (callCC_bind @Int @Int)\n```\n\n(Additional setup is required for the `callCC` example.)\n\n## Summary\n\nQuickCheck has a cute trick to implicitly convert functions\n`Thing -\u003e Bool` or `Thing -\u003e Gen Bool` to testable properties,\nprovided `Thing` is an instance of `Arbitrary` and `Show`,\ni.e., there is a random generator, a shrinker, and a printer for `Thing`.\nSadly, those constraints limit the range of types that `Thing` can be.\nIn particular, they rule out functions and other values of \"infinite size\".\n\nThis library, *quickcheck-higherorder*, lifts that limitation, generalizing\nthat technique to arbitrary types, and provides other related quality-of-life\nimprovements for property-based test suites.\n\nThe key idea is to separate the `Thing` manipulated by the application under\ntest, of arbitrary structure, from its *representation*, which is manipulated\nby QuickCheck and needs to be \"concrete\" enough to be possible to generate,\nshrink, and show.\n\n### Constructible types\n\nThe `Constructible` type class relates types `a` to representations\n`Repr a` from which their values can be constructed.\nConstraints for `Arbitrary` and `Show` are thus attached to those\nrepresentations instead of the raw type that will be used in properties.\n\n```haskell\nclass (Arbitrary (Repr a), Show (Repr a)) =\u003e Constructible a where\n  type Repr a :: Type\n  fromRepr :: Repr a -\u003e a\n```\n\nTo illustrate what it enables, here's an example of higher-order property:\n\n```haskell\nprop_bool :: (Bool -\u003e Bool) -\u003e Bool -\u003e Property\nprop_bool f x =\n  f (f (f x)) === f x\n```\n\nIn vanilla QuickCheck, it needs a little wrapping to actually run it:\n\n```haskell\nmain :: IO ()\nmain = quickCheck (\\(Fn f) x -\u003e prop_bool f x)\n```\n\nThe simpler expression `quickCheck prop_bool` would not typecheck\nbecause `Bool -\u003e Bool` is not an instance of `Show`.\n\nWith \"higher-order\" QuickCheck, that wrapping performed by `Fn` is instead\ntaken care of by the `Constructible` class, so we can write simply:\n\n```haskell\nmain :: IO ()\nmain = quickCheck' prop_bool\n```\n\nThis is especially convenient when the function type is not\ndirectly exposed in the type of the property (as in `prop_bool`),\nbut may be hidden inside various data types or newtypes.\n\n### Testable equality\n\nIn a similar vein, the `Eq` class is limited to types with\n*decidable equality*,\nwhich typically requires them to have values of \"finite size\".\nMost notably, the type of functions `a -\u003e b` cannot be an instance of `Eq` in\ngeneral.\n\nBut testing can still be effective with a weaker constraint, dubbed\n*testable equality*. To compare two functions, we can generate some random\narguments and compare their images.\nThat is useful even if we can't cover the whole domain:\n\n- if we find two inputs with distinct outputs, then the two functions\n  are definitely not equal, and we now have a very concrete counterexample to\n  contemplate;\n- if we don't find any difference, then we can't conclude for sure, but:\n\n    1. we can always try harder (more inputs, or rerun the whole property from\n       scratch);\n    2. in some situations, such as implementations of algebraic structures,\n       bugs cause extremely obvious inequalities. If only we would look at them.\n       The point of this new feature is to lower the bar for testing equations\n       between higher-order values in the first place.\n\nThis package introduces a new type class `TestEq`, for testable equality.\n\n```haskell\nclass TestEq a where\n  (=?) :: a -\u003e a -\u003e Property\n```\n\nThe codomain being `Property` offers some notable capabilities:\n\n1. as explained earlier, we can use randomness to choose finite subsets\n   of infinite values (such as functions) to compare;\n\n2. we can also provide detailed context in the case of failure,\n   by reporting the observations which lead to unequal outcomes.\n\nFor example, we can rewrite `prop_bool` as an algebraic property of functions\nusing `TestEq`:\n\n```haskell\nprop_bool :: (Bool -\u003e Bool) -\u003e Property\nprop_bool f = (f . f . f) =? f\n```\n\n### More types of properties\n\nMany common properties are quite simple, like `prop_bool`.\nHowever, QuickCheck's way of declaring properties as functions with result type\n`Property` introduces some unexpected complexity in the types.\n\nFor example, try generalizing the property `prop_bool` above to\narbitrary types instead of `Bool`.\nSince we use testable equality of functions `a -\u003e a`, we incur constraints that\nthe domain must be `Constructible`, and the codomain itself must have\ntestable equality.\n\n```haskell\nprop_fun :: (Constructible a, TestEq a) =\u003e (a -\u003e a) -\u003e Property\nprop_fun f = (f . f . f) =? f\n```\n\nThis type tells us both too much and too little.\nToo much, because the constraints leak details about the very specific\nway in which the comparison is performed. Too little, because a `Property`\ncan do a lot of things besides testing the equality of two values;\nin fact that is one cause for the previous concern.\n\nA more precise formulation is the following:\n\n```haskell\nprop_fun :: (a -\u003e a) -\u003e Equation (a -\u003e a)\nprop_fun f = (f . f . f) :=: f\n```\n\nThis does not actually do the comparison, but exposes just the necessary\namount of information to do it in whatever way one deems appropriate.\nIndeed, `Equation` is simply a type of pairs:\n\n```haskell\ndata Equation a = a :=: a\n```\n\nIt is equipped with a `Testable` instance that will require a `TestEq`\nconstraint indirectly at call sites only.\n\n## Full example\n\n```haskell\nimport Test.QuickCheck (quickCheck)\nimport Test.QuickCheck.HigherOrder (property', Equation((:=:)), CoArbitrary)\n\nimport Control.Monad.Cont (Cont, ContT(..), callCC)\n\n\n-- Example property\ncallCC_bind :: forall r a. Cont r a -\u003e Equation (Cont r a)\ncallCC_bind m = callCC ((\u003e\u003e=) m) :=: m\n\n\nmain :: IO ()\nmain = quickCheck' (callCC_bind @Int @Int)\n\n\n-- Newtype boilerplate\n\nimport Test.QuickCheck (Gen)\nimport Test.QuickCheck.HigherOrder (CoArbitrary, TestEq(..), Constructible(..))\n\n-- Constructible instances\ninstance (CoArbitrary Gen (m r), Constructible a, Constructible (m r)) =\u003e Constructible (ContT r m a) where\n  type Repr (ContT r m a) = Repr ((a -\u003e m r) -\u003e m r)\n  fromRepr = ContT . fromRepr\n\ninstance (TestEq ((a -\u003e m r) -\u003e m r)) =\u003e TestEq (ContT r m a) where\n  ContT f =? ContT g = f =? g\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flysxia%2Fquickcheck-higherorder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flysxia%2Fquickcheck-higherorder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flysxia%2Fquickcheck-higherorder/lists"}