<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://gwaithimirdain.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://gwaithimirdain.github.io/" rel="alternate" type="text/html" /><updated>2026-08-07T06:41:44+00:00</updated><id>https://gwaithimirdain.github.io/feed.xml</id><title type="html">Gwaith-i-Mírdain</title><subtitle>The Jewel-smiths of Eregion.</subtitle><entry><title type="html">Modal type theory + AI redux</title><link href="https://gwaithimirdain.github.io/2026/07/19/modal-redux.html" rel="alternate" type="text/html" title="Modal type theory + AI redux" /><published>2026-07-19T00:00:00+00:00</published><updated>2026-07-19T00:00:00+00:00</updated><id>https://gwaithimirdain.github.io/2026/07/19/modal-redux</id><content type="html" xml:base="https://gwaithimirdain.github.io/2026/07/19/modal-redux.html"><![CDATA[<p>Modal type theory has landed in Narya!  I’m super excited about this: I’ve been looking forward to having a true multimodal proof assistant for over a decade, ever since I started writing about <a href="https://arxiv.org/abs/1509.07584">cohesive type theory</a>.  To my knowledge (please correct me if I’m wrong), Narya is the first full dependently-typed proof assistant to implement general multimodal type theory, including mode theories that are not locally posetal (have more than one 2-cell between some parallel modalities).  I plan to say more in another post about the difficulties involved in dealing with the non-locally-posetal case, and why Narya happened to already be ideally positioned to solve them.</p>

<p>I’m not going to write much about how to <em>use</em> modal type theory here, because I just did a whole lot of that in the Narya documentation.  You should go read it, and try it out!</p>

<ul>
  <li><a href="https://narya.readthedocs.io/en/latest/modal.html">Modal type theory</a></li>
  <li><a href="https://narya.readthedocs.io/en/latest/discrete.html">Modal parametric discreteness</a></li>
  <li><a href="https://narya.readthedocs.io/en/latest/external.html">External parametricity</a></li>
</ul>

<p>But I do want to comment on a few choices related to previous blog posts, and on my use of AI agents in coding it.</p>

<ul>
  <li>
    <p>I went with the syntax <code class="language-plaintext highlighter-rouge">(x :□| A) → B</code> for modal function-types.  Only <em>generating</em> modalities have names, so if you want to annotate a variable by a composite of modalities you would in general have to separate their names by a space as in <code class="language-plaintext highlighter-rouge">(x :△ □| A)</code>.  However, it seems that often we give modalities single-character names, so I implemented a simplification that if all the modalities have single-character names, you can run them together as in <code class="language-plaintext highlighter-rouge">(x :△□| A)</code> and Narya will split them up for you.</p>
  </li>
  <li>
    <p>The syntax I settled on for keys is <code class="language-plaintext highlighter-rouge">M #key</code>.  I like this because being postfix, it looks like a “property” or a “version” of <code class="language-plaintext highlighter-rouge">M</code>, which it is, and the symbol <code class="language-plaintext highlighter-rouge">#</code> it uses is on a standard US keyboard.  (I like all syntax to at least have an equivalent version that can be typed on a standard US keyboard without fancy input methods; unfortunately I broke that rule with the names of the modalities for built-in mode theories, although at least you can rename them with a command-line flag.)</p>
  </li>
  <li>
    <p>For window modalities and modal field projections, I stuck with the same kind of modal annotations rather than introduce anything new: <code class="language-plaintext highlighter-rouge">match (x :□| _) [ ... ]</code> and <code class="language-plaintext highlighter-rouge">(x :□| _) .fld</code>.  Although it may be a bit uglier that some other possibilities, I think it reduces the cognitive load on the user (which is already heavy in a modal type theory) to use the same syntax every time a term is being checked in a locked context.</p>
  </li>
  <li>
    <p>The above choice means that to define a record type with a modal field, you <em>must</em> use the self variable syntax for record types!</p>
  </li>
  <li>
    <p>Claude’s function to strip keys off an environment did not work.  I tried writing the same function in a different way, but that didn’t work either.  Eventually I settled on another way that I’m pretty confident works.  However, it seems hard to justify semantically, as it involves using “imaginary faces” of cubes that temporarily exist but eventually get canceled by a degeneracy.  (This doesn’t happen in plain modal type theory, which is semantically fine, only in the more experimental version with “discrete modalities”.)</p>
  </li>
</ul>

<p>In general, over the past month Claude has gotten better at doing what I want, or at least I’ve gotten better at getting it to do what I want.  For instance, it’s been a long time since it tried to use <code class="language-plaintext highlighter-rouge">Obj.magic</code> or claim that a possible case was impossible.  I think it’s partly because it’s recording some of the things I tell it in its internal memory about the project.  (And also the models are probably continuing to improve; I had a brief promotional access to Fable for a couple of weeks.)  It’s very good, for instance, at generating the boilerplate code for implementing new mode theories, given a description in words of the desired theory, which is very helpful given that all mode theories have to be implemented in OCaml so far.  And I’ve learned, as everyone else also says, that when you want it to do anything substantial, it makes a big difference if you explicitly ask it to “make a plan” first and then give it feedback on its plan before it starts implementing.</p>

<p>However, I still can’t rely on it to come up with algorithms, or even to fill in the details of algorithms.  If I give it a clear and precise description of the algorithm I want, it’s usually impressively good at implementing it.  But if my description is vague or missing pieces, then sometimes it will spin for a long time trying to figure out how to fill it in, or come back to me and complain that it’s stuck – or, which is maybe worse, it will make guesses about how to fill things in, which compile and look plausible, but often turn out to be subtly wrong.  I’ve had to fix a fair number of Claude-introduced bugs.</p>

<p>To be fair, though, Claude is also quite helpful at finding bugs, at least after I’ve discovered a test case.  Sometimes it can even fix them, although as often as not I can think of a better fix myself.  But <em>tracing through the code</em> to find exactly <em>where and why</em> an error is getting emitted is one of my least favorite parts of programming Narya, and Claude is very good at doing it for me.</p>

<p>Claude is also good at finding ways to speed up the code, which I’ve never been very good at.  If you haven’t built Narya for a while, you may notice that the test suite runs substantially faster.</p>

<p>On balance, Claude has definitely saved me a lot of time: Modal Narya would definitely not be ready for use today without it.  And I believe that, <em>after</em> all the back and forth, the code quality is comparable to what I would have produced myself.  (Of course, I also make mistakes all on my own!)</p>

<p>I do have to discipline myself to look over all the code that it writes.  It’s tempting to just trust it, especially when it also writes tests that pass; but I’ve been burned by doing that too many times.  I don’t try to read and understand exactly <em>everything</em> the code is doing line by line, particularly the parts that are doing things like type-level arithmetic to make the parameters work out correctly: in those cases I can be fairly confident that anything that compiles is correct.  But I do try to understand at a high level what the code is doing, and make sure that I agree with it.  Most of the time there are some changes I want to make, sometimes minor, sometimes more substantial.  And it matters to me that I “own” the Narya codebase, in the sense that I have a good idea of all the moving parts and how they fit together, even if I didn’t write it all myself.</p>

<p>I am also fairly impressed at how well Claude can write <em>Narya</em> code for new blackbox tests: it can’t have a very big library of examples to be drawing from because there just <em>isn’t</em> very much Narya code in the world yet.  Apparently Claude has somehow “learned how to program” in a way that is, if not completely language-independent, adaptable to new languages without much difficulty.  I’m curious how much of Claude’s knowledge about Narya syntax comes from reading the existing Narya code in the other blackbox tests, how much comes from reading the Narya documentation (which is, of course, right there in the same repository), and how much comes from reading the Narya source code – but I suppose I’ll never know.</p>

<p>One thing I don’t let Claude do, by the way, is write the documentation.  I’m sure it could do a passable job, and sometimes it offers to; but to me the documentation is a creative product, like a novel or a mathematical paper, and I want it to be written by a human who thought at least a little about every word.  Maybe that’s old-fashioned and maybe I’ll change my mind in the future, but at the moment that’s how I feel.</p>

