The Liar’s paradox is often the first paradox someone dealing with logic, even in an informal setting, encounters. It is *intuitively* paradoxical: how can a sentence be both true, and false? This contradicts (ahem) the law of non-contradiction, that states that “no proposition is both true and false”, or, symbolically, $\neg (A \land \neg A)$. Appealing to symbols like that gives us warm fuzzy feelings, because, *of course, the algebra doesn’t lie!*

There’s a problem with that the appeal to symbols, though. And it’s nothing to do with non-contradiction: It’s to do with well-formedness. How do you accurately translate the “this sentence is false” sentence into a logical formula? We can try by giving it a name, say $L$ (for liar), and state that $L$ must represent some logical formula. Note that the equality symbol $=$ here is *not* a member of the logic we’re using to express $L$, it’s a symbol of this discourse. It’s *meta*logical.

$L = \dots$

But what should fill in the dots? $L$ is the sentence we’re symbolising, so “this sentence” must mean $L$. Saying “X is false” can be notated in a couple of equivalent ways, such as $\neg X$ or $X \to \bot$. We’ll go with the latter: it’s a surprise tool that will help us later. Now we know how to fill in the dots: It’s $L \to \bot$.

$A$ | $\neg A$ |
---|---|

$\top$ | $\bot$ |

$\bot$ | $\top$ |

$A$ | $A \to \bot$ |
---|---|

$\top$ | $\bot$ |

$\bot$ | $\top$ |

But wait. If $L = L \to \bot$, then $L = (L \to \bot) \to \bot$, and also $L = ((L \to \bot) \to \bot) \to \bot$, and so… forever. There is no finite, well-formed formula of first-order logic that represents the sentence “This sentence is false”, thus, assigning a truth value to it is meaningless: Saying “This sentence is false” is true is just as valid as saying that it’s false, both of those are as valid as saying “$\neg$ is true”.

Wait some more, though: we’re not done. It’s known, by the Curry-Howard isomorphism, that logical systems correspond to type systems. Therefore, if we can find a type-system that assigns a meaning to our sentence $L$, then there *must* exist a logical system that can express $L$, and so, we can decide its truth!

Even better, we don’t need to analyse the truth of $L$ logically, we can do it type-theoretically: if we can build an inhabitant of $L$, then it is true; If we can build an inhabitant of $\neg L$, then it’s false; And otherwise, I’m just not smart enough to do it.

So what is the smallest type system that lets us assign a meaning to $L$?

We do not need a complex type system to express $L$: a simple extension over the basic simply-typed lambda calculus $\lambda_{\to}$ will suffice. No fancy higher-ranked or dependent types here, sorry!

As a refresher, the simply-typed lambda calculus has *only*:

- A set of base types $\mathbb{B}$,
- Function types $\tau \to \sigma$,
- For each base type $b \in \mathbb{B}$, a set of base terms $\mathbb{T}_b$,
- Variables $v$,
- Lambda abstractions $\lambda v. e$, and
- Application $e\ e'$.

$\frac{x : \tau \in \Gamma}{\Gamma \vdash x : \tau}$

$\frac{b \in \mathbb{B} \quad x \in \mathbb{T}_{b}}{\Gamma \vdash x : b}$

$\frac{\Gamma, x : \sigma \vdash e : \tau}{\Gamma \vdash \lambda x. e : \sigma \to \tau}$

$\frac{\Gamma, e : \sigma \to \tau \quad \Gamma \vdash e' : \sigma}{\Gamma \vdash e\ e' : \tau}$

First of all, we’ll need a type to represent the logical proposition $\bot$. This type is empty: It has no type formers. Its elimination rule corresponds to the principle of explosion, and we write it $\mathtt{absurd}$. The inference rule:

$\frac{\Gamma \vdash e : \bot}{\mathtt{absurd}\ e : A}$

We’re almost there. What we need now is a type former that serves as a solution for equations of the form $v = ... v ...$. That’s right: we’re just *inventing* a solution to this class of equations—maths!

These are the *equirecursive* types, $\mu a. \tau$. The important part here is *equi*: these types are entirely indistinguishable from their unrollings. Formally, we extend the set of type formers with type variables $a$ and $\mu$-types $\mu a. \tau$, where $\mu a$ acts as a binder for $a$.

Since we invented $\mu$ types as a solution for equations of the form $a = \tau$, we have that $\mu a. \tau = \tau[\mu a.\tau/a]$, where $\tau[\sigma{}/a]$ means “substitute $\sigma{}$ everywhere $a$ occurs in $\tau$”. The typing rules express this identity, saying that anywhere a term might have one as a type, the other works too:

$\frac{\Gamma \vdash e : \tau[\mu a.\tau / a]}{\Gamma \vdash e : \mu a. \tau}$

$\frac{\Gamma \vdash e : \mu a.\tau}{\Gamma \vdash e : \tau[\mu a. \tau / a]}$

Adding these rules, along with the one for eliminating $\bot$, to the $\lambda_{\to}$ calculus nets us the system $\lambda_{\text{oh no}}$. With it, one can finally formulate a representation for our $L$-sentence: it’s $\mu a. a \to \bot$.

There exists a closed term of this type, namely $\lambda k. k\ k$, which means: The “this sentence is false”-sentence is true. We can check this fact ourselves, or, more likely, use a type checker that supports equirecursive types. For example, OCaml with the `-rectypes`

compiler option does.

We’ll first define the empty type `void`

and the type corresponding to $L$:

Now we can define our proof of $L$, called `yesl`

, and check that it has the expected type:

However. This same function is also a proof that… $\neg L$. Check it out:

Bertrand Russell (anecdotally) once proved, starting from $1 = 0$, that he was the Pope. I am also the Pope, as it turns out, since I have on hand a proof that $L$ and $\neg L$, in violation of non-contradiction; By transitivity, I am Bertrand Russell. $\blacksquare$

Alright, maybe I’m not Russell (drat). But I am, however, a trickster. I tricked you! You thought that this post was going to be about a self-referential sentence, but it was actually about typed programming language design (not very shocking, I know). It’s a demonstration of how recursive types (in any form) are logically inconsistent, and of how equirecursive types *are wrong*.

The logical inconsistency, we all deal with, on a daily basis. It comes with Turing completeness, and it annoys me to no end every single time I accidentally do `let x = ... x ...`

. I *really* wish I had a practical, total functional programming language to use for my day-to-day programming, and this non-termination *everywhere* is a great big blotch on Haskell’s claim of purity.

