What are the drawbacks of encoding properties using functions? - agda

In Agda, it seems that often there are two ways to refine a set. One is by simply writing a function that checks if a property holds, and lift. For example:
has_true : List Bool -> Bool
has_true (true ∷ xs) = true
has_true (false ∷ xs) = has_true xs
has_true [] = false
Truthy : List Bool -> Set
Truthy list = T (has_true list)
Here, Truthy list is a proof that a boolean list has at least one true element. The other way is by encoding that property directly, as an inductive type:
data Truthy : List Bool -> Set where
Here : (x : Bool) -> (x ≡ true) -> (xs : List Bool) -> Truthy (x ∷ xs)
There : (x : Bool) -> (xs : List Bool) -> Truthy xs -> Truthy (x ∷ xs)
Here, Truthy list also proves the same thing.
I believe I have read a comparison before, but I don't remember. Is there a name for those different styles? What are the advantages and drawbacks of using one style over another? And is there a third option?

So far you listed two ways of defining predicates:
A -> Bool functions.
Inductive predicates.
I would add one more:
A -> Set functions. Could be also called "recursively defined", or "defined by large elimination".
The third version is the following in Agda:
open import Data.Bool
open import Data.Unit
open import Data.Empty
open import Data.List
hastrue : List Bool → Set
hastrue [] = ⊥ -- empty type
hastrue (false ∷ bs) = hastrue bs
hastrue (true ∷ bs) = ⊤ -- unit type
First, let's talk about what kind of predicates are representable using the three options. Here's an ASCII table. * is a wildcard standing for yes/no.
| P : A -> Set | P : A -> Bool | data P : A -> Set |
|-------------------|--------------|---------------|-------------------|
| Proof irrelevant | * | yes | * |
| Structural | yes | yes | * |
| Strictly positive | * | N/A | yes |
| Decidable | * | yes | * |
Proof irrelevance means that all proofs for P x are equal. In the Bool case, a proof is usually some p : P x ≡ true, or p : IsTrue (P x) with IsTrue = λ b → if b then ⊤ else ⊥, and in both cases all proofs are equal indeed. We may or may not want predicates to be irrelevant.
Structural means that P x can only be defined using elements of A which are structurally smaller than x. Functions are always structural, so if some predicate isn't, then it can only be defined inductively.
Strictly positive means that P cannot occur recursively to the left of a function arrow. Non-strictly positive predicates are not definable inductively. An example for a proof-relevant non-strictly positive predicate is the interpretation of codes of function types:
data Ty : Set where
top : Ty
fun : Ty → Ty → Ty
⟦_⟧ : Ty → Set
⟦ top ⟧ = ⊤
⟦ fun A B ⟧ = ⟦ A ⟧ → ⟦ B ⟧ -- you can't put this in "data"
Decidable is self-explanatory; A -> Bool functions are necessarily decidable, which makes them unsuitable for predicates which are undecidable or cannot be easily written out as a structural Bool function. The advantage of decidability is excluded middle reasoning, which is not possible with the non-Bool predicate definitions without postulates or additional decidability proofs.
Second, about the practical ramifications in Agda/Idris.
You can do dependent pattern matching on proofs of inductive predicates. With recursive and Boolean predicates, you have to pattern match on the A values first, to make predicate witnesses compute. Sometimes this makes inductive predicates convenient, e.g. you could have an enumeration type with 10 constructors, and you want a predicate to hold at only one constructor. The inductively defined predicate lets you only match on the true case, while the other versions require you to match on all cases all the time.
On the other hand, Boolean and recursive predicates compute automatically as soon as you know that an A element has a given shape. This can be used in Agda to make type inference fill in proofs automatically, without tactics or instances. For example, a hole or implicit argument with type hastrue xs can be solved by eta rules for pairs and the unit type, whenever xs is a list expression with a known true-containing prefix. This works analogously with Boolean predicates.

Related

How to recover intermediate computation results from a function using "with"?