<p>Anyway, enough about AI.  We had a little demo/tutorial here at <a href="https://ct2026.com/">CT26</a> and folks were excited about the modal type theory features.  I’m looking forward to seeing what people can do with them.  Please give them a try, and send along any feedback!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Modal type theory has landed in Narya! I’m super excited about this: I’ve been looking forward to having a true multimodal proof assistant for over a decade, ever since I started writing about cohesive type theory. To my knowledge (please correct me if I’m wrong), Narya is the first full dependently-typed proof assistant to implement general multimodal type theory, including mode theories that are not locally posetal (have more than one 2-cell between some parallel modalities). I plan to say more in another post about the difficulties involved in dealing with the non-locally-posetal case, and why Narya happened to already be ideally positioned to solve them.]]></summary></entry><entry><title type="html">Self variables</title><link href="https://gwaithimirdain.github.io/2026/06/17/self-variables.html" rel="alternate" type="text/html" title="Self variables" /><published>2026-06-17T00:00:00+00:00</published><updated>2026-06-17T00:00:00+00:00</updated><id>https://gwaithimirdain.github.io/2026/06/17/self-variables</id><content type="html" xml:base="https://gwaithimirdain.github.io/2026/06/17/self-variables.html"><![CDATA[<p>In Narya there are two ways to define a record type.  One is the more common, where the fields are named and referred to like local variables:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def Σ (A : Type) (B : A → Type) : Type ≔ sig (
  fst : A,
  snd : B fst )
</code></pre></div></div>
<p>The other uses a “self variable” belonging to the record type itself, with the fields accessed using the usual field projection syntax from that variable:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def Σ (A : Type) (B : A → Type) : Type ≔ sig (
  x .fst : A,
  x .snd : B (x .fst) )
</code></pre></div></div>
<p>(You can’t mix the two syntaxes: you have to pick one or the other and use it for all the fields.)</p>

<p>Although self variables are a common way to access members and methods in mainstream object-oriented programming languages, they aren’t particularly common in proof assistants.  But Narya not only permits them for record types, it <em>mandates</em> them for codatatypes.  The <em>only</em> way to define streams is</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def Stream (A : Type) : Type ≔ codata [
| x .head : A
| x .tail : Stream A ]
</code></pre></div></div>

<p>My original reason for using self variables was that in <a href="https://arxiv.org/abs/2311.18781">displayed type theory</a>, we wanted to consider a kind of codatatype that uses the self variable in a <em>different</em> way than just projecting out its fields.  In particular, we have the coinductive definition of semi-simplicial types:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def SST : Type ≔ codata [
| X .z : Type
| X .s : X .z → SST⁽ᵈ⁾ X ]
</code></pre></div></div>
<p>However, in the process of implementation I discovered that self variables are much easier to deal with than field variables.  For instance, rather than the types of the fields being stored as a telescope, each in a longer context than the previous one, they can all be stored separately in a context that is only <em>one</em> variable longer (the self variable).  So in the internals of Narya, everything is self variables; the translation from field variables (when the user writes the first syntax for a record type) happens at typechecking time.</p>

<p>Today I realized another benefit of self variables.  Namely, field variables are kind of funny in that they look like variables, but they are not subject to alpha-equivalence.  If you rename the fields of a record type, you get a different record type: isomorphic to the first one, but different.  This, in turn, prevents arbitrary alpha-equivalence from being applied to other ordinary variables in enclosing binders.</p>

<p>Normally, if I have a nested λ-abstraction such as</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>x y z ↦ f x y z
</code></pre></div></div>
<p>I’m free to rename the variable <code class="language-plaintext highlighter-rouge">y</code> to anything I want, as long as it doesn’t shadow an outer variable such as <code class="language-plaintext highlighter-rouge">x</code>, at the cost of possibly also renaming other inner variables such as <code class="language-plaintext highlighter-rouge">z</code>.  For instance, I can write that abstraction as</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>x w z ↦ f x w z
</code></pre></div></div>
<p>and also</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>x z z′ ↦ f x z z′
</code></pre></div></div>
<p>This order of alpha-renaming is convenient when printing a term, transforming the internal De Bruijn representation into a named variable one: as we descend through abstractions, at each point we choose a name for the bound variable that doesn’t conflict with any previously used names for outer variables.  Thus, at the point of (re)naming <code class="language-plaintext highlighter-rouge">y</code> we are free to choose <code class="language-plaintext highlighter-rouge">z</code>, and then later we get to <code class="language-plaintext highlighter-rouge">z</code> and we just choose a different name for it there.</p>

<p>This is what breaks if field variables can’t be renamed.  An equivalent way to write the above definition of <code class="language-plaintext highlighter-rouge">Σ</code> is</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def Σ : (A : Type) (B : A → Type) → Type ≔ A B ↦ sig (
  fst : A,
  snd : B fst )
</code></pre></div></div>
<p>But in the λ-abstraction</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A B ↦ sig ( fst : A, snd : B fst )
</code></pre></div></div>
<p>the ordinary variable <code class="language-plaintext highlighter-rouge">B</code> can’t be renamed to <code class="language-plaintext highlighter-rouge">fst</code>, because it would conflict with the <em>inner</em> field variable <code class="language-plaintext highlighter-rouge">fst</code>.  We can’t print <code class="language-plaintext highlighter-rouge">Σ</code> as</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A fst ↦ sig ( fst′ : A, snd : fst fst′ )
</code></pre></div></div>
<p>because that would be a different record type, with a field called <code class="language-plaintext highlighter-rouge">fst′</code> rather than <code class="language-plaintext highlighter-rouge">fst</code>.</p>

<p>One might think of trying to rename “backwards”, renaming any outer variables that conflict with a field variable name.  But aside from the difficulty of implementing something like that (if we’re displaying the term by a normal recursive traversal, other parts of the term referring to that outer variable by its original name might already have been displayed), the outer variable could <em>also</em> be a non-renameable field variable.</p>

<p>For instance, here’s a definition written using self variables that can’t be written directly using field variables:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def foo₁ : Type ≔ sig (
  x .fst : Type,
  x .snd : sig (
    y .fst : Type,
    y .snd : x .fst ))
</code></pre></div></div>
<p>If we try to write it with field variables, we get stuck:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def foo₂ : Type ≔ sig (
  fst : Type,
  snd : sig (
    fst : Type
    snd : ? ))
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">?</code> is supposed to refer to the <em>outer</em> field variable <code class="language-plaintext highlighter-rouge">fst</code>, but that is shadowed by the <em>inner</em> field variable <code class="language-plaintext highlighter-rouge">fst</code>, and we can’t rename either of them.  The only thing we can do is something like</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def foo₃ : Type ≔ sig (
  fst : Type,
  snd : let outer_fst ≔ fst in sig (
    fst : Type
    snd : outer_fst ))
</code></pre></div></div>
<p>But it’s a bit much to expect a pretty-printer to insert <code class="language-plaintext highlighter-rouge">let</code>-bindings.  So if the user writes the above definition of <code class="language-plaintext highlighter-rouge">foo₃</code>, then internally it gets normalized (making the <code class="language-plaintext highlighter-rouge">let</code>-binding disappear) and represented like <code class="language-plaintext highlighter-rouge">foo₁</code>, and the pretty-printer gets stuck trying to print it like <code class="language-plaintext highlighter-rouge">foo₂</code>.  Thus, having the option of a user-facing syntax that uses self-variables is necessary for the pretty-printer to have something to output in this case.  Probably it would stick with field variables for the outer record type, and then when it reaches the inner one and discovers a conflict, it falls back to self variables for that:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def foo₄ : Type ≔ sig (
  fst : Type,
  snd : sig (
    y .fst : Type
    y .snd : fst ))
</code></pre></div></div>

<p>Now, I should admit that most other proof assistants would not let you write <em>any</em> of these definitions, so the question of field variable alpha-equivalence doesn’t arise.  In proof assistants like Rocq, Agda, and Lean, a record type definition is a top-level declaration that can’t appear as a term inside any other definition.  Interestingly, the language feature I know of that comes closest to what Narya allows is OCaml’s <a href="https://ocaml.org/manual/5.4/labeledtuples.html">labeled tuples</a>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>fst:int * snd:(fst:int * snd:string)
</code></pre></div></div>
<p>However, these are not dependently typed, so the issue of names doesn’t come up.</p>

<p>I should also admit that although Narya will let you <em>write</em> <code class="language-plaintext highlighter-rouge">foo₁</code>, <code class="language-plaintext highlighter-rouge">foo₃</code>, and <code class="language-plaintext highlighter-rouge">foo₄</code>, the question of how to print them doesn’t come up at present, because record types are generative and nominal.  That is, all of those definitions define a new type <code class="language-plaintext highlighter-rouge">foo</code> (or whatever) that doesn’t reduce to any previously existing thing such as <code class="language-plaintext highlighter-rouge">sig ( ... )</code>; instead the <code class="language-plaintext highlighter-rouge">≔ sig ( ... )</code> serves to specify the <em>behavior</em> of <code class="language-plaintext highlighter-rouge">foo</code>.  However, it’s useful to be able to inspect the definitions of existing types without having to search for their definition, and I’m working on an extension that will print them.  In that case, this issue will arise, and the printer will fall back to self variables in case of a conflict of field names, as I described above.  (And in fact, I discovered this issue while working on that extension: Claude wrote some code that uniquified the field names, producing <code class="language-plaintext highlighter-rouge">fst′</code> and so on, and I said Hey, that’s not right!)</p>