The kind of recursive types you get in Haskell is *fine*. They’re not *great* if you like the propositions-as-types interpretation, since it’s trivial to derive a contradiction from them, but they’re good enough for programming that implementing a positivity checker to ensure your definitions are strictly inductive isn’t generally worth the effort.

Unless your language claims to have “zero runtime errors”, in which case, if you implement isorecursive types instead of inductive types, you are *wrong*. See: Elm. God damn it.

Equirecursive types, however, are a totally different beast. They are *basically* useless. Sure, you might not have to write a couple of constructors, here and there… at the cost of *dramatically* increasing the set of incorrect programs that your type system accepts. Suddenly, typos will compile fine, and your program will just explode at runtime (more likely: fail to terminate). Isn’t this what type systems are meant to prevent?

Thankfully, very few languages implement equirecursive types. OCaml is the only one I know of, and it’s gated behind a compiler flag. However, that’s a footgun that should *not* be there.

The reason for the name will become obvious soon enough.↩︎

Continuations are a criminally underappreciated language feature. Very few languages (off the top of my head: most Scheme dialects, some Standard ML implementations, and Ruby) support the—already very expressive—undelimited continuations—the kind introduced by `call/cc`

—and even fewer implement the more expressive delimited continuations. Continuations (and tail recursion) can, in a first-class, functional way, express *all* local and non-local control features, and are an integral part of efficient implementations of algebraic effects systems, both as language features and (most importantly) as libraries.

Continuations, however, are notoriously hard to understand. In an informal way, we can say that a continuation is a first-class representation of the “future” of a computation. By this I do not mean a future in the sense of an asynchronous computation, but in a temporal sense. While descriptions like this are “correct” in some sense, they’re also not useful. What does it mean to store the “future” of a computation in a value?

Operationally, we can model continuations as just a segment of control stack. This model is perhaps better suited for comprehension by programmers familiar with the implementation details of imperative programming languages, which is decidedly not my target audience. Regardless, this is how continuations (especially the delimited kind) are generally presented.

With no offense to the authors of these articles, some of which I greatly respect as programming language designers and implementers, I do not think that this approach is very productive in a functional programming context. This reductionist, imperative view would almost be like explaining the *concept* of a proper tail call by using a trampoline, rather than presenting trampolines as one particular implementation strategy for the tail-call-ly challenged.

*Fig 1. A facsimile of a diagram you’d find attached to an explanation of delimited continuations. Newer frames on top.*

In a more functional context, then, I’d like to present the concept of continuation as a *reification* of an *evaluation context*. I stress that this presentation is not novel, though it is, perhaps, uncommon outside of the academic literature on continuations. Reification, here, is the normal English word. It’s a posh way of saying “to make into a thing”. For example, an `(eval)`

procedure is a *reification* of the language implementation—it’s an interpreter made into a thing, a thing that looks, walks and quacks like a procedure.

The idea of evaluation contexts, however, seems to have remained stuck in the ivory towers that the mainstream so often accuse us of inhabiting.

What *is* an evaluation context? Unhelpfully, the answer depends on the language we’re talking about. Since the language family you’re most likely to encounter continuations in is Scheme, we’ll use a Scheme-*like* language (not corresponding to any of the R^{K}RS standards). Our language has the standard set of things you’d find in a minimalist functional language used for an academic text: lambda expressions, written `(lambda arg exp)`

that close over variables in lexical scope; applications, also n-ary, written `(func arg )`

, `if`

-expressions written `(if condition then else)`

, with the `else`

expression being optional and defaulting to the false value `#f`

, integers, integer operations, and, of course, variables.

As an abbreviation one can write `(lambda (a . args) body)`

to mean `(lambda a (lambda args body))`

(recursively), and similarily for applications (associating to the left), but the language itself only has unary application and currying. This is in deviation from actual Scheme implementations which have complex parameter passing schemes, including variadic arguments (collecting any overflow in a list), keyword and optional arguments, etc. While all of these features are important for day-to-day programming, they do nothing but cloud the presentation here with needless verbosity.

The set of values in this miniScheme language inclues lambda expressions, the booleans `#t`

and `#f`

, and the integers; Every other expression can potentially take a step. Here, taking a step means applying a *reduction rule* in the language’s semantics, or finding a *congruence* rule that allows some sub-expression to suffer a reduction rule.

An example of reduction rule is β-reduction, which happens when the function being applied is a λ-abstraction and all of its arguments have been reduced to values. The rule, which says that an application of a lambda to a value can be reduced to its body in one step, is generally written in the notation of sequent calculus as below.

However, let’s use the Lisp notation above and write reduction rules as if they were code. The notation I’m using here is meant to evoke Redex, a tool for defining programming semantics implemented as a Racket language. Redex is really neat and I highly recommend it for anyone interested in studying programming language semantics formally.

These rules, standard though they may be, have a serious problem. Which, you might ask? They only apply when the expressions of interest are already fully evaluated. No rule matches for when the condition of an `if`

expression is a function application, or another conditional; The application rule, also, only applies when the argument has already been evaluated (it’s *call-by-value*, or *strict*). What can we do? Well, a simple and obvious solution is to specify *congruence* rules that let us reduce in places of interest.

Hopefully the problem should be clear. If it isn’t, consider adding binary operators for the field operations: Each of the 4 (addition, subtraction, multiplication, division) needs 2 congruence rules, one for reducing either argument, even though they each have a single reduction rule. In general, an N-ary operator will have N congruence rules, one for each of its N operands, but only one reduction rule!

The solution to this problem comes in the form of *evaluation contexts*. We can define a grammar of “expressions with holes”, generally written as $\operatorname{E}[\cdot]$, where the $\cdot$ stands for an arbitrary expression. In code, we’ll denote the hole with `<>`

, perhaps in evocation of the macros `cut`

and `cute`

from SRFI 26^{1}.

Our grammar of evaluation contexts, which we’ll call `E`

in accordance with tradition, looks like this:

Now we can write all our congruence rules by appealing to a much simpler, and most importantly, singular, *context rule*, that says reduction is legal anywhere in an evaluation context.

```
[(--> e v)
----------------------------------
(--> (in-hole E e) (in-hole E v)]
```

*Redex uses the notation (in-hole E e) to mean an evaluation context E with e “plugging” the hole <>.*

What do evaluation contexts actually have to do with programming language implementation, however? Well, if you squint a bit, and maybe introduce some parameters, evaluation contexts look a lot like what you’d find attached to an operation in…

I’m not good at structuring blog posts, please bear with me.

Continuation-passing style, also known as CPS, is a popular intermediate representation for functional language compilers. The goal of CPS is to make evaluation order explicit, and to implement complex control operations like loops, early returns, exceptions, coroutines and more in terms of only lambda abstraction. To achieve this, the language is stratified into two kinds of expressions, “complex” and “atomic”.

