this is a non-traditional CS theory course, of my own design, evolving
traditional courses “climb a ladder” of formal languages & machines
this course will cover some traditional topics (but less thoroughly)
emphasis here is on interpreted formal languages, programming language theory
influences from functional programming, type theory, denotational semantics, category theory
(also structuralism, intuitionism, homotopy type theory, linear logic & ludics, Montague semantics, categorial grammar,…)
this semester deploying major new intro section on FAST type system
early emphasis on “soft” topics (history, philosophy, cultural aspects, etc.), later more technical
Language and formalism
history of language
purpose(s) of language
language presumably developed primarily for interpersonal communication, but also plays a role in consciousness (internal language), memory (e.g.. externalizing memory) and conceptual clarification
the form or structure of language has dual nature: both a linear surface structure (strings), but also a recursive structure built from hierarchical phrases (terms or trees)
kinds of meanings (semantics)
from abstract (based on behavior, like interfaces), denotational (in terms of pre-understood “objects”), or operational (roughly, an “implementation” in terms of data structures)
constants might start out binary (void type 0 and unit type 1), but we can “upgrade” to numeric or symbolic later
the void type 0 has no values; the unit type 1 has only one, say “•”
as numbers (i.e., flattened by evaluation), sums are just addition
as grids, we just have a two-element grid (or n-element, or symbol-tagged) with nested types = nested grids
to write (give, construct, express) a choice, we use a tag (followed by a value of the chosen type)
we can upgrade from binary (A+B), to numeric (A+B+…+X+Y), or symbolic (a:A+b:B+ …) “tags”
simple sums over the (void and) unit type 1 reduce to coded finite choices—we can add numeric “constant” types n, with values being just k < n (i.e., {0, …, n-1})
we can also use symbolic alphabets as types and values: types re just strings, values just “characters”
visually, we can view successive binary choices as a binary tree with values at the leaves
the code is just the path to the value
if the tree is complete, there are 2^h values, where h is the height of the tree = length o path to leaves
if the tree is not complete, some binary codes of length h will be invalid as codes
as numbers (i.e., flattened by evaluation), exponentials are just powers
but note that A→B becomes BA
we can also usefully think of function values as grids: f: A→B is an A-shaped grid of B-values
as a grid … well, it is hard to visualize (esp. at higher-orders), but it is something like a multi-dimensional product (because exponential = iterated product)
even though hard to visualize in higher dimensions, we can see various flattenings of function space grids via functin tables (like truth tables) or listings (see immediately below)
to write (give, construct, express) a function f: A→B, we can write out all the result values (of type B) in the proper order of argument values (of type A)
a function f: A→Bool (i.e., the type 2, roughly) is like a subset of the set of values of type A
the power-set constructor ℘(S) is often written as 2S
think: pizza topping specification = list of booleans in topping-order
Numbers and numerals
numbers are the abstract meanings (semantics); numerals are the names or symbols (syntax)
natural numbers are whole numbers, starting from 0 (usually) and going up by (+1), as high as you like (i.e., without limit)
according to Peano, natural numbers are either zero, or the successor of some natural number
we can represent these as simple terms (or constructed values) built from Zero or Succ (say in Haskell)
data Nat = Zero | Succ Nat
the fold function for the Nat type replaces Succ and Zero with a function call and a value
we can write out numerals (symbolic codes for numbers) as tallies (using “base 1”)
tallies almost physically mimic the“piles of stones” used for early counting
we can also write out numerals for “structured numbers” using a mixed radix form, based on products-of-sums (see time format example) and lexicographic order
finally, we can write unstructured numbers as numerals using a fixed base (e.g., 2, 10, or 16)
but we normally put the most-sgnificant (= major) digits on the left, for cultural reasons
we can use Horner’s technique (written as a fold) or its reverse (written as an unfold) to convert numerals to numbers (and vice versa)
we have to use left folds and unfolds (foldl and unfoldl) due to the cultural ordering of digits
… or Fritz’s recursive prime-decomposition form (code and examples)
Algebraic terms
once we have names for specific values (constants or literals) and functions (unary or binary, etc.) on a domain, we can make terms, tree-like structures that express patterns of application
we can represent the tree-like structures directly with Haskell data types
there are natural ways to write out or “pretty-print” these trees as strings
… but perhaps with different orders (prefix, infix, postfix) and punctuation (aprens needed for infix)
“PEMDAS” allows us to eliminate some parentheses in favor of “order of operations” conventions
terms are most easily “evaluated” using folds—this tends to focus us on the changes between applications, due to re-parameterization of the fold for different purposes
we think of terms abstractly/informally as somewhere in-between the tree-like structures and the strings
Polynomial functions
if we allow just sum and product (possibly also their opposites), but add a single distinguished variable (x), terms have meanings as polynomial functions
we can add and multiply polynomials using generalizations of the grade-school algorithms for numerals
coefficients of a polynomial are like the digits of a numeral (in this case, the variable x represents the base)
we can use Horner’s technique to evaluate polynomials
we can also perform (e.g.) differentiation (derivatives) directly on polynomials, rather than on terms-with-variables