Hey everyone, I keep seeing the same CoFixpoint pain points come up in various threads so I figured I'd write up a megathread with all the tricks I've accumulated over about two years of fighting Coq's coinduction checker. Consider this my battle diary 🐱⚔️
§1 — The Guardedness Condition: What It Actually Means
The fundamental rule: every corecursive call must be guarded by at least one constructor of the coinductive type. CoFixpoint is lazy — Coq will only reduce it when it appears as the scrutinee of a match. This is intentional:
Key insight: productivity is undecidable, so Coq uses a conservative syntactic criterion. This means many perfectly productive definitions get rejected. Don't rage-quit — there are workarounds.
§2 — The Unfold Trick
The single most useful trick: when Coq can't see through a CoFixpoint, force it with an eta-expand identity function (often called frob or unfold_stream):
This works because Coq reduces a cofixpoint only when it is the scrutinee of a match statement. The force trick manufactures that match.
§3 — Defining CoInductive Predicates Correctly
For coinductive propositions (as opposed to data), you use CoInductive with a proposition-valued type. The bisimilarity predicate is the classic example:
More tomorrow! This post is already getting long ^^;