Skip to content

Instantly share code, notes, and snippets.

@VictorTaelin
Created January 21, 2025 23:56
Show Gist options
  • Save VictorTaelin/964911cef2f3b1785f07cc7822bdc1e1 to your computer and use it in GitHub Desktop.
Save VictorTaelin/964911cef2f3b1785f07cc7822bdc1e1 to your computer and use it in GitHub Desktop.
my prompt
I'm going to share my research insights with you. The info below MIT-licensed.
First, some background information and context.
# The Interaction Calculus - An Introduction
The Interaction Calculus is very similar to the Lambda Calculus, except:
1. All variables are Affine (meaning they can't be occur more than once).
2. All variables can occur globally (no "closed scopes").
3. There is a new primitive, the "Superposition", that enables:
- A. A limited, but still powerful, form of cloning.
- B. Computing on superposed structures, for optimization.
This calculus has its roots on the optimal λ-calculus evaluation literature, and
it can be seen as a distillation of the optimal evaluation algorithm, into a new
core theory that is independent from the λ-calculus. Its AST is:
type Term =
| Var (nam: Name)
| Lam (nam: Name) (bod: Term)
| App (fun: Term) (arg: Term)
| Sup (fst: Term) (snd: Term)
| Dup (a: Term) (b: Term) (val: Term) (bod: Term)
And below is its syntax:
Term ::=
| var: x
| lam: "λ" Name "." Term
| app: "(" Term Term ")"
| sup: "{" Term Term "}"
| dup: "!" "{" x y "}" "=" Term ";" Term
Note that, just like a lambda is eliminated by an application, a superposition
is eliminated by a duplication. The interactions go as expected:
(λx.body arg)
----- APP-LAM
x <- body
arg
!{p q} = {r s};
body
----- DUP-SUP
a <- fst
b <- snd
body
Note that, here, x <- body performs a *global* substitution (since x can
occur outside of its binding lambda's body!). Note also that the DUP-SUP rule is
very similar to a tuple projection, except, again, the substitutions are global.
Now, you may be wondering: what happens when we try to apply a SUP, or duplicate
a LAM? Naively, one could assume these would be type errors. Here, we actually
define these interactions as:
({u v} a)
---------------- APP-SUP
! {x0 x1} = a;
{(u x0) (v x1)}
! {p q} = λx f;
body
--------------- DUP-LAM
p <- λx0.r
q <- λx1.s
x <- {x0 x1}
! {r s} = f;
body
Note that x0, x1, r, s are new, fresh variables. Defining these (otherwise
undefined) cases is interesting, because they allow us to do some powerful new
things. The DUP-LAM rule gives us back the ability to clone lambdas (otherwise
impossible in an affine language), and it also lets us do so in a very efficient
way, as will become clear later. And the APP-SUP rule allows us to do something
entirely new: apply superposed functions to a value, which enables us to explore
the image of a function very efficiently.
Note that, for improved readability, it is usually a good idea to represent a term
by first writing all DUP nodes, followed by a DUP-free body. Example:
! {x0 x1} = some_term
! {y0 y1} = other_term
...
dup_free_term
Below is an example reduction, in small step sequent notation:
(λf.λx.(f x) λk.k)
----- APP-LAM #0
λx(λk.k x)
----- APP-LAM #1
λx.x
Below is a more complex example:
!{f0 f1} = f;
(λf.λx.(f0 (f1 x)) λa.λb.(b a))
----- APP-LAM #0
!{f0 f1} = λa.λb.(b a);
λx.(f0 (f1 x))
----- DUP-LAM #1
!{f0 f1} = λb.(b {a0 a1});
λx.(λa0.f0 (λa1.f1 x))
----- DUP-LAM #2
!{f0 f1} = ({b0 b1} {a0 a1});
λx.(λa0.λb0.f0 (λa1.λb1.f1 x))
----- APP-SUP #3
!{k0 k1} = {a0 a1}
!{f0 f1} = {(b0 k0) (b1 k1)};
λx.(λa0.λb0.f0 (λa1.λb1.f1 x))
----- DUP-SUP #4
!{f0 f1} = {(b0 a0) (b1 a1)};
λx.(λa0.λb0.f0 (λa1.λb1.f1 x))
----- DUP-SUP #5
λx.(λa0.λb0.(b0 a0) (λa1.λb1.(b1 a1) x))
----- APP-LAM #6
λx.(λa0.λb0.(b0 a0) λb1.(b1 x))
----- APP-LAM #7
λx.λb0.(b0 λb1.(b1 x))
In special, notice how, on steps #1 and #2, the variables a0, a1, b0,
b1, temporarily occurred outside of their bound λ's bodies. In this format,
substitutions, like [x <- term], are very simple to compute: we literally just
replace the variable x by term, wherever they occur (even if outside of the
lambda that binds x). This happens, for example, on step #0. Substitution here
is simpler than on the traditional λ-calculus, since variables are affine, so,
we don't need to perform expensive copying operations; the only surprising bit
is that x can occur outside, but, while unusual, that's actually
straightforward, and substitutions are a simple O(1) swap. Now, despite these
"temporarily scaping scopes", at the end of the computation, the final result is
still a valid λ-term. That is common in this system, and that effect is
responsible for its optimality!
TASK: implement a TypeScript evaluator for the Interaction Calculus. As the
input, it must receive a string in the Interaction Calculus. As the output, it
must return its normal form and interaction count. Don't use classes or objects.
Instead, represent ADTs like {$:"Cons",head,tail}, and represent substitution
maps using plain objects.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment