How to write function accepting nested Vect with specific element type? - idris

I want to write a function that accepts arbitrarily-nested Vects whose final elements are of a limited set of types incl. Double and Integer (the "dtype"), and the function is aware of how many Vects there were, and the size of each (i.e. the "shape").
For example, if my function's called const, I want const 0.0 and const [[0.0], [1.0]] to compile, and the type-checker know that the first has no Vects, and the second has two, with lengths 2 and 1. And I don't want const [["foo"], ["bar"]] to compile.
What I've tried
defining a type alias for nested vects and using namespaces for overloads
Array : (shape : Vect rank Nat) -> Type -> Type
Array [] ty = ty
Array (d :: ds) ty = Vect d (Array ds ty)
namespace double
const : Array shape Double -> Foo shape Double
namespace integer
const : Array shape Integer -> Foo shape Integer
but because this is slightly backwards (the compiler has to infer the type from the return type not argument of const), this has trouble inferring the types (even for simple cases like const 0.0) in ambiguous contexts.
defining an interface to keep track of the shape
interface Array ty where
rank : Nat
shape : Vect rank Nat
dtype : Type
Array Double where -- same for other types
rank = 0
shape = []
dtype = Double
{len : Nat} -> Array ty => Array (Vect len ty) where
rank = 1 + rank {ty}
shape = len :: shape {ty}
dtype = dtype {ty}
const : Array ty => ty -> Foo (shape {ty}) (dtype {ty})
but this doesn't work because Idris doesn't disambiguate between Data.Vect.:: and Prelude.:: in const [0.0], even though I've only implemented Array for Vect not List.

Related

How to prove propositional equality between a complicated expression and `True`?

