Hackle's blog
between the abstractions we want and the abstractions we get.
In Fin we've seen a version of type-safe indexFin (which is basically a copy of the index
function defined in Data.Vect
) which once compiles, guarantees to return a valid element from a vector.
import Data.Fin
import Data.Vect
indexFin : {n: Nat} -> Fin n -> Vect n a -> a
indexFin FZ (x :: xs) = x
indexFin (FS s) (x :: xs) = indexFin s xs
Which is simply called as such:
*fin> indexFin 3 [1,2,3,4]
4 : Integer
*fin> indexFin 0 [1,2,3,4]
1 : Integer
*fin> indexFin 3 [1,2,3]
(input):1:10:When checking argument prf to function Data.Fin.fromInteger:
When using 3 as a literal for a Fin 3
3 is not strictly less than 3
Out of sheer curiosity - can we have an alternative to this?
Suppose I can prove an index argument is less than the length of the vector, I should be able to get the same type-safety?
elementAt
elementAt : (idx: Nat) -> Vect (S n) a -> { auto prf: n `GTE` idx } -> a
A few interesting things:
S n
rather than simply n
, which is an Idris way of saying this vect must not be emptyGTE
idx which means idx
must be within bound.(Read more about auto proof here)
I don't know about you but I feel pretty impressed (by Idris and myself) how expressive the types alone can be!
Let's now get to my favorite part - coding with Idris!
Naturally, Ctrl+Alt+A to add a definition (with Atom), so we have:
elementAt : (idx: Nat) -> Vect (S n) a -> { auto prf: n `GTE` idx } -> a
elementAt idx xs = ?elementAt_rhs
Well, don't think twice, just case split, and case split again.
elementAt : (idx: Nat) -> Vect (S n) a -> { auto prf: n `GTE` idx } -> a
elementAt Z (x :: xs) = ?elementAt_rhs_2
elementAt (S k) (x :: xs) = ?elementAt_rhs_3
Next we'll do a proof search on the hole elementAt_rhs_2
, as expected, x
is returned, which makes sense - if the idx
is 0
, then we should return the very first element in the list. So far so good!
elementAt : (idx: Nat) -> Vect (S n) a -> { auto prf: n `GTE` idx } -> a
elementAt Z (x :: xs) = x
elementAt (S k) (x :: xs) = ?elementAt_rhs_3
Now if we also do a proof search on elementAt_rhs_3
, we get x
as well - which is in the correct type, but won't make much sense for our function.
When in doubt, type search. Idris tells us:
a : Type
k : Nat
x : a
n : Nat
xs : Vect n a
prf : LTE (S k) n
--------------------------------------
elementAt_rhs_3 : a
There is a proof LTE (S k) n
that tells us S k
is less than or equal to n
. Apparently this is just another form of n `GTE` idx
(S k
is one form of idx
). Informative, but not very helpful.
To jump the gun here, the only way I see how - is just to return elementAt k xs
. Seems reasonable right? Both the index and the size of the vector decrease by one, in another word, we are just using good-old recursion. So we get:
elementAt : (idx: Nat) -> Vect (S n) a -> { auto prf: n `GTE` idx } -> a
elementAt Z (x :: xs) = x
elementAt (S k) (x :: xs) = let x1 = elementAt k xs in ?elementAt_rhs_3
Try type check, no - the compiler does not like it...
When checking right hand side of elementAt with expected type
a
When checking argument prf to Main.elementAt:
Can't find a value of type
LTE k n
This is telling us we need to prove that k
is less than or equal to n
. But we already have GTE n (S k)
?
While I think surely this makes perfect sense, Idris does not understand that if idx <= n + 1
then idx - 1 <= n
. We have to prove it to Idris.
A bit lost, and honestly I don't know what to do except bringing the auto prf
proof to scope. But the compiler still gives the same error.
elementAt : (idx: Nat) -> Vect (S n) a -> { auto prf: n `GTE` idx } -> a
elementAt Z (x :: xs) = x
elementAt (S k) (x :: xs) {prf} = let x1 = elementAt k xs in ?elementAt_rhs_3
Still lost, the only thing left to do, is to case split on prf
, before that though, I have to revert the previous step, now I get:
elementAt : (idx: Nat) -> Vect (S n) a -> { auto prf: n `GTE` idx } -> a
elementAt Z (x :: xs) = x
elementAt (S k) (x :: xs) {prf = (LTESucc y)} = ?elementAt_rhs_1
Now a type search on the hole returns:
a : Type
k : Nat
x : a
right : Nat
xs : Vect (S right) a
y : LTE k right
--------------------------------------
elementAt_rhs_1 : a
A few things to note here
prf
can be constructed, is LTESucc y
, which by the name of it, means it's a successive definition over another proof y
y
is LTE k right
, and S right
is the length of xs
. Previously we needed to prove LTE k n
, and n
is the length of xs
too. That means we've found our proof now!?elementAt : (idx: Nat) -> Vect (S n) a -> { auto prf: n `GTE` idx } -> a
elementAt Z (x :: xs) = x
elementAt (S k) (x :: xs) {prf = (LTESucc y)} = elementAt k xs
Ah, now it compiles! Let's see if it really works?
*elementAt> elementAt 0 [1,2,3]
1 : Integer
*elementAt> elementAt 1 [1,2,3]
2 : Integer
*elementAt> elementAt 2 [1,2,3]
3 : Integer
*elementAt> elementAt 3 [1,2,3]
(input):1:1-19:When checking argument prf to function Main.elementAt:
Can't find a value of type
LTE 3 2
Yes, it does!
The Idris compiler is so powerful that if we express our problems well enough in types, it can guide us to a solution by giving us very useful information every step of the way.
For List
, there is also an index
function defined as such:
index : (n : Nat) ->
(xs : List a) ->
{auto ok : InBounds n xs} -> a
Note there is an auto proof ok
? What's different is that a Vect
has a length in its type, but a List
does not, so how does List
do it? And can the same proof be used for Vect
? Why or why not?