<p>Until now, I always thought of self-variable syntax for records (as opposed to codata) as a bit of an odd beast.  I implemented it in response to a request from a user (who can identify themselves if they wish) who wanted to write some code that (I thought) was at least a little semantically suspect, referring to the self variable of a record in a non-field-access way similar to the codata definition of <code class="language-plaintext highlighter-rouge">SST</code>, and I’ve always been ambivalent about it.  However, the realization that self variables are necessary to consistently print all nested record types makes me much happier about them (although when we get around to implementing consistency checks like termination and positivity, we should probably check that the self variable of a record type <em>is</em> only used to access its fields, and at least issue a warning if it is not).  Perhaps we should even deprecate field variables!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[In Narya there are two ways to define a record type. One is the more common, where the fields are named and referred to like local variables: def Σ (A : Type) (B : A → Type) : Type ≔ sig ( fst : A, snd : B fst ) The other uses a “self variable” belonging to the record type itself, with the fields accessed using the usual field projection syntax from that variable: def Σ (A : Type) (B : A → Type) : Type ≔ sig ( x .fst : A, x .snd : B (x .fst) ) (You can’t mix the two syntaxes: you have to pick one or the other and use it for all the fields.)]]></summary></entry><entry><title type="html">Opening up some issues</title><link href="https://gwaithimirdain.github.io/2026/06/14/opening-issues.html" rel="alternate" type="text/html" title="Opening up some issues" /><published>2026-06-14T00:00:00+00:00</published><updated>2026-06-14T00:00:00+00:00</updated><id>https://gwaithimirdain.github.io/2026/06/14/opening-issues</id><content type="html" xml:base="https://gwaithimirdain.github.io/2026/06/14/opening-issues.html"><![CDATA[<p>I’ve been accumulating a lot of small ideas and to-dos on Narya for a while, mostly as notes to myself.  Today I took a bunch of those and made them into <a href="https://github.com/gwaithimirdain/narya/issues">Github issues</a>, for the benefit of anyone else who’d like to contribute to Narya and wants a place to get started.  There isn’t yet really a good “design document” or introduction to the Narya code, but I’m happy to talk with any potential contributors and answer questions.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I’ve been accumulating a lot of small ideas and to-dos on Narya for a while, mostly as notes to myself. Today I took a bunch of those and made them into Github issues, for the benefit of anyone else who’d like to contribute to Narya and wants a place to get started. There isn’t yet really a good “design document” or introduction to the Narya code, but I’m happy to talk with any potential contributors and answer questions.]]></summary></entry><entry><title type="html">Adventures with Agentic Coding</title><link href="https://gwaithimirdain.github.io/2026/06/12/adventures-with-ai.html" rel="alternate" type="text/html" title="Adventures with Agentic Coding" /><published>2026-06-12T00:00:00+00:00</published><updated>2026-06-12T00:00:00+00:00</updated><id>https://gwaithimirdain.github.io/2026/06/12/adventures-with-ai</id><content type="html" xml:base="https://gwaithimirdain.github.io/2026/06/12/adventures-with-ai.html"><![CDATA[<p>I’ve been slowly experimenting with agentic coding, using Gemini CLI (now “Antigravity”) and Claude Code.  It’s interesting to see in particular what they’re good at and what they’re not so good at.</p>

<p>They’re impressively good at GADT hacking, e.g. doing arithmetic on type-level nats and other stuff to ensure that types match up.  And the presence of the strong type information means that I’m more confident in the results they produce: in many cases as long as the function has the right type, and the OCaml compiler accepts it, and a cursory examination indicates that it has the correct recursive structure, I feel like I can be pretty sure it’s right without following all the type-level arithmetic myself.</p>

<p>However, they do like to try to circumvent the type system.  On one project Claude made lots of calls to <code class="language-plaintext highlighter-rouge">Obj.magic</code>, OCaml’s unsafe coercion function with type <code class="language-plaintext highlighter-rouge">'a -&gt; 'b</code> that doesn’t change the runtime representation but just tells the compiler “trust me”.  It claimed they were all fine because the runtime representations agree; it was obviously wrong.  It also liked to close off difficult cases with exceptions, claiming they were unreachable, when they obviously were not.  And it was very lazy: it kept telling me that what I wanted was too big of a project to accomplish “in one session” until I shouted at it that yes, it’s a big project, and I want you to GET STARTED ON IT.  I expect a more skilled AI-user would be able to guide it past these issues more smoothly, but I’m managing.</p>

<p>I mentioned in a previous post that it’s great for generating boilerplate, like functors between type-level free categories.  It’s also great for implementing little features that I’ve wanted to do for a while but never had the time, like <a href="https://github.com/gwaithimirdain/narya/pull/108">this pull request</a> that allows you to specify different “default variable names” for particular canonical types.  That one required understanding Narya’s parser and postprocessor as well as the readback and printing functions; I was impressed at how well Claude managed that.</p>

<p>For the moment, I’m keeping a tighter rein on parts of the code that involve serious mathematical understanding of the typechecking and normalization algorithm.  But I did ask Claude to help me past a sticky point in the implementation of normalization for modal type theory with nonparametric modalities, reimplementing a recursive function (that I originally wrote) to strip the keps off of an environment to handle intermediate operations in several different ways.  I haven’t yet successfully completed the implementation, but Claude’s contribution looks promising, although it does still have some issues that are work in progress.</p>

<p>In other news, Ambrus and Chaitanya are visiting this week and we’ve made some real progress on proving that the universe is fibrant in HOTT.  One of these days I’ll finally post about how that relates to modal type theory.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I’ve been slowly experimenting with agentic coding, using Gemini CLI (now “Antigravity”) and Claude Code. It’s interesting to see in particular what they’re good at and what they’re not so good at.]]></summary></entry><entry><title type="html">Modal datatypes and codatatypes</title><link href="https://gwaithimirdain.github.io/2026/06/02/modal-data-and-codata.html" rel="alternate" type="text/html" title="Modal datatypes and codatatypes" /><published>2026-06-02T00:00:00+00:00</published><updated>2026-06-02T00:00:00+00:00</updated><id>https://gwaithimirdain.github.io/2026/06/02/modal-data-and-codata</id><content type="html" xml:base="https://gwaithimirdain.github.io/2026/06/02/modal-data-and-codata.html"><![CDATA[<p>In multimodal Narya, the modal operators are not built in, but rather are defined by the user as special cases of modal datatypes and codata/record types (just as Σ-types, for instance, are user-defined as particular record types).  However, I’m running into some interesting questions of notation and typechecking when specifying these.</p>

<h2 id="modal-datatypes">Modal datatypes</h2>

<p>Like most type formers, modal operators can be classified as positive or negative.  The positive ones are simpler and were the only ones included in the original <a href="https://arxiv.org/abs/2011.15021">multimodal dependent type theory</a>.  If <code class="language-plaintext highlighter-rouge">□</code> is a positive modal operator, the idea is that a variable of type <code class="language-plaintext highlighter-rouge">□ A</code> can be “destructed” into an ordinary variable of type <code class="language-plaintext highlighter-rouge">A</code> that is annotated by the modality (corresponding to) <code class="language-plaintext highlighter-rouge">□</code>.  In premise-conclusion type theory syntax, this is written as follows (from the MTT paper):</p>

<p><img src="/images/positive-modal-simple-elim.png" alt="Image" /></p>

<p>The main thing to note is how we replace the element <code class="language-plaintext highlighter-rouge">M₀</code> of the modal type <code class="language-plaintext highlighter-rouge">⟨μ|A⟩</code> by a variable <code class="language-plaintext highlighter-rouge">y</code> that belongs to <code class="language-plaintext highlighter-rouge">A</code> but is annotated with the modality <code class="language-plaintext highlighter-rouge">μ</code>.</p>

<p>In multimodal Narya, positive modalities are not built-in, but can be defined by the user as a special case of a <em>datatype with modal constructor(s)</em>.  More generally, <em>any</em> of the arguments of a constructor can be annotated with a nontrivial modality, in which case when we match against that constructor, the pattern variable corresponding to that argument becomes similarly annotated.  For instance, here’s a definition of an ordinary positive modal operator:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def □ (A :□| Type) : Type ≔ data [
| box. (x :□| A) ]
</code></pre></div></div>

<p>There is some punning going on here: the <code class="language-plaintext highlighter-rouge">□</code> in the annotations <code class="language-plaintext highlighter-rouge">:□|</code> is the name of a modality (a morphism in the mode theory 2-category), which lives in a different namespace from user-defined constants, while the <code class="language-plaintext highlighter-rouge">□</code> being defined is a user-defined constant (a modal datatype).  I regard the possibility of such punning as a <em>good</em> argument for this notation for modal variable annotations, although of course it allows the user to shoot themselves in the foot by creating mismatches instead.</p>

