🫤👉 [index]
🫤👉 [index]
About Me [About]
About Me [About]
Hi, This is Qu Zhuoyuan, you can call me KonKonjac online. I am currently a Master student at Nagoya University, Japan, supervised by Prof. Jacques Garrigue. My research interest is in type theory and programming language theory.
Contact
You can reach me via email at quzhuoyuan@gmail.com
Links
About this site
This site is a place where I write down my notes and blog on various topics. This website is generated with Kodama.
1. Blog [blog]
1. Blog [blog]
1.1. A Way to Implement Dependent Pattern Matching with Normalization by Evaluation [blog/dpm-nbe]
- Apr. 6. 2026
- Blog
1.1. A Way to Implement Dependent Pattern Matching with Normalization by Evaluation [blog/dpm-nbe]
- Apr. 6. 2026
- Blog
In my final year of college, I was working on a toy project named ShiTT (I rewrote it completely half a year ago), which is a dependent type checker in Agda style based on András Kovács’ elaboration-zoo, with dependent pattern matching (DPM) support. And I feel that I need to write a blog post to share some of the interesting ideas I came up.
One of the challenging problems I encountered was how to implement DPM in NbE settings. As we know, implementing DPM needs to unify the terms and when matching the pattern against the type . This process is to build a substitution and apply it to the body of the pattern matching. However, in NbE implementations, we usually have no simple way to substitute.
A Naive Solution
Take elaboration-zoo/02-typecheck-closures-debruijn for instance, there is an evaluation environment where contains the values of all variables. An intuitive way to do DPM is to modify the evaluation environment by changing the value of the unified variable. For example,
If the placeholder is a variable pattern , the type checking context of should be,
and the evaluation environment should be,
If is the constructor pattern , then the evaluation environment should be,
Then all the occurences of in RHS will directly evaluate to . But this approach comes with a problem: this kind of unification will break the wellformedness of the evaluation environment. The evaluation environment stores the values of all variables. And the type can be considered as a representation for the normal forms. We changed the value of from a variable to an other value, which might make some of other variables that depends on to be ill-formed. e.g. if there is a variable in the evaluation environment, after we change to , the value of will be , which is not a normal form, which will make other variables depend on also ill-formed, and so on.
A Better Solution
A workaround for this problem is to update the environment every time we need to unify two terms.
For example, we have a well-formed evaluation environment , such that . And we have a unification assignment where is a value. We have shown that directly substituting for in will fail. Instead, we introduce a new operation, I call it
Here implies that the environment need not to be well-formed. There might be a smarter way to implement without quoting it back, but let us just use this for now.
One can imagine as a substitution operation on . e.g. , here is the naive substitution of for in the environment (which, as we have seen, is not well-formed).
Then, we can map to every value in . We call this operation .
Thus we obtain . Is well-formed and is it the environment we want?
The answer is yes.
We can define the ill-formedness of an environment with a level parameter. For a value under an environment , if for any free variable in , , we say is well-formed or say it is -ill-formed. If there is a free variable in such that and is -ill-formed, then we say is -ill-formed. An environment is -ill-formed if it contains a value that is -ill-formed. For example,
We can prove that if is -ill-formed, then is -ill-formed.
If is well-formed, and and there are some other variables in depending on , then it is easy to see that is -ill-formed. And after applying on it we get a well-formed environment.
Let us rewrite the type of to make it more clear,
Where indicates the level of ill-formedness.
And also, don’t forget to using the updated environment to update all the types in type checking context as well.
A Limited Version of Dependent Pattern Matching
This approach comes with an efficiency problem. Every time we need to unify two terms, we re-evaluate the whole environment, which is quite expensive. A workaround is to limit the length of the context. One can write the following Agda code,
module M (x : Nat) where
f : Id x 0 → ...
f refl = ...
A function has ability to unify the outer module variable . We can limit the ability of dependent pattern matching in ShiTT by only allowing it to unify the variables that are introduced in the current function (Well, I haven’t done it yet). So we rewrite the above code as,
module M (x : Nat) where
f' : (x : Nat) → Id x 0 → ...
f' x refl = ...
f = f' x
For a language with match expressions support, there is an extra reason to do this restriction. LightQuantum occurred this to me.
If we have the following data type,
data T : Set → Set where
C : T Bool
And we have a function with a match expression,
f : (A : Set) → T A → A
f A = λ t → match t with
| C => true -- here A = Bool
And we can define,
g1, g2 : T Nat → Nat
g1 = f Nat
g2 = λ t → match t with
| C => true
g1 is well-typed, while g2 is not, but one can find that g2 = f A [Nat / A] = g1. Substitution does not preserve typing!
The solution is as same as before, we limit the ability of dependent pattern matching by requiring all the unifying variables must in match expressions. So the above code will be rewritten as,
f A = λ t → match A , t with
| .Bool , C => true
After that, g2 ≠ f A [Nat / A] anymore, and it is not well-typed as expected.
All though Agda have no native support for match expressions, but it has lambda-case expressions which lead to the same problem. But seems they consider the lambda-case is a syntactic sugar for top-level pattern matching, which means the substitution is still type-preserving for Agda expressions (Since there is no pattern matching in Agda expressions).
Upon those two reasons, I think it might be a good idea to limit the ability of dependent pattern matching.
Update. Apr. 10. 2026
I just realized that the match expression example is wrong, the above code is also not well-typed under substitution. The final solution is to use lambda-case only (or equivalently, only allow matching on variables), and forbid unification on outer variables. So the above code will be rewritten as,
f = λ { A C -> true } : (A : Set) → T A → A
And lambda case only reduces when it is fully applied.
Update. Apr. 24. 2026
Turns out Rocq’s match-in-return-with expressions work just fine, no such problem as I mentioned above.
I so silly.
References
1.2. Syntax for pattern matching QIT (and maybe HIT) in OTT (and maybe HOTT) [blog/qit-syntax]
- 2026/8/10
1.2. Syntax for pattern matching QIT (and maybe HIT) in OTT (and maybe HOTT) [blog/qit-syntax]
- 2026/8/10
By OTT, we mean Loïc Pujet and Nicolas Tabareau’s Impredicative Observational Equality.
A type a : A b : A
------------------------
a ~_A b : Prop
cast A B : A ~_U B → A → B
Open Data Types
In OCaml (and a proposal of Haskell), there is a feature alowing you to extend a data type with new constructors. Which is called “Extensible Variant Types” in OCaml, and “Open Data Types” in Haskell. For some reasons, it is never supported by GHC.
In OCaml, the eliminator (pattern matching) of an extensible type should always have a default case for the future extended constructors. While in Haskell, the eliminator of an open data type is an open function, a function defined with pattern matching that can adding new clauses. For example
open data Expr = Add Expr Expr
open pp : Expr → String
pp (Add a b) = pp a ++ " + " ++ pp b
data Expr += Lit Int
pp (Lit x) = show x
Those features can work perfectly with Agda style pattern matching syntax.
A Syntax for QIT in Non Cubical TT
In CuTT (and XTT), A HIT should be considered as an inductive type with “boundary conditions”, and functions should eliminate such types with respect to the conditions. But in OTT, we do not have such thing.
Basically, this note works out a syntax for, 1. how to define a QIT 2. how to eliminate it.
Define a QIT
Suppose there are indexed inductive types Eq_A a b : Prop over all QIT A where a b : A.
(Note. For cubical indexed inductive types, we need to add a new constructor transpX. In OTT, we need the similar thing, but Eq is a irrelevent proposition family, we can omit that on it, because we do not need to compute a proposition)
The observational equality ~ computes to Eq when acting on QIT. i.e.
a ~_A b = Eq_A a b for QIT A.
Thus we can translate a QIT, for example, the interval as follow,
data I : Type where
i0 i1 : I
data EqI : I → I → Prop where
seg : i0 ~ i1
For indexed inductive types,
data Ty : Cx → Type where
...
_[_] : {G D : Cx} → Ty D → Sb G D → Ty G
data EqTy {G : Cx} : Ty G → Ty G → Prop where
...
id[] : A [ id ] ~ A
-- A ~_{Ty G} B = EqTy {G} A B
The sur-syntax is as usual,
data I : Type where
i0 i1 : I
seg : i0 ~ i1
data Ty : Type where
...
_[_] : {G D : Cx} → Ty D → Sb G D → Ty Γ
...
id[] : A [ id ] ~ A
Eliminate a QIT
We define an open indexed inductive type,
A : U i x : A ⊢ B : U j
----------------------------- Formation
'(x : A) → B : U (imax i j)
f : '(x : A) → B a : A
--------------------------- Elimination
f $ a : B [ a / x ]
Sometimes we omit $, just write f a.
The introduction and computation rules are generated by pattern matching functions. For example, when we have the following definition,
add : ℕ → ℕ → ℕ
add zero y = y
add (suc x) y = suc (x + y)
We actually defined,
add : 'ℕ → ℕ → ℕ -- an introduction rule, so add is a constructor
(add $ zero) = λ y → y -- a computation rule
(add $ (suc x)) = λ y → suc (add x y) -- a computation rule
The elaborator should insert ' and $ automatically.
Note. ($) is an open function.
We have another open function for '(x : A) → B
apd : (f : '(x : A) → B) (e : u ~_A v) → cast B[u] B[v] (ap B[_] e) (f u) ~ f v
apd is a trivial function and can be automatically generated, if A is just a non-quotient inductive type.
So we focus only on the case when A is a QIT.
Every time we pattern match a QIT, e.g.
f : 'I → Bool
f $ i0 = true
f $ i1 = true
Then apd is lacking a clause for f.
apd f (e : a ~_I b) = ...
we can split on e, because a ~_I b is a inductive type (Eq_I).
apd f (seg : i0 ~_I i1) = ... : f i0 ~_Bool f i1
f i0 = true = f i1, so the hole can be filled by refl
apd f seg = refl
I hope all of these can make sense in some way, and I will try to write a experimental implementation of it to see if it works. XD
Examples
===============
F : I -> Bool -> Bool
F i0 true = true
F i0 false = false
F i1 true = true
F i1 false = false
find elimination F : 'I -> Bool -> Bool
missing apd clause
apd {a} {b} F (e : a ~_I b) : F a ~ F b
===============
f : I -> I -> Bool
f i0 i0 = True
f i0 i1 = False
f i1 i0 = True
f i1 i1 = False
find elimination f : 'I -> I -> Bool
missing apd clause
apd {a} {b} f (e : a ~_I b) : f a ~ f b
find elimination f (x : I) : 'I -> Bool
missing apd clause
apd {a} {b} (f x) (e : a ~_I b) : f x a ~ f x b
x can be further eliminated in apd
apd {a} {b} (f i0) e = ...
apd {a} {b} (f i1) e = ...
no need to proof congruence for apd
in apd POV, f and F are constructors in pattern matching sense
we don’t need to prove congruence if the target of elimination is a prop
Intuition:
'(x : A) -> Bis an open indexed inductive type, indexed byAandx.B,each functor defined with pattern matching onAis a constructor to this type.a ~_A bis an indexed inductive type overA,each quotient path is a new constructor to this type.apdis an open function eliminating'(x : A) -> Banda ~_A b, for quotient inductive typeA.when'(x : A) -> Bis extended, the apd function should also be extended with new clauses,which corresponds to the congruence.
1.3. Old Blog Posts [blog/old]
1.3. Old Blog Posts [blog/old]
There are some old blog posts that I wrote before I started using Kodama. You can find most of them in my Zhihu profile.
2. Notes [notes]
2. Notes [notes]
3. A Naive Encoding of Russell’s Paradox in Type Theory [naive-encode-of-russell-paradox]
3. A Naive Encoding of Russell’s Paradox in Type Theory [naive-encode-of-russell-paradox]
Russell’s paradox is the most easily understandable way to illustrate the inconsistency of naïve set theory. This note proposes a direct encoding of Russell’s paradox with type-in-type universe, sigma types, and either extensional identity or intensional identity with the uniqueness of identity proofs (UIP).
See here for note in pdf.
See here for the formalization in Agda and Rocq.
4. 胡言乱语 [RandomWords]
4. 胡言乱语 [RandomWords]
2026/4/13 凌晨 名古屋
想家.
2026/4/12 名古屋
小学的时候想炸小学, 上了中学开始想念小学,想炸中学 上了大学爽了,啥也不想炸了。
现在只想回到大学,和朋友们再喝一杯酒
2026/4/11 名古屋
饥饿, 想食大便 贫穷, 想上班
2026/4/6 名古屋
我宣布, 人工智能是个笨蛋, 谁都不许害怕它.
2026/4/5 名古屋
我想家, 我和这里没有联系. 我用酒精麻痹自己.
我想家, 想回家.
为什么.
这辈子说的最多的就是,
为什么.
为什么.
不知道.
人群, 城市, 马路, 变形金刚.
饭店, 酒吧, 路牌, 一堆鞋子.
少年是什么
想念的那条街
确实如此.
晚安.
–
我明天要吃蛋炒饭.

2026/5/13 名古屋 凌晨
时间过去的越久, 越觉得有必要写些文字记录一下自己的大学生活, 但出于自己的社交焦虑本性, 总是写了又删, 删了又写, 像是露阴癖一般半遮半掩地展示自己的过往.
大概记得本科毕业前夕写过一篇, 酒醒之后再看到就删了.
我的确是一个不断地否定过去的自己的人.
但是我总是想, 总是想念着过去, 总是怀念着, 听着过去熟悉的音乐, 熟悉的旋律, 害怕着未来的自己会否定现在的我. 每当这种想法出现的时候, 我都会看着我的手, 问自己到底在干什么. 但是我自己显然也没有一个能够说服未来自己的答案.
想问未来, 问他有没有过上想要的生活, 有没有原谅现在的我.
再过一个月就是我本科毕业一年的日子, 每次想起过去总会怀念, 但是我真的站在那条承载我青春的街道上的时候, 我总是茫然, 我会想问我的朋友们呢, 我的青春呢, 我的过去呢, 我的回忆呢, 我当时喝到早上七点的馄饨铺呢.
但是正因如此, 我过去的一切都已经消失了 – 我草你妈了个臭逼, 我的时间平移对称性呢 – 这也许正是她美丽之处, 回忆美丽之处, 没有忧愁, 没有焦虑, 不必担心未来会发生什么, 因为老子他妈现在就在他妈的未来.
所以我现在来写一些文字, 并且祈祷明天的自己不会删除.
…
我第一次喝醉是在一个路边饭店, 青岛, 大二下学期, 应该是, 被疫情憋疯了, 和徐同学以及另外一个人去的, 在旁边超市买了三斤白酒, 一人喝了一瓶, 太他妈恶心了, 喝吐了, 吐在饭店路边, 打车回学校, 吐在学校路边.
但是开始发现, 喝了酒之后, 我能开始说话了, 我能自由地表达情感了, 我能丫的想说什么就说什么了, 我不会再在说话之前思考说了这句话之后我的未来会遭遇什么了, 我终于可以不去思考未来的我会怎么样地恨我了, 我像是自由了一样.
我的酒精生涯一直在追寻这样的自由.
我喝酒成瘾的原因是我终于能在饮酒后忽略别人的一切评价, 自己的一切考虑. 未来的一切焦虑.
社交焦虑或者说一切的焦虑本质, 在我看来, 都是过度思考, 而酒精能直接压制我的过度思考.
这叫我怎么能不上瘾.
所以我喜欢这个分子, 她能把我从未来的枷锁中解救出来.
下次喝醉再写吧, 晚安\
2026/6/28 名古屋 凌晨
我有独酌喝多了,
吼吼吼
第一次喝醉是和徐同学以及另外一个同学, 我忘了他的名字, 也许是喝多了导致的, 也许是我真的忘了, 我分不清.
哦, 这段讲过了, 总之白酒的味道恶心到我了.
在那之后, 还是在那同时, 我记不太清, 我和徐同学去了一些无聊酒吧, 一开始我们去了漳州二路的海伦斯, 实属无聊, 当时我买了一包QQ糖, 然后, 然后我就不记得了, 应该是回宿舍了? 你不觉得这很恐怖吗, 曾经发生的事情, 因为你的记忆而消失, 再也寻不回来.
然后我们去了漳州二路以东的一家猫头鹰, 那里有一张台球桌, 我们去了几次, 打了几次台球, 遇见了同校的几个新疆同学. 无所谓. 但是有一天, 那天下雪, 应该是23年末/24年初的冬天, 我懒得考证了, 徐同学和我一起去猫头鹰, 我们和另外的一男两女拼桌, 徐同学和其中一个女生成功得吃(我希望这个词不会冒犯任何人, 如果冒犯了你, 我道歉, 并且希望你告诉我这个词的正确表示方式, 然后我现在喝多了, 真诚地希望您能谅解, 然后我将极速更改), 我那天蛮不开心的, 因为我要独自一人走回宿舍.
在那之后, 我和徐同学以及另一个常同学开始一起混酒吧, 我们开始不满足于去猫头鹰, 我们开始想要去一些更加犄角旮旯的酒吧, 其中之一是江西路的画廊酒吧.
当时, 我们三人, 一进酒吧, 老板就开直接库库库开了三瓶啤酒, 我们当即意识到这不是一个可以自在喝酒的地方.
那天晚上, 我们三人喝了三瓶被迫啤酒之后都对今夜有所不甘, 我们决定去江西路的鸡尾酒吧, 当时我们去了一家看起来最小的一家赫本, 彼时我喝了一杯古典, 感觉相当报喝, 一人一杯之后, 我们去了南隔壁的查理斯吧, 那是一切故事的开始.
同上回一样, 等我喝多了再写吧, 否则我又要删帖了.