Proving `weaken` doesn't change the value of a number - idris

Let's say we want to prove that weakening the upper bound of a Data.Fin doesn't change the value of the number. The intuitive way to state this is:
weakenEq : (num : Fin n) -> num = weaken num
Let's now generate the defini... Hold on! Let's think a bit about that statement. num and weaken num have different types. Can we state the equality in this case?
The documentation on = suggests we can try to, but we might want to use ~=~ instead. Well, anyway, let's go ahead and generate the definition and case-split, resulting in
weakenEq : (num : Fin n) -> num = weaken num
weakenEq FZ = ?weakenEq_rhs_1
weakenEq (FS x) = ?weakenEq_rhs_2
The goal in the weakenEq_rhs_1 hole is FZ = FZ, which still makes sense from the value point of view. So we optimistically replace the hole with Refl, but only to fail:
When checking right hand side of weakenEq with expected type
FZ = weaken FZ
Unifying k and S k would lead to infinite value
A somewhat cryptic error message, so we wonder if that's really related to the types being different.
Anyway, let's try again, but now with ~=~ instead of =. Unfortunately, the error is still the same.
So, how one would state and prove that weaken x doesn't change the value of x? Does it really make sense to? What should I do if that's a part of a larger proof where I might want to rewrite a Vect n (Fin k) with Vect n (Fin (S k)) that's obtained by mapping weaken over the original vector?

If you really want to prove that the value of the Fin n does not change after applying the weaken function, you would need to prove the equality of these values:
weakenEq: (num: Fin n) -> finToNat num = finToNat $ weaken num
weakenEq FZ = Refl
weakenEq (FS x) = cong $ weakenEq x

To your second problem/Markus' comment about map (Data.Fin.finToNat) v = map (Data.Fin.finToNat . Data.Fin.weaken) v:
vectorWeakenEq : (v: Vect n (Fin k)) ->
map Fin.finToNat v = map (Fin.finToNat . Fin.weaken) v
vectorWeakenEq [] = Refl
vectorWeakenEq (x :: xs) =
rewrite sym $ weakenEq x in
cong {f=(::) (finToNat x)} (vectorWeakenEq xs)
And to see why num = weaken num won't work, let's take a look at a counterexample:
getSize : Fin n -> Nat
getSize _ {n} = n
Now with x : Fin n, getSize x = n != (n + 1) = getSize (weaken x). This won't happen with functions which only depend on the constructors, like finToNat. So you have to constrain yourself to those and prove that they behave like that.

Related

How does Fin "know" not to go past its type bound?

How does Fin actually work?
data Nat = Z | S Nat
data Fin : (n : Nat) -> Type where
FZ : Fin (S k)
FS : Fin k -> Fin (S k)
Where does k come from?
Why isn't FZ always the value of (n: Nat) + 1?
How does Fin "know" not to exceed its type bound?
This mailing list talks about implicits, but I'm lost as to how and where these values come from.
Given this basic example:
t1 : Fin 3
t1 = FZ
What is t1's value? t1's type is Fin 3, but FZ experimentally (appears) to represent zero. Which is why we can "count" with it just like we do with Nat:
t2 : Fin 3
t3 = FS (FS FZ) -- this is equal to 2?
But this confuses me given the constructor!
FZ : Fin (S k)
If we're saying that t1 has type Fin 3, does that mean that k takes on the value of the n in the type? If that were the case, and FZ calls S on that Nat, then why isn't FZ representing 4?
This is obviously not the case.
So I'm completely and absolutely lost on how FZ acts like 0, and where k comes from, and how the compiler knows when k has become >= n just based on this data type.
I think what's confusing you is that you're thinking k is the value of the Fin n. It's not. It's just book-keeping to make sure the Fin n is always less than n.
Where does k come from?
In
FZ : Fin (S k)
k is an (erased) implicit argument. It's shorthand for
FZ : {0 k : _} -> Fin (S k)
so it's passed implicitly, but it's still an argument to FZ. In most cases, it can be unambiguously inferred from the context. The compiler will tell you if it can't.
Why isn't FZ always the value of (n: Nat) + 1?
FZ always represents zero*. This
t1 : Fin 3
t1 = FZ
is the same as
t1 : Fin 3
t1 = FZ {k = 2}
It's a zero that's less than three. These are also all zero
a : Fin 1
a = FZ
b : Fin 4
b = FZ
c : Fin 100
c = FZ
How does Fin "know" not to exceed its type bound?
Since FZ is always zero, and FS FZ is always one, and so on, it's impossible to express a Fin n with value greater than or equal to n. Let's try to create a Fin 2 of value two.
x : Fin 2
x = FS (FS FZ) -- this won't compile
To see why this won't compile, fill in the implicits (note that FS also has an implicit argument called k):
x : Fin 2
x = FS {k = 1} (FS {k = 0} (FZ {k = ?!?}))
We're creating a Fin 2 so the outer-most FS must have S k = 2 so k = 1. Each successive FS we go down we reduce k by one (see the argument to FS is Fin k to produce a Fin (S k) - so one less). Eventually we reach FZ but by this point there's isn't a Nat to represent this value of k. The compiler balks with such a statement
If Data.Fin.FZ: When unifying:
Fin (S ?k)
and:
Fin 0
Mismatch between: S ?k and 0.
(Interactive):1:17--1:19
1 | :let x = FS (FS FZ)
^^
You can do the same for a Fin 2 of value three etc. Have a try.
Other qus
Given
t1 : Fin 3
t1 = FZ
What is t1's value? It's FZ, and FZ represents zero.
t2 : Fin 3
t2 = FS (FS FZ) -- this is equal to 2?
Yes, it's two.
If we're saying that t1 has type Fin 3, does that mean that k takes on the value of the n in the type? If that were the case, and FZ calls S on that Nat, then why isn't FZ representing 4? S k takes on the value of n.
*this is actually a matter convention, but I'll avoid confusing things by going into that

