Is there a notion of "heterogenous collection of a given shape"? - idris

A common pattern in functional programming languages with a sufficiently advanced type system to to have a type of "heterogeneous lists". For instance, given a list defined as:
data List a = Nil | Cons a (List a)
(Note: For concreteness, I will use Idris in this question, but this could also be answered in Haskell (with the right extensions), Agda, etc...)
We can define HList:
data HList : List a -> Type where
Nil : HList []
(::) : a -> HList as -> HList (a :: as)
This is a list which holds a different type (specified by the type-level List a) at each "position" of the List data type. This made me wonder: Can we generalize this construction? For instance, given a simple tree-like structure:
data Tree a = Branch a [Tree a]
Does it make sense to define a heterogenous tree?
where HTree : Tree a -> Type where
...
More generally in a dependently-typed language, is it possible to define a general construction:
data Hetero : (f : Type -> Type) -> f a -> Type where
....
that takes a data type of kind Type -> Type and returns the "heterogeneous container" of shape f? Has anyone made use of this construction before if possible?

We can talk about the shape of any functor using map and propositional equality. In Idris 2:
Hetero : (f : Type -> Type) -> Functor f => f Type -> Type
Hetero f tys = (x : f (A : Type ** A) ** map fst x = tys)
The type (A : Type ** A) is the type of non-empty types, in other words, values of arbitrary type. We get heterogeneous collections by putting arbitrarily typed values into functors, then constraining the types elementwise to particular types.
Some examples:
ex1 : Hetero List [Bool, Nat, Bool]
ex1 = ([(_ ** True), (_ ** 10), (_ ** False)] ** Refl)
data Tree : Type -> Type where
Leaf : a -> Tree a
Node : Tree a -> Tree a -> Tree a
Functor Tree where
map f (Leaf a) = Leaf (f a)
map f (Node l r) = Node (map f l) (map f r)
ex2 : Hetero Tree (Node (Leaf Bool) (Leaf Nat))
ex2 = (Node (Leaf (_ ** False)) (Leaf (_ ** 10)) ** Refl)

Related

Defining groups in Idris

I defined monoid in Idris as
interface Is_monoid (ty : Type) (op : ty -> ty -> ty) where
id_elem : () -> ty
proof_of_left_id : (a : ty) -> ((op a (id_elem ())) = a)
proof_of_right_id : (a : ty) -> ((op (id_elem ())a) = a)
proof_of_associativity : (a, b, c : ty) -> ((op a (op b c)) = (op (op a b) c))
then tried to define groups as
interface (Is_monoid ty op) => Is_group (ty : Type) (op : ty -> ty -> ty) where
inverse : ty -> ty
proof_of_left_inverse : (a : ty) -> (a = (id_elem ()))
but during compilation it showed
When checking type of Group.proof_of_left_inverse:
Can't find implementation for Is_monoid ty op
Is there a way around it.
The error message is a bit misleading, but indeed, the compiler does not know which implementation of Is_monoid to use for your call to id_elem in your definition of proof_of_left_inverse. You can make it work by making it making the call more explicit:
proof_of_left_inverse : (a : ty) -> (a = (id_elem {ty = ty} {op = op} ()))
Now, why is this necessary? If we have a simple interface like
interface Pointed a where
x : a
we can just write a function like
origin : (Pointed b) => b
origin = x
without specifying any type parameters explicitly.
One way to understand this is to look at interfaces and implementations through the lens of other, in a way more basic Idris features. x can be thought of as a function
x : {a : Type} -> {auto p : PointedImpl a} -> a
where PointedImpl is some pseudo type that represents the implementations of Pointed. (Think a record of functions.)
Similarly, origin looks something like
origin : {b : Type} -> {auto j : PointedImpl b} -> b
x notably has two implicit arguments, which the compiler tries to infer during type checking and unification. In the above example, we know that origin has to return a b, so we can unify a with b.
Now i is also auto, so it is not only subject to unification (which does not help here), but in addition, the compiler looks for "surrounding values" that can fill that hole if no explicit one was specified. The first place to look after local variables which we don't have is the parameter list, where we indeed find j.
Thus, our call to origin resolves without us having to explicitly specify any additional arguments.
Your case is more akin to this:
interface Test a b where
x : a
y : b
test : (Test c d) => c
test = x
This will error in the same manner your example did. Going through the same steps as above, we can write
x : {a : Type} -> {b -> Type} -> {auto i : TestImpl a b} -> a
test : {c : Type} -> {d -> Type} -> {auto j : TestImpl c d} -> c
As above, we can unify a and c, but there is nothing that tells us what d is supposed to be. Specifically, we can't unify it with b, and consequently we can't unify TestImpl a b with TestImpl c d and thus we can't use j as value for the auto-parameter i.
Note that I don't claim that this is how things are implemented under the covers. This is just an analogy in a sense, but one that holds up to at least some scrutiny.

