Implicit arguments can trip you
Implicit arguments are very useful. Without them the type declarations could get very noisy. Though they may produce errors that take a while to solve.
A tip about export modifiers
Before we start, lets add the module definition, this way this is a small literate file. There's something I want to show you there though.
module Cantrip%default total%access public export
These are the ways to change the default export modifiers.
Sometimes it's useful to drop total because sometimes
the totality checker trips when you're still working on the program.
Other times I'd prefer this was inverted and I should declare things
partial if I want to disable totality checking.
The public export is the broadest export modifier
and exports everything you write into the module.
I tend to use this because I keep my Idris code in two files when I work on it.
When I get something done I move it into the another file.
This helps a bit with the slow type checker.
Exposing content of proofs outside the module
allows other modules depend on them,
meaning things can break on distance when you change the module next time.
This is why Idris export
exposes declarations without their definition.
Injective functions (again)
Here's a definition of injectivity again.
Injective : {t,u:Type} -> (t -> u) -> TypeInjective f {t} = (x,y:t) -> (f x = f y) -> (x = y)
There is an implicit. It sows the seed for what we are about to experience.
List indices and content definitions
First I am going to introduce some more familiar structures.
First we are getting indices of a list.
We also need to know that XS is injective function,
though it is trivial because all constructors are injective.
data Nth : (xs : List a) -> Type whereXZ : Nth (x :: xs)XS : Nth xs -> Nth (x :: xs)XS_inj : Injective XSXS_inj x x Refl = Refl
The next one is a proposition stating that item is in a list.
This is a bit shorter and easier to handle
than similar structures produced from Nth.
data In : a -> (xs : List a) -> Type whereHere : In x (x :: xs)There : In y xs -> In y (x :: xs)
In each containment proof there appear to be an index as well.
The index_of function is injective.
index_of : In x xs -> Nth xsindex_of Here = XZindex_of (There k) = XS (index_of k)index_of_inj : Injective Cantrip.index_ofindex_of_inj Here Here pf = Reflindex_of_inj Here (There k) Refl impossibleindex_of_inj (There k) Here Refl impossibleindex_of_inj (There k) (There j) pf =cong (index_of_inj k j (XS_inj (index_of k) (index_of j) pf))
If you try to use the injectivity, you may find out you're in trouble:
usecase : (f:{v:a} -> In v xs -> In v ys) -> Injective f-> (x:In v xs) -> (y:In w xs) -> (index_of (f x) = index_of (f y)) -> x = yusecase f f_inj x y prf = lettest = index_of_inj (f x) (f y) prfin ?usecase_rhs
You likely get this kind of an error.
*Sets> :load Cantrip.idrType checking ./Cantrip.idrCantrip.idr:38:10-37:|38 | test = index_of_inj (f x) (f y) prf| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~When checking right hand side of usecase with expected typex = yType mismatch betweenIn w xsandIn v xsHoles: Cantrip.usecase
The implicit variable within the Injective Cantrip.index_of
is not entirely filled up, and it is being filled up from the context.
Therefore the index_of_inj only proves that the containment proofs with
same index are same,
when we know that list contains the same variable in that point.
We have to prove this, although it is trivial:
same_variable : (x:In a xs) -> (y:In b xs)-> (index_of x = index_of y) -> (a = b)same_variable Here Here prf = Reflsame_variable Here (There _) Refl impossiblesame_variable (There _) Here Refl impossiblesame_variable (There k1) (There k2) prf1 = letprf1 = XS_inj (index_of k1) (index_of k2) prf1in same_variable k1 k2 prf2
Next going to conclude in the usecase that it's the same variable:
usecase : (f:{v:a} -> In v xs -> In v ys) -> Injective f-> (x:In v xs) -> (y:In w xs) -> (index_of (f x) = index_of (f y)) -> x = yusecase f f_inj x y prf1 = case same_variable (f x) (f y) prf1 ofRefl => letprf2 = index_of_inj (f x) (f y) prf1in f_inj x y prf2
But we have an another implicit that needs treatment:
Type checking ./Cantrip.idrCantrip.idr:45:8-21:|45 | in f_inj x y prf2| ~~~~~~~~~~~~~~When checking right hand side of Cantrip.case block in usecase at Cantrip.idr:42:33-62 with expected typex = yWhen checking an application of f_inj:Type mismatch betweenf w x = f w y (Type of prf2)andf v6 x = f v6 y (Expected type)Specifically:Type mismatch betweenf w yandf v6 yHoles: Cantrip.usecase
It doesn't let you to fill it up directly, for some reason it thinks that you fill up non-implicit arguments as well:
Cantrip.idr:40:56-74:|40 | usecase : (f:{v:a} -> In v xs -> In v ys) -> ((q:a) -> Injective (f {v=q}))| ~~~~~~~~~~~~~~~~~~~When checking type of Cantrip.usecase:When checking an application of Cantrip.Injective:Type mismatch betweenIn a v6 ys (Type of f v6 _)andt -> u (Is f v6 _ applied to too many arguments?)Specifically:Type mismatch betweenIn a v6and\uv => t -> uv
Passing it in at the argument of the Injective works though.
usecase : (f:{v:a} -> In v xs -> In v ys)-> ((q:a) -> Injective f {t=In q xs})-> (x:In v xs) -> (y:In w xs)-> (index_of (f x) = index_of (f y)) -> x = yusecase f f_inj {v} x y prf1 = case same_variable (f x) (f y) prf1 ofRefl => letprf2 = index_of_inj (f x) (f y) prf1in f_inj v x y prf2
The errors due to implicits tend to happen frequently. They will surprise you because they can point out mismatches between variables that you have already forgotten you had, or that you think should just match up. It can take a while to figure it out when you meet an error where implicit variables do not match up.
I'm not sure how would I improve the type declarations I presented here. But anyway I'm finished with the post for today.