<p>Given this definition, here is the proof that <code class="language-plaintext highlighter-rouge">□</code> is functorial:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def □map (A B :□| Type) (f :□| A → B) (u : □ A) : □ B ≔ match u [
| box. x ↦ box. (f x) ]
</code></pre></div></div>

<p>In the single branch of this match, the variable <code class="language-plaintext highlighter-rouge">x</code> is <code class="language-plaintext highlighter-rouge">□</code>-annotated.  Therefore, since <code class="language-plaintext highlighter-rouge">f</code> is also <code class="language-plaintext highlighter-rouge">□</code>-annotated, <code class="language-plaintext highlighter-rouge">f x</code> is also, and thus we can apply the constructor <code class="language-plaintext highlighter-rouge">box.</code> to it.  (More precisely, the argument of the constructor <code class="language-plaintext highlighter-rouge">box.</code> is checked in a <code class="language-plaintext highlighter-rouge">□</code>-locked context, and we can access <code class="language-plaintext highlighter-rouge">f</code> and <code class="language-plaintext highlighter-rouge">x</code> in that context because their annotation matches the lock.)</p>

<p>This is great, but to get all the desired behavior of modal operators in general we need a stronger version of their elimination rule:</p>

<p><img src="/images/positive-modal-elim.png" alt="Image" /></p>

<p>The difference here is that the term <code class="language-plaintext highlighter-rouge">M₀</code> in the modal type <code class="language-plaintext highlighter-rouge">⟨μ|A⟩</code> is in a context that’s locked by some other modality <code class="language-plaintext highlighter-rouge">ν</code>, and then the variable <code class="language-plaintext highlighter-rouge">x</code> that it’s destructed into is annotated by the composite modality <code class="language-plaintext highlighter-rouge">ν ∘ μ</code>.  This generality is necessary, for instance, when proving that modal types depend functorially on modes: if <code class="language-plaintext highlighter-rouge">μ</code> and <code class="language-plaintext highlighter-rouge">ν</code> are composable modalities, then to define a map from <code class="language-plaintext highlighter-rouge">⟨ ν | ⟨μ|A⟩ ⟩</code> to <code class="language-plaintext highlighter-rouge">⟨ ν ∘ μ | A ⟩</code> we first destruct on <code class="language-plaintext highlighter-rouge">ν</code>, and then we get an element of <code class="language-plaintext highlighter-rouge">⟨μ|A⟩</code> annotated by <code class="language-plaintext highlighter-rouge">ν</code> and we have to destruct that, requiring the above generalized elimination rule.  This extra modality <code class="language-plaintext highlighter-rouge">ν</code> is sometimes called a “window” modality because we “look through it” to find the modality <code class="language-plaintext highlighter-rouge">μ</code> to destruct.</p>

<p>The question is, (how) should the window modality should be notated in a Narya <code class="language-plaintext highlighter-rouge">match</code>?  If we are matching against a free variable, there is no need to notate it at all, since the context annotation on that variable must also be the window modality.  But Narya also allows matches on arbitrary terms, and in that case the term doesn’t determine the window modality; indeed, we can’t even <em>typecheck</em> the term until we <em>know</em> the window modality.</p>

<p>Here are some possibilities I can think of for notating a window modality <code class="language-plaintext highlighter-rouge">◇</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>match (M :◇| _) [ box. x ↦ ? ]
match M :◇| _ [ box. x ↦ ? ]
match M return (_ :◇| _) ↦ _ [ box. x ↦ ? ]
match M through ◇ [ box. x ↦ ? ]
match M under ◇ [ box. x ↦ ? ]
match M behind ◇ [ box. x ↦ ? ]
</code></pre></div></div>

<p>The first three don’t require any new keywords, only re-using the same variable annotation syntax.  (The <code class="language-plaintext highlighter-rouge">return</code> clause is necessary for a dependent match against a non-variable term anyway, and is allowed for non-dependent matches with a <code class="language-plaintext highlighter-rouge">_</code> as the output.)  These three syntaxes seem logical to me, but I think they’re also a bit ugly.  The discriminee of a match must synthesize a datatype, so under normal circumstances it would never need to be ascribed; so we can put a <code class="language-plaintext highlighter-rouge">_</code> there as the type, but that looks noisy.  We could allow putting the actual type there instead too, of course, but that would nearly always be redundant information.</p>

<p>The last three look less ugly to me, but they all involve introducing a new keyword such as <code class="language-plaintext highlighter-rouge">through</code>, <code class="language-plaintext highlighter-rouge">under</code>, or <code class="language-plaintext highlighter-rouge">behind</code>.</p>

<p>Anyone have any opinions?</p>

<h2 id="modal-codata-and-record-types">Modal codata and record types</h2>

<p>Negative modalities were introduced to an MTT-like theory in <a href="https://dl.acm.org/doi/10.1145/3514241">Modalities and Parametric Adjoints</a>, and appear to require more structure on the relevant modality.  The original paper used a parametric right adjoint, but using ordinary adjoints is simpler and covers the majority of examples, and that’s what I’m planning for multimodal Narya.  In this case, the rules as I wrote them in <a href="https://arxiv.org/abs/2303.02572">Semantics of multimodal adjoint type theory</a> are:</p>

<p><img src="/images/negative-modal-intro.png" alt="Image" /></p>

<p><img src="/images/negative-modal-elim.png" alt="Image" /></p>

<p>To be “sinister”, a modality <code class="language-plaintext highlighter-rouge">μ</code> must have a right adjoint <code class="language-plaintext highlighter-rouge">μ†</code>.  The first, introduction, rule, is then the same as that for the positive modality associated to <code class="language-plaintext highlighter-rouge">μ†</code>; but the elimination rule uses <code class="language-plaintext highlighter-rouge">μ</code> instead.</p>

<p>In multimodal Narya, negative modalities are also not built-in, but can be defined by the user as a special case of a <em>codata or record type with modal fields</em>.  And more generally, any of the fields of a codata or record type can be annotated by an adjunction of modalities.</p>

<p>But since there are two modalities in an adjunction, it’s already not entirely obvious how to notate the <em>definition</em> of such a codatatype.  If the adjunction is <code class="language-plaintext highlighter-rouge">♭ ⊣ ♯</code> as in spatial type theory, then we could annotate the “self variable” of the field by the left adjoint <code class="language-plaintext highlighter-rouge">♭</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def ♯ (A : Type) : Type ≔ codata [
| (x :♭| _) .unsharp : A ]
</code></pre></div></div>

<p>The annotation by <code class="language-plaintext highlighter-rouge">♭</code> makes sense because in the elimination rule, i.e. when projecting out the field of an element of <code class="language-plaintext highlighter-rouge">♯ A</code>, that element must be in a <code class="language-plaintext highlighter-rouge">♭</code>-locked context.  Annotating the self variable with its type is not currently allowed, but it would also be required if we wanted to implement “indexed codatatypes”.</p>

<p>However, we could instead try to annotate the <em>output</em> by the <em>right</em> adjoint:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def ♯ (A : Type) : Type ≔ codata [
| x .unsharp :♯| A ]
</code></pre></div></div>

<p>This annotation makes sense because in the <em>introduction</em> rule, i.e. when defining an element of <code class="language-plaintext highlighter-rouge">♯ A</code>, the value of <code class="language-plaintext highlighter-rouge">.usharp</code> must be defined in a <code class="language-plaintext highlighter-rouge">♯</code>-locked context, i.e. it “has type” <code class="language-plaintext highlighter-rouge">:♯| A</code>.</p>

<p>One problem with the second version is that the <code class="language-plaintext highlighter-rouge">:♯|</code> notation conflicts with the use of <code class="language-plaintext highlighter-rouge">|</code> to separate multiple fields of a codatatype, especially if the modality <code class="language-plaintext highlighter-rouge">♯</code> is replaced by a composite of named generating modalities, or has a longer name.  This seems like a poor reason to reject it in the abstract (since we could change that notation instead), but we need <em>some</em> reason to choose one or the other.</p>

<p>Anyway, there’s a more serious problem with modal codatatypes.  In Narya, field names are not scoped: when you write <code class="language-plaintext highlighter-rouge">x .fst</code>, Narya doesn’t know what codata or record type <code class="language-plaintext highlighter-rouge">fst</code> refers to until it synthesizes a type for <code class="language-plaintext highlighter-rouge">x</code> and inspects its definition.  This generally works perfectly with bidirectional type checking, and means you can freely use fields of the same name for different record types without any worry about whether they will conflict or which of them is “opened” at any time.  (Similarly, constructors don’t know what datatype they belong to, and hence must always be used in a checking context.)</p>

