#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)$], )