Atomic expressions, or atoms, are not radioactive or explosive: In fact, they’re quite the opposite! The Atoms of CPS are the values of our direct-style language. Forms like `#t`

, `#f`

, numbers and lambda expressions will not undergo any more evaluation, and thus may appear anywhere. Complex expressions are those that *do* cause evaluation, such as conditionals and procedure application.

To make sure that evaluation order is explicit, every complex expression has a *continuation* attached, which here boils down to a function which receives the return value of the expression. Procedures, instead of returning to a caller, will instead tail-call their continuation.

The grammar of our mini Scheme after it has gone CPS transformation is as follows:

Note that function application now has *three* components, but all of them are atoms. Valid expressions include things like `(f x halt)`

, which means “apply `f`

to `x`

such that it returns to `halt`

”, but do *not* include `(f (g x y) (h y z))`

, which have an ambiguous reduction order. Instead, we must write a λ-abstraction to give a name to the result of each intermediate computation.

For example, the (surface) language application `(e_1 e_2)`

, where both are complex expressions, has to be rewritten as either of the following expressions, which correspond respectively to evaluating the function first or the argument first. `(e_1 (lambda r1 (e_2 (lambda r2 (r1 r2))))`

If you have, at any point while reading the previous 2 or so paragraphs, squinted, then you already know where I’m going with this. If not, do it now.

The continuation of an expression corresponds to its evaluation context. The `v`

s in our discussion of semantics are the `atom`

s of CPS, and most importantly, contexts `E`

get closed over with a lambda expression `(lambda x E[x])`

, replacing the hole `<>`

with a bound variable `x`

. Evaluating an expression `(f x)`

in a context `E`

, say `(<> v)`

corresponds to `(f x (lambda x (x v)))`

.

If a language implementation uses the same representation for both user procedures and lambda expressions—which is **very** inefficient, let me stress—then we get first-class *control* for “free”. First-class in the sense that control operations, like `return`

, can be stored in variables, or lists, passed as arguments to procedures, etc. The fundamental first-class control operator for undelimited continuations is called `call-with-current-continuation`

, generally abbreviated to `call/cc`

.

Using `call/cc`

and a mutable cell holding a list we can implement cooperative threading. The `(yield)`

operator has the effect of capturing the current continuation and adding it to (the end) of the list, dequeueing a potentially *different* saved continuation from the list and jumping there instead.

```
(define threads '())
; Jump to the next thread (read: continuation) or exit the program
; if there are no more threads to schedule
(define exit
(let ((exit exit))
(lambda ()
(if (null? threads) ; are we out of threads to switch to?
(exit) ; if so, exit the program
(let ((thr (car threads))) ; select the first thread
(set! threads (cdr threads)) ; dequeue it
(thr)))))))) ; jump there
; Add a function to the list of threads. After finishing its work,
; the function needs to (exit) so another thread can take over.
(define (fork f)
(set! threads (append threads
(list (lambda () (f) (exit))))))
; Capture the current continuation, enqueue it, and switch to
; a different thread of execution.
(define (yield)
(call/cc (lambda (cc)
(set! threads (append threads (list cc)))
(exit))))
```

That’s a cooperative threading implementation in 25 lines of Scheme! If whichever implementation you are using has a performant `call/cc`

, this will correspond ropughly to the normal stack switching that a cooperative threading implementation has to do.

That last paragraph is a bit of a weasel, though. What’s a “performant call/cc” look like? Well, `call/cc`

continuations have *abortive* behaviour^{2}, which means they have the effect of *replacing* the current thread of control when invoked, instead of prepending a segment of stack—which is known as “functional continuations”. That is, it’s basically a spiffed up `longjmp`

, which in addition to saving the state of the registers, copies the call stack along with it.

However, `call/cc`

is a bit overkill for applications such as threads, and even then, it’s not the most powerful control abstraction. For one `call/cc`

*always* copies the entire continuation, with no way to *ahem* delimit it. Because of this, abstractions built on `call-with-current-continuation`

do not compose.

We can fix all of these problems, ironically enough, by adding *more* power. We instead introduce a *pair* operators, `prompt`

and `control`

, which…

This shtick again?

Delimited continuations are one of those rare ideas that happen once in a lifetime and revolutionise a field—maybe I’m exaggerating a bit. They, unfortunately, have not seen very widespread adoption. But they do have all the characteristics of one of those revolutionary ideas: they’re simple to explain, simple to implement, and very powerful.

The idea is obvious from the name, so much that it feels insulting to repeat it: instead of capturing the continuation, have a marker that *delimits* what’s going to be captured. What does this look like in our reduction semantics?

The syntax of evaluation contexts does not change, only the operations. We gain operations `(prompt e)`

and `(control k e)`

, with the idea being that when `(control k e)`

is invoked inside of a `(prompt)`

form, the evaluation context from the `control`

to the nearest outermost `prompt`

is reified as a function and bound to `k`

.

By not adding `(prompt E)`

to the grammar of evaluation contexts, we ensure that `E`

is devoid of any prompts by construction. This captures the intended semantics of “innermost enclosing prompt”—if `E`

were modified to include prompts, the second rule would instead capture to the *outermost* enclosing prompt, and we’re back to undelimited call/cc.

Note that `prompt`

and `control`

are not the only pair of delimited control operators! There’s also `shift`

and `reset`

(and `prompt0`

/`control0`

). `reset`

is basically the same thing as `prompt`

, but `shift`

is different from `control`

in that the captured continuation has the prompt—uh, the delimiter—reinstated, so that it cannot “escape”.

Yet another pair, which I personally prefer, is what you’d find in Guile’s `(ice-9 control)`

, namely `(call-with-prompt tag thunk handler)`

and `(abort-to-prompt tag value)`

. These are significantly more complex than bare `shift`

and `reset`

since they implement *multi-prompt* delimited continuations. They’re more like exception handlers than anything, with the addded power that your “exception handler” could restart the code after you `throw`

.

```
(define-syntax reset
(syntax-rules ()
((reset . body)
(call-with-prompt (default-prompt-tag)
(lambda () . body)
(lambda (cont f) (f cont))))))
(define-syntax shift
(syntax-rules ()
((shift k . body)
(abort-to-prompt (default-prompt-tag)
(lambda (cont)
((lambda (var) (reset . body))
(lambda vals (reset (apply cont vals))))))
```

`call-with-prompt`

and `abort-to-prompt`

subsume `shift`

and `reset`

.

Taken from the Guile Scheme implementation.

The operators `call-with-prompt`

