Skip to content

ngernest/chamelean

 
 

Repository files navigation

Chamelean

Chamelean is an extension of Lean's Plausible property-based testing library which automatically derives generators, enumerators and checkers for inductive relations.

Our design is heavily inspired by Coq/Rocq's QuickChick library and the following papers:

Overview

Like QuickChick, we provide the following typeclasses:

  • Arbitrary: unconstrained random generators for inhabitants of algebraic data types
  • ArbitrarySuchThat: constrained generators which only produce random values that satisfy a user-supplied inductive relation
  • ArbitrarySized, ArbitrarySizedSuchThat: versions of the two typeclasses above where the generator's size parameter is made explicit
  • Enum, EnumSuchThat, EnumSized, EnumSizedSuchThat: Like their Arbitrary counterparts but for deterministic enumerators instead
  • DecOpt: Checkers (partial decision procedures that return Option Bool) for inductive propositions

We provide various top-level commands which automatically derive generators for Lean inductives:

1. Deriving unconstrained generators/enumerators
An unconstrained generator produces random inhabitants of an algebraic data type, while an unconstrained enumerator enumerates (deterministically) these inhabitants.

Users can write deriving Arbitrary and/or deriving Enum after an inductive type definition, e.g..

inductive Foo where
  ...
  deriving Arbitrary, Eunm

Alternatively, users can also write deriving instance Arbitrary for T1, ..., Tn (or deriving instance Enum ...) as a top-level command to derive Arbitrary / Enum instances for types T1, ..., Tn simultaneously.

To sample from a derived unconstrained generator, users can simply call runArbitrary, specify the type for the desired generated values and provide some Nat to act as the generator's size parameter (10 in the example below):

#eval runArbitrary (α := Tree) 10

Similarly, to return the elements produced form a derived enumerator, users can call runEnum like so:

#eval runEnum (α := Tree) 10

2. Deriving constrained generators (for inductive relations)
A constrained producer only produces values that satisfy a user-specified inductive relation.