Plus vs S in a function type

The following declaration of vector cons
cons : a -> Vect n a -> Vect (n + 1) a
cons x xs = x :: xs
fails with the error
Type mismatch between
S n
and
plus n 1
while the following vector append compiles and works
append : Vect n a -> Vect m a -> Vect (n + m) a
append xs ys = xs ++ ys
Why type-level plus is accepted for the second case but not for the first. What's the difference?
Why does x :: xs : Vect (n + 1) a lead to a type error?
(+) is defined by induction on its first argument so n + 1 is stuck (because n is a stuck expression, a variable in this case).
(::) is defined with the type a -> Vect m a -> Vect (S m) a.
So Idris needs to solve the unification problem n + 1 =? S m and because you have a stuck term vs. an expression with a head constructor these two things simply won't unify.
If you had written 1 + n on the other hand, Idris would have reduced that expression down to S n and the unification would have been successful.
Why does xs ++ ys : Vect (n + m) a succeeds?
(++) is defined with the type Vect p a -> Vect q a -> Vect (p + q) a.
Under the assumption that xs : Vect n a and ys : Vect m a, you will have to solve the constraints:
Vect n a ?= Vect p a (xs is the first argument passed to (++))
Vect m a ?= Vect q a (ys is the first argument passed to (++))
Vect (n + m) a ?= Vect (p + q) a (xs ++ ys is the result of append)
The first two constraints lead to n = p and m = q respectively which make the third constraint hold: everything works out.
Consider append : Vect n a -> Vect m a -> Vect (m + n) a.
Notice how I have swapped the two arguments to (+) in this one. You would then be a situation similar to your first question: after a bit of unification, you would end up with the constraint m + n ?= n + m which Idris, not knowing that (+) is commutative, would'nt be able to solve.
Solutions? Work arounds?
Whenever you can it is infinitely more convenient to have a function defined using the same recurrence pattern as the computation that happens in its type. Indeed when that is the case the type will be simplified by computation in the various branches of the function definition.
When you can't, you can rewrite proofs that two things are equal (e.g. that n + 1 = S n for all n) to adjust the mismatch between a term's type and the expected one. Even though this may seem more convenient than refactoring your code to have a different recurrence pattern, and is sometimes necessary, it usually is the start of path full of pitfalls.

Understanding 'impossible'

Type-Driven Development with Idris presents:
twoPlusTwoNotFive : 2 + 2 = 5 -> Void
twoPlusTwoNotFive Refl impossible
Is the above a function or value? If it's the former, then why is there no variable arguments, e.g.
add1 : Int -> Int
add1 x = x + 1
In particular, I'm confused at the lack of = in twoPlusTwoNotFive.
impossible calls out combinations of arguments which are, well, impossible. Idris absolves you of the responsibility to provide a right-hand side when a case is impossible.
In this instance, we're writing a function of type (2 + 2 = 5) -> Void. Void is a type with no values, so if we succeed in implementing such a function we should expect that all of its cases will turn out to be impossible. Now, = has only one constructor (Refl : x = x), and it can't be used here because it requires ='s arguments to be definitionally equal - they have to be the same x. So, naturally, it's impossible. There's no way anyone could successfully call this function at runtime, and we're saved from having to prove something that isn't true, which would have been quite a big ask.
Here's another example: you can't index into an empty vector. Scrutinising the Vect and finding it to be [] tells us that n ~ Z; since Fin n is the type of natural numbers less than n there's no value a caller could use to fill in the second argument.
at : Vect n a -> Fin n -> a
at [] FZ impossible
at [] (FS i) impossible
at (x::xs) FZ = x
at (x::xs) (FS i) = at xs i
Much of the time you're allowed to omit impossible cases altogether.
I slightly prefer Agda's notation for the same concept, which uses the symbol () to explicitly pinpoint which bit of the input expression is impossible.
twoPlusTwoNotFive : (2 + 2 ≡ 5) -> ⊥
twoPlusTwoNotFive () -- again, no RHS
at : forall {n}{A : Set} -> Vec A n -> Fin n -> A
at [] ()
at (x ∷ xs) zero = x
at (x ∷ xs) (suc i) = at xs i
I like it because sometimes you only learn that a case is impossible after doing some further pattern matching on the arguments; when the impossible thing is buried several layers down it's nice to have a visual aid to help you spot where it was.

