Thank you for your interest in contributing to Strata! We greatly value feedback and contributions from the community. Please read this document before submitting any pull requests or issues.
We aim for Strata to be both highly trustworthy and widely adopted. To facilitate this, we have several subsidiary goals.
-
Allow multiple implementations. These could be in other ITPs or standard programming languages. In particular, we would like it to be easy to generate Strata code from front ends written in other languages.
-
Lower barriers to contribution. We would like contributions from a broad community of users, and don't want to insist that they write Lean code. And, if they do write Lean code, we don't want it to need to use the most complex features of the Lean language.
-
Enable verification of production tools. Ultimately, the code for Strata is a tool implementation rather than an academic formalization. Any verification we do is to convince ourselves that our tools are correct. Therefore, the primary artifacts are the executable definitions, which should form the basis for a usable tool.
These goals lead us to limit the use of certain Lean constructs, and to clearly separate implementation from proof.
When writing programs, rather than proofs, it can be tempting to use
partial functions. In some cases, for top-level code that handles
system interaction, file I/O, concurrency, and similar functionality,
this can be okay. However, because we aim for it to be possible to
verify core functionality, we prefer that operations over programs or
terms -- such as transformations, verification condition generation, or
testing -- be total functions and that this fact can, at least in
principle, be proved. That a function is total is also usually a
prerequisite for any proofs that use the function in question.
In some cases, however, it may be valuable to prove properties of code
that may, in fact, not terminate. For this use case, we prefer the use
of partial_fixpoint instead of partial, and to structure the code
such that partial_fixpoint is possible.
To avoid partial functions, it is sometimes necessary to allow functions
to indicate failure or the lack of a useful result. When constructing
formal semantics and other purely proof-oriented developments, making
things as concise as possible is often the goal, and the Option type
is the simplest way to make a partial function total.
When producting a tool for production use, however, helpful feedback is critical. Because of this, we prefer using failure types that carry information about what went wrong. So, for instance, a type checker should have a return type that includes an error message indicating what went wrong in the case where its input is not well-typed.
For example, consider a function to calculate the median of a list of numbers. Although there are calculations to get around this, let's assume this is only well-defined for lists of odd length. We might write something like:
def median (xs : List Int) : String ⊕ Int :=
if xs.length = 0 then .inl "Empty list"
else if xs.length % 2 = 0 then .inl "List of even size"
else .inr (List.mergeSort xs)[xs.length / 2]!In principle, both error cases could be handled by the xs.length % 2 = 0 check. When optimizing for a production implementation, however, it
can be useful to provide different feedback for an empty list.
Given the median function above, we may want to know that it always
produces an Int given a list of even size. We could have included a
precondition that the list has even size, avoiding the need for error
handling, but that would have precluded the possibility of providing
useful feedback in the case of working with unfiltered input.
However, if we happen to know we're working with a list of odd size,
we can write a theorem that shows that median will always return an
Int value.
theorem medianOddRight
(xs : List Int)
(isOdd : ¬ (xs.length % 2 = 0)) :
(median xs).isRight := by
unfold median
if h1: xs.length = 0 then
simp [h1] at isOdd
else if h2: xs.length % 2 = 0 then
simp [h1, h2, isOdd]; contradiction
else simp [h1, h2]When doing proofs, it's often the case that you only care whether a function succeeded or failed, and not why, so the extra information in the implementation can clutter things up.
def median' (xs : List Int) (isOdd : ¬ (xs.length % 2 = 0)) : Int :=
let res := median xs
have right : res.isRight := medianOddRight xs isOdd
match res with
| .inl s => by simp at right
| .inr m => mWe encourage contributors to test their code, even if it is
verified. Strata's Lean code extensively uses #guard_msgs for three
main purposes:
-
For unit tests immediately after somewhat complicated functions (e.g., type unification, etc.). These indicate -- sooner rather than later -- if/when any changes break core functionality. They also aid in code comprehension by providing usage examples.
-
For higher-level tests, e.g., in the
StrataTestdirectory, which does not contain an end-to-end Strata application but tests for core components (e.g., just the DDM, just the partial evaluator of a specific dialect, etc.). These tests serve as guides to understand how to set up, use, and compose these core components.StrataTest/Languages/[Core|C_Simp]/Examplesshowcases end-to-end Strata applications and may include output from external solvers.
- File names should be in
UpperCamelCase.<extension>. - Function names should use
lowerCamelCase. - Type names (e.g., inductive types, structures, etc.) should use
UpperCamelCase. - Proofs and theorem names should use
snake_case.
Strive to keep lines under 80 characters, never exceeding 100 characters.
The file header should contain:
- the copyright information,
All imports should be done immediately after this header. All public-facing
files should have a module docstring (/-! ... -/) immediately following
the imports, like so:
/-!
# Title
In this file, we define... and prove...
## Main Results
...
-/
We recommend using sections or namespaces for code
organization. Using a visual separator (e.g., an 80-character line
formed by -) is also acceptable. The use of module docstrings for
sectioning comments, with appropriate levels of headings, is strongly
encouraged.
Every major function and theorem must be documented (NOTE: use /-- ... -/ and NOT /- ... -/). Use backticks to format code or math
expressions in the docstring (e.g., x + y).
Describe implementation-level details in single- (--) or multi-line
comments (/- .. -/) interspersed with the code.
-
Focused Changes: Create small, focused PRs that address a single issue or implement a specific feature.
-
Maintainable Proofs: Write any proofs in a maintainable way, even if doing so causes them to become more verbose. Strata follows the no nonterminal
simprule, which says that unlesssimpis closing the goal, it should always be converted to asimp only [X, Y, Z]call. -
External Dependencies: Do not introduce any unnecessary new external dependencies into the codebase -- be mindful of what you import. Exceptions are possible, but only when absolutely necessary.
-
Documentation: Add relevant documentation and comments to your code. Please refer to the Syntactic Style Guidelines.
When filing an issue, first check whether the same issue has already been reported previously by someone else. In your issue, include details like:
- Illustrative, minimal, and reproducible test cases
- Commit hash of Strata that you used
- Suggested modifications, if any
-
Be Patient: Reviewing your contribution may take some time! The necessary conditions for a PR to the
mainbranch to be merged are:- All CI checks must pass on your PR.
- No changes must be requested.
- All conversations must be resolved.
- Approval from at least two Strata maintainers is required.
- Any changes to code with an owner requires approval from at least one of the corresponding code owners.
-
Not All PRs Get Merged: We value every contribution. However, to enable code maintainability and quality, we will merge only those PRs that align with our priorities and goals.
-
Work in Progress: Strata is under active development, and there may be breaking changes!