Proof-Search Friendly Non-Equality Proofs
This commit is contained in:
parent
99b7ca322e
commit
106d46cc3c
4 changed files with 85 additions and 1 deletions
16
db/posts/8.json
Normal file
16
db/posts/8.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"edited-at": [
|
||||
],
|
||||
"hidden": false,
|
||||
"idris": true,
|
||||
"ipkg": "/home/nathan/Projects/Blog/projects/Idris/Idris.ipkg",
|
||||
"posted-at": "2025-07-05T22:48:03.372522-04:00",
|
||||
"slugs": [
|
||||
],
|
||||
"source": "/home/nathan/Projects/Blog/projects/Idris/src/TipsAndTricks/ProofSearchAndNonEquality.md",
|
||||
"special": false,
|
||||
"tags": [
|
||||
"idris",
|
||||
"tip"
|
||||
]
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"desc": "Introductions to the building blocks of dependently typed programs.</p><p>The posts in this series are 'living posts', they will be referenced as library modules by future posts, and will be updated as needs change.",
|
||||
"post-ids": [
|
||||
7
|
||||
7,
|
||||
8
|
||||
],
|
||||
"title": "Dependent Nuggets"
|
||||
}
|
|
@ -20,6 +20,7 @@ modules = Idris
|
|||
, Posts.HelloWorld
|
||||
, LessMacrosMoreTypes.Printf
|
||||
, DependentNuggets.FreshLists
|
||||
, TipsAndTricks.ProofSearchAndNonEquality
|
||||
|
||||
-- main file (i.e. file to load at REPL)
|
||||
-- main =
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# Proof-Search Friendly Non-Equality Proofs
|
||||
|
||||
```idris hide
|
||||
module TipsAndTricks.ProofSearchAndNonEquality
|
||||
|
||||
import Data.List.Elem
|
||||
import Data.So
|
||||
import Decidable.Decidable
|
||||
import Decidable.Equality
|
||||
```
|
||||
|
||||
## The Problem
|
||||
|
||||
When designing interfaces, you may occasionally find a need to thread a proof of non-equality through API, and for ergonomics reasons, you would like that proof to be an `auto` implicit so the user doesn't have to generate the proof manually. Often, these proofs seem like they really should be trivial for the compiler to generate.
|
||||
|
||||
I recently came across an instance of this implementing the _Brown Benchmark for Table Types_[^1], where I needed to encode a proof that a given value was non-equal to every value currently in a list (much like a _FreshList_[^2], but with propositional equality). In many cases, this list is going to be know statically at compile time, so I would like the compiler to just generate the structure when possible to keep the API easy to use.
|
||||
|
||||
It may seem natural to use a value of type `Not (x = y)`, but this has a bit of an issue, the compiler will refuse to generate even seemingly trivial values of this type:
|
||||
|
||||
```idris
|
||||
failing
|
||||
oneIsNotTwo : Not (1 = 2)
|
||||
oneIsNotTwo = %search
|
||||
```
|
||||
|
||||
This is because `Not x` is an alias for `x -> Void`, a function type. Proof search won't try to produces values of function types, it only searches for value types. More generally, it is a lot easier to find positive proof of a statement than negative proof of a statement.
|
||||
|
||||
## The Solution
|
||||
|
||||
We can sneak around the compiler's limitations here by generating a positive proof of non-equality instead. One cheeky way that comes to mind is to use our old friend `Data.So`, instead asking for a `So`bearing witness that the result of calling `decEq x y` satisfies the `isNo` function.
|
||||
|
||||
Lets take a look at it in action, here's an example of a type that serves as proof that some value `x` is unique compared to every value in some list:
|
||||
|
||||
```idris
|
||||
data IsUnique : (value : t) -> List t -> Type where
|
||||
NotInTheEmptyList : IsUnique value []
|
||||
NotHere : DecEq t => {x : t} ->
|
||||
(prf : So (isNo (decEq value x))) -> IsUnique value xs
|
||||
-> IsUnique value (x :: xs)
|
||||
```
|
||||
|
||||
This sneaks our proof of non-equality into a value type, as the argument to `So` is a `Bool`, not a function, allowing proof search to generate these values by actively calling the `decEq` and `isNo` functions.
|
||||
|
||||
This `So (isNo (decEq x y))` type is equivalent to `Not (x = y)`. Don't believe me? Here's proof:
|
||||
|
||||
```idris
|
||||
soNoIsNot : DecEq t => {x, y : t} -> So (isNo (decEq x y)) -> Not (x = y)
|
||||
soNoIsNot {x} {y} soPrf with (decEq x y)
|
||||
soNoIsNot {x} {y} soPrf | Yes prf = absurd soPrf
|
||||
soNoIsNot {x} {y} soPrf | No contra = contra
|
||||
```
|
||||
|
||||
We can therefore use this `IsUnique v xs` proof to, for example, produce a proof of `Not (Elem v xs)`:
|
||||
|
||||
```idris
|
||||
isUniqueMeansNotElem : {v : t} -> IsUnique v xs -> Not (Elem v xs)
|
||||
isUniqueMeansNotElem (NotHere prf x) Here =
|
||||
let notPrf = soNoIsNot {x = v} {y = v} prf
|
||||
in notPrf Refl
|
||||
isUniqueMeansNotElem (NotHere prf x) (There y) = isUniqueMeansNotElem x y
|
||||
```
|
||||
|
||||
|
||||
[^1]: <https://github.com/brownplt/B2T2>
|
||||
|
||||
[^2]: [FreshLists: Containers That Only Accept "Fresh" Elements](/posts/by-slug/freshlists-containers-that-only-accept-fresh.html)
|
Loading…
Add table
Add a link
Reference in a new issue