I wrote a function on the natural numbers that uses the operator _<?_ with the with-abstraction.
open import Data.Maybe
open import Data.Nat
open import Data.Nat.Properties
open import Relation.Binary.PropositionalEquality
open import Relation.Nullary
fun : ℕ → ℕ → Maybe ℕ
fun x y with x <? y
... | yes _ = nothing
... | no _ = just y
I would like to prove that if the result of computing with fun is nothing then the original two values (x and y) fulfill x < y.
So far all my attempts fall short to prove the property:
prop : ∀ (x y)
→ fun x y ≡ nothing
→ x < y
prop x y with fun x y
... | just _ = λ()
... | nothing = λ{refl → ?} -- from-yes (x <? y)}
-- This fails because the pattern matching is incomplete,
-- but it shouldn't. There are no other cases
prop' : ∀ (x y)
→ fun x y ≡ nothing
→ x < y
prop' x y with fun x y | x <? y
... | nothing | yes x<y = λ{refl → x<y}
... | just _ | no _ = λ()
--... | _ | _ = ?
In general, I've found that working with the with-abstraction is painful. It is probably due to the fact that with and | hide some magic in the background. I would like to understand what with and | really do, but the "Technical details" currently escape my understanding. Do you know where to look for to understand how to interpret them?
Concrete solution
You need to case-split on the same element on which you case-split in your function:
prop : ∀ x y → fun x y ≡ nothing → x < y
prop x y _ with x <? y
... | yes p = p
In the older versions of Agda, you would have had to write the following:
prop-old : ∀ x y → fun x y ≡ nothing → x < y
prop-old x y _ with x <? y
prop-old _ _ refl | yes p = p
prop-old _ _ () | no _
But now you are able to completely omit a case when it leads to a direct contradiction, which is, in this case, that nothing and just smth can never be equal.
Detailed explanation
To understand how with works you first need to understand how definitional equality is used in Agda to reduce goals. Definitional equality binds a function call with its associated expression depending on the structure of its input. In Agda, this is easily seen by the use of the equal sign in the definition of the different cases of a function (although since Agda builds a tree of cases some definitional equalities might not hold in some cases, but let's forget this for now).
Let us consider the following definition of the addition over naturals:
_+_ : ℕ → ℕ → ℕ
zero + b = b
(suc a) + b = suc (a + b)
This definition provides two definitional equalities that bind zero + b with b and (suc a) + b with suc (a + b). The good thing with definitional equalities (as opposed to propositional equalities) is that Agda automatically uses them to reduce goals whenever possible. This means that, for instance, if in a further goal you have the element zero + p for any p then Agda will automatically reduce it to p.
To allow Agda to do such reduction, which is fundamental in most cases, Agda needs to know which of these two equalities can be exploited, which means a case-split on the first argument of this addition has to be made in any further proof about addition for a reduction to be possible. (Except for composite proofs based on other proofs which use such case-splits).
When using with you basically add additional definitional equalities depending on the structure of the additional element. This only makes sense, understanding that, that you need to case-split on said element when doing proofs about such a function, in order for Agda once again to be able to make use of these definitional equalities.
Let us take your example and apply this reasoning to it, first without the recent ability to omit impossible cases. You need to prove the following statement:
prop-old : ∀ x y → fun x y ≡ nothing → x < y
Introducing parameters in the context, you write the following line:
prop-old x y p = ?
Having written that line, you need to provide a proof of x < y with the elements in the context. x and y are just natural so you expect p to hold enough information for this result to be provable. But, in this case, p is just of type fun x y ≡ nothing which does not give you enough information. However, this type contains a call to function fun so there is hope ! Looking at the definition of fun, we can see that it yields two definitional equalities, which depend on the structure of x <? y. This means that adding this parameter to the proof by using with once more will allow Agda to make use of these equalities. This leads to the following code:
prop-old : ∀ x y → fun x y ≡ nothing → x < y
prop-old x y p with x <? y
prop-old _ _ p | yes q = ?
prop-old _ _ p | no q = ?
At that point, not only did Agda case-split on x <? y, but it also reduced the goal because it is able, in both cases, to use a specific definitional equality of fun. Let us take a closer look at both cases:
In the yes q case, p is now of type nothing ≡ nothing and q is of type x < y which is exactly what you want to prove, which means the goal is simply solved by:
prop-old _ _ p | yes q = q
I the no q case, something more interesting happens, which is somewhat harder to understand. After reduction, p is now of type just y ≡ nothing because Agda could use the second definitional equality of fun. Since _≡_ is a data type, it is possible to case-split on p which basically asks Agda: "Look at this data type and give me all the possible constructors for an element of type just y ≡ nothing". At first, Agda only finds one possible constructor, refl, but this constructor only builds an element of a type where both sides of the equality are the same, which is not the case here by definition because just and nothing are two distinct constructors from the same data type, Maybe. Agda then concludes that there are no possible constructors that could ever build an element of such type, hence this case is actually not possible, which leads to Agda replacing p with the empty pattern () and dismissing this case. This line is thus simply:
prop-old _ _ () | no _
In the more recent versions of Agda, as I explained earlier, some of these steps are done directly by Agda which allows us to directly omit impossible cases when the emptiness of a pattern can be deduced behind the curtain, which leads to the prettier:
prop : ∀ x y → fun x y ≡ nothing → x < y
prop x y _ with x <? y
... | yes p = p
But it is the same process, just done a bit more automatically. Hopefully, these elements will be of some use in your journey towards understanding Agda.

Provable coherence in OTT

I'm playing with observational type theory.
Here is equality of π-types (π is the lowercase Π, i.e. π A B is the code for (x : A) -> B x) defined mutually with coercions:
π A₁ B₁ ≃ π A₂ B₂ = σ (A₂ ≃ A₁) λ P -> π _ λ x -> B₁ (coerce P x) ≃ B₂ x
and equality of functions defined accordingly (σ is the lowercase Σ):
_≅_ {A = π A₁ B₁} {π A₂ B₂} f₁ f₂ = σ (A₂ ≃ A₁) λ P -> π _ λ x -> f₁ (coerce P x) ≅ f₂ x
So instead of "equal functions map equal inputs to equal outputs" we have "equal functions map definitionally equal inputs to equal outputs".
In this setting coherence
coerce : ∀ {α β} {A : Univ α} {B : Univ β} -> ⟦ A ≃ B ⟧ᵀ -> ⟦ A ⟧ᵀ -> ⟦ B ⟧ᵀ
coherence : ∀ {α β} {A : Univ α} {B : Univ β}
-> (P : ⟦ A ≃ B ⟧ᵀ) -> (x : ⟦ A ⟧ᵀ) -> ⟦ x ≅ coerce P x ⟧ᵀ
(Univ 0 is Prop, Univ (suc α) is Type α)
is provable. The only thing I needed to postulate is
postulate ≃-refl : ∀ {α} -> (A : Univ α) -> ⟦ A ≃ A ⟧ᵀ
But we can tweak equality to handle A ≃ A as a special case (I think, trustMe needs a friend _≟_ : ∀ {α} {A : Set α} (x y : A) -> Maybe (x ≡ y)).
We still need to postulate something to define subst and other stuff.
Did I miss something? Do we lose any irrelevance? It seems suspicious to mention type equality in the definition of equality of functions. Do we lose much by restricting inputs of equal functions to be definitionally equal? Is there anything good about having strongly normalizing coherence or it doesn't matter, since it's computationally irrelevant anyway?
The code (I ignored positivity, termination and cumulativity issues altogether).
Firstly, thanks for asking about Observational Type Theory. Secondly, what you've done here does seem to hang together, even though it has things in different places from where Thorsten Altenkirch, Wouter Swierstra and I put them in our version of the story. Thirdly, it's no surprise (at least not to me) that coherence is derivable, leaving reflexivity the only postulate. That's true of our OTT as well, and Wouter did the proofs in Agda 1, back when we wrote that paper. Proof irrelevance and the shortness of life meant I didn't port his proofs to Agda 2.
If you've missed anything, it's lurking in your remark
We still need to postulate something to define subst and other stuff.
If you have some P : X -> Set, some a, b : X and some q : a = b, you expect to get a function in P a -> P b. The "equal functions take equal inputs to equal outputs" formulation gives you that, as refl P : P = P, so from q, we can deduce P a = P b. Your "equal functions take a given input to equal outputs" formulation does not allow you to let q bridge the gap from a to b.
In the presence of refl and subst, "two equal inputs" amounts to the same thing as "one input used in two places". It seems to me that you've moved the work into whatever else you need to get subst. Depending on how lazy your definition of coerce is (and that's how you get proof irrelevance), you will need only a postulate.
With your particular formulation, you might even get away with a homogeneous value equality. If you're fixing type gaps with coercions rather than equations, you might save yourself some trouble (and maybe get rid of that equation on the domain type in function equality). Of course, in that case, you'd need to think about how to replace the statement of coherence.
We tried quite hard to keep coercion out of the definition of equality, to retain some sort of symmetry, and to keep type equations out of value equations, mostly to have less to think about at one go. It's interesting to see that at least some parts of the construction might get easier with "a thing and its coercion" replacing "two equal things".