I have some code that looks like this:
allLessThan : Ord t => (v1 : Vect n t) -> (v2 : Vect n t) -> Bool
allLessThan v1 v2 = all (\(x,y) => x < y) (zip v1 v2)
unravelIndexUnsafe : (order : ArrayOrder) ->
(shape : ArrayShape (S n)) ->
(position : Vect (S n) Nat) ->
Nat
unravelIndexUnsafe order shape position = ?someImplementation
unravelIndexSafe : (order : ArrayOrder) ->
(shape : ArrayShape (S n)) ->
(position : Vect (S n) Nat) ->
{auto 0 prfPositionValid : (allLessThan position shape) = True} ->
Nat
unravelIndexSafe order shape position = unravelIndexUnsafe order shape position
unravelIndex : (order : ArrayOrder) ->
(shape : ArrayShape (S n)) ->
(position : Vect (S n) Nat) ->
Maybe Nat
unravelIndex order shape position =
case allLessThan position shape of
True => Just $ unravelIndexSafe order shape position
False => Nothing
I omitted the implementation of unravelIndexUnsafe which I think is irrelevant to the question.
I get a type error in the definition of unravelIndex, saying that it can't find an implementation for prfPositionValid to use with unravelIndexSafe*.
This was surprising to me, because I am explicitly case splitting on allLessThan position shape, and only calling unravelIndexSafe in the True branch. I expected that Idris would be able to infer from this information that the proposition (allLessThan position shape) = True holds.
Is there a straightforward way to solve the problem? Maybe something I can explicitly construct and pass for the prfPositionValid implicit argument? Or is there an entirely different approach I should use here? Do I need to express prfPositionValid or allLessThan differently? Do I need to rewrite something?
* More precisely, it can't find an implementation for this monstrous "fully-expanded" version of prfPositionValid:
foldl (\acc, elem => acc && Delay (case block in allLessThan (S n) Nat (MkOrd (\{arg:354}, {arg:355} => compare arg arg) (\{arg:356}, {arg:357} => == (compare arg arg) LT) (\{arg:358}, {arg:359} => == (compare arg arg) GT) (\{arg:360}, {arg:361} => not (== (compare arg arg) GT)) (\{arg:362}, {arg:363} => not (== (compare arg arg) LT)) (\{arg:364}, {arg:365} => if == (compare arg arg) GT then x else y) (\{arg:366}, {arg:367} => if == (compare arg arg) LT then x else y)) shape position elem)) True (zipWith (\{__leftTupleSection:0}, {__infixTupleSection:0} => (__leftTupleSection, __infixTupleSection)) position shape) = True
Solution: use decidable equality
The answer is to use "decidable equality", because Idris is not as smart as a human.
Note that the special = syntax is equivalent to the builtin operator (===), which is equivalent to the type Equal. The constructor for Equal is Refl. In order to prove a proposition of the form Equal a b, Idris must be able to figure out that a and b are in fact the same thing (call it c). If you can invoke Refl c with type Equal a b, then you have proven Equal a b. Conversely, the only way to obtain an instance of Equal a b is by invoking Refl c.
Idris 2 cannot infer propositional equality by case-splitting. I, a human, know that we are trying to show that allLessThan position shape is propositionally equal to True. In Idris, this means we want to be able to write Refl True. Case-splitting on allLessThan position shape does result in a Bool, but this alone does not constitute an invocation of Refl True with type Equal (allLessThan position shape) True. Therefore case-splitting as in the original code is not sufficient for Idris to infer a proof of Equal (allLessThan position shape) True.
We know that allLessThan position shape is a decidable predicate, so we can use decEq to obtain the proof/implementation that we need. Therefore we can write unravelIndex as:
unravelIndex : (order : ArrayOrder) ->
(shape : ArrayShape (S n)) ->
(position : Vect (S n) Nat) ->
Maybe Nat
unravelIndex order shape position =
case decEq (allLessThan position shape) True of
Yes proof => Just $ unravelIndexSafe order shape position
No contra => Nothing
The proof in Yes proof is precisely the Refl True we were looking for, which implements Equal (allLessThan position shape) True. Therefore Idris will be able to infer a value for the prfPositionValid auto-implicit, because a value of the right type is available in scope.
You could also write _ instead of proof and contra, because the proofs are not explicitly used in the code anywhere, so they don't need names.
Refactoring
Note that this allLessThan position shape is somewhat ad-hoc. In particular, stating the conditions of the property requires the programmer to memorize a specific expression. However we would like to write a tidier API, in which the programmer can invoke a function isPositionValidForShape to check validity, and use a type IndexValidForShape to represent the "valid" state.
allLessThan : Ord t => (v1 : Vect n t) -> (v2 : Vect n t) -> Bool
allLessThan v1 v2 = all (\(x,y) => x < y) (zip v1 v2)
IndexValidForShape : (shape : ArrayShape ndim) ->
(position : ArrayIndex ndim) ->
Type
IndexValidForShape shape position =
let isValid = allLessThan position shape
in Equal isValid True
isIndexValidForShape : (shape : ArrayShape (S n)) ->
(position : ArrayIndex (S n)) ->
Dec (IndexValidForShape shape position)
isIndexValidForShape shape position =
decEq (allLessThan position shape) True
unravelIndexUnsafe : (order : ArrayOrder) ->
(shape : ArrayShape (S n)) ->
(position : ArrayIndex (S n)) ->
Nat
unravelIndexUnsafe order shape position =
sum $ zipWith (*) (strides order shape) position
unravelIndexSafe : (order : ArrayOrder) ->
(shape : ArrayShape (S n)) ->
(position : ArrayIndex (S n)) ->
{auto 0 prfIndexValid : IndexValidForShape shape position} ->
Nat
unravelIndexSafe order shape position =
unravelIndexUnsafe order shape position
unravelIndex : (order : ArrayOrder) ->
(shape : ArrayShape (S n)) ->
(position : ArrayIndex (S n)) ->
Maybe Nat
unravelIndex order shape position =
case isIndexValidForShape shape position of
Yes _ => Just $ unravelIndexSafe order shape position
No _ => Nothing
Now, the end user don't have to know or care what exactly IndexValidForShape entails, or that you need to use allLessThan to check for it.
In fact, we can now change what it means for an index to be "valid", mostly without affecting downstream code user; maybe there are additional checks I want to put in place, that I only learn about after I find a logic bug.
Alternatively, it should be possible to re-design IndexValidForShape to be more "structural", wherein you inductively define a data type that represents the desired property. For example, refer to Data.Vect.Elem and its description in Chapter 9 of Type-Driven Development.
Glossary
decidable: "a property is decidable if you can always say whether the property holds for some specific values" (quoted from Type-Driven Development, page 245).
Dec: The type representing the validity of a decidable property. Its constructors are:
Yes : property -> Dec property - the property holds.
No : (property -> Void) -> Dec property - the property is a contradiction.
DecEq: The interface for data types for which equality can be determined as a decidable property.
decEq: The method of DecEq that determines if two things are decidably equal. Its type is DecEq t => (x1 : t) -> (x2 : t) -> Dec (Equal x1 x2).
References & Further reading
Type-Driven Development with Idris (Edwin Brady, 2017, ISBN 9781617293023), especially section 9.1.5 which covers Decidable.Equality.

