136 lines
4 KiB
Text
136 lines
4 KiB
Text
|
#import "../common.typ": *
|
||
|
|
||
|
= Adjoint Functional Programming
|
||
|
|
||
|
== Lecture 1. Linear functional programming
|
||
|
|
||
|
- Origins of linearity is from linear logic
|
||
|
- from 1987 or before
|
||
|
|
||
|
#let isValue(x) = $#x "value"$
|
||
|
|
||
|
Language to be studied is called "snax"
|
||
|
- Features
|
||
|
- Substructural programming
|
||
|
- Inference
|
||
|
- Overloading? writing the same function that works both linearly and non-linearly
|
||
|
- "Proof-theoretic compiler"
|
||
|
- Everything up til the target C is either natural deduction, sequent calculus, etc.
|
||
|
- Types
|
||
|
- "if there's something you don't need in the language, don't put it in the language"
|
||
|
- no empty type
|
||
|
- unit type
|
||
|
- 1
|
||
|
- #tree(axi[], uni[$() : 1$])
|
||
|
- #tree(axi[], uni[$isValue(())$])
|
||
|
- +
|
||
|
- ${e : A}{"inl" e : A + B}$
|
||
|
- ${e : B}{"inr" e : A + B}$
|
||
|
- #tree(axi[$isValue("v")$], uni[$isValue("inl" v)$])
|
||
|
- #tree(axi[$isValue("v")$], uni[$isValue("inr" v)$])
|
||
|
- $A + B :equiv +\{"inl":A, "inr": B\}$
|
||
|
- label set $L eq.not emptyset$ , finite
|
||
|
- finiteness is important
|
||
|
- nat = $+\{ "zero" : 1 , "succ" : "nat" \}$
|
||
|
- recursion is required to define an infinite amount of things
|
||
|
- "equirecursive" types
|
||
|
- nat is "equal" to the recursive definition in some sense
|
||
|
- k
|
||
|
- $\{(k \in l) e : A_e}{k(e) : +\{l : A_l\}_{l : L}}$
|
||
|
- $ceil(0) = "zero" ()$
|
||
|
|
||
|
- Computational rules
|
||
|
|
||
|
Define negation of booleans
|
||
|
|
||
|
$"not" (x : "bool") : "bool" \
|
||
|
"not" x = "match" x "with" \
|
||
|
| "true" u arrow.r "false" u\
|
||
|
| "false" u arrow.r "true" u \
|
||
|
$
|
||
|
|
||
|
Function definitiosn like these operate at the meta level.
|
||
|
Avoid introducing function types today.
|
||
|
|
||
|
Linear functional programming means *every variable must be used exactly once.*
|
||
|
|
||
|
*NOTE:* Above cannot be defined $"true" u arrow.r "false" ()$, since $u$ is not used.
|
||
|
|
||
|
DOn't need a garbage collector for linear functional programming language
|
||
|
|
||
|
#tree(
|
||
|
axi[$e : +{l : A_l}_(l in L)$],
|
||
|
axi[$x : A_l$],
|
||
|
axi[$tack.r e_l : C(l in L)$],
|
||
|
nary(3)[$"match" e "with" (l(x) arrow.r.double e_l)_(l in L) : C$],
|
||
|
)
|
||
|
|
||
|
Cannot write a function $"duplicate"(x) = (x , x)$ because of linear programming
|
||
|
|
||
|
#tree(
|
||
|
axi[$Delta tack.r e_1: A$],
|
||
|
axi[$Gamma tack.r e_2 : B$],
|
||
|
bin[$Delta,Gamma tack.r (e_1, e_2) : A times B$],
|
||
|
)
|
||
|
|
||
|
so introducing contexts
|
||
|
|
||
|
$Gamma :equiv (dot) | Gamma , x : A$
|
||
|
|
||
|
where the variable $x$ must be unique.
|
||
|
|
||
|
#tree(axi[], uni[$x : A tack.r x : A$])
|
||
|
|
||
|
*NOTE:* Left side is $x : A$, not $Gamma , x : A$ because those others would be unused, violating the single use rule.
|
||
|
|
||
|
#tree(
|
||
|
axi[$Delta , e : A times B$],
|
||
|
axi[$Gamma , x : A, y : B tack.r e' : C$],
|
||
|
nary(2)[$Delta , Gamma tack.r "match" e "with" (x , y) => e' : C$],
|
||
|
)
|
||
|
|
||
|
Have to split your variables to check $e$ and $e'$. This is not the way to implement it.
|
||
|
|
||
|
To implement it, check $"FV"(e)$ and $"FV"(e')$. These correspond to $Delta$ and $Gamma$ respectively. This is expensive.
|
||
|
|
||
|
- An easier way is to have each judgement give back another context containing the variables not use. Called a "subtractive" approach.
|
||
|
|
||
|
- Additive approach returns the variables used, and then check to make sure variables don't exist on the output.
|
||
|
|
||
|
GOing backk
|
||
|
|
||
|
#tree(
|
||
|
axi[],
|
||
|
uni[$dot tack.r () : 1$],
|
||
|
)
|
||
|
|
||
|
// #tree(
|
||
|
// axi[$Delta tack.r e : +{l : A_l}_(l in L)$],
|
||
|
// axi[$Gamma $]
|
||
|
// )
|
||
|
|
||
|
Defining plus:
|
||
|
|
||
|
-
|
||
|
$"plus" (x : "nat") (y : "nat") : "nat" \
|
||
|
"plus" x " " y = "match" x "with" \
|
||
|
| "zero" () arrow.r.double #text(fill: red)[$"zero" ()$] \
|
||
|
| "succ" x' arrow.r.double "succ" ("plus" x' y)
|
||
|
$
|
||
|
|
||
|
This is problematic because it doesn't use up $y$ in the first branch.
|
||
|
|
||
|
-
|
||
|
$"plus" (x : "nat") (y : "nat") : "nat" \
|
||
|
"plus" x " " y = "match" x "with" \
|
||
|
| "zero" () arrow.r.double y \
|
||
|
| "succ" x' arrow.r.double "succ" ("plus" x' y)
|
||
|
$
|
||
|
|
||
|
This uses up all inputs exactly.
|
||
|
|
||
|
#tree(
|
||
|
axi[$Gamma tack.r e : A_k (k in L)$],
|
||
|
uni[$Gamma tack.r k(e) : +{l : A_l}_(l in L)$],
|
||
|
)
|