<p>However, in order to synthesize a type for <code class="language-plaintext highlighter-rouge">x</code>, Narya needs to know its <em>context</em>.  And in the case of a modal field, the element being projected out of is supposed to be typechecked/synthesized in a <em>locked</em> context.  So how should Narya figure out the modality to lock by?</p>

<p>One option would be to continue overloading modal ascription syntax:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(x :♭| _) .unsharp
</code></pre></div></div>

<p>This has the same pros and cons as the similar syntaxes for window modalities in <code class="language-plaintext highlighter-rouge">match</code>, above.</p>

<p>Another option would be to incorporate the modality in the name of the field:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>x .♭.unsharp
</code></pre></div></div>

<p>Adding information to field names (which never contain internal periods themselves) has some precedent in the syntax for higher coinductive types (and although that syntax is arguably ugly itself, I haven’t thought of anything better).  And we could also use this in defining modal codatatypes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def ♯ (A : Type) : Type ≔ codata [
| x .♭.unsharp : A ]
</code></pre></div></div>

<p>However, if we did this, there would be a potential conflict with higher coinduction: a field declaration like <code class="language-plaintext highlighter-rouge">.♭.ee</code> could mean either a <code class="language-plaintext highlighter-rouge">♭</code>-modal field named <code class="language-plaintext highlighter-rouge">ee</code> or an <code class="language-plaintext highlighter-rouge">ee</code>-dimensional higher field named <code class="language-plaintext highlighter-rouge">♭</code> (both dimension letters and modality names belong to different namespaces than field names).  The latter could be disambiguated by <code class="language-plaintext highlighter-rouge">.♭..e.e</code>, though, so we could default <code class="language-plaintext highlighter-rouge">.♭.ee</code> to mean the former (and I think actual ambiguity would be exceedingly unlikely to arise in practice).</p>

<p>Finally, another approach would be to try to synthesize “just enough” of <code class="language-plaintext highlighter-rouge">x</code> to find which codatatype it belongs to, and then inspect that to find the modality to lock its context to actually synthesize the whole thing.  However, in general “just enough” couldn’t be guaranteed to be any less work than synthesizing the whole thing, so it seems like this could add significant extra overhead to typechecking.</p>

<p>We could probably manage it so that if <code class="language-plaintext highlighter-rouge">x</code> synthesizes fully in an unlocked context (as for an ordinary non-modal field), it doesn’t need to be synthesized <em>twice</em>, so this wouldn’t impact performance of non-modal fields.  But if we did take this approach, it would probably be a good idea to also allow something like <code class="language-plaintext highlighter-rouge">(x :♭| _) .unsharp</code>, to allow the user to eliminate the double typechecking and improve performance with an explicit annotation.</p>

<p>Anyone have any thoughts?</p>]]></content><author><name></name></author><summary type="html"><![CDATA[In multimodal Narya, the modal operators are not built in, but rather are defined by the user as special cases of modal datatypes and codata/record types (just as Σ-types, for instance, are user-defined as particular record types). However, I’m running into some interesting questions of notation and typechecking when specifying these.]]></summary></entry><entry><title type="html">Well-scoped syntax and type-level categories</title><link href="https://gwaithimirdain.github.io/2026/05/30/well-scoped-syntax.html" rel="alternate" type="text/html" title="Well-scoped syntax and type-level categories" /><published>2026-05-30T00:00:00+00:00</published><updated>2026-05-30T00:00:00+00:00</updated><id>https://gwaithimirdain.github.io/2026/05/30/well-scoped-syntax</id><content type="html" xml:base="https://gwaithimirdain.github.io/2026/05/30/well-scoped-syntax.html"><![CDATA[<p>In coding Narya, one of my general principles is to use types as much as possible to eliminate the possibility of errors statically (that is, at compile time).  Which is, of course, what types are for, from a programming perspective.</p>

<p>In a fully dependently-typed language, it’s theoretically possible to eliminate <em>all</em> errors statically.  If you can write a sufficiently detailed type for a function that basically says “this function does exactly what it’s supposed to do”, then as soon as you define a function of that type that compiles successfully, you can be absolutely sure (at least insofar as you trust the compiler) that it will, in fact, do exactly what it’s supposed to do.  There are various problems with this theoretical dream, but I still find it useful as an ideal touchstone of typed programming.</p>

<p>One of the reasons we often can’t achieve that dream is that we may not be programming in a fully dependently typed language.  Whether it’s practical yet to write large-scale code in an actually dependently typed language is a discussion for another day; suffice it for now to say that at present I am not.  Narya is written in OCaml, a language which is in many ways a joy to program in, but it is not fully dependently typed: in general, types can’t depend on terms.  However, types can depend on <em>other types</em>, and the type system of OCaml is flexible enough that we can encode a whole separate layer of “type-level terms” for our types to depend on.</p>

<p>This is a well-known technique in OCaml (and other languages that support it, like Haskell).  I’m using it for lots of things in Narya, but one of the most important is <em>static scoping</em>.</p>

<h2 id="well-scoped-indices">Well-scoped indices</h2>

<p>Like many implementations of type theory, Narya represents variables (in typechecked terms) by <a href="https://en.wikipedia.org/wiki/De_Bruijn_index">De Bruijn indices</a>.  However, in Narya all De Bruijn indices are all <em>intrinsically well-scoped</em>.  That is, it’s impossible for the code to accidentally create a De Bruijn index that’s too large and points outside all the enclosing binders: the <em>OCaml</em> typechecker won’t allow it.</p>

<p>This is accomplished by indexing terms by a “phantom type” that records the length of their context, as a “type-level natural number”.  Here is how we encode the natural numbers as types:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">zero</span> <span class="o">=</span> <span class="n">private</span> <span class="nc">Dummy_zero</span>
<span class="k">type</span> <span class="k">'</span><span class="n">a</span> <span class="n">suc</span> <span class="o">=</span> <span class="n">private</span> <span class="nc">Dummy_suc</span>
</code></pre></div></div>

<p>(If you’re going to read this blog you should probably know at least basic <a href="https://ocaml.org/manual/5.4/index.html">OCaml syntax</a>.)  Thus, among the usual types <code class="language-plaintext highlighter-rouge">int</code>, <code class="language-plaintext highlighter-rouge">string</code> and so on, we now also have types <code class="language-plaintext highlighter-rouge">zero</code>, <code class="language-plaintext highlighter-rouge">zero suc</code>, <code class="language-plaintext highlighter-rouge">zero suc suc</code>, and so on, which we view as a copy of the natural numbers.  Now we can define the type of De Bruijn indices as a <a href="https://ocaml.org/manual/5.4/gadts-tutorial.html">GADT</a> depending on the length of their context:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">_</span> <span class="n">index</span> <span class="o">=</span>
  <span class="o">|</span> <span class="nc">Top</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">suc</span> <span class="n">index</span>
  <span class="o">|</span> <span class="nc">Pop</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">index</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">suc</span> <span class="n">index</span>
</code></pre></div></div>

<p>This is just the usual definition of the “finite types” in dependent type theory, but now with the indexing natural number being a <em>type</em> rather than an element of a type of natural numbers.  Thus we have the following indices:</p>

<ul>
  <li>there is nothing in <code class="language-plaintext highlighter-rouge">zero index</code></li>
  <li><code class="language-plaintext highlighter-rouge">Top : zero suc index</code></li>
  <li><code class="language-plaintext highlighter-rouge">Top : zero suc suc index</code> and <code class="language-plaintext highlighter-rouge">Pop Top : zero suc suc index</code></li>
  <li><code class="language-plaintext highlighter-rouge">Top : zero suc suc suc index</code> and <code class="language-plaintext highlighter-rouge">Pop Top : zero suc suc suc index</code> and <code class="language-plaintext highlighter-rouge">Pop (Pop Top) : zero suc suc suc index</code></li>
</ul>

<p>and so on.  Now when we define terms, we use these indices to denote variables:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">_</span> <span class="n">term</span> <span class="o">=</span>
  <span class="o">|</span> <span class="nc">Var</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="n">index</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">term</span>
  <span class="o">...</span>
</code></pre></div></div>

<p>and <em>voila</em>: it’s impossible for De Bruijn indices to be out of scope.</p>

<h2 id="dimension-indexed-indices">Dimension-indexed indices</h2>

<p>In fact, what what Narya actually does is more complicated than this.  In observational higher type theories, at least the way Narya implements them, each “variable” in the context can actually be a collection of variables forming a cube of some dimension.  An ordinary single variable is a zero-dimensional cube.  A one-dimensional cube consists (in the arity-2 case) of three variables: two endpoints and a path.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(x₀ : A) (x₁ : A) (x₂ : Id A x₀ x₁)
</code></pre></div></div>