and `abort-to-prompt`

are very convenient for the implementation of many control structures, like generators:

```
(define-syntax for/generator
(syntax-rules ()
((_ name gen . body)
(begin
(define (work cont)
(call-with-prompt
'generator-tag
cont
(lambda (cont name) (begin . body)
(work cont))))
(work gen)))))
(define (yield x) (abort-to-prompt 'generator-tag x))
(for/generator x (lambda ()
(yield 1)
(yield 2)
(yield 3))
(display x) (display #\newline))
```

Exception handlers and threading in terms of `shift`

and `reset`

are left as an exercise to the reader.

Control abstractions appeal to our—or at least mine—sense of beauty. Being able to implement control flow operations as part of the language is often touted as one of the superpowers that Haskell gets from its laziness and purity, and while that certainly is true, control operators let us model many, *many* more control flow abstractions, namely those involving non-local exits and entries.

Of course, all of these can be implemented in the language directly—JavaScript, for example, has `async`

/`await`

, stackless generators, *and* exceptions. However, this is not an advantage. These significantly complicate the implementation of the language (as opposed to having a single pair of operators that’s not much more complicated to implement than regular exception handlers) while also significantly *diminishing* its expressive power! For example, using our definition of generators above, the Scheme code on the right does what you expect, but the JavaScript code on the left only yields `20`

.

Delimited continuations can also be used to power the implementation of algebraic effects systems, such as those present in the language Koka, which much lower overhead (both in terms of code size and speed) than the type-driven local CPS transformation that Koka presently uses.

Language implementations which provide delimited control operators can also be extended with effect system support post-hoc, in a library, with an example being the Eff library for Haskell and the associated GHC proposal to add delimited control operators (`prompt`

and `control`

).

In reality it’s because I’m lazy and type setting so that it works properly both with KaTeX and with scripting disabled takes far too many keystrokes. Typing some code is easier.↩︎

People have pointed out to me that “abortive continuation” is a bit oxymoronic, but I guess it’s been brought to you by the same folks who brought you “clopen set”.↩︎

With Haskell now more popular than ever, a great deal of programmers deal with lazy evaluation in their daily lives. They’re aware of the pitfalls of lazy I/O, know not to use `foldl`

, and are masters at introducing bang patterns in the right place. But very few programmers know the magic behind lazy evaluation—graph reduction.

This post is an abridged adaptation of Simon Peyton Jones’ and David R. Lester’s book, *“Implementing Functional Languages: a tutorial.”*, itself a refinement of SPJ’s previous work, 1987’s *“The Implementation of Functional Programming Languages”*. The newer book doesn’t cover as much material as the previous: it focuses mostly on the evaluation of functional programs, and indeed that is our focus today as well. For this, it details three abstract machines: The G-machine, the Three Instruction Machine (affectionately called Tim), and a parallel G-machine.

In this post we’ll take a look first at a stack-based machine for reducing arithmetic expressions. Armed with the knowledge of how typical stack machines work, we’ll take a look at the G-machine, and how graph reduction works (and where the name comes from in the first place!)

This post is written as a Literate Haskell source file, with Cpp conditionals to enable/disable each section. To compile a specific section, use GHC like this:

Stack machines are the base for all of the computation models we’re going to explore today. To get a better feel of how they work, the first model of computation we’re going to describe is stack-based arithmetic, better known as reverse polish notation. This machine also forms the basis of the programming language FORTH. First, let us define a data type for arithmetic expressions, including the four basic operators (addition, multiplication, subtraction and division.)

```
data AExpr
= Lit Int
| Add AExpr AExpr
| Sub AExpr AExpr
| Mul AExpr AExpr
| Div AExpr AExpr
deriving (Eq, Show, Ord)
```

This language has an ‘obvious’ denotation, which can be realised using an interpreter function, such as `aInterpret`

below.

```
aInterpret :: AExpr -> Int
aInterpret (Lit n) = n
aInterpret (Add e1 e2) = aInterpret e1 + aInterpret e2
aInterpret (Sub e1 e2) = aInterpret e1 - aInterpret e2
aInterpret (Mul e1 e2) = aInterpret e1 * aInterpret e2
aInterpret (Div e1 e2) = aInterpret e1 `div` aInterpret e2
```

Alternatively, we can implement the language through its *operational* behaviour, by compiling it to a series of instructions that, when executed in an appropriate machine, leave it in a *final state* from which we can extract the expression’s result.

Our abstract machine for aritmethic will be a *stack* based machine with only a handful of instructions. The type of instructions is `AInstr`

.

The state of the machine is simply a pair, containing an instruction stream and a stack of values. By our compilation scheme, the machine is never in a state where more values are required on the stack than there are values present; This would not be the case if we let programmers directly write instruction streams.

We can compile a program into a sequence of instructions recursively.

```
aCompile :: AExpr -> [AInstr]
aCompile (Lit i) = [Push i]
aCompile (Add e1 e2) = aCompile e1 ++ aCompile e2 ++ [IAdd]
aCompile (Mul e1 e2) = aCompile e1 ++ aCompile e2 ++ [IMul]
aCompile (Sub e1 e2) = aCompile e1 ++ aCompile e2 ++ [ISub]
aCompile (Div e1 e2) = aCompile e1 ++ aCompile e2 ++ [IDiv]
```

And we can write a function to represent the state transition rules of the machine.

```
aEval :: ([AInstr], [Int]) -> ([AInstr], [Int])
aEval (Push i:xs, st) = (xs, i:st)
aEval (IAdd:xs, x:y:st) = (xs, (x + y):st)
aEval (IMul:xs, x:y:st) = (xs, (x * y):st)
aEval (ISub:xs, x:y:st) = (xs, (x - y):st)
aEval (IDiv:xs, x:y:st) = (xs, (x `div` y):st)
```

A state is said to be *final* when it has an empty instruction stream and a single result on the stack. To run a program, we simply repeat `aEval`

until a final state is reached.

```
aRun :: [AInstr] -> Int
aRun is = go (is, []) where
go st | Just i <- final st = i
go st = go (aEval st)
final ([], [n]) = Just n
final _ = Nothing
```

A very important property linking our compiler, abstract machine and interpreter together is that of *compiler correctness*. That is:

As an example, the arithmetic expression $2 + 3 \times 4$ produces the following code sequence:

You can interactively follow the execution of this program with the tool below. Pressing the Step button is equivalent to `aEval`

. The stack is drawn in boxes to the left, and the instruction sequence is presented on the right, where the `>`

marks the currently executing instruction (the “program counter”, if you will).

`amc-prove`

