Formal Specification Driven Development
A rigorous workflow for implementing code with formal verification using Idris2.
Workflow Steps
Phase 1: Requirements Discussion
- Engage with the user to understand the problem thoroughly
- Ask clarifying questions about:
- Input/output types and constraints
- Edge cases and error conditions
- Performance requirements
- Invariants that must hold
- Summarize the requirements for user confirmation
Phase 2: Idris2 Specification
- Create an Idris2 specification that captures:
- Type definitions with dependent types where appropriate
- Function signatures with precise types (with explicit multiplicities if needed)
- Proofs of key properties (totality, correctness conditions)
- Example usage and test cases
- Place specification in
.specs/directory (create if needed) - Use clear naming:
<feature-name>.idr
Phase 3: Initial Compilation & Verification
- Immediately compile the Idris2 specification:
idris2 --check <file>.idr - Fix any type errors or proof obligations
- Ensure the specification is total and type-checks
- Iterate automatically until compilation succeeds (no user involvement needed)
- If fundamental design issues arise, discuss with user
Phase 4: Specification Review
- Present the compiled and verified Idris2 specification to the user
- Explain key design decisions:
- Why certain types were chosen
- What properties are being enforced
- What the proofs guarantee
- Ask for feedback and iterate if needed (recompile after changes)
- Use AskUserQuestion for critical design choices
- Get user approval before proceeding to implementation
Phase 5: Target Language Implementation
- Ask user which target language to use (if not specified):
- Python
- Rust
- TypeScript
- Implement the specification in the target language:
- Preserve type information using language features (type hints, generics, etc.)
- Add runtime validation where compile-time guarantees aren't available
- Include comments referencing the Idris2 spec
- Maintain the same logical structure
- Add comprehensive tests based on Idris2 examples
- Document any gaps between formal spec and implementation
Phase 6: Final Review
- Show both the Idris2 spec and implementation side-by-side
- Highlight how the implementation preserves formal properties
- Discuss any limitations or assumptions
- Get user approval before integrating into project
Best Practices
For Idris2 Specifications
- Start with simple types, add dependent types only when needed
- Write proofs for critical properties
- Include concrete examples as test cases
- Use
%default totalto ensure all functions terminate - Document assumptions clearly
- Be explicit with multiplicity annotations when using linear types
- Import necessary modules from
base(Prelude is minimal in Idris2) - Use qualified names when disambiguation is needed
For Target Language Implementation
- Python: Use type hints, runtime validation with
assert, dataclasses - Rust: Leverage type system, use
Result/Option, implement traits - TypeScript: Use strict mode, generics, branded types for validation
Communication
- Always explain trade-offs clearly
- Don't proceed to next phase without user approval
- If Idris2 compilation fails, discuss the issue before fixing
- Make the formal specification human-readable, not just machine-checkable
Example Interaction
User: "I need a function to merge sorted lists"
Assistant Phase 1: Let me understand the requirements:
- Should this preserve duplicates?
- What should happen with empty lists?
- Do we need to prove the result is sorted?
[... discussion ...]
Assistant Phase 2: I'll create an Idris2 specification.
[Creates .specs/merge-sorted.idr with dependent types]
Assistant Phase 3: Compiling and verifying the specification...
[Runs idris2 --check, fixes errors, ensures totality]
[Iterates until successful compilation]
Assistant Phase 4: Here's the verified specification. Notice how the type
guarantees the output is sorted. Do you approve?
[... review ...]
Assistant Phase 5: Which language should I implement this in?
[User chooses Rust]
[Implements with proper types and tests]
Assistant Phase 6: Here's the final implementation. The Rust version
uses the type system to maintain similar guarantees...
Directory Structure
This skill expects/creates:
project-root/
├── .specs/ # Idris2 specifications
│ └── feature.idr
├── src/ # Target language implementations
│ └── feature.{py,rs,ts}
└── tests/ # Tests derived from specs
└── test_feature.*
Idris2-Specific Guidelines
Key Differences from Idris 1
Quantitative Type Theory (QTT):
- Variables have multiplicities:
0(erased),1(linear), or unrestricted - Implicit arguments may be erased at runtime (use
0prefix) - Linear resources require explicit
1multiplicity annotation
Smaller Prelude:
- Always import needed modules explicitly:
Data.List,Data.Nat,Data.Vect, etc. - Functions previously auto-available now require imports
Syntax Changes:
- Use
%default totalinstead of individualtotalkeywords letbindings are now lambda-equivalent (use local functions for recursive definitions)- Named application syntax:
f {arg = value} - New dot notation for record access:
record.field
Type System:
- Less aggressive disambiguation - use qualified names more often
- Missing interface implementations now cause compile errors
- Pattern matching on erased variables (
0multiplicity) is restricted
Common Patterns:
-- Explicit imports (required in Idris2)
import Data.Vect
import Data.Fin
-- Default totality (recommended)
%default total
-- Linear resource (1 multiplicity)
useOnce : (1 x : Resource) -> IO ()
-- Erased proof (0 multiplicity)
compute : (0 prf : IsValid x) -> (x : Nat) -> Nat
Notes
- Idris2 must be installed and available in PATH
- The
.specs/directory serves as formal documentation - Link implementation files to their specs in comments
- Use git to track both specs and implementations together
- This workflow is most valuable for critical algorithms and data structures
- When migrating Idris 1 code, watch for implicit erasure and import requirements
Scan to join WeChat group