How to obtain a list of values from a Data.AVL.Tree?

I'm easily able to obtain a list of Keys, as follows:
open import Relation.Binary
open import Relation.Binary.PropositionalEquality using (_≡_)
module AVL-Tree-Functions
{ k v ℓ } { Key : Set k }
( Value : Key → Set v )
{ _<_ : Rel Key ℓ }
( isStrictTotalOrder : IsStrictTotalOrder _≡_ _<_ )
where
open import Data.AVL Value isStrictTotalOrder public
open import Data.List.Base
open import Function
open import Data.Product
keys : Tree → List Key
keys = Data.List.Base.map proj₁ ∘ toList
But I'm not clear on how to specify the type of function that returns a list of values. Here's my first attempt:
-- this fails to typecheck
values : Tree → List Value
values = Data.List.Base.map proj₂ ∘ toList
Relatedly, I'm also confused about the declaration of Value in Data.AVL. With ( Value : Key → Set v ), it looks like the type of each Value in the tree is dependent on the key? Or, something like that. I then figured that proj₂ would be returning something of type Set v, so I tried this:
-- this also fails to typecheck
values : Tree → List (Set v)
values = Data.List.Base.map proj₂ ∘ toList
But that doesn't work either (it fails with a different error). Please show how to get a list of values from a Data.AVL.Tree (or explain why it's impossible). Bonus: explain why my two attempts failed.
P.s. This is using version 2.4.2.3 of Agda and the agda-stdlib.
it looks like the type of each Value in the tree is dependent on the
key?
Yes. And that's why your code doesn't typecheck — Lists are homogeneous, but different Values have different indices (i.e. depend on different Keys) and hence different types.
You can use heterogeneous lists as in gallais' answer, but indexed lists might be enough in your case:
open import Level
data IList {ι α} {I : Set ι} (A : I -> Set α) : Set (ι ⊔ α) where
[]ᵢ : IList A
_∷ᵢ_ : ∀ {i} -> A i -> IList A -> IList A
projs₂ : ∀ {α β} {A : Set α} {B : A -> Set β} -> List (Σ A B) -> IList B
projs₂ [] = []ᵢ
projs₂ ((x , y) ∷ ps) = y ∷ᵢ projs₂ ps
Or you can combine the techniques:
data IHList {ι α} {I : Set ι} (A : I -> Set α) : List I -> Set (ι ⊔ α) where
[]ᵢ : IHList A []
_∷ᵢ_ : ∀ {i is} -> A i -> IHList A is -> IHList A (i ∷ is)
projs₂ : ∀ {α β} {A : Set α} {B : A -> Set β}
-> (xs : List (Σ A B)) -> IHList B (Data.List.Base.map proj₁ xs)
projs₂ [] = []ᵢ
projs₂ ((x , y) ∷ ps) = y ∷ᵢ projs₂ ps
What Value : Key → Set v means is that the type of the value may depend on the key it is associated to. This means that an AVL tree may contain Booleans, Nats, and so on as long as the key they are stored in reflects that fact. A bit like the fact that records can store values of different types (the types are determined by the field's name).
Now, they are different ways to do this: you can extract the content of the whole tree to a list of key / value pairs (because a list's elements are all the same, you need to build a pair here so that everything has the same type Σ Key Value). This is what toList does.
An alternative is to use what is usually called an HList (the H stands for heterogeneous) which stores in a list at the type level the type each one of its elements is supposed to have. I define it here by induction on the set of elements for size reasons but it is not at all crucial (if you were to define it as a datatype, it would live one level higher):
open import Level
open import Data.Unit
HList : {ℓ : Level} (XS : List (Set ℓ)) → Set ℓ
HList [] = Lift ⊤
HList (X ∷ XS) = X × HList XS
Now, you can give the type of the HList of values. Given t a Tree, it uses your keys to extract the list of keys and turns them into Sets by mapping Value over the list.
values : (t : Tree) → HList (List.map Value (keys t))
Extracting the values can then be done with the help of an auxiliary function working along the list produced by toList:
values t = go (toList t) where
go : (kvs : List (Σ Key Value)) → HList (List.map Value $ List.map proj₁ kvs)
go [] = lift tt
go (kv ∷ kvs) = proj₂ kv , go kvs

Decidable Predicates in Agda

I am new to Agda and I need help to understand the Decidable function and Dec type.
I am trying to define a first-order-logic predicate, and I want to encode with the proof some sort of boolean value. I found the way to do this is using the Dec type..
Now, as far as I get it, to be able to do this, I have to re-define all logic operators to be of type decidable rather than of type set. to do so, I sort of embedded it into new type, this is how I did it for the and operator:
data _∧_ (A B : Set) : Set where
_&_ : A → B → A ∧ B
Dec∧ : {A B : Set} → A ∧ B → Dec (A ∧ B)
Dec∧ A∧B = yes (A∧B)
Is it the way to do it, or is there another way?
Then, I want to use this operator to define a relation on Nat values, so I did something like this:
_◆_ : ℕ → ℕ → Dec∧ (Rel ℕ lzero) (ℕ → Set)
x ◆ y = (0 < x) ∧ (x ² ≡ 2 * y ²)
but this gives a type error..
I am not sure how to work with Dec and I would appreciate if anyone can guide me to tutorials or examples using it for proving logical statements..
Basically decidable predicate is a predicate for which we have an algorithm which terminates in finite time and returns either a yes together with a proof that it's true, or no together with a proof of it's negation. For example, for each two natural numbers we can either prove that they are equal or that they aren't equal.
What you wrote doesn't type check. Your function should return Dec (Rel ℕ lzero) (ℕ → Set), the first argument is correct, the second however, isn't. It should be a function, for example, \x -> 2 * x.
P.S. To me the function makes no sense. What do you want to accomplish with it?

How to prove that equal function types have equal domains?

I want to prove
∀ {ℓ} {A B C D : Set ℓ} → (A → B) ≡ (C → D) → A ≡ C
(and similar for the codomain).
If I had a function domain that returns the domain of a function type, I could write the proof as
cong domain
but I don't think it's possible to write such a function.
Is there any way to do this?
I posed a very similar question on the Agda mailing list a few months ago, see: http://permalink.gmane.org/gmane.comp.lang.agda/5624. The short answer is that you cannot prove this in Agda.
The technical reason is that the unification algorithm used internally by Agda for pattern matching doesn't include a case for problems of the form (A → B) ≡ (C → D), so this definition does not typecheck:
cong-domain : ∀ {ℓ} {A B C D : Set ℓ} → (A → B) ≡ (C → D) → A ≡ C
cong-domain refl = refl
It is also impossible to define the function domain directly. Think about it: what should be the domain of a type that is not a function type, e.g. Bool?
The deeper reason why you cannot prove this is that it would be incompatible with the univalence axiom from Homotopy Type Theory. In an answer given by Guillaume Brunerie on my mail, he gives the following example: Consider the two types Bool -> Bool and Unit -> (Bool + Bool). Both have 4 elements, so we can use the univalence axiom to give a proof of type Bool -> Bool ≡ Unit -> (Bool + Bool) (in fact there are 24 different proofs). But clearly we do not want Bool ≡ Unit! So in the presence of univalence, we cannot assume that equal function types have equal domains.
In the end, I 'solved' this problem by passing an extra argument of type A ≡ C everywhere it was needed. I know it's not ideal, but maybe you can do the same.
I should also note that Agda does include an option for injective type constructors, which you can enable by putting {-# OPTIONS --injective-type-constructors #-} at the top of your .agda file. This allows you for example to prove A ≡ B from List A ≡ List B, but unfortunately this only works for type constructors such as List, and not for function types.
You could of course always make a feature request at https://code.google.com/p/agda/issues/list to add a option --injective-function-types to Agda. This option would be incompatible with univalence, but so is --injective-type-constructors, yet for many applications this is not a real problem. I feel that the main Agda developers are usually very open to such requests, and very fast to add them to the development version of Agda.

Resources