is a smallish tool to automatically prove (some) sentences of constructive quantifier-free^{1} first-order logic using the Amulet compiler’s capability to suggest replacements for typed holes.

In addition to printing whether or not it could determine the truthiness of the sentence, `amc-prove`

will also report the smallest proof term it could compute of that type.

- Function types
`P -> Q`

, corresponding to $P \to Q$ in the logic. - Product types
`P * Q`

, corresponding to $P \land Q$ in the logic. - Sum types
`P + Q`

, corresponding to $P \lor Q$ in the logic `tt`

and`ff`

correspond to $\top$ and $\bot$ respectively- The propositional bi-implication type
`P <-> Q`

stands for $P \iff Q$ and is interpreted as $P \to Q \land Q \to P$

Amulet will not attempt to pattern match on a sum type nested inside a product type. Concretely, this means having to replace $(P \lor Q) \land R \to S$ by $(P \lor Q) \to R \to S$ (currying).

`amc-prove`

’s support for negation and quantifiers is incredibly fiddly. There is a canonical empty type, `ff`

, but the negation connective `not P`

expands to `P -> forall 'a. 'a`

, since empty types aren’t properly supported. As a concrete example, take the double-negation of the law of excluded middle $\neg\neg(P \lor \neg{}P)$, which holds constructively.

If you enter the direct translation of that sentence as a type, `amc-prove`

will report that it couldn’t find a solution. However, by using `P -> ff`

instead of `not P`

, a solution is found.

```
? not (not (P + not P))
probably not.
? ((P + (P -> forall 'a. 'a)) -> forall 'a. 'a) -> forall 'a. 'a
probably not.
? ((P + (P -> ff)) -> ff) -> ff
yes.
fun f -> f (R (fun b -> f (L b)))
```

`amc-prove`

is bundled with the rest of the Amulet compiler on Github. You’ll need Stack to build. I recommend building with `stack build --fast`

since the compiler is rather large and `amc-prove`

does not benefit much from GHC’s optimisations.

```
% git clone https://github.com/tmpim/amc-prove.git
% cd amc-prove
% stack build --fast
% stack run amc-prove
Welcome to amc-prove.
?
```

Here’s a small demonstration of everything that works.

```
? P -> P
yes.
fun b -> b
? P -> Q -> P
yes.
fun a b -> a
? Q -> P -> P
yes.
fun a b -> b
? (P -> Q) * P -> Q
yes.
fun (h, x) -> h x
? P * Q -> P
yes.
fun (z, a) -> z
? P * Q -> Q
yes.
fun (z, a) -> a
? P -> Q -> P * Q
yes.
fun b c -> (b, c)
? P -> P + Q
yes.
fun y -> L y
? Q -> P + Q
yes.
fun y -> R y
? (P -> R) -> (Q -> R) -> P + Q -> R
yes.
fun g f -> function
| (L y) -> g y
| (R c) -> f c
? not (P * not P)
yes.
Not (fun (a, (Not h)) -> h a)
(* Note: Only one implication of DeMorgan's second law holds
constructively *)
? not (P + Q) <-> (not P) * (not Q)
yes.
(* Note: I have a marvellous term to prove this proposition,
but unfortunately it is too large to fit in this margin. *)
? (not P) + (not Q) -> not (P * Q)
yes.
function
| (L (Not f)) ->
Not (fun (a, b) -> f a)
| (R (Not g)) ->
Not (fun (y, z) -> g z)
```

You can find the proof term I redacted from DeMorgan’s first law here.

Technically, amc-prove “supports” the entire Amulet type system, which includes things like type-classes and rank-N types (it’s equal in expressive power to System F). However, the hole-filling logic is meant to aid the programmer while she codes, not exhaustively search for a solution, so it was written to fail early and fail fast instead of spending unbounded time searching for a solution that might not be there.↩︎

Amulet, unlike some other languages, has records figured out. Much like in ML (and PureScript), they are their own, first-class entities in the language as opposed to being syntax sugar for defining a product constructor and projection functions.

Being entities in the language, it’s logical to characterize them by their introduction and elimination judgements^{1}.

Records are introduced with record literals:

$\frac{\mathrm{\Gamma}\u22a2\stackrel{\u203e}{e\downarrow \tau}}{\mathrm{\Gamma}\u22a2\{\stackrel{\u203e}{\mathtt{x}=e}\}\downarrow \{\stackrel{\u203e}{\mathtt{x}:\tau}\}}$

And eliminated by projecting a single field:

$\frac{\mathrm{\Gamma}\u22a2r\downarrow \{\alpha \mathrm{\mid}\mathtt{x}:\tau \}}{\mathrm{\Gamma}\u22a2r\mathrm{.}\mathtt{x}\uparrow \tau}$

Records also support monomorphic update:

$\frac{\mathrm{\Gamma}\u22a2r\downarrow \{\alpha \mathrm{\mid}\mathtt{x}:\tau \}\phantom{\rule{1em}{0ex}}\mathrm{\Gamma}\u22a2e\downarrow \tau}{\mathrm{\Gamma}\u22a2\{r\text{}\mathtt{w}\mathtt{i}\mathtt{t}\mathtt{h}\text{}\mathtt{x}=e\}\downarrow \{\alpha \mathrm{\mid}\mathtt{x}:\tau \}}$

Unfortunately, the rather minimalistic vocabulary for talking about records makes them slightly worthless. There’s no way to extend a record, or to remove a key; Changing the type of a key is also forbidden, with the only workaround being enumerating all of the keys you *don’t* want to change.

And, rather amusingly, given the trash-talking I pulled in the first paragraph, updating nested records is still a nightmare.

```
> let my_record = { x = 1, y = { z = 3 } }
my_record : { x : int, y : { z : int } }
> { my_record with y = { my_record.y with z = 4 } }
_ = { x = 1, y = { z = 4 } }
```

Yikes. Can we do better?

Amulet recently learned how to cope with functional dependencies. Functional dependencies extend multi-param type classes by allowing the programmer to restrict the relationships between parameters. To summarize it rather terribly:

```
(* an arbitrary relationship between types *)
class r 'a 'b
(* a function between types *)
class f 'a 'b | 'a -> 'b
(* a one-to-one mapping *)
class o 'a 'b | 'a -> 'b, 'b -> 'a
```

As of today, Amulet knows the magic `row_cons`

type class, inspired by PureScript’s class of the same name.

```
class
row_cons 'record ('key : string) 'type 'new
| 'record 'key 'type -> 'new (* 1 *)
, 'new 'key -> 'record 'type (* 2 *)
begin
val extend_row : forall 'key -> 'type -> 'record -> 'new
val restrict_row : forall 'key -> 'new -> 'type * 'record
end
```