<p>For instance, these are what we add to the context when defining an equality of functions in <code class="language-plaintext highlighter-rouge">Id (A → B) f₀ f₁</code>, since that identity type is (isomorphic to)</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(x₀ : A) (x₁ : A) (x₂ : Id A x₀ x₁) → Id B (f₀ x₀) (f₁ x₁)
</code></pre></div></div>

<p>When we write this on a page, or even in proof assistant code, we write the variables <code class="language-plaintext highlighter-rouge">x₀</code>, <code class="language-plaintext highlighter-rouge">x₁</code>, <code class="language-plaintext highlighter-rouge">x₂</code> in linear order, and usually think of simply abstracting over three ordinary variables one at a time.  But one of the insights that makes Narya work is that internally, it’s better to treat these three variables together as a “bundle”, using a data structure that associates them directly to the faces of a cube (more on that in some other post, maybe), and <em>only</em> linearize them at the boundary of concrete syntax when interacting with the user.  This eliminates a whole class of potential bugs that could arise from inconsistent ways to order the faces of a cube, and makes the logic more intuitive and geometric: you are always just iterating over the faces of a cube, with no need to think about the order in which it happens.</p>

<p>Now, however, when we put a whole cube of variables into the context at once, we need more information to access a single variable: not just a De Bruijn index, which points to the whole <em>cube</em>, but a <em>face</em> of that cube.  This introduces a new source of potential “scoping” errors: what if the face has the wrong dimension to match the cube in the context?</p>

<p>Fortunately, we can also eliminate this possibility statically.  We represent <em>dimensions</em> as types: for now you can just think of them as another copy of the type-level natural numbers above.  Faces of a cube are then indexed by their domain and codomain, for instance an element of <code class="language-plaintext highlighter-rouge">(zero suc, zero suc suc) face</code> is a 1-dimensional face of a 2-dimensional cube (of which there are four).  And the parameter of a term, that above was just the <em>length</em> of a context, now becomes a <em>type-level list of dimensions</em> – in fact, a <a href="https://github.com/RedPRL/ocaml-bwd">backwards list</a>.  Here are the constructors of type-level backwards lists:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">emp</span> <span class="o">=</span> <span class="n">private</span> <span class="nc">Dummy_emp</span>
<span class="k">type</span> <span class="p">(</span><span class="k">'</span><span class="n">xs</span><span class="o">,</span> <span class="k">'</span><span class="n">x</span><span class="p">)</span> <span class="n">snoc</span> <span class="o">=</span> <span class="n">private</span> <span class="nc">Dummy_snoc</span>
</code></pre></div></div>

<p>Thus, for instance, here is a type-level backwards list of type-level natural numbers corresponding to <code class="language-plaintext highlighter-rouge">[1; 2; 0]</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(((</span><span class="n">emp</span><span class="o">,</span> <span class="n">zero</span> <span class="n">suc</span><span class="p">)</span> <span class="n">snoc</span><span class="o">,</span> <span class="n">zero</span> <span class="n">suc</span> <span class="n">suc</span><span class="p">)</span> <span class="n">snoc</span><span class="o">,</span> <span class="n">zero</span><span class="p">)</span> <span class="n">snoc</span>
</code></pre></div></div>

<p>One final change is to parametrize the type of indices by the dimension that some index points to in a type-level backwards list of dimensions:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="p">(</span><span class="n">_</span><span class="o">,</span> <span class="n">_</span><span class="p">)</span> <span class="n">index</span> <span class="o">=</span>
  <span class="o">|</span> <span class="nc">Top</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">n</span><span class="o">,</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">n</span><span class="p">)</span> <span class="n">snoc</span><span class="p">)</span> <span class="n">index</span>
  <span class="o">|</span> <span class="nc">Pop</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">n</span><span class="o">,</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">index</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">n</span><span class="o">,</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span><span class="o">,</span> <span class="k">'</span><span class="n">k</span><span class="p">)</span> <span class="n">snoc</span><span class="p">)</span> <span class="n">index</span>
</code></pre></div></div>

<p>Now we can say the variables that appear in terms are determined by an index and a face <em>of the same dimension</em>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">_</span> <span class="n">term</span> <span class="o">=</span>
  <span class="o">|</span> <span class="nc">Var</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">n</span><span class="o">,</span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="n">index</span> <span class="o">*</span> <span class="p">(</span><span class="k">'</span><span class="n">k</span><span class="o">,</span> <span class="k">'</span><span class="n">n</span><span class="p">)</span> <span class="n">face</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="n">term</span>
  <span class="o">...</span>
</code></pre></div></div>

<p>and the OCaml typechecker will guarantee at compile-time that we never have a mismatch.  (This isn’t exactly what’s in the Narya code, but it’s the basic idea.)</p>

<h2 id="free-categories">Free categories</h2>

<p>My current project is to enhance Narya with <a href="https://arxiv.org/abs/2011.15021">Multimodal Type Theory</a>.  (I always intended to do that, but I’m doing it now because I have hopes that it will be useful in completing the implementation of HOTT; more on that later.)  In MTT, contexts aren’t built just out of typed variables, but also <em>locks</em> arising from modalities.  Specifically, each context has a <em>mode</em>, and if <code class="language-plaintext highlighter-rouge">μ : p → q</code> is a modality (mode morphism) then a <code class="language-plaintext highlighter-rouge">q</code>-context can be <code class="language-plaintext highlighter-rouge">μ</code>-locked to become a <code class="language-plaintext highlighter-rouge">p</code>-context.</p>

<p>Of course, this opens up yet another class of potential bugs: the source or target of a lock modality might not match up correctly.  And so, of course, I set out on a quest to eliminate such errors statically.</p>

<p>Actually – no, I didn’t, not to start with.  At first, I put in the locks without any static checking.  And I got things mostly working, but there were some weird bugs that I didn’t entirely understand, but which seemed like they had to do with putting the wrong modalities or modes in the wrong places.  So, instead of spending the time tracking down those specific bugs, I decided to rip everything apart and put in static modality-dependence in the context.  This had the result of not only squashing the bugs I can see now, but preventing any future such bugs from occurring.</p>

<p>This may seem like a strange way to code, but it’s worked for me numerous times.  In particular, when I do this, the bugs I started out looking at do, in fact, get fixed, even though it’s not always obvious to me exactly at what point I fixed them!  Sometimes I have a guess about more or less the point at which they got fixed, but other times I have no idea.  The latter is what happened with modal locks: to make the code compile under the new statically-typed locking regime, I had to rewrite enough of it more or less from scratch that I still have no idea where the bug was in the old version.  But it doesn’t matter, because the OCaml typechecker promises me that the new version is free of (those) bugs.</p>

<p>Personally, I find this kind of coding to be a joy.  Among other things, it means that I spend more of my time thinking about how things <em>should</em> work and doing them correctly, rather than trying to figure out why something <em>isn’t</em> working.  And it’s also very category-theoretic, which of course appeals to me as a category theorist.</p>

<p>Why is it category-theoretic?  I mean that partly in <a href="https://golem.ph.utexas.edu/category/2010/03/a_perspective_on_higher_catego.html">Tom Leinster’s sense</a> that doesn’t refer at all to categories: it solves problems in a conceptual way by moving to a higher level of generality.  But more concretely, this approach tends to incorporate more of the category-theoretic framework of mathematical descriptions of type theory into the implementation.</p>

<p>Static context locking is a case in point.  The parameter of a context should now be a type-level backwards list that contains two kinds of elements: dimensions, representing variables, and modalities, representing locks.  (Each modality, like each dimension, is itself represented by a type; I’ll say more in some other post about how that works.)  In addition, each dimension is <em>also</em> paired with a modality, because variables in the context can be “modally annotated”.</p>

<p>However, not any old list of (modality, dimension) pairs and modalities is a valid “context length”, because the modes have to match up: if <code class="language-plaintext highlighter-rouge">μ : p → q</code> is a modality, then only a <code class="language-plaintext highlighter-rouge">q</code>-context can be <code class="language-plaintext highlighter-rouge">μ</code>-locked, and likewise only a variable in a <code class="language-plaintext highlighter-rouge">q</code>-context can be annotated by <code class="language-plaintext highlighter-rouge">μ</code>.  Mathematically, these context lengths, or “type-level contexts”, are the <em>free category</em> generated by a <a href="https://ncatlab.org/nlab/show/quiver">quiver</a> whose vertices are the modes, and with two kinds of edges: each modality <code class="language-plaintext highlighter-rouge">μ : p → q</code> yields a “lock” edge from <code class="language-plaintext highlighter-rouge">p</code> to <code class="language-plaintext highlighter-rouge">q</code>, while each pair <code class="language-plaintext highlighter-rouge">(μ , n)</code> of a modality <code class="language-plaintext highlighter-rouge">μ : p → q</code> with a dimension <code class="language-plaintext highlighter-rouge">n</code> yields a “variable” edge from <code class="language-plaintext highlighter-rouge">q</code> to <code class="language-plaintext highlighter-rouge">q</code> (adding a variable doesn’t change the <em>mode</em> of the context).</p>