In Idris, what's the type of a list of lists of doubles, where all the lengths are known?

An example value: [[1, 2], [1]]
Here, I would know that there are 2 lists, and that the first list has length 2, while the second one has length 1. Ideally, this function would compute those types:
func : (n ** Vect n Nat) -> Type
But I don't know how to write it. I'm pretty sure it's something to do with dependent pairs, but I'm not sure how to write it.
To clarify, I know it'd be possible to simply use (n ** Vect n (p ** Vect p Double)) as the type of the example value. However, n only constrains the number of lists, not the number of their elements, because inside the list, p could be anything. I would most likely need something where the first element of the dependent pair is a vector of lengths, not just the number of lists. So something like (Vect n Nat ** Vect n (Vect m Double))--where each m is the corresponding element of the first vector.
You could define a new vector type which contains possibly differently indexed elements of an indexed type at each position:
import Prelude
import Data.Vect
-- heterogeneously indexed vector
data IVect : Vect n ix -> (ix -> Type) -> Type where
Nil : IVect Nil b
(::) : b i -> IVect is b -> IVect (i :: is) b
-- of which a special case is a vector of vectors
VVect : Vect n Nat -> Type -> Type
VVect is a = IVect is (flip Vect a)
test1 : VVect [2, 2, 2] Nat
test1 = [[1, 2], [3, 4], [5, 6]]
test2 : VVect [0, 1, 2] Bool
test2 = [[], [True], [False, True]]
Alternatively, you can define VVect using dependent pairs and map, but this is more cumbersome to use:
VVect' : Vect n Nat -> Type -> Type
VVect' {n = n} is a = (xs : Vect n (m ** Vect m a) ** (map fst xs = is))
test3 : VVect' [0, 1, 2] Bool
test3 = ([(_ ** []), (_ ** [True]), (_ ** [False, False])] ** Refl)
You have some choice though whether to use lists or vectors. With lists as the inner container, values look more compact:
VVect'' : Vect n Nat -> Type -> Type
VVect'' {n = n} is a = (xs : Vect n (List a) ** (map length xs = is))
test4 : VVect'' [0, 1, 2] Bool
test4 = ([[], [True], [False, True]] ** Refl)

Dependent types: enforcing global properties in inductive types