This class has built-in solving rules corresponding to the two functional dependencies:

- If the original
`record`

, the`key`

to be inserted, and its`type`

are all known, then the`new`

record can be solved for; - If both the
`key`

that was inserted, and the`new`

record, it is possible to solve for the old`record`

and the`type`

of the`key`

.

Note that rule 2 almost lets `row_cons`

be solved for in reverse. Indeed, this is expressed by the type of `restrict_row`

, which discovers both the `type`

and the original `record`

.

Using the `row_cons`

class and its magical methods…

- Records can be extended:

- Records can be restricted:

And, given a suitable framework of optics, records can be updated nicely:

It’s worth pointing out that making an optic that works for all fields, parametrised by a type-level string, is not easy or pretty, but it is work that only needs to be done once.

```
type optic 'p 'a 's <- 'p 'a 'a -> 'p 's 's
class
Amc.row_cons 'r 'k 't 'n
=> has_lens 'r 'k 't 'n
| 'k 'n -> 'r 't
begin
val rlens : strong 'p => proxy 'k -> optic 'p 't 'n
end
instance
Amc.known_string 'key
* Amc.row_cons 'record 'key 'type 'new
=> has_lens 'record 'key 'type 'new
begin
let rlens _ =
let view r =
let (x, _) = Amc.restrict_row @'key r
x
let set x r =
let (_, r') = Amc.restrict_row @'key r
Amc.extend_row @'key x r'
lens view set
end
let r
: forall 'key -> forall 'record 'type 'new 'p.
Amc.known_string 'key
* has_lens 'record 'key 'type 'new
* strong 'p
=> optic 'p 'type 'new =
fun x -> rlens @'record (Proxy : proxy 'key) x
```

Sorry for the short post, but that’s it for today.

Record fields $\mathtt{x}$ are typeset in monospaced font to make it apparent that they are unfortunately not first-class in the language, but rather part of the syntax. Since Amulet’s type system is inherently bidirectional, the judgement $\Gamma \vdash e \uparrow \tau$ represents type inference while $\Gamma \vdash e \downarrow \tau$ stands for type checking.↩︎

Compositional type-checking is a neat technique that I first saw in a paper by Olaf Chitil^{1}. He introduces a system of principal *typings*, as opposed to a system of principal *types*, as a way to address the bad type errors that many functional programming languages with type systems based on Hindley-Milner suffer from.

Today I want to present a small type checker for a core ML (with, notably, no data types or modules) based roughly on the ideas from that paper. This post is *almost* literate Haskell, but it’s not a complete program: it only implements the type checker. If you actually want to play with the language, grab the unabridged code here.

```
module Typings where
import qualified Data.Map.Merge.Strict as Map
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import Data.Foldable
import Data.List
import Data.Char
import Control.Monad.Except
```

We’ll begin, like always, by defining data structures for the language. Now, this is a bit against my style, but this system (which I shall call ML_{$\Delta$} - but only because it sounds cool) is not presented as a pure type system - there are separate grammars for terms and types. Assume that `Var`

is a suitable member of all the appropriate type classes.

```
data Exp
= Lam Var Exp
| App Exp Exp
| Use Var
| Let (Var, Exp) Exp
| Num Integer
deriving (Eq, Show, Ord)
data Type
= TyVar Var
| TyFun Type Type
| TyCon Var
deriving (Eq, Show, Ord)
```

ML_{$\Delta$} is *painfully* simple: It’s a lambda calculus extended with `Let`

since there needs to be a demonstration of recursion and polymorphism, and numbers so there can be a base type. It has no unusual features - in fact, it doesn’t have many features at all: no rank-N types, GADTs, type classes, row-polymorphic records, tuples or even algebraic data types.

I believe that a fully-featured programming language along the lines of Haskell could be shaped out of a type system like this, however I am not smart enough and could not find any prior literature on the topic. Sadly, it seems that compositional typings aren’t a very active area of research at all.

The novelty starts to show up when we define data to represent the different kinds of scopes that crop up. There are monomorphic $\Delta$-contexts, which assign *types* to names, and also polymorphic $\Gamma$-contexts, that assign *typings* to names instead. While we’re defining `newtype`

s over `Map`

s, let’s also get substitutions out of the way.

```
newtype Delta = Delta (Map.Map Var Type)
deriving (Eq, Ord, Semigroup, Monoid)
newtype Subst = Subst (Map.Map Var Type)
deriving (Eq, Show, Ord, Monoid)
newtype Gamma = Gamma (Map.Map Var Typing)
deriving (Eq, Show, Ord, Semigroup, Monoid)
```

The star of the show, of course, are the typings themselves. A typing is a pair of a (monomorphic) type $\tau$ and a $\Delta$-context, and in a way it packages both the type of an expression and the variables it’ll use from the scope.

With this, we’re ready to look at how inference proceeds for ML_{$\Delta$}. I make no effort at relating the rules implemented in code to anything except a vague idea of the rules in the paper: Those are complicated, especially since they deal with a language much more complicated than this humble calculus. In an effort not to embarrass myself, I’ll also not present anything “formal”.

```
infer :: Exp -- The expression we're computing a typing for
-> Gamma -- The Γ context
-> [Var] -- A supply of fresh variables
-> Subst -- The ambient substitution
-> Either TypeError ( Typing -- The typing
, [Var] -- New variables
, Subst -- New substitution
)
```

There are two cases when dealing with variables. Either a typing is present in the environment $\Gamma$, in which case we just use that with some retouching to make sure type variables aren’t repeated - this takes the place of instantiating type schemes in Hindley-Milner. However, a variable can also *not* be in the environment $\Gamma$, in which case we invent a fresh type variable $\alpha$^{2} for it and insist on the monomorphic typing $\{ v :: \alpha \} \vdash \alpha$.

```
infer (Use v) (Gamma env) (new:xs) sub =
case Map.lookup v env of
Just ty -> -- Use the typing that was looked up
pure ((\(a, b) -> (a, b, sub)) (refresh ty xs))
Nothing -> -- Make a new one!
let new_delta = Delta (Map.singleton v new_ty)
new_ty = TyVar new
in pure (Typing new_delta new_ty, xs, sub)
```

Interestingly, this allows for (principal!) typings to be given even to code containing free variables. The typing for the expression `x`

, for instance, is reported to be $\{ x :: \alpha \} \vdash \alpha$. Since this isn’t meant to be a compiler, there’s no handling for variables being out of scope, so the full inferred typings are printed on the REPL- err, RETL? A read-eval-type-loop!