We provide two commands for deriving constrained generators/enumerators. For example, support we want to derive constrained producers of Trees satisfying some inductive relation balanced n t (height-n trees that are balanced. To do so, the user would write:

-- `#derive_generator` & `#derive_enumerator` derive constrained generators/enumerators 
-- for `Tree`s that are balanced at some height `n`,
-- where `balanced n t` is a user-defined inductive relation
#derive_generator (fun (t : Tree) => balanced n t) 
#derive_enumerator (fun (t : Tree) => balanced n t)

To sample from the derived producer, users invoke runSizedGen / runSizedEnum & specify the right instance of the ArbitrarySizedSuchThat / EnumSizedSuchThat typeclass (along with some Nat to act as the generator size):

-- For generators:
#eval runSizedGen (ArbitrarySizedSuchThat.arbitrarySizedST (fun t => balanced 5 t)) 10

-- For enumerators:
-- (we recommend using a smaller `Nat` as the fuel for enumerators to avoid stack overflow)
#eval runSizedEnum (EnumSizedSuchThat.enumSizedST (fun t => balanced 5 t)) 3

Some extra details about the grammar of the lambda-abstraction that is passed to #derive_generator / #derive_enumerator:

Specifically: in the command

#derive_generator (fun (x : t) => P x1 ... x .... xn)

P must be an inductively defined relation, x is the value to be generated (the type annotation on x is required), and x1 ... xn are (implicitly universally quantified) variable names. Following QuickChick, we expect x1, ..., xn to be variable names (we don't support literals in the position of the xi currently).

3. Deriving checkers (partial decision procedures) (for inductive relations)
A checker for an inductively-defined Prop is a Nat -> Option Bool function, which takes a Nat argument as fuel and returns none if it can't decide whether the Prop holds (e.g. it runs out of fuel), and otherwise returns some true/some false depending on whether the Prop holds.

We provide a command elaborator which elaborates the #derive_checker command:

-- `#derive_checker` derives a checker which determines whether `Tree`s `t` 
-- satisfy the `balanced` inductive relation mentioned above 
#derive_checker (balanced n t)

Repo overview

Building & compiling:

  • To compile, run lake build from the top-level repository.
  • To run snapshot tests, run lake test.
  • To run linter checks, run lake lint.

Typeclass definitions:

  • Arbitrary.lean: The Arbitrary & ArbitrarySized typeclasses for unconstrained generators, adapted from QuickChick
  • ArbitrarySizedSuchThat.lean: The ArbitrarySuchThat & ArbitrarySizedSuchThat typeclasses for constrained generators, adapted from QuickChick
  • DecOpt.lean: The DecOpt typeclass for partially decidable propositions, adapted from QuickChick
  • Enumerators.lean: The Enum, EnumSized, EnumSuchThat, EnumSizedSuchThat typeclasses for constrained & unconstrained enumeration

Combinators for generators & enumerators:

  • GeneratorCombinators.lean: Extra combinators for Plausible generators (e.g. analogs of the sized and frequency combinators from Haskell QuickCheck)
  • OptionTGen.lean: Generator combinators that work over the OptionT Gen monad transformer (representing generators that may fail)
  • EnumeratorCombinators.lean: Combinators over enumerators

Algorithm for deriving constrained producers & checkers (adapted from the QuickChick papers):

Derivers for unconstrained producers:

  • DeriveArbitrary.lean: Deriver for unconstrained generators (instances of the Arbitrary / ArbitrarySized typeclasses)
  • DeriveEnum.lean: Deriver for unconstrainted enumerators (instances of the Enum / EnumSized typeclasses)

Miscellany:

  • TSyntaxCombinators.lean: Combinators over TSyntax for creating monadic do-blocks & other Lean expressions via metaprogramming
  • LazyList.lean: Implementation of lazy lists (used for enumerators)
  • Idents.lean: Utilities for dealing with identifiers / producing fresh names
  • Utils.lean: Other miscellaneous utils

Tests & Examples:

Tests

Overview of snapshot test corpus:

  • The Test subdirectory contains snapshot tests (aka expect tests) for the #derive_generator & #derive_arbitrary command elaborators.
  • Run lake test to check that the derived generators in Test typecheck, and that the code for the derived generators match the expected output.
  • See DeriveBSTGenerator.lean & DeriveBalancedTreeGenerator.lean for examples of snapshot tests. Follow the template in these two files to add new snapshot test file, and remember to import the new test file in Test.lean afterwards.

Key Value Store Example:

  • KeyValueStore.lean: Definitions for a hypothetical key-value store, in which inductive types are used to encode API calls and K/V states, and inductive relations are used to define API call semantics
  • TestKeyValueStoreCheckerGenerators.lean: The derived checkers & generators for the K/V store example (in particular, the derived generator produces well-formed sequences of API calls)

Cedar Example:

Tests for Unconstrained Generators (#derive_arbitrary):

Tests for Constrained Generators (#derive_generator):

Tests for Checkers (#derive_checker):

Tests for Unconstrained Enumerators (#derive_enum):

Tests for Constrained Enumerators (#derive_enumerator):

Enumerator Infrastructure Tests:

Auxiliary definitions for snapshot tests:

  • BinaryTree.lean: Binary tree datatype with BST (Binary Search Tree) and Between relations
  • FunctionCallInConclusion.lean: Example inductive relation with function calls in constructor conclusions
  • ListRelations.lean: Various inductive relations over lists, some of which require pattern-matching on multiple inputs
  • Permutation.lean: Inductive relation for list permutations
  • STLCDefinitions.lean: Simply-Typed Lambda Calculus (STLC) definitions including types, terms, typing judgments, and lookup relations

Plausible Tests (inherited from the original Plausible repo):

  • Tactic.lean: Tests the plausible tactic on core Lean types
  • Testable.lean: Tests for the Testable typeclass infrastructure with custom types

About

Property-based testing for Lean via metaprogramming

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Lean 100.0%