Experimenting with some basic programming in Java 18 and see the following:
- If Java competes with existing established independently typed languages for writing compilers, like Haskell
- If capture-avoiding substitution is not too bad (because with nestable pattern matching, de-Bruijn indices are too bad)
- Cody told me that locally nameless is survivable, hmm
- If my understanding of basic programming is appropriate
"Basic programming" includes:
- A "De Morgan flavored" cubical type theory with redtt flavored generalized path types (the so-called "extension types")
A β¨new syntaxβ¨ for cofibration theory that is more convenient for confluence checking and hopefully is equivalent to existing ones- Update: it's not. The new syntax is implemented, tested, and removed for not working well with substitutions.
- Unlike Cubical Agda, Guest0x0 sticks to the paper syntax and is not constraint-based.
- Inductive types with pattern matching (hopefully) and "simpler indices" (see my TyDe paper)
- An equalizer of the first projection of evaluation and just the first projection
Make sure you listen to Red Hot Chili Peppers while looking at this project.
- MLTT
- Capture-avoiding substitution
- Pi, Sigma, Universe
- Function definition and delta reduction
- Typed conversion (bidirectional conversion checking)
- Inductive type and pattern matching
- CHM
- Extension type (generalized path type)
- Cofibration theory
- Partial elements (with a dedicated type like in Cubical Agda)
- Cubical subtypes
- Generalized transport
- Pi, Sigma, Universe
- Higher inductive type
- Glue/V
- Homogenous composition
- Pi, Sigma, Universe
- Higher inductive type
- Glue/V
Ported some code from Anqur to here. This should not affect any implemented functions, but should be helpful for future development on (higher) inductive types.
At this point, Guest0x0 has 1308 lines of Java code
Added a generalized Partial
type to the cubical infra.
Implement the typing and basic reduction of hcomp
:
def =-trans-hcomp (A : Type) (p : I -> A)
(q : [| i |] A {| i = 0 |-> p 1 |})
: [| i |] A {| i = 0 |-> p 0 | i = 1 |-> q 1 |}
=> \i. hc A #{i = 0 /\ i = 1} (\j.
\{| i = 0 |-> p 0
| i = 1 |-> q j
|}) on (inS p i)
Moved some code from base to cubical.
Fixed another bug conjectured by @imkiva on conversion of partial elements. In the past, the code below would fail to type check (there is another bug related to substitution, which is also fixed):
def par1 (A : U) (a : A) (i : I) : Partial A #{i = 0} => \ {| i = 0 |-> a |}
def par2 (A : U) (b : A) (j : I) : Partial A #{j = 0} => \ {| j = 0 |-> b |}
def cmp (A : U) (x : A)
: [| i j |] (Partial A #{j = 0}) {| i = 0 |-> par1 A x j |}
=> \ i. \ j. par2 A x j
Refactored the internal representation of partial elements, introduced cubical subtypes. We may now represent the reduction of transp
using cubical subtypes now:
def transSub (A' : U) (r : I)
(A : Pi (i : I) -> Sub U {| r = 1 |-> A' | i = 0 |-> A' |})
(a : A') : Sub outS (A 1) {| r = 1 |-> a |}
=> inS (tr (\ i. outS (A i)) #{r = 1} a)
Started working on hcomp
. At this point, the total lines of code of Java in the base module is 1524, and the cubical infra is 307.
Fixed a bug in module-info.java with updated Aya, as reported by @imkiva. Started working on cubical subtypes.
The maven identifier is changed from org.aya-prover:guest0x0-base
to org.aya-prover.guest0x0:base
,
similarly for cubical
and cli
.
Refactored the implementation of extension types. It now looks like this:
def Eq (A : U) (a b : A) : U =>
[| j |] A {| j = 0 |-> a | j = 1 |-> b |}
def EqP (A : I -> U) (a : A 0) (b : A 1) : U =>
[| j |] A j {| j = 0 |-> a | j = 1 |-> b |}
def =-trans (A : Type) (p : I -> A) (q : [| i |] A {| i = 0 |-> p 1 |})
: [| i |] A {| i = 0 |-> p 0 | i = 1 |-> q 1 |} =>
subst A (Eq A (p 0)) (\i. q i) (\i. p i)
Slightly improved the UX of the editor. Updated the tutorial.
Implemented a simple TikZ 3D cube WYSIWYG editor using java.io.Serialization
serialization. It has a separate serialized-to-tex compiler and a jimgui-based editor. The generated cube should be referenced in a LaTeX document with \ExecuteMetadata[cubes]{cube name}
.
Started working on the implementation notes (as a cubical tutorial).
Added a dedicated type for cofibrations (F
), added partial element syntax {| i = 0 |-> u | i = 1 /\ j = 0 |-> v |}
(this is a demonstration) and the dedicated type Partial #{i = 0 \/ i = 1 /\ j = 0}
, like in Cubical Agda. Elimination of contradiction in cofibration is also implemented. Here are some examples of partial elements typing:
def par1 (A : Type) (u : A) (i : I) : Partial A #{i = 0} =>
{| i = 0 |-> u |}
def par2 (A : Type) (u : A) (i : I) : Partial A #{i = 0} =>
{| i = 0 |-> u | i = 1 |-> u |}
def par3 (A : Type) (u : A) (v : A) (i : I) : Partial A #{i = 0 \/ i = 1} =>
{| i = 0 |-> u | i = 1 |-> v |}
def par4 (A : Type) (u : A) (v : A) (i : I) (j : I) : Partial A #{i = 0 \/ i = 1 /\ j = 0} =>
{| i = 0 |-> u | i = 1 /\ j = 0 |-> v |}
def par5 (A : Type) (u : A) (v : A) (i : I) (j : I) : Partial A #{i = 0 \/ i = 1 /\ j = 0} =>
{| i = 0 |-> u | i = 1 |-> v |}
Some counterexamples:
def par (A : Type) (u : A) (v : A) (i : I) (j : I) : Partial A #{i = 0 /\ j = 0} =>
{| i = 0 |-> u | i = 0 /\ j = 0 |-> v |}
Raises:
Boundaries disagree.
Umm, v != u on [| i = 0 |-> u | i = 0 /\ j = 0 |-> v |]
In particular, u != v
And this one:
def par (A : Type) (u : A) (v : A) (i : I) (j : I) : Partial A #{i = 0 \/ i = 1} =>
{| i = 0 |-> u | i = 1 /\ j = 0 |-> v |}
Raises:
The faces in the partial element i = 0 \/ (i = 1 /\ j = 0) must cover the face(s) specified
in type: i = 0 \/ i = 1
Total lines of Java code in base (including spaces and comments): 1322, and in cubical infra: 377.
Changed the syntax of transport from A #{cof}
to tr A #{cof}
for future convenience, and implemented many tools for systems. Added some tests for cofibration substitution, and fixed some pretty-printing bugs. Below is a snippet that already works in old versions:
def transId (A : U) (a : A) : Eq A a (tr (\i. A) #{1=1} a) => refl A a
def forward (A : I -> Type) (r : I) : A r -> A 1 =>
tr (\i. A (r \/ i)) #{r = 1}
The project is now split into three subprojects instead of two, and I believe that from now on counting the lines of Java code is pointless (as we are eventually going to grow). The new subproject guest0x0-cubical
contains some generic utilities for face restrictions (cofibrations in cartesian cubical type theory) and boundaries. The constant check for generalized transport has finally been implemented π. Consider the snippet below:
def trauma (A : I -> U) (a : A 0) (i : I) : A i => (\j. A (i /\ j)) #{i = 1} a
Under the cofibration i = 1
, A (i /\ j)
reduces to A j
according to de Morgan laws, so Guest0x0 will reject the code with the following message:
The cover \j. A (i /\ j) has to be constant under the cofibration i = 1 but applying
a variable `?` to it results in A ? which contains a reference to `?`, oh no
Replacing the cofibration with i = 0
makes A (i /\ j)
reduce to A 0
, which is a constant.
Refactored the implementation of restrictions, and finished splitting disjunctions. The number of lines of Java code is 1366 now, quite a lot. There aren't many new features, so no new code snippets this time.
Overhauled the cofibration syntax. It is now similar to a combination of CCHM and ABCFHL (I used 1=0
, 0=1
for empty face and 1=1
, 0=0
for truth face because I'm cool).
def trans (A : I -> U) (a : A 0) : A 1 => A #{0=1} a
def transPi (A : I -> U) (B : Pi (i : I) -> A i -> U)
(f : Pi (x : A 0) -> B 0 x) : Pi (x : A 1) -> B 1 x =>
\x. trans (\j. B j ((\i. A (j \/ ~ i)) #{j = 1} x))
(f (trans (\i. A (~ i)) x))
Two things are yet to be done: split disjunctions in conjunctions and make i = 0 \/ i = 1
false. Also, some small bugs are fixed (like dim vars are not removed from scope, causing bloated hole info) The codebase is growing larger and larger, to 1335 lines of code. Some small useful lemmata:
def subst (A : Type) (P : A -> Type) (p : I -> A)
(lhs : P (p 0)) : P (p 1) =>
trans (\i. P (p i)) lhs
def =-trans (A : Type) (p : I -> A) (q : [| i |] A { | 0 => p 1 })
: [| i |] A { | 0 => p 0 | 1 => q 1 } =>
subst A (Eq A (p 0)) (\i. q i) (\i. p i)
Updated the CLI frontend to hide stack traces by default. Fixed some core theory bugs (many thanks to AmΓ©lia, MBones, and Daniel for their helps), implemented structural lures for the universe, Pi, and Sigma types. 1179 lines of Java. I'm sort of giving up on lines of code thingies -- I don't want to sacrifice readability.
def transPiEq (A : I -> U) (B : Pi (i : I) -> A i -> U)
: Eq ((Pi (x : A 0) -> B 0 x) -> (Pi (x : A 1) -> B 1 x))
(transPi A B)
(trans (\i. Pi (x : A i) -> B i x))
=> \i. transPi A B
def transSigma (A : I -> U) (B : Pi (i : I) -> A i -> U)
(t : Sig (x : A 0) ** B 0 x) : Sig (x : A 1) ** B 1 x =>
<< trans A (t.1),
trans (\j. B j ((\i. A (j /\ i)) ~@ j { | 0 } (t.1))) (t.2) >>
def transSigmaEq (A : I -> U) (B : Pi (i : I) -> A i -> U)
: Eq ((Sig (x : A 0) ** B 0 x) -> (Sig (x : A 1) ** B 1 x))
(transSigma A B)
(trans (\i. Sig (x : A i) ** B i x))
=> \i. transSigma A B
A work-in-progress transp
implementation, denoted ~@
(very beautiful with JetBrains Mono or Fira Code). The syntax is adapted from the new cofibration theory syntax. 1027 lines of Java, good.
The IsOne
constraint used in Cubical Agda is expressed as "the instantiations of a set of dimension variables match a pattern", and the type checking criteria becomes "the type line instantiated to every set of patterns gives the constant function". When matched, transp
computes as identity. Other lures are yet unimplemented.
We now have the ability to test some de Morgan laws:
def trans (A : I -> U) (a : A 0) : A 1 => A ~@ {} a
def trans^-1 (A : I -> U) (a : A 1) : A 0 => (\i. A (~ i)) ~@ {} a
def transFn (A B : I -> U) (f : A 0 -> B 0) : A 1 -> B 1 =>
\a. trans B (f (trans (\i. A (~ i)) a))
def transPi (A : I -> U) (B : Pi (i : I) -> A i -> U)
(f : Pi (x : A 0) -> B 0 x) : Pi (x : A 1) -> B 1 x =>
\x. trans (\j. B j ((\i. A (j \/ ~ i)) ~@ j { | 1 } x))
(f (trans (\i. A (~ i)) x))
Fixed many bugs in the previous version, including proper conversion lures for path application and full de morgan laws. 924 lines of Java. These functions are now accepted:
def rotate (A : U) (a b : A) (p q : Eq A a b)
(s : Eq (Eq A a b) p q)
: Eq (Eq A b a) (sym A a b q) (sym A a b p)
=> \i j. s (~ i) (~ j)
def minSq (A : U) (a b : A) (p : Eq A a b)
: [| i j |] A { | 0 _ => a | 1 0 => a | 1 1 => b }
=> \i j. p (i /\ j)
def maxSq (A : U) (a b : A) (p : Eq A a b)
: [| i j |] A { | 0 0 => a | 1 0 => b | _ 1 => b }
=> \i j. p (i \/ j)
Also, a simple CLI interface is implemented.
Interval connections and involution and de Morgan laws. 915 lines of Java code. Time to implement transp
. I decide to design a new syntax for it, because I think of it as the elimination principle of I
, and it has a special typing rule (i.e. you can't alias it as a function).
def sym (A : U) (a b : A) (p : Eq A a b) : Eq A b a => \i. p (~ i)
Implemented boundaries' confluence check and the new syntax for multidimensional cubes ("extension types"), 855 lines of Java now. The multi-case trees utilities from Aya tools are used, very neat. Error message for holes is now more informative (displays shadowing information). The following code has some endpoints do not agree:
def Guest0x0 (A : U) (a b c d : A)
(ab : Eq A a b) (cd : Eq A c d) : U =>
[| i j |] A { | 0 _ => ab j -- 0 0 => a, 0 1 => b
| 1 _ => cd j -- 1 0 => c, 1 1 => d
| 1 1 => a } -- violation
Guest0x0 will reject the above with The 3rd and 2nd boundaries do not agree!!
.
Added some cosmetics features like pretty printing with precedence, one more conversion rule, JPMS support, interval terms, a bunch of helper methods, and unicode keyword. I wish to keep the project small, but with that we have 736 lines of Java now. That's a lot more. Just one more theorem we can define:
def pmap (A B : U) (f : A -> B) (a b : A) (p : Eq A a b)
: Eq B (f a) (f b) => \i. f (p i)
This is what Guest0x0 says about the funExt
function body:
f : Pi (_ : A) β B
i : I
p : Pi (a : A) β [| j |] B {
| 0 β f a
| 1 β g a
}
B : U
j : I
g : Pi (_ : A) β B
A : U
----------------------------------
(\A. \a. \b. [| j |] A {
| 0 β a
| 1 β b
}) (Pi (_ : A) β B) f g
|β
[| j |] Pi (_ : A) β B {
| 0 β f
| 1 β g
}
Lambdas are overloaded as paths, and paths reduce according to the boundaries. Total lines of Java code: 580, including blank/comments. The following are missing:
- Confluence check (boundaries need to agree to be a well-formed cube)
- Interval connections and endpoints (no parsing yet)
- Higher-dimensional extension types (unhandled case in elaborator)
- Type-directed eta conversion/expansion (currently term-directed in the unifier)
def Eq (A : U) (a b : A) : U => [| j |] A { | 0 => a | 1 => b }
def refl (A : U) (a : A) : Eq A a a => \i. a
def funExt (A B : U) (f g : A -> B)
(p : Pi (a : A) -> Eq B (f a) (g a))
: Eq (A -> B) f g => \i a. p a i
Minimal type checker with definitions, pi, sigma, and universe. 484 lines of Java code (including comments and blank lines and import
statements in many files), comparable to Mini-TT (Main.hs and Core/Abs.hs, 358 + 46 = 404 lines in Haskell). But Mini-TT supports (a cursed version of) sum types.
def Eq (A : Type) (a : A) (b : A) : Type => Pi (P : A -> Type) -> P a -> P b
def refl (A : Type) (a : A) : Eq A a a => \P. \pa. pa
def sym (A : Type) (a : A) (b : A) (e : Eq A a b) : Eq A b a => e (\b. Eq A b a) (refl A a)