```
> x
{ x :: a } ⊢ a
```

Moreover, this system does not have type schemes: Typings subsume those as well. Typings explicitly carry information regarding which type variables are polymorphic and which are constrained by something in the environment, avoiding a HM-like generalisation step.

```
where
refresh :: Typing -> [Var] -> (Typing, [Var])
refresh (Typing (Delta delta) tau) xs =
let tau_fv = Set.toList (ftv tau `Set.difference` foldMap ftv delta)
(used, xs') = splitAt (length tau_fv) xs
sub = Subst (Map.fromList (zip tau_fv (map TyVar used)))
in (Typing (applyDelta sub delta) (apply sub tau), xs')
```

`refresh`

is responsible for ML_{$\Delta$}’s analogue of instantiation: New, fresh type variables are invented for each type variable free in the type $\tau$ that is not also free in the context $\Delta$. Whether or not this is better than $\forall$ quantifiers is up for debate, but it is jolly neat.

The case for application might be the most interesting. We infer two typings $\Delta \vdash \tau$ and $\Delta' \vdash \sigma$ for the function and the argument respectively, then unify $\tau$ with $\sigma \to \alpha$ with $\alpha$ fresh.

```
infer (App f a) env (alpha:xs) sub = do
(Typing delta_f type_f, xs, sub) <- infer f env xs sub
(Typing delta_a type_a, xs, sub) <- infer a env xs sub
mgu <- unify (TyFun type_a (TyVar alpha)) type_f
```

This is enough to make sure that the expressions involved are compatible, but it does not ensure that the *contexts* attached are also compatible. So, the substitution is applied to both contexts and they are merged - variables present in one but not in the other are kept, and variables present in both have their types unified.

```
let delta_f' = applyDelta mgu delta_f
delta_a' = applyDelta mgu delta_a
delta_fa <- mergeDelta delta_f' delta_a'
pure (Typing delta_fa (apply mgu (TyVar alpha)), xs, sub <> mgu)
```

If a variable `x`

has, say, type `Bool`

in the function’s context but `Int`

in the argument’s context - that’s a type error, one which that can be very precisely reported as an inconsistency in the types `x`

is used at when trying to type some function application. This is *much* better than the HM approach, which would just claim the latter usage is wrong. There are three spans of interest, not one.

Inference for $\lambda$ abstractions is simple: We invent a fresh monomorphic typing for the bound variable, add it to the context when inferring a type for the body, then remove that one specifically from the typing of the body when creating one for the overall abstraction.

```
infer (Lam v b) (Gamma env) (alpha:xs) sub = do
let ty = TyVar alpha
mono_typing = Typing (Delta (Map.singleton v ty)) ty
new_env = Gamma (Map.insert v mono_typing env)
(Typing (Delta body_delta) body_ty, xs, sub) <- infer b new_env xs sub
let delta' = Delta (Map.delete v body_delta)
pure (Typing delta' (apply sub (TyFun ty body_ty)), xs, sub)
```

Care is taken to apply the ambient substitution to the type of the abstraction so that details learned about the bound variable inside the body will be reflected in the type. This could also be extracted from the typing of the body, I suppose, but *eh*.

`let`

s are very easy, especially since generalisation is implicit in the structure of typings. We simply compute a typing from the body, *reduce* it with respect to the let-bound variable, add it to the environment and infer a typing for the body.

```
infer (Let (var, exp) body) gamma@(Gamma env) xs sub = do
(exp_t, xs, sub) <- infer exp gamma xs sub
let exp_s = reduceTyping var exp_t
gamma' = Gamma (Map.insert var exp_s env)
infer body gamma' xs sub
```

Reduction w.r.t. a variable `x`

is a very simple operation that makes typings as polymorphic as possible, by deleting entries whose free type variables are disjoint with the overall type along with the entry for `x`

.

```
reduceTyping :: Var -> Typing -> Typing
reduceTyping x (Typing (Delta delta) tau) =
let tau_fv = ftv tau
delta' = Map.filter keep (Map.delete x delta)
keep sigma = not $ Set.null (ftv sigma `Set.intersection` tau_fv)
in Typing (Delta delta') tau
```

Parsing, error reporting and user interaction do not have interesting implementations, so I have chosen not to include them here.

Compositional typing is a very promising approach for languages with simple polymorphic type systems, in my opinion, because it presents a very cheap way of providing very accurate error messages much better than those of Haskell, OCaml and even Elm, a language for which good error messages are an explicit goal.

As an example of this, consider the expression `fun x -> if x (add x 0) 1`

(or, in Haskell, `\x -> if x then (x + (0 :: Int)) else (1 :: Int)`

- the type annotations are to emulate ML_{$\Delta$}’s insistence on monomorphic numbers).

```
Types Bool and Int aren't compatible
When checking that all uses of 'x' agree
When that checking 'if x' (of type e -> e -> e)
can be applied to 'add x 0' (of type Int)
Typing conflicts:
· x : Bool vs. Int
```

The error message generated here is much better than the one GHC reports, if you ask me. It points out not that x has some “actual” type distinct from its “expected” type, as HM would conclude from its left-to-right bias, but rather that two uses of `x`

aren’t compatible.

```
<interactive>:4:18: error:
• Couldn't match expected type ‘Int’ with actual type ‘Bool’
• In the expression: (x + 0 :: Int)
In the expression: if x then (x + 0 :: Int) else 0
In the expression: \ x -> if x then (x + 0 :: Int) else 0
```

Of course, the prototype doesn’t care for positions, so the error message is still not as good as it could be.

Perhaps it should be further investigated whether this approach scales to at least type classes (since a form of ad-hoc polymorphism is absolutely needed) and polymorphic records, so that it can be used in a real language. I have my doubts as to if a system like this could reasonably be extended to support rank-N types, since it does not have $\forall$ quantifiers.

**UPDATE**: I found out that extending a compositional typing system to support type classes is not only possible, it was also Gergő Érdi’s MSc. thesis!

**UPDATE**: Again! This is new. Anyway, I’ve cleaned up the code and thrown it up on GitHub.

Again, a full program implementing ML_{$\Delta$} is available here. Thank you for reading!

Olaf Chitil. 2001. Compositional explanation of types and algorithmic debugging of type errors. In Proceedings of the sixth ACM SIGPLAN international conference on Functional programming (ICFP ’01). ACM, New York, NY, USA, 193-204. DOI.↩︎

Since I couldn’t be arsed to set up monad transformers and all, we’re doing this the lazy way (ba dum tss): an infinite list of variables, and hand-rolled reader/state monads.↩︎