<p>The foundation of static context locking is, therefore, a library for type-level free categories.  Doing this right required a fair amount of boilerplate, but fortunately these days AI makes it easy to generate boilerplate.  <a href="https://github.com/gwaithimirdain/narya/blob/2d95834d220cd47ff400bb7ee2d9b3a7a9fb3e69/lib/util/category.ml">Here</a> is the definition of type-level categories, and <a href="https://github.com/gwaithimirdain/narya/blob/2d95834d220cd47ff400bb7ee2d9b3a7a9fb3e69/lib/util/path.ml">here</a> is the free category on a quiver (whose morphisms are “paths” of edges in the quiver).</p>

<p>Finally, as usual, once we have a good abstraction, it turns out to be useful for lots of things.  For example:</p>

<ul>
  <li>
    <p>When extending a context by a telescope, we “compose morphisms” in this “context category”.</p>
  </li>
  <li>
    <p>There is a functor from the category of modes to this “context category” which makes every modality into a lock.  Locking a context is then just composition with the image of some modality under this functor.  (This is less trivial than it seems because for technical reasons, the basic “lock” constructor actually just locks by a <em>generating</em> modality.)</p>
  </li>
  <li>
    <p>There is also a functor in the other direction that discards all variables and composes up the locks in a context.  This is important when looking up a variable in a context: we need the composite of all the locks to its right to know what sort of “key” 2-cell is required to unlock it.</p>
  </li>
</ul>

<p>Multimodal Narya is not yet ready for release, but to whet your appetite here is some code that now compiles under the mode theory for an idempotent comonad <code class="language-plaintext highlighter-rouge">□</code>, exhibiting the functoriality and monad operations:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def □ (A :□| Type) : Type ≔ data [ box. (x :□| A) ]

def □map (A B :□| Type) (f :□| A → B) : □ A → □ B ≔ [ box. x ↦ box. (f x) ]

def ε (A :□| Type) (u :□ A) : A ≔ match u [ box. x ↦ x ]

def △ (A :□| Type) (u :□ A) : □ (□ A) ≔ match u [ box. x ↦ box. (box. x) ]

def □ε∘△ (A :□| Type) (u :□ A) : Id (□ A) (□map (□ A) A (ε A) (△ A u)) u
  ≔ match u [ box. x ↦ refl (box. x) ]

def ε□∘△ (A :□| Type) (u :□ A) : Id (□ A) (ε (□ A) (△ A u)) u ≔ match u [
| box. x ↦ refl (box. x)]
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[In coding Narya, one of my general principles is to use types as much as possible to eliminate the possibility of errors statically (that is, at compile time). Which is, of course, what types are for, from a programming perspective.]]></summary></entry><entry><title type="html">Syntax for modal type theory</title><link href="https://gwaithimirdain.github.io/2026/05/22/modal-syntax.html" rel="alternate" type="text/html" title="Syntax for modal type theory" /><published>2026-05-22T00:00:00+00:00</published><updated>2026-05-22T00:00:00+00:00</updated><id>https://gwaithimirdain.github.io/2026/05/22/modal-syntax</id><content type="html" xml:base="https://gwaithimirdain.github.io/2026/05/22/modal-syntax.html"><![CDATA[<p>Let’s start the blog off by tempting <a href="https://wiki.haskell.org/Wadler%27s_Law">Wadler’s Law</a>!  (Actually, Wadler’s Law does not align with my experience.  Perhaps due to the now-well-known nature of the Law itself and the perjorative view it implies of discussions of syntax, I’ve found that hardly anyone else ever wants to talk about syntax at all!  At least I’m <a href="https://www.youtube.com/watch?v=kQjrcSMYpaA">not the only one</a>.)</p>

<p>My current project is to enhance Narya with Multimodal Type Theory.  (I always intended to do that, but I’m doing it now because I hope that it will be useful in completing the implementation of HOTT; more on that later.)  I’ll have more to say about the implementation later, but right now I’m pondering some questions of concrete syntax.</p>

<h2 id="syntax-for-modes">Syntax for modes</h2>

<p>In modern modal type theories, the modal behavior is parametrized by a <em>mode theory</em>: a 2-category whose objects are called <em>modes</em> and whose morphisms are called <em>mode morphisms</em> or <em>modalities</em>.  There is a separate ordinary type theory at each mode, and the modalities induce modal operators mapping between them: a modality <code class="language-plaintext highlighter-rouge">μ : p → q</code> can induce a modal operator taking <code class="language-plaintext highlighter-rouge">p</code>-types to <code class="language-plaintext highlighter-rouge">q</code>-types.</p>

<p>In the literature on modal type theory, one tends to find judgments annotated by mode, such as <code class="language-plaintext highlighter-rouge">Γ ⊢ A typeₚ</code> or <code class="language-plaintext highlighter-rouge">Γ ⊢ A type @ p</code>.  In the concrete syntax of a proof assistant (at least, of Narya), the only judgment the user interacts with is a typing judgment.  But it would be quite annoying to have to constantly write <code class="language-plaintext highlighter-rouge">x :ₚ A</code> or <code class="language-plaintext highlighter-rouge">x : A @ p</code>.</p>

<p>I think a workable solution to this is to say that the names of modes are the same as the names of the corresponding universes of types at that mode.  Thus, if there is only one mode, that mode is called <code class="language-plaintext highlighter-rouge">Type</code>.  If there is another mode of “discrete types”, as in <a href="https://doi.org/10.1017/S096012952510025X">dTT</a>, we could call that mode <code class="language-plaintext highlighter-rouge">Disc</code>, and so on.  Then when we use one of these universe-names, it indicates directly which mode the corresponding type lives at, and the modes of essentially all other judgments should be inferrable.</p>

<h2 id="syntax-for-modal-variables">Syntax for modal variables</h2>

<p>The way modern modal type theories incorporate the modalities is that some variables are <em>annotated</em> by some modality.  In general, a variable in a context or judgment at mode <code class="language-plaintext highlighter-rouge">q</code> can be annotated by a modality <code class="language-plaintext highlighter-rouge">μ : p → q</code>, in which case the type of that variable must live at mode <code class="language-plaintext highlighter-rouge">p</code> (in a context which is “locked” by the modality <code class="language-plaintext highlighter-rouge">μ</code>, but that’s a story for another day).  Semantically, such a variable has the covariant modal operator associated to <code class="language-plaintext highlighter-rouge">μ</code> applied to its type, but syntactically it is a judgmental structure used to give rules for that modal operator as a type former.</p>

<p>There are two ways that these modal annotations are generally denoted syntactically in the literature.  One is to modify the typing colon.  For example, in crisp/spatial type theory the modal variables are denoted <code class="language-plaintext highlighter-rouge">x ∷ A</code>.  The corresponding modal operator, say <code class="language-plaintext highlighter-rouge">♭</code>, can then be defined as an inductive type generated by a constructor with a modal argument:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def ♭ (A ∷ Type) : Type ≔ data [
| flat. (x ∷ A) : ♭ A ]
</code></pre></div></div>

