Practically any paper I read has a table that looks something like this:
{{<figuresrc="rules.png"caption="Inference rules from [Logarithm and program testing](https://dl.acm.org/doi/abs/10.1145/3498726) by Kuen-Bang Hou (Favonia) and Zhuyang Wang"class="fullwide">}}
And I, for one, love it! They're a precise and concise way to describe static
and dynamic behavior of programs. I might've written this elsewhere on the blog,
but whenever I read a paper, my eyes search for the rules first and foremost.
But to those just starting their PL journey, inference rules can be quite cryptic
-- I know they were to me! The first level of difficulty are the symbols: we have
lots of Greek (\\(\\Gamma\\) and \\(\\Delta\\) for environments, \\(\\tau\\) and perhaps \\(\\sigma\\)
for types), and the occasional mathematical symbol (the "entails" symbol \\(\\vdash\\) is the most
common, but for operational semantics we can have \\(\\leadsto\\) and \\(\\Downarrow\\)).
If you don't know what they mean, or if you're still getting used to them, symbols
in judgements are difficult enough to parse.
The second level of difficulty is making sense of the individual rules:
sense of one rule can be challenging. The following rule from the Calculus of
Inductive Constructions is a doozy, for instance.
{{<figuresrc="CIC.png"caption="The `match` inference rule from [Introduction to the Calculus of Inductive Constructions](https://inria.hal.science/hal-01094195/document) by Christine Paulin-Mohring"class="fullwide">}}
Just look at the metavariables! We have \\(\\textit{pars}\\), \\(t_1\\) through \\(t_p\\),
\\(x_1\\) through \\(x_n\\), plain \\(x\\), and at least two other sets of variables. Not
only this, but the rule requires at least some familiarity with [GADTs](https://en.wikipedia.org/wiki/Generalized_algebraic_data_type) to understand
completely.
The third level is making sense of how the rules work, _together_. In my
programming languages class in college, a familiar question was:
> the [Hindley-Milner type system](https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system)
> supports let-polymorphism only. What is it about the rules that implies let-polymorphism,
> and not any other kind of polymorphism?
If you don't know the answer, or the question doesn't make sense do you, don't
worry about it -- suffice to say that whole systems of inference rules exhibit
certain behaviors, and it takes familiarity with several rules to spot these
behaviors.
### Seeing What Works and What Doesn't
Maybe I'm just a tinker-y sort of person, but for me, teaching inference rules
just by showing them is not really enough. For instance, let me show you two
There's something to be said about the conversion between Bergamot's rules,
encoded as plain text, and pretty LaTeX-based inference rules that the users
see. Crucially, __we don't want to hardcode how any particular Bergamot
expression is rendered__. For one, this is a losing battle: we can't possibly
keep up with all the notation that people use in PL literature, and even if
we focused ourselves on only "beginner" notation, there wouldn't be one way to do it!
Different PL papers and texts use slightly different variations of notation.
For instance, I render my pairs as \\((a, b)\\), but the very first screenshot
in this post demonstrates a PL paper that writes pairs as \\(\\langle a, b \\rangle\\).
Neither way (as far as I know!) is right or wrong. But if we hardcode one,
we lose the ability to draw the other.
More broadly, one aspect about writing PL rules is that _we control the notation_.
We are free to define shorthands, symbols, and anything else that would make
reading our rules easier for others. As an example, a paper from POPL22 about
programming language semantics with garbage collection used a literal trash
symbol in their rules:
{{<figuresrc="trashrule.png"caption="A rule that uses a trashcan icon as notation, from [A separation logic for heap space under garbage collection](https://dl.acm.org/doi/10.1145/3498672) by Jean-Marie Madiot and François Pottier">}}
Thus, what I want to do is __encourage the (responsible) introduction of new notation__.
This can only be done if Bergamot itself supports custom notation.
When thinking about how I'd like to implement this custom notation, I was imagining
some sort of templated rule engine, that would define how each term in a Bergamot
program can be converted to its LaTeX variant. But then I realized: Bergamot
is already a rule engine! Instead of inventing yet another language or format
for defining LaTeX pretty printing, I could just use Bergamot. This turned
out to work quite nicely -- the "Presentation Rules" tab in the demo above
should open a text editor with Bergamot rules that handle the conversion
of Bergamot notation into LaTex. Here are some example rules: