I would like to partition a vector in two new vectors.
We cannot know what the length of the individual vectors will be, but the sum of the resulting vectors must be equal to the argument. I tried to capture this property as following:
partition : (a -> Bool) -> Vect (m+n) a -> (Vect m a, Vect n a)
partition p [] = ([], [])
partition p (x::xs)
= let (ys,zs) = partition p xs
in case p xs of
True => (x::ys, zs)
False => (ys, zs)
But Idris reports (pointing to "partition p []") that when elaborating left hand side of Main.partition:
Can't unify
Vect 0 a
with
Vect (m + n) a
Specifically:
Can't unify
0
with
plus m n
Why is this the case?
To me it seems quite obvious that if "0 = m + n" than m = n = 0. How can if convince Idris of this?
Remember that unification is a syntactic operation, which in languages like Idris is augmented with straightforward reductions according to pattern-matching. It doesn't know all of the facts that we can prove!
We can certainly prove in Idris that if n+m=0 then m = 0 and n = 0:
but this doesn't make the unifier know about this fact, as that would make type checking undecidable.
Returning to your original issue: if I translate the type of your partition into English, it says "for all natural numbers
m
andn
, for all Boolean predicatesp
overa
, given a vector of lengthplus m n
, I can produce a pair consisting of a vector of lengthm
and vector of lengthn
". In other words, to call your function, I would need to know in advance how many elements of the vector satisfy the predicate, because I need to supplym
andn
at the call site!What I think you really want is a
partition
function that, given a vector of lengthn
, returns a pair of vectors whose lengths add up ton
. We can express this using a dependent pair, which is the type theory version of existential quantification. The translation of "a pair of vectors whose lengths add up ton
" is "there exists somem
andm'
and vectors with these lengths such that the sum ofm
andm'
is my inputn
."This type looks like this:
and the complete implementation looks like this:
That's a bit of a mouthful, so let's dissect it. To implement the function, we begin by pattern-matching on the input Vect:
Note that the only possible output is what's on the right-hand side, or else we couldn't have constructed the
refl
. We know thatn
isZ
due to the unification ofn
with the index of the constructorNil
ofVect
.In the recursive case, we examine the first element of the vector. Here, I use the
with
rule because it's readable, but we could have used anif
on the right hand side instead of matching onp x
on the left.In the
True
case, we add the element to the first subvector. Becauseplus
reduces on its first argument, we can construct the equality proof usingrefl
because the addition becomes exactly the right thing:In the
False
case, we need to do a bit more work, becauseplus m (S m')
can't unify withS (plus m m')
. Remember how I said unification doesn't have access to the equalities that we can prove? The library functionplusSuccRightSucc
does what we need, though:For reference, the type of
plusSuccRightSucc
is:and the type of
sym
is:One thing that's missing in the above function is the fact that the function actually partitions the
Vect
. We can add this by making the result vectors consist of dependent pairs of elements and evidence that each element satisfies eitherp
ornot p
:If you want to get even crazier, you can also have each element prove that it was an element of the input vector, and that all elements of the input vector are in the output exactly once, and so forth. Dependent types give you the tools to do these things, but it's worth considering the complexity tradeoff in each case.