
A deep read of Edsko de Vries's 17 July explanation of falsify 0.4.0: why integrated shrinking changes generator design, and when its new context-aware sizing helps find counterexamples.
falsify-0.4.0, the Haskell property-testing library he developed for the Haskell Symposium. The release adds getContext and its derived sized helper, but the deeper subject is how a property-testing library should represent shrinking: as part of the generator rather than as a second piece of test code. 1falsify as a universal replacement for QuickCheck or Hedgehog. He is showing where its design buys something, where the new release helps, and where the library is still young.falsify workshop there. The group first built mini-falsify, a small implementation intended to expose the library's ideas without making readers work through the full codebase. They then used the actual library in the Clash ecosystem. 12falsify-0.4.0 was partly the result of workshop-driven work and partly overdue maintenance. The article is therefore two things at once: an account of a new API and a report on what became clearer when the library was reduced to its basic machinery.getContext:getContext :: Property ContextContext describes the current test case. Its documented fields include static run information, the zero-based current test number, and whether the current execution is an initial run, a shrinking step, or a final run. The static part includes the configured number of tests, the maximum number of shrinks, and the maximum discarded-test ratio. 3Word64 equals 5. A primitive generator over all Word64 values has an enormous search space, so the one forbidden value is unlikely to appear quickly. getContext lets the property react to how far the run has progressed. Early iterations can use a small upper bound; later iterations can widen it. 1falsify packages that pattern as:sized :: (ProperFraction -> a) -> Property aProperFraction is a value in the half-open interval [0, 1). A property can scale that fraction into a domain-specific bound, then use the bound to construct a generator. In the article's example, the range grows toward 100, making the value 5 easy to encounter while retaining a simple way to vary the search domain across iterations. 14sized is intentional but limited. QuickCheck's helper receives an Int size and returns a generator. falsify's helper receives a normalized fraction and returns a property-level value. Both expose a notion of scale, but falsify's version is tied to the iteration context that the new API makes available. 45sized a precise role. It is a search-control tool, not a substitute for a good generator. Use it when the distribution should change with iteration, and keep the property independent of the context when possible. The API documents an important constraint here: execution context must not affect whether a test passes or fails, because doing so would make shrinking behavior undefined. 3falsify interesting is harder to see until the post is read alongside its API documentation.falsify takes that integrated approach and adds an internal mechanism intended to keep shrinking effective across monadic bind, a place where a later generator depends on an earlier generated value. 16genList produces lists of numbers, then applying filter even through the generator produces only even numbers, and the evenness constraint remains in force while the failing case is shrunk. The generator is carrying a restriction that the shrinker does not have to rediscover later. 4n, then using n to decide how many values to generate. Shrinking only the values may leave an unnecessarily large structure; shrinking only n may invalidate the dependent part. The falsify documentation says its internal strategy can shrink n and the values produced afterward in either order, while also recommending dedicated compound generators when they express the relationship more directly. 40.4.0 is also a major cleanup release and is backward-incompatible with 0.3 in several ways. The changelog records a new top-level Test.Falsify module for the common unqualified-import surface, a clearer split between Test.Falsify.Generator and the new Data.Falsify.* hierarchy, and the removal of tasty integration from the main package into tasty-falsify. It also lists removed deprecated functions, renamed functions, and several type aliases replaced by newtype definitions. 7Test.Tasty.Falsify in the separate tasty-falsify package, while falsify now exposes Test.Falsify.Driver for driver integration. The package is documented as property-based testing with internal integrated shrinking, and Hackage lists version 0.4.0 as the current version in the package history. 6falsify is aimed at the same pain point, using an internal strategy inspired by Hypothesis while retaining a Haskell-oriented model of generators, including support for infinite data types and functions. 1falsify is experimental and not as battle-tested as Hedgehog, let alone QuickCheck. The QBayLogic work has made the project more mature, but the article does not present that as proof that the trade-offs have disappeared. 1filter even <$> genList example is more significant than its size suggests. 4falsify's answer is not to remove the dependency. It makes the dependency part of the structure that shrinking can inspect. The trade-off is that generator design becomes more important: a generator that hides meaningful structure behind awkward monadic steps may still shrink poorly, and the package docs point users toward dedicated combinators when they better represent the relationship. 4Context is a controlled escape hatchgetContext introduces information about the test runner into that property. Used carelessly, that can make the property depend on its position in the run rather than on the generated input.Context distinguishes static run configuration, the current iteration, and execution phase. sized exposes only a normalized scale, which is enough for staged generation without forcing every property to inspect the runner's entire state. The docs also say that execution context must not decide pass or fail. 35 is deliberately artificial. It isolates a common testing tension: a counterexample can be statistically valid but operationally hard to find. Sampling Word64 uniformly makes 5 vanishingly unlikely; searching small ranges first makes it obvious. 1getContext or sized when the search domain should expand across iterations, not as a way to make the assertion position-dependent.0.4.0 as a migration as well as a feature release. The package split and module cleanup are backward-incompatible in places. 7"Thefalsifylibrary takes its main inspiration from the Python Hypothesis library, though it is not a direct translation.""shrinking is never truly free."
falsify-0.4.0 is therefore best read as a sharper statement of a testing philosophy. Let the generator describe the values worth exploring, let its structure carry the path toward simpler failures, and use iteration-aware sizing only when the search itself needs staging. That is a smaller claim than "the next QuickCheck," but it is a much more useful one.well-typed.com
github.com
hackage-content.haskell.org
hackage-content.haskell.org
hackage.haskell.org
hackage.haskell.org
hackage.haskell.org
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.