Inspired by this blog post and this code I thought I'd try some category theory in Idris using its interfaces (type-classes).
I defined Category as follows, which works fine:
interface Category (obj : Type) (hom : obj -> obj -> Type) where
id : {a : obj} -> hom a a
comp : {a, b, c : obj} -> hom b c -> hom a b -> hom a c
Then, in the spirit of the Verified module, I thought I'd define a verified category:
interface Category obj hom =>
VerifiedCategory (obj : Type) (hom : obj -> obj -> Type) where
categoryAssociativity : {a, b, c, d : obj} ->
(f : hom c d) -> (g : hom b c) -> (h : hom a b) ->
(comp (comp f g) h = comp f (comp g h))
categoryLeftIdentity : {a, b : obj} -> (f : hom a b) -> (comp id f = f)
categoryRightIdentity : {a, b : obj} -> (f : hom a b) -> (comp f id = f)
Unfortunately, Idris rejects that code with the following error message:
When checking type of constructor of CategoryTheory.VerifiedCategory:
Can't find implementation for Category obj hom
Am I doing something wrong, or am I trying to do something with multi-parameter sub-classes that Idris cannot do?
All this code is in its own module, called CategoryTheory, without any imports.
I'm working with Idris v0.12.
I don't know why (and would be very curious to find out!) but it works if you specify all the implicit arguments to id and comp in VerifiedCategory explicitly. It is pretty ugly and tedious to figure out, but this typechecks:
interface Category obj hom => VerifiedCategory (obj : Type) (hom : obj -> obj -> Type) where
categoryAssociativity : {a, b, c, d : obj} ->
(f : hom c d) ->
(g : hom b c) ->
(h : hom a b) ->
(comp {a = a} {b = b} {c = d} (comp {a = b} {b = c} {c = d} f g) h = comp {a = a} {b = c} {c = d} f (comp {a = a} {b = b} {c = c} g h))
categoryLeftIdentity : {a, b : obj} ->
(f : hom a b) ->
(comp {a = a} {b = b} {c = b} (id {a = b}) f = f)
categoryRightIdentity : {a, b : obj} ->
(f : hom a b) ->
(comp {a = a} {b = a} {c = b} f (id {a = a}) = f)
Edit: Another way I just found is to explicitly designate hom as a determining parameter, i.e. the parameter of the type class that is sufficient to find an implementation. This has to happen in Category as well as in VerifiedCategory (I'm not sure, why), like so:
interface Category (obj : Type) (hom : obj -> obj -> Type) | hom where
-- ...
interface Category obj hom => VerifiedCategory (obj : Type) (hom : obj -> obj -> Type) where
-- ...
i.e. by putting a | hom before the where.
One thing you have to do in addition to that is qualify the usage of id in VerifiedCategory, because otherwise it is interpreted as implicit parameter for whatever reason:
categoryLeftIdentity : {a, b : obj} ->
(f : hom a b) ->
comp CategoryTheory.id f = f
categoryRightIdentity : {a, b : obj} ->
(f : hom a b) ->
comp f CategoryTheory.id = f
See also this reddit thread that might be illuminating in the future.
Related
I am trying to write a library in Idris to work with categories and functors. For my use case, each type can be a category in at most one way (and I would like to use overloading for id and .), so using an interface suits my needs:
infixr 9 .
interface CategoryI (o : Type) where
data Hom : o -> o -> Type
(.) : {a : o} -> {b : o} -> {c : o} -> Hom b c -> Hom a b -> Hom a c
id : (a : o) -> Hom a a
However, for functors, I need a datatype rather than an interface, since there can be multiple functors between the same two categories that I would like to consider.
data Functor : Type -> Type -> Type where
MkFunctor : (CategoryI s, CategoryI t) =>
(f : s -> t)
-> (Hom x y -> Hom (f x) (f y))
-> Functor s t
To make use of this, I have written accesser functions:
src : Functor s t -> Type
src (MkFunctor fo fm) = s
tgt : Functor s t -> Type
tgt (MkFunctor fo fm) = t
f_obj : (CategoryI s, CategoryI t) => Functor s t -> (s -> t)
f_obj (MkFunctor fo fm) = fo
But I am having problems with the following:
f_map : (CategoryI s, CategoryI t) => (f : Functor s t)
-> (Hom x y -> Hom ((f_obj f) x) ((f_obj f) y))
f_map (MkFunctor fo fm) = fm
The compiler complains:
When checking right hand side of f_map with expected type
Hom x y -> Hom (f_obj (MkFunctor fo fm) x) (f_obj (MkFunctor fo fm) y)
Type mismatch between
Hom x y -> Hom (fo x) (fo y) (Type of fm x y)
and
Hom x y -> Hom (fo x) (fo y) (Expected type)
Specifically:
Type mismatch between
constraint1
and
constraint
I can't even determine what the compiler is complaining abut here. IT somehow can't unify two constraints (which ones?), but the constraints on my constructor and on my f_map function are the same, so I don't see what the issue is.
How can I resolve this issue?
This fails:
> the ({A : Type} -> A -> {B : Type} -> B -> (A, B)) MkPair
(input):1:5:When checking argument value to function Prelude.Basics.the:
Type mismatch between
A -> B1 -> (A, B1) (Type of MkPair)
and
A1 -> B -> (A1, B) (Expected type)
Specifically:
Type mismatch between
Pair A
and
\uv => uv -> uv
This works:
> ({A : Type} -> {B : Type} -> A -> B -> (A, B)) MkPair
\A1, B1 => MkPair : A -> B -> (A, B)
Oddly:
q : {A : Type} -> A -> {B : Type} -> B -> (A, B)
q a b = MkPair a b
> :t q
q : A -> B -> (A, B)
> :t MkPair
MkPair : A -> B -> (A, B)
Why do q and MkPair appear to have the same type? Do they really have the same type? Why does the order of implicit arguments matter?
In some sense implicit arguments are no different from non-implicit ones. The compiler infers them for you most of the time, but they are still arguments and must be present because at the level of the core language there are no implicit arguments. You can ask REPL to show implicits for you:
λΠ> :set showimplicits
λΠ> :t MkPair
Builtins.MkPair : {A : Type} -> {B : Type} -> (a : A) -> (b : B) -> (A, B)
λΠ> :t q
Main.q : {A : Type} -> A -> {B : Type} -> B -> (A, B)
If you substitute ordinary parentheses for curly braces in the above types, you'll see that the types of MkPair and q are different because of the different order of their parameters.
What's the meaning of this (a, b, c: Nat) argument in:
g : (a, b, c: Nat) -> Int
g (a,b,c) = 42
?
Evidently the first argument is a triple, i.e. 3-tuple.
g: (a,b,c: Nat) -> Int
is just a short cut for
g: (a: Nat) -> (b: Nat) -> (c: Nat) -> Int
If you expand on g: (a,b,c: Nat) -> Int you will get
g: (a, b, c: Nat) -> Int
g a b c = ?g_rhs
A named tuple parameter (AFAIK idris does not have built-in triples) would be specified as
g: (a: (Nat, Nat)) -> Int
g a = ?g_rhs
I've been writing something similar to a Stream. I am able to prove each functor law but I can not figure out a way to prove that it's total:
module Stream
import Classes.Verified
%default total
codata MyStream a = MkStream a (MyStream a)
mapStream : (a -> b) -> MyStream a -> MyStream b
mapStream f (MkStream a s) = MkStream (f a) (mapStream f s)
streamFunctorComposition : (s : MyStream a) -> (f : a -> b) -> (g : b -> c) -> mapStream (\x => g (f x)) s = mapStream g (mapStream f s)
streamFunctorComposition (MkStream x y) f g =
let inductiveHypothesis = streamFunctorComposition y f g
in ?streamFunctorCompositionStepCase
---------- Proofs ----------
streamFunctorCompositionStepCase = proof
intros
rewrite inductiveHypothesis
trivial
Gives:
*Stream> :total streamFunctorComposition
Stream.streamFunctorComposition is possibly not total due to recursive path:
Stream.streamFunctorComposition, Stream.streamFunctorComposition
Is there a trick to proving the functor laws over codata which also passes the totality checker?
I was able to get a little help on IRC from Daniel Peebles (copumpkin) who explained that being able to use propositional equality over codata is just generally not something usually permitted. He pointed out that it's possible to define a custom equivalence relation, like how Agda defines ones for Data.Stream:
data _≈_ {A} : Stream A → Stream A → Set where
_∷_ : ∀ {x y xs ys}
(x≡ : x ≡ y) (xs≈ : ∞ (♭ xs ≈ ♭ ys)) → x ∷ xs ≈ y ∷ ys
I was able to do a straight forward translation of this definition to Idris:
module MyStream
%default total
codata MyStream a = MkStream a (MyStream a)
infixl 9 =#=
data (=#=) : MyStream a -> MyStream a -> Type where
(::) : a = b -> Inf (as =#= bs) -> MkStream a as =#= MkStream b bs
mapStream : (a -> b) -> MyStream a -> MyStream b
mapStream f (MkStream a s) = MkStream (f a) (mapStream f s)
streamFunctorComposition : (s : MyStream a) -> (f : a -> b) -> (g : b -> c) -> mapStream (\x => g (f x)) s =#= mapStream g (mapStream f s)
streamFunctorComposition (MkStream x y) f g =
Refl :: streamFunctorComposition y f g
And this easily passed the totality checker as we're now just doing simple coinduction.
This fact is a little disappointing since it seems like it means we can't define a VerifiedFunctor for our stream type.
Daniel also pointed out that Observational Type Theory does allow propositional equality over codata, which is something to look into.
In order for my question to be meaningful, I must provide some background.
I think it would be useful to have a dependently typed language that can infer the existence and type of an argument a for a function whose other parameters and/or return value have types that depend on a. Consider the following snippet in a language I am designing:
(* Backticks are used for infix functions *)
def Cat (`~>` : ob -> ob -> Type) :=
sig
exs id : a ~> a
exs `.` : b ~> c -> a ~> b -> a ~> c
exs lid : id . f = f
exs rid : f . id = f
exs asso : (h . g) . f = h . (g . f)
end
If we make two (admittedly, unwarranted) assumptions:
No dependencies must exist that cannot be inferred from explicitly provided information.
Every free variable must be converted into an implicit argument of the last identifier introduced using def or exs.
We can interpret the above snippet as being equivalent to the following one:
def Cat {ob} (`~>` : ob -> ob -> Type) :=
sig
exs id : all {a} -> a ~> a
exs `.` : all {a b c} -> b ~> c -> a ~> b -> a ~> c
exs lid : all {a b} {f : a ~> b} -> id . f = f
exs rid : all {a b} {f : a ~> b} -> f . id = f
exs asso : all {a b c d} {f : a ~> b} {g} {h : c ~> d}
-> (h . g) . f = h . (g . f)
end
Which is more or less the same as the following Agda snippet:
record Cat {ob : Set} (_⇒_ : ob → ob → Set) : Set₁ where
field
id : ∀ {a} → a ⇒ a
_∙_ : ∀ {a b c} → b ⇒ c → a ⇒ b → a ⇒ c
lid : ∀ {a b} {f : a ⇒ b} → id ∙ f ≡ f
rid : ∀ {a b} {f : a ⇒ b} → f ∙ id ≡ f
asso : ∀ {a b c d} {f : a ⇒ b} {g} {h : c ⇒ d} → (h ∙ g) ∙ f ≡ h ∙ (g ∙ f)
Clearly, two unwarranted assumptions have saved us a lot of typing!
Note: Of course, this mechanism only works as long as the original assumptions hold. For example, we cannot correctly infer the implicit arguments of the dependent function composition operator:
(* Only infers (?2 -> ?3) -> (?1 -> ?2) -> (?1 -> ?3) *)
def `.` g f x := g (f x)
In this case, we have to explicitly provide some additional information:
(* If we omitted {x}, it would become an implicit argument of `.` *)
def `.` (g : all {x} (y : B x) -> C y) (f : all x -> B x) x := g (f x)
Which can be expanded into the following:
def `.` {A} {B : A -> Type} {C : all {x} -> B x -> Type}
(g : all {x} (y : B x) -> C y) (f : all x -> B x) x := g (f x)
Here is the equivalent Agda definition, for comparison:
_∘_ : ∀ {A : Set} {B : A → Set} {C : ∀ {x} → B x → Set}
(g : ∀ {x} (y : B x) → C y) (f : ∀ x → B x) x → C (f x)
(g ∘ f) x = g (f x)
End of Note
Is the mechanism described above feasible? Even better, is there any language that implements something resembling this mechanism?
This sounds like implicit generalization in Coq.