<p>If there are multiple modalities, this style requires additional “kinds of colons”.  There are a limited number of Unicode colon-like operators, but of course we can make more by combining characters:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(x :~ A) (y :: B) (z :# C) (w ∷♯ D)
</code></pre></div></div>

<p>and so on.</p>

<p>This approach has the advantage of conciseness.  However, it has several drawbacks:</p>

<ul>
  <li>
    <p>There are, to be sure, an unbounded number of colon-operators, but it seems a little awkward to have to keep thinking of a new one whenever we need a new modality, and we’ll quickly run out of the nicest-looking ones like <code class="language-plaintext highlighter-rouge">∷</code>.</p>
  </li>
  <li>
    <p>Are <code class="language-plaintext highlighter-rouge">::</code> and <code class="language-plaintext highlighter-rouge">∷</code> two different operators, or is the second a Unicode-variation of the first?  What characters are allowed in a colon-operator?  Narya’s existing lexer allows <code class="language-plaintext highlighter-rouge">x:A</code> to mean <code class="language-plaintext highlighter-rouge">x : A</code> because <code class="language-plaintext highlighter-rouge">:</code> is an ASCII symbol while <code class="language-plaintext highlighter-rouge">x</code> and <code class="language-plaintext highlighter-rouge">A</code> are identifiers.  I don’t want to give that up, but without modification the same wouldn’t be true of <code class="language-plaintext highlighter-rouge">x∷A</code>, which would be inconsistent and surprising.</p>
  </li>
  <li>
    <p>We need a “name” to refer to each modality, for instance in error messages like <code class="language-plaintext highlighter-rouge">♯-annotated variable is inaccessible behind ♭ lock</code>.  We could use the colon-operators as names, like <code class="language-plaintext highlighter-rouge">:#-annotated variable is inaccessible behind ∷ lock</code>, but that looks a little ugly and could be hard to understand.  And if modalities had names that were different from their colon-operators, the user would have to remember which names go with which colons.</p>
  </li>
  <li>
    <p>Some mode theories are finitely <em>generated</em> but not finite, and the user (or whoever specifies the mode theory) can only be expected to explicitly give names or colon-operators to the generators.  If the lock is a composite of generators, then we need to be able to say something like <code class="language-plaintext highlighter-rouge">♯-annotated variable is inaccessible behind ♭ ♭ ♭ lock</code>, which looks even uglier and more confusing as <code class="language-plaintext highlighter-rouge">:#-annotated variable is inaccessible behind ∷ ∷ ∷ lock</code>.</p>
  </li>
</ul>

<p>The other syntax for modal annotations in the literature is the one in the <a href="https://arxiv.org/abs/2011.15021">MTT paper</a>, where the type is annotated with the modality and a bar operator: <code class="language-plaintext highlighter-rouge">x : (♭ | A)</code>.  This solves all of these problems since we can use a more natural “name” for the modality such as <code class="language-plaintext highlighter-rouge">♭</code>, and a sequence of names to represent a composite of modalities as in <code class="language-plaintext highlighter-rouge">x : (♭ ♭ | A)</code>.  I’ve resisted using this syntax on paper because I think it’s fairly verbose, but something like it may be unavoidable in an implementation.</p>

<p>I am thinking of simplifying it a bit by removing the parentheses around <code class="language-plaintext highlighter-rouge">♭ | A</code>, since in practice an annotated variable is always inside outer parentheses anyway, as for instance in a modal function type <code class="language-plaintext highlighter-rouge">(x : ♭ | A) → B x</code>.  And as long as the modality names are valid identifiers (not ASCII symbols), the Narya lexer would allow us to write this as <code class="language-plaintext highlighter-rouge">(x :♭| A) → B x</code>, which is not that far from the modified-colon approach.  I’m curious if anyone else has thoughts.</p>

<p>One specific thing to note is that the names of modalities would (I think) be a separate category from other identifiers.  In particular, even if <code class="language-plaintext highlighter-rouge">♭</code> is a modality name, one could also <code class="language-plaintext highlighter-rouge">def ♭</code> to mean something inside the theory, without risk of parsing ambiguity since the <code class="language-plaintext highlighter-rouge">:</code> and <code class="language-plaintext highlighter-rouge">|</code> always bracket a modality name.  Of course, defining <code class="language-plaintext highlighter-rouge">♭</code> to mean something totally different would be confusing, but we might very well want to use it for the modal operator defined as above:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def ♭ (A :♭| Type) : Type ≔ data [
| flat. (x :♭| A) : ♭ A ]
</code></pre></div></div>

<h2 id="syntax-for-key-operators">Syntax for key operators</h2>

<p>To <em>use</em> a modally annotated variable, you have to “unlock” it with a “key” that is a 2-cell in the mode 2-category.  In the simplest case, if <code class="language-plaintext highlighter-rouge">μ : p → p</code> is an endo-modality, then to use a variable <code class="language-plaintext highlighter-rouge">x :μ| A</code> you need to unlock it with a 2-cell <code class="language-plaintext highlighter-rouge">α : μ ⇒ idₚ</code>.  More generally, if <code class="language-plaintext highlighter-rouge">μ : p → q</code> then <code class="language-plaintext highlighter-rouge">x :μ| A</code> can be unlocked with <code class="language-plaintext highlighter-rouge">α : μ ⇒ ν</code> if the context is “locked” by <code class="language-plaintext highlighter-rouge">ν</code> (I’m omitting what that means because I want to concentrate right now on syntax).</p>

<p>Many mode theories are locally posetal, so such 2-cells are unique when they exist.  In that case, there’s no need for a syntax for them as they can be inferred automatically.  But for a general mode theory, there might be multiple parallel 2-cells, so the user needs a way to specify which they want to use for unlocking.</p>

<p>Key operators have some formal similarities to higher-dimensional degeneracies, so I’ve been tempted to use a superscript syntax for them akin to Narya’s existing <code class="language-plaintext highlighter-rouge">x⁽ᵉᵉ⁾</code> for degeneracies.  However, on balance I think this would be too unwieldy: the superscripts would get too long to be easily readable.</p>

<p>One simple approach is a function-like prefix notation, so for instance <code class="language-plaintext highlighter-rouge">α x</code> would mean <code class="language-plaintext highlighter-rouge">x</code> unlocked with the key 2-cell <code class="language-plaintext highlighter-rouge">α</code>.  (This also has a precedent in degeneracies, where <code class="language-plaintext highlighter-rouge">refl x</code> means the same as <code class="language-plaintext highlighter-rouge">x⁽ᵉ⁾</code>.)</p>

<p>In the case of a mode theory that’s finitely generated but not finite, where only the generating 2-cells have names, we need to derive names for the non-generating ones.  I think we can get away without syntax for “vertical” composites of 2-cells (along 1-cells), because the key action of such a composite is just a composite of actions, so we can just write <code class="language-plaintext highlighter-rouge">β (α x)</code> instead of having a syntax for composing <code class="language-plaintext highlighter-rouge">α</code> and <code class="language-plaintext highlighter-rouge">β</code>.</p>

<p>Notating horizontal composition (along 0-cells) is more tricky.  The simplest option would be something like <code class="language-plaintext highlighter-rouge">α β x</code>.  As long as the names of 2-cells are not also available as identifiers, I think this would be technically unambiguous — but it could still be confusing, with <code class="language-plaintext highlighter-rouge">α β f x</code> meaning “act on the function-variable <code class="language-plaintext highlighter-rouge">f</code> with the horizontal composite of 2-cells <code class="language-plaintext highlighter-rouge">α β</code>, then apply the result to the argument <code class="language-plaintext highlighter-rouge">x</code>”.</p>

<p>A more verbose option would be for all key actions to be labeled by some keyword or symbol, such as <code class="language-plaintext highlighter-rouge">key α x</code> or <code class="language-plaintext highlighter-rouge">🗝️ α x</code>.  Then the first argument would always be a 2-cell, perhaps including a parenthesized (horizontal) composite of generating 2-cells: <code class="language-plaintext highlighter-rouge">🗝️ (α β) x</code>.</p>

<p>We could combine the two options by saying that if the 2-cell argument is just a single generator, the key can be omitted: <code class="language-plaintext highlighter-rouge">α x</code> means <code class="language-plaintext highlighter-rouge">🗝️ α x</code>, but <code class="language-plaintext highlighter-rouge">🗝️ (α β) x</code> has no analogous abbreviated form.  However, unlike the pure-key option, this would require that the names of 2-cells can’t be re-used for user definitions.</p>

<p>I’m especially curious if anyone else has thoughts about this.  Which versions look best to you?  If we need a “key” label, do you prefer the keyword <code class="language-plaintext highlighter-rouge">key</code>, the symbol <code class="language-plaintext highlighter-rouge">🗝️</code> (or <code class="language-plaintext highlighter-rouge">🔑</code>), or something else?</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Let’s start the blog off by tempting Wadler’s Law! (Actually, Wadler’s Law does not align with my experience. Perhaps due to the now-well-known nature of the Law itself and the perjorative view it implies of discussions of syntax, I’ve found that hardly anyone else ever wants to talk about syntax at all! At least I’m not the only one.)]]></summary></entry><entry><title type="html">Welcome</title><link href="https://gwaithimirdain.github.io/2026/05/21/welcome.html" rel="alternate" type="text/html" title="Welcome" /><published>2026-05-21T00:00:00+00:00</published><updated>2026-05-21T00:00:00+00:00</updated><id>https://gwaithimirdain.github.io/2026/05/21/welcome</id><content type="html" xml:base="https://gwaithimirdain.github.io/2026/05/21/welcome.html"><![CDATA[<p>This is the Gwaith-i-Mírdain development blog.  I’m planning to post thoughts and plans here semi-regularly as I work on coding Narya and speculate about what I might do in the future.  Please comment and share your thoughts!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[This is the Gwaith-i-Mírdain development blog. I’m planning to post thoughts and plans here semi-regularly as I work on coding Narya and speculate about what I might do in the future. Please comment and share your thoughts!]]></summary></entry></feed>