I have the following inductive type MyVec:
import Data.Vect
data MyVec: {k: Nat} -> Vect k Nat -> Type where
Nil: MyVec []
(::): {k, n: Nat} -> {v: Vect k Nat} -> Vect n Nat -> MyVec v -> MyVec (n :: v)
-- example:
val: MyVec [3,2,3]
val = [[2,1,2], [0,2], [1,1,0]]
That is, the type specifies the lengths of all vectors inside a MyVec.
The problem is, val will have k = 3 (k is the number of vectors inside a MyVec), but the ctor :: does not know this fact. It will first build a MyVec with k = 1, then with 2, and finally with 3. This makes it impossible to define constraints based on the final shape of the value.
For example, I cannot constrain the values to be strictly less than k. Accepting Vects of Fin (S k) instead of Vects of Nat would rule out some valid values, because the last vectors (the first inserted by the ctor) would "know" a smaller value of k, and thus a stricter contraint.
Or, another example, I cannot enforce the following constraint: the vector at position i cannot contain the number i. Because the final position of a vector in the container is not known to the ctor (it would be automatically known if the final value of k was known).
So the question is, how can I enforce such global properties?
There are (at least) two ways to do it, both of which may require tracking additional information in order to check the property.
Enforcing properties in the data definition
Enforcing all elements < k
I cannot constrain the values to be strictly less than k. Accepting Vects of Fin (S k) instead of Vects of Nat would rule out some valid values...
You are right that simply changing the definition of MyVect to have Vect n (Fin (S k)) in it would not be correct.
However, it is not too hard to fix this by generalizing MyVect to be polymorphic, as follows.
data MyVec: (A : Type) -> {k: Nat} -> Vect k Nat -> Type where
Nil: {A : Type} -> MyVec A []
(::): {A : Type} -> {k, n: Nat} -> {v: Vect k Nat} -> Vect n A -> MyVec A v -> MyVec A (n :: v)
val : MyVec (Fin 3) [3,2,3]
val = [[2,1,2], [0,2], [1,1,0]]
The key to this solution is separating the type of the vector from k in the definition of MyVec, and then, at top level, using the "global value of k to constrain the type of vector elements.
Enforcing vector at position i does not contain i
I cannot enforce that the vector at position i cannot contain the number i because the final position of a vector in the container is not known to the constructor.
Again, the solution is to generalize the data definition to keep track of the necessary information. In this case, we'd like to keep track of what the current position in the final value will be. I call this index. I then generalize A to be passed the current index. Finally, at top level, I pass in a predicate enforcing that the value does not equal the index.
data MyVec': (A : Nat -> Type) -> (index : Nat) -> {k: Nat} -> Vect k Nat -> Type where
Nil: {A : Nat -> Type} -> {index : Nat} -> MyVec' A index []
(::): {A : Nat -> Type} -> {k, n, index: Nat} -> {v: Vect k Nat} ->
Vect n (A index) -> MyVec' A (S index) v -> MyVec' A index (n :: v)
val : MyVec' (\n => (m : Nat ** (n == m = False))) 0 [3,2,3]
val = [[(2 ** Refl),(1 ** Refl),(2 ** Refl)], [(0 ** Refl),(2 ** Refl)], [(1 ** Refl),(1 ** Refl),(0 ** Refl)]]
Enforcing properties after the fact
Another, sometimes simpler way to do it, is to not enforce the property immediately in the data definition, but to write a predicate after the fact.
Enforcing all elements < k
For example, we can write a predicate that checks whether all elements of all vectors are < k, and then assert that our value has this property.
wf : (final_length : Nat) -> {k : Nat} -> {v : Vect k Nat} -> MyVec v -> Bool
wf final_length [] = True
wf final_length (v :: mv) = isNothing (find (\x => x >= final_length) v) && wf final_length mv
val : (mv : MyVec [3,2,3] ** wf 3 mv = True)
val = ([[2,1,2], [0,2], [1,1,0]] ** Refl)
Enforcing vector at position i does not contain i
Again, we can express the property by checking it, and then asserting that the value has the property.
wf : (index : Nat) -> {k : Nat} -> {v : Vect k Nat} -> MyVec v -> Bool
wf index [] = True
wf index (v :: mv) = isNothing (find (\x => x == index) v) && wf (S index) mv
val : (mv : MyVec [3,2,3] ** wf 0 mv = True)
val = ([[2,1,2], [0,2], [1,1,0]] ** Refl)

Idris Vect.fromList usage with generated list

I am trying to feel my way into dependent types. Based on the logic of the windowl function below, I want to return a list of vectors whose length depend on the size provided.
window : (n : Nat) -> List a -> List (Vect n a)
window size = map fromList loop
where
loop xs = case splitAt size xs of
(ys, []) => if length ys == size then [ys] else []
(ys, _) => ys :: loop (drop 1 xs)
windowl : Nat -> List a -> List (List a)
windowl size = loop
where
loop xs = case List.splitAt size xs of
(ys, []) => if length ys == size then [ys] else []
(ys, _) => ys :: loop (drop 1 xs)
When I attempt to load the function into Idris, I get the following:
When checking argument func to function Prelude.Functor.map:
Type mismatch between
(l : List elem) -> Vect (length l) elem (Type of fromList)
and
a1 -> List (Vect size a) (Expected type)
Specifically:
Type mismatch between
Vect (length v0) elem
and
List (Vect size a)
When reading the documentation on fromList I notice that it says
The length of the list should be statically known.
So I assume that the type error has to do with Idris not knowing that the length of the list is corresponding to the size specified.
I am stuck because I don't even know if it is something impossible I want to do or whether I can specify that the length of the list corresponds to the length of the vector that I want to produce.
Is there a way to do that?
Since in your case it is not possible to know the length statically, we need a function which can fail at run-time:
total
fromListOfLength : (n : Nat) -> (xs : List a) -> Maybe (Vect n a)
fromListOfLength n xs with (decEq (length xs) n)
fromListOfLength n xs | (Yes prf) = rewrite (sym prf) in Just (fromList xs)
fromListOfLength n xs | (No _) = Nothing
fromListOfLength converts a list of length n into a vector of length n or fails. Now let's combine it and windowl to get to window.
total
window : (n : Nat) -> List a -> List (Vect n a)
window n = catMaybes . map (fromListOfLength n) . windowl n
Observe that the window function's type is still an underspecification of what we are doing with the input list, because nothing prevents us from always returning the empty list (this could happen if fromListOfLength returned Nothing all the time).