Jesus, it’s been a while. Though my last post was almost 6 months ago (give or take a few), I’ve been busy working on Amulet, which continues to grow, almost an eldritch abomination you try desperately, but fail, to kill.

Since my last post, Amulet has changed a ton, in noticeable and not-so-noticeable ways. Here are the major changes to the compiler since then.

No language is good to use if it’s inconvenient. So, in an effort to make writing code more convenient, we’ve removed the need for `;;`

after top-level declarations, and added a *bunch* of indentation sensitivity, thus making several keywords optional: `begin`

and `end`

are implicit in the body of a `fun`

, `match`

, or `let`

, which has made those keywords almost entirely obsolete. The body of a `let`

also need not be preceded by `in`

if meaning is clear from indentation.

To demonstrate, where you would have

One can now write

Moreover, we’ve added shorthand syntax for building and destructuring records: `{ x, y, z }`

is equivalent to `{ x = x, y = y, z = z }`

in both pattern and expression position.

Whereas `{ x with a = b }`

would extend the record `x`

to contain a new field `a`

(with value `b`

), it’s now *monomorphic update* of the record `x`

. That is: `x`

must *already* contain a field called `a`

, with the same type as `b`

.

This lets you write a function for updating a field in a record, such as the one below, which would previously be impossible. Supporting polymorphic update is not a priority, but it’d be nice to have. The way PureScript, another language with row-polymorphic records, implements polymorphic update does not fit in with our constraint based type system. A new type of constraint would have to be introduced specifically for this, which while not impossible, is certainly annoying.

The impossibility of supporting polymorphic update with regular subsumption constraints $a \le b$ stems from the fact that, when faced with such a constraint, the compiler must produce a coercion function that turns *any* $a$ into a $b$ *given the types alone*. This is possible for, say, field narrowing—just pick out the fields you want out of a bigger record—but not for update, since the compiler has no way of turning, for instance, an `int`

into a `string`

.

Changes to how we handle subsumption have made it possible to store polymorphic values in not only tuples, but also general records. For instance:

```
let foo = {
apply : forall 'a 'b. ('a -> 'b) -> 'a -> 'b =
fun x -> x
} (* : { apply : forall 'a 'b. ('a -> 'b) -> 'a -> 'b } *)
```

`foo`

is a record containing a single polymorphic application function. It can be used like so:

A feature I’ve desired for a while now, `let`

expressions (and declarations!) can have a pattern as their left-hand sides, as demonstrated above. These can be used for any ol’ type, including for cases where pattern matching would be refutable. I haven’t gotten around to actually implementing this yet, but in the future, pattern matching in `let`

s will be restricted to (arbitrary) product types only.

```
type option 'a = Some of 'a | None
type foo = Foo of { x : int }
let _ =
let Some x = ... (* forbidden *)
let Foo { x } = ... (* allowed *)
```

Even more “in-the-future”, if we ever get around to adding attributes like OCaml’s, the check for this could be bypassed by annotating the declaration with (say) a `[@partial]`

attribute.

Unfortunately, since Amulet *is* a strict language, these are a bit limited: They can not be recursive in *any* way, neither directly nor indirectly.

A verification pass is run over the AST if type-checking succeeds, to forbid illegal uses of recursion (strict language) and, as an additional convenience, warn when local variables go unused.

For instance, this is forbidden:

And this gives a warning:

Plans for this include termination (and/or productivity) (as a warning) and exhaustiveness checks (as an error).

`main`

Since pattern-matching `let`

s are allowed at top-level, there’s no more need for `main`

. Instead of

Just match on `()`

at top-level:

This gets rid of the (not so) subtle unsoundness introduced by the code generator having to figure out how to invoke `main`

, and the type checker not checking that `main`

has type `unit -> 'a`

, and also allows us to remove much of the silly special-casing around variables called `main`

.

A bit like Scala’s, these allow marking a function’s parameter as implicit and having the type checker find out what argument you meant automatically. Their design is based on a bit of reading other compiler code, and also the paper on modular implicits for OCaml. However, we do not have a ML-style module system at all (much to my dismay, but it’s being worked on), much less first class modules.

Implicit parameters allow ad-hoc overloading based on dictionary passing (like type classes, but with less inference).

```
type show 'a = Show of 'a -> string
let show ?(Show f) = f
let implicit show_string =
Show (fun x -> x)
let "foo" = show "foo"
```

Here, unification makes it known that `show`

is looking for an implicit argument of type `show string`

, and the only possibility is `show_string`

, which is what gets used.

There is a built-in type `lazy : type -> type`

, a function `force : forall 'a. lazy 'a -> 'a`

for turning a thunk back into a value, and a keyword `lazy`

that makes a thunk out of any expression. `lazy 'a`

and `'a`

are mutual subtypes of eachother, and the compiler inserts thunks/`force`

s where appropriate to make code type-check.

```
let x && y = if x then force y else false
let () =
false && launch_the_missiles ()
(* no missiles were launched in the execution of this program *)
```

Literal patterns are allowed for all types, and they’re tested of using

`(==)`

.Amulet only has one type constructor in its AST for all its kinds of functions:

`forall 'a. 'a -> int`

,`int -> string`

and`show string => unit`

are all represented the same internally and disambiguated by dependency/visibility flags.Polymorphic recursion is checked using “outline types”, computed before fully-fledged inference kicks in based solely on the shape of values. This lets us handle the function below without an annotation on its return type by computing that

`{ count = 1 }`

*must*have type`{ count : int }`

beforehand.Combined with the annotation on

`x`

, this gives us a “full” type signature, which lets us use checking for`size`

, allowing polymorphic recursion to happen.

```
type nested 'a = Nested of nested ('a * 'a) * nested ('a * 'a) | One of 'a
let size (x : nested 'a) =
match x with
| One _ -> { count = 1 }
| Nested (a, _) -> { count = 2 * (size a).count }
```

The newtype elimination pass was rewritten once and, unfortunately, disabled, since it was broken with some touchy code.

Operator declarations like

`let 2 + 2 = 5 in 2 + 2`

are admissible.Sanity of optimisations is checked at runtime by running a type checker over the intermediate language programs after all optimisations

Local opens are allowed, with two syntaxes: Either

`M.( x + y)`

(or`M.{ a, b }`

) or`let open M in x + y`

.Amulet is inconsistent in some more ways, such as

`type : type`

holding.There are no more kinds.

This post was a bit short, and also a bit hurried. Some of the features here deserve better explanations, but I felt like giving them an *outline* (haha) would be better than leaving the blag to rot (yet again).

Watch out for a blog post regarding (at *least*) implicit parameters, which will definitely involve the changes to subtyping involving records/tuples.