Constraining input arguments to a function

Lets say I want to define the Fibonacci function as following function:
fibo : Int -> Int
fibo 1 = 1
fibo 2 = 2
fibo n = fibo (n-1) + fibo (n-2)
This function is obviously not total since its undefined for integers below 1, so I need to constrain the input argument somehow..
I've tried playing around with defining a new data type MyInt. Something along the lines:
-- bottom is the lower limit
data MyInt : (bottom: Int) -> (n: Int) -> Type
where
...
fibo : MyInt 1 n -> Int
...
However I get lost rather quickly.
How can I constraint the input argument to, for example, my fibo function to be integer values of 1 or above?
There are actually two reasons why Idris will not recognise the fibo function as total. Firstly, as you pointed out, it is not defined for integers less than 1, but secondly, it calls itself recursively. Although Idris is capable of recognising the totality of recursive functions, it can generally only do so when it can be shown that the argument to the recursive call is 'smaller' (i.e. closer to a base case*) than the original argument (for example, if a function receives a list as an argument, it can call itself with the tail of the list without necessarily sacrificing totality, because the tail is a substructure of the original list and thus closer to Nil). The problem with expressions like (n-1) and (n-2), when they are of type Int, is that although they are numerically smaller than n, they are not structurally smaller, because Int is not inductively defined and so has no base cases. Therefore the totality checker is unable to satisfy itself that the recursion will always eventually reach a base case (even though it might seem obvious to us), and so it will not consider fibo to be total.
First off, let's solve the recursion problem. Instead of Int, we can use an inductively-defined datatype such as Nat:
data Nat =
Z | S Nat
(A natural number is either zero, or the successor of another natural number.)
This allows us to rewrite fibo as:
fibo : Nat -> Int
fibo (S Z) = 1
fibo (S (S Z)) = 2
fibo (S (S n)) = fibo (S n) + fibo n
(Note how in the recursive case, instead of calculating (n-1) and (n-2) explicitly, we produce them by pattern matching on the argument, thereby demonstrating to Idris that they are structurally smaller.)
This new definition of fibo is still not entirely total, though, because it lacks a case for Z (i.e. zero). If we don't want to provide for such a case, then we need to give Idris some assurance that it will not be allowed to occur. One way we can do this is to require a proof that the argument to fibo is greater than or equal to one (or equivalently, one is less than or equal to the argument):
fibo : (n : Nat) -> LTE 1 n -> Int
fibo Z LTEZero impossible
fibo Z (LTESucc _) impossible
fibo (S Z) _ = 1
fibo (S (S Z)) _ = 2
fibo (S (S (S n))) _ = fibo (S (S n)) (LTESucc LTEZero) + fibo (S n) (LTESucc LTEZero)
LTE 1 n is the type whose values are proofs that 1 ≤ n (within the natural numbers). LTEZero represents the axiom that zero ≤ any natural number, and LTESucc represents the rule that if n ≤ m, then (successor of n) ≤ (successor of m). The impossible keyword indicates that a given case cannot occur. In the above definition, it is impossible for the first argument to fibo to be zero because there is no way to prove that 1 ≤ 0. For any other natural number n, we can prove that 1 ≤ n using (LTESucc LTEZero).
Now at last fibo is total, but it's rather cumbersome to have to provide it with an explicit proof that its argument is greater than or equal to 1. Luckily, we can mark the proof argument as auto implicit:
fibo : (n : Nat) -> {auto p : LTE 1 n} -> Int
fibo Z {p = LTEZero} impossible
fibo Z {p = (LTESucc _)} impossible
fibo (S Z) = 1
fibo (S (S Z)) = 2
fibo (S (S (S n))) = fibo (S (S n)) + fibo (S n)
Idris will now automatically find a proof that 1 ≤ n where possible, otherwise we will still be required to provide one ourselves.
* There may well be some codata-related subtleties that I'm glossing over here without realising, but this is the broad principle.

Maintaining a Nat within a fixed range

I'd like to have a Nat that remains within a fixed range. I would like functions incr and decr that fail if they are going to push the number outside the range. This seems like it might be a good use case for Fin, but I'm not really sure how to make it work. The type signatures might look something like this:
- Returns the next value in the ordered finite set.
- Returns Nothing if the input element is the last element in the set.
incr : Fin n -> Maybe (Fin n)
- Returns the previous value in the ordered finite set.
- Returns Nothing if the input element is the first element in the set.
decr : Fin n -> Maybe (Fin n)
The Nat will be used to index into a Vect n. How can I implement incr and decr? Is Fin even the right choice for this?
I guess the easiest way is to use some standard Fin↔Nat conversion functions from Data.Fin:
incr, decr : {n : Nat} -> Fin n -> Maybe (Fin n)
incr {n=n} f = natToFin (succ $ finToNat f) n
decr {n=n} f = case finToNat f of
Z => Nothing
S k => natToFin k n