Idris non-trivial type computation for tensor indexing

I've been messing around with a simple tensor library, in which I have defined the following type.
data Tensor : Vect n Nat -> Type -> Type where
Scalar : a -> Tensor [] a
Dimension : Vect n (Tensor d a) -> Tensor (n :: d) a
The vector parameter of the type describes the tensor's "dimensions" or "shape". I am currently trying to define a function to safely index into a Tensor. I had planned to do this using Fins but I ran into an issue. Because the Tensor is of unknown order, I could need any number of indices, each of which requiring a different upper bound. This means that a Vect of indices would be insufficient, because each index would have a different type. That drove me to look at using tuples (called "pairs" in Idris?) instead. I wrote the following function to compute the necessary type.
TensorIndex : Vect n Nat -> Type
TensorIndex [] = ()
TensorIndex (d::[]) = Fin d
TensorIndex (d::ds) = (Fin d, TensorIndex ds)
This function worked as I expected, calculating the appropriate index type from a dimension vector.
> TensorIndex [4,4,3] -- (Fin 4, Fin 4, Fin 3)
> TensorIndex [2] -- Fin 2
> TensorIndex [] -- ()
But when I tried to define the actual index function...
index : {d : Vect n Nat} -> TensorIndex d -> Tensor d a -> a
index () (Scalar x) = x
index (a,as) (Dimension xs) = index as $ index a xs
index a (Dimension xs) with (index a xs) | Tensor x = x
...Idris raised the following error on the second case (oddly enough it seemed perfectly okay with the first).
Type mismatch between
(A, B) (Type of (a,as))
and
TensorIndex (n :: d) (Expected type)
The error seems to imply that instead of treating TensorIndex as an extremely convoluted type synonym and evaluating it like I had hoped it would, it treated it as though it were defined with a data declaration; a "black-box type" so to speak. Where does Idris draw the line on this? Is there some way for me to rewrite TensorIndex so that it works the way I want it to? If not, can you think of any other way to write the index function?
Your definitions will be cleaner if you define Tensor by induction over the list of dimensions whilst the Index is defined as a datatype.
Indeed, at the moment you are forced to pattern-match on the implicit argument of type Vect n Nat to see what shape the index has. But if the index is defined directly as a piece of data, it then constrains the shape of the structure it indexes into and everything falls into place: the right piece of information arrives at the right time for the typechecker to be happy.
module Tensor
import Data.Fin
import Data.Vect
tensor : Vect n Nat -> Type -> Type
tensor [] a = a
tensor (m :: ms) a = Vect m (tensor ms a)
data Index : Vect n Nat -> Type where
Here : Index []
At : Fin m -> Index ms -> Index (m :: ms)
index : Index ms -> tensor ms a -> a
index Here a = a
index (At k i) v = index i $ index k v
Your life becomes so much easier if you allow for a trailing () in your TensorIndex, since then you can just do
TensorIndex : Vect n Nat -> Type
TensorIndex [] = ()
TensorIndex (d::ds) = (Fin d, TensorIndex ds)
index : {ds : Vect n Nat} -> TensorIndex ds -> Tensor ds a -> a
index {ds = []} () (Scalar x) = x
index {ds = _ :: ds} (i, is) (Dimension xs) = index is (index i xs)
If you want to keep your definition of TensorIndex, you'll need to have separate cases for ds = [_] and ds = _::_::_ to match the structure of TensorIndex:
TensorIndex : Vect n Nat -> Type
TensorIndex [] = ()
TensorIndex (d::[]) = Fin d
TensorIndex (d::ds) = (Fin d, TensorIndex ds)
index : {ds : Vect n Nat} -> TensorIndex ds -> Tensor ds a -> a
index {ds = []} () (Scalar x) = x
index {ds = _ :: []} i (Dimension xs) with (index i xs) | (Scalar x) = x
index {ds = _ :: _ :: _} (i, is) (Dimension xs) = index is (index i xs)
The reason this works and yours didn't is because here, each case of index corresponds exactly to one TensorIndex case, and so TensorIndex ds can be reduced.