Trying to bring implicit argument into scope on the left side of a definition in Idris results in "is f applied to too many arguments" error

The function applyRule is supposed to extract the implicit argument n that is used in another arguments it gets, of type VVect.
data IVect : Vect n ix -> (ix -> Type) -> Type where -- n is here
Nil : IVect Nil b
(::) : b i -> IVect is b -> IVect (i :: is) b
VVect : Vect n Nat -> Type -> Type -- also here
VVect is a = IVect is (flip Vect a)
-- just for completeness
data Expression = Sigma Nat Expression
applyRule : (signals : VVect is Double) ->
(params : List Double) ->
(sigmas : List Double) ->
(rule : Expression) ->
Double
applyRule {n} signals params sigmas (Sigma k expr1) = cast n
Without referring to {n}, the code type-checks (if cast n is changed to some valid double). Adding it in, however, results in the following error:
When checking left hand side of applyRule:
Type mismatch between
Double (Type of applyRule signals params sigmas rule)
and
_ -> _ (Is applyRule signals
params
sigmas
rule applied to too many arguments?)
This doesn't seem to make sense to me, because I'm not pattern-matching on any parameter that could have a dependency on n, so I thought that simply putting it in curly braces would bring it into scope.
You can only bring n into scope if it is defined somewhere (e.g. as a variable in the arguments). Otherwise it would be hard to figure out where the n comes from – at least for a human.
applyRule : {is : Vect n Nat} ->
(signals : VVect is Double) ->
(params : List Double) ->
(sigmas : List Double) ->
(rule : Expression) ->
Double
applyRule {n} signals params sigmas (Sigma k expr1) = cast n

Implementing Foldable for a user defined version of Vect with flipped type parameters

