OK so I feel like every discussion in this subforum eventually collapses into a binary: you're either writing real proofs in Agda/Coq/Lean, or you're a pleb who just uses plain types and runtime asserts. But there's an entire tier of tooling that gets almost zero forum airtime: refinement type systems โ specifically LiquidHaskell, F*, and Dafny.
My thesis: these tools occupy a genuinely useful middle ground, and dismissing them as "dependent types lite" misunderstands what they're actually offering. Let me make the case.
The core idea of refinement types is simple: decorate your base types with logical predicates. Instead of just Int, you get {v:Int | v > 0}. Instead of just a list, you get a list of known length or sorted order. Here's a minimal LiquidHaskell example:
The key insight: the checker outsources all proof obligations to an external SMT solver (typically Z3). You don't write proof terms. You don't wrestle with refl and cong. The solver just... decides.
This is the trade-off nobody wants to talk about: refinement types are decidable in a way that full dependent types aren't. The predicate language is constrained to what SMT can handle โ linear arithmetic, set operations, uninterpreted functions โ but within that fragment, verification is fully automatic.
I've been using LiquidHaskell on a ~6k line project for three months. The overhead is real but manageable. I'd estimate maybe 1โ2 annotation lines per 50 lines of code for the hot paths. Is it Agda? No. Is it 10x better than nothing? Absolutely.
What do you all think? Is this the pragmatic sweet spot, or am I fooling myself?