I have defined my own Vect data type as follows
data MyVect : (n : Nat) -> (t : Type) -> Type where
Nil : MyVect Z t
(::) : (x : t) -> (xs : MyVect n t) -> MyVect (S n) t
And then started to implement the Foldable interface for the data type
Foldable MyVect where
foldr = ?res2
However when reloading the file, Idris complaints
When checking argument t to type constructor Prelude.Foldable.Foldable:
Type mismatch between
Nat -> Type -> Type (Type of MyVect)
and
Type -> Type (Expected type)
Specifically:
Type mismatch between
Nat
and
TypeUnification failure
After scratching my head a bit, I guessed that I could obey Idris demands for the type constructor by writing
Foldable (MyVect n) where
foldr = ?res2
Then I started to think "what if I had defined MyVect with the type parameters flipped?..."
data MyVect : (t : Type) -> (n : Nat) -> Type where
Nil : MyVect t Z
(::) : (x : t) -> (xs : MyVect t n) -> MyVect t (S n)
Is it possible to implement the Foldable interface for this 'parameter-flipped' version of MyVect? (and how?)
The source of type errors you see is in type of Foldable:
Idris> :t Foldable
Foldable : (Type -> Type) -> Type
Whereas your first version of MyVect has type:
Idris> :t MyVect
MyVect : Nat -> Type -> Type
and second one has:
Idris> :t MyVect
MyVect : Type -> Nat -> Type
You're right that you can partially apply types as you can do with plain old functions.
Thus Foldable (MyVect n) works because MyVect n has type Type -> Type which is exactly what Foldable interface wants.
After we convinced ourselves that types behaves like functions you can come up with flipped type aliases for MyVect and everything will work:
FlippedVect : Nat -> Type -> Type
FlippedVect n t = MyVect t n
Foldable (FlippedVect n) where
You can also use already defined functions to implement similar stuff:
Idris> :t flip
flip : (a -> b -> c) -> b -> a -> c
Idris> :t flip MyVect
flip MyVect : Nat -> Type -> Type
And now you can write:
Foldable (flip MyVect n) where
You can even define instance for anonymous functions. Here is complete version:
Foldable (\a => MyVect a n) where
foldr f z Nil = z
foldr {n=S k} f z (x :: xs) = x `f` foldr {t=\a => MyVect a k} f z xs
foldl = believe_me -- i'm on Idris-0.12.3, I got type errors for `foldl`
-- but you can implement it in the same way
After writing all that information to teach you how to do it I should say that under any circumstances you definitely shouldn't do it.

Strange error message with Idris interfaces

I'm trying to implement a simple algebraic structures hierarchy using Idris interfaces. The code is as follows:
module AlgebraicStructures
-- definition of some algebraic structures in terms of type classes
%access public export
Associative : {a : Type} -> (a -> a -> a) -> Type
Associative {a} op = (x : a) ->
(y : a) ->
(z : a) ->
(op x (op y z)) = (op (op x y) z)
Identity : {a : Type} -> (a -> a -> a) -> a -> Type
Identity op v = ((x : a) -> (op x v) = x,
(x : a) -> (op v x) = x)
Commutative : {a : Type} -> (a -> a -> a) -> Type
Commutative {a} op = (x : a) ->
(y : a) ->
(op x y) = (op y x)
infixl 4 <**>
interface IsMonoid a where
empty : a
(<**>) : a -> a -> a
assoc : Associative (<**>)
ident : Identity (<**>) empty
interface IsMonoid a => IsCommutativeMonoid a where
comm : Commutative (<**>)
But, Idris is giving this strange error message:
When checking type of constructor of AlgebraicStructures.IsCommutativeMonoid:
Can't find implementation for IsMonoid a
I believe that Idris interfaces works like Haskell's type classes. In Haskell, it should work. Am I doing something silly?
I believe it may be complaining because I don't know that there's anything that constrains the a in the expression Commutative (<**>) - so it doesn't know that you can invoke <**> on that type.
Explicitly specifying the a seems to work for me - Commutative {a} (<**>) - I hope that that means that the a from the interface signature is in scope and available for explicitly passing to other types.

Unsolved metavariable for function that has no inhabited arguments

I am getting an unsolved metavariable for foo in the code below:
namespace Funs
data Funs : Type -> Type where
Nil : Funs a
(::) : {b : Type} -> (a -> List b) -> Funs (List a) -> Funs (List a)
data FunPtr : Funs a -> Type -> Type where
here : FunPtr ((::) {b} _ bs) b
there : FunPtr bs b -> FunPtr (_ :: bs) b
total foo : FunPtr [] b -> Void
How do I convince Idris that foo has no valid patterns to match on?
I've tried adding
foo f = ?foo
and then doing a case split in Emacs on f (just to see what might come up), but that just removes the line, leaving foo as an unsolved meta.
It turns out all I need to do is enumerate all possible patterns for foo's argument, and then Idris is able to figure out, one by one, that they are un-unifyable with foo's type:
foo : FunPtr [] b -> Void
foo here impossible
foo (there _) impossible