V Material since the midterm
V Scoped variables
V syntax: "let" definitions (as in Haskell)
* Expr a b c = ... | Var c | Let c Expr Expr
* (really the recuurrences of Expr should be "Expr a b c")
V example: let x = y*3-7 in x+y*2
* here y comes from the outer/global scope, whereas x is locally defined
V semantics
* evaluate the definition, then use an extended environment for the body
* (first Expr is the definition of the variable, second is the body, where the var is used)
V a "syntactic semantics" might be to simply replace the variable by the definition in the body
* (but in some cases this would be inefficient, both conseptually and in implementation)
V implementation
* we need to pass environments in (as for unscoped/free variables), but we also need to locally "update" (or extend) the environment for evaluation of the body expression
V Imperative variables
V syntax: assignment expressions (or statements) (as in Java)
* Expr a b c = ... | Var c | Set c Expr
* (really the recuurrence of Expr should be "Expr a b c")
V example: ((x:=x+1)*((x*y)-(x:=3))
* the ":=" syntax is used in honor of Pascal, to signify that this is a change to the value of the variable, over time, during the evaluation of the expression (thus "imperative")
V semantics
* we evaluate the right-hand side (RHS) and update the variable in the environment, then pass the updated environment in to the rest of the expression
* the order of evaluation becomes crucial, since changes made in one part of the expression may affect the value of other parts
V implementation
* we need to pass environments in and out of the evaluation of each expression; i.e., we must pass in the environment to supply "current" values of variables, then pass it out again (with the overall value) as they change
* we can either write out this "plumbing" explicitly or (in Haskell) we can package up the management of the state in a Monad and use special syntax to hide the plumbing
V beyond simple expressions and environments
* environments which "change" in this way are often called states or stores
V one approach to managing them is to restrict expressions to pure values, and to define an "upper layer" of syntax (often called statements) which include expressions, but have an environment-transformation semantics
* eval :: Expr -> Env -> Val
* perf :: Stmt -> Env -> Env
V Parsing combinators
V nature (type) of parsers
V in most basic form, a function from strings to values (for some type of values)
* String -> a
V but we also need to keep track of the "remaining string" left over for the rest of the parse
* String -> (a,String)
V and, the parse might fail, in which case there is no output at all
* String -> Maybe (a,String)
V but there might also be multiple possible parses, each with ts own value and remainder
* Parser a = String -> [(a,String)]
V combinator approach
* we define some simple functions in terms of the above type (e.g., to parse a single character)
* we then define higher-order functions which combine simple parsers into more complex ones
V typical combinators do such things as:
* eliminate whitespace (token)
* allow either of two alternatives (+++)
* parse multiple times with some separator character between (chainl)
* parse aprentheses around some other parsed value
V monads and parser combinators
* used directly, this approach involves a lot of "plumbing" to pass around the remaining strings and to account for the multiplicity of ambiguous parses
* we can wrap up the plumbing in a Monad in order to manage it behind the scenes, then use special syntax (do) to hide it
V Monads (generally)
V Monads are a techniques by which tiresome but necessary "plumbing" in a program can be suppressed or hidden
V examples:
* environment (or state) during expression evaluation
* remaining String and ambiguity/multiplicity during parsing
* exceptions or errors during computation (the Maybe monad)
* a good metaphor is to think of try/catch blocks in Java: explicit checking of errors after each method call would be tiresome
V syntax
V in Haskell, Monads are a type class
* class Monad m where
* return :: a -> m a
* (>>=) :: m a -> (a -> m b) -> m b
V in order to define a monad, we make an instance of a type class
* instance Monad Maybe where
return = Just
Nothing >>= f = Nothing
(Just x) >>= f = f x
V Haskell includes special syntax for monads (the "do" notation)
* example: do { x <- p; y -< q; return f p q }
* this syntax is translated into uses of bind and lambda
V semantics
V the type class tells what functions must be defined (return and bind (>>=)), but it doesn't enforce some necessary properties of these functions (the monad laws)
* (return x) >>= f == f x
* m >>= return == m
* (m >>= f) >>= g == m >>= (\x -> f x >>= g)
V Lambda calculus
V Introduction and motivations
V a formal theory of functions, but one which respects the notion of function as algorithm, i.e., as having a descriptive aspect
* (in contrast to the set-theoretic understanding, which codes functions as certain sets of ordered pairs)
V Formal syntax
* using M,N as meta-variables for terms, and x,y, … for variables:
* M ::= x | λx.M | M N
* terms are either variables, (lambda) abstractions with a bound variable and a body, or applications of a function to an argument
V Abbreviations and conventions
* we abbreviate to avoid excessive parentheses, and to consolidate multiple successive lambdas, as follows
* λxy.M ≡λx.λy.M
* λx.M N ≡ (λx.(M N)) ≠ (λx.M) N
* M N P ≡ ((M N) P) ≠ M (N P) [just as in Haskell]
V Free variables and substitution
* we need to distinguish between variables which occur within a lambda that binds the same variable (bound occurrences) and ones which have no enclosing binding (free variables)
V formally, we have a function which tells us the free variables of a term:
* FV(x) = {x}
* FV(M N) = FV(M) ∪ FV(M)
* FV(λx.M) = FV(M) \ {x} [here "\" is set difference: remove x]
* less formally, if we encounter a binding of a variable on the way "out" from where it occurs in a term, it is bound; else it is free
* free variables must get their meaning from context (e.g., an environment); bound variables gain their meaning from how they are bound
V in some sense the actual bound variable used in any context is immaterial: we could change it to another one just as well
* (λx.x) is the identity function, but so is (λy.y), as is(λz.z), etc.
* this idea gets formalized via the α rule of equality between terms: (λx.M) = (λy. M[y/x] )
V Reduction and normal form
V in addition to the alpha rule above, we have beta (β) and eta (η) rules which relate equal terms
* β: (λx.M) N = M [N/x]
* η: (λx.M x) = M (if x is not free in M)
* the eta rule can be read to imply that "everything is a function", since any term is equivalent to an overt (lambda) function
V the beta rule is used to reduce (or compute, or evaluate) terms
* every time we see an abstraction as a function "next to" an argument term, we can reduce according to the rule
* if we do so repeatedly (and according to a certin strategy), we will eventually arrive at an answer, if possible
* the strategy (called normal order) is to reduce the leftmost/outermost such term first
* if we reduce arguments to a function first, we may needlessly evaluate them (as the function may not depend on them)
V in general, reduction may not terminate
* consider the term (λx. xx)(λx. xx): it reduces to itself
V Lambda calculus—representations
V general idea
* just as we can encode numbers, booleans, functions, etc. in set theory, we can do so in lambda calculus
* when we do this, we must still recognize that everything in (pure, untyped) lambda calculus is a function (by the eta rule)
V without a type system (or at least careful choices, or even just good luck), we may end up "mixing" representations
* (e.g., adding a boolean to a number)
* the following definitions are coherent in the sense that the operations given will yield correct answers on the representations of their arguments
* see the Church encoding page in the on-line references for more information
V representing booleans
* True ≡λxy.x [given two things, pick the first]
* False ≡λxy.y [given two things, pick the second]
* If B then M else N ≡ B M N [given two things, pick the one specified by B]
V representing natural numbers
* Zero ≡λfx.x = (λf. (λx.x)) [given a function, return a function which applies it zero times, i.e. just the identity]
* One ≡λfx. f x [given a function, return a function which applies it once]
* Two ≡λfx. f (f x) [given a function, return a function which applies it twice]
* Etc.
* plus ≡λmn. (λfx. m f (n f x))
* times ≡λmn. (λfx. n (m f) x)
* exp ≡λmn. (λfx. n m f x) λmn. n m
V representing pairs
* <M,N> ≡λb. b M N [a pair is a function on Booleans]
* fst M ≡ M True
* snd M ≡ M False
V representing recursion
* we can represent recursion using a special term Y (also called the Y combinator)
* Y has the property (for all terms F) that Y F = F (Y F)
* Y = λf. (λx. f (x x)) (λx. f (x x))
V Types—general issues
V types arise naturally through the use of encodings/representations
* in set theory
* in lambda calculus
* in bit-strings
V types are forced by presence of constants
* so far we have seen “pure” systems of lambda calculus (and set theory)
V once we add constants “from the outside”, we need types since constants aren’t functions
* of course, even with encodings, types are helpful, since they help avoid accidental “mixing” of codes
V what types buy us (and cost us)
V freedom from “obvious” errors
V static typing (vs. dynamic typing) and strong typing
* static means that we can determine type correctness by static (compile-time) analysis of program
* strong means “no type mis-matches”; we may need to use dynamic (run-time) checks
* esp. if statically typed, we can avoid “embarrassment” of run-time type failures
V types constitute a useful tool for “coarse-grained”, high-level descriptions of systems
* useful locus of information in “sketches” of a system design
* allow for documentation of intended behavior (e.g., can see how to use a function)
* give a coarse-grained partial correctness proof (e.g., “units” calculations)
* provide interface description for interpretation by linkers, compilers
V freedom from paradox
* Russell first proposed types to resolve set-theoretic paradoxes
* most paradoxes arise from reflection plus “negation”
V consider adding logical constants T, F, λ to lambda calculus
* define P = λx. not(x x);
then P P = not(P P)
* alternatively, in any system with recursion, define Q = æQ
V termination and “analyzability”
* for most of the simpler type systems, reduction will always terminate
* … this means that equality is decidable (!), unlike for functions in general
* furthermore, we will be able (generally) to decide type-correctness statically
* but, types impose limitations on the expressiveness of our language (see below)
V Types—type systems
* for the bulk of the rules themselves, see the handout from lecture
V type statements/judgements
* a typing statement or judgement is a formal statement relating a context (Gamma: Γ), a term (M) and a type (tau: τ)
* Γ⊢ M : τ
* it should be read as stating that the term M has the type tau, assuming the types for free variables given by Gamma
V we write the typing context Gamma as a set of variable-type associations, but use it informally as a function
* if Γ = {x:τ, y:s, …} then Γ(x) = τ
V inference rules and derivations
* we give (see handout) a bunch of rules which tell when a certain term has a certain type
* for a given term, if we can find a derivation (upside-down tree, or proof) that the term has some type, then it does (and only then)
* note that the rules don't tell us how to find such a proof: we need an algorithm (type-checking) to find a proof or reject the term
V Types and logic: Curry-Howard correspondence
* [this is some new material as requested by Zac, for the exam]
V consider a proposition (boolean formula with variables) such as you implemented for Lab 4, but assuming a top-level implication (⇒):
* (P&(P⇒Q))⇒Q
* (P&Q)⇒(Q&P)
* (P∨Q)⇒(P⇒Q)⇒Q
* (P∨Q)&(Q⇒R)⇒R [not a tautology!]
V we can translate each such proposition into a type, replacing:
* boolean variable (P,Q, etc.) by type variables (a,b, etc.)
* implication (⇒) by function space ()
* conjunction (&) by cross product or pairing ( (_,_) )
* disjunction (∨) by disjoint sum, datatype enumeration or Haskell's Either type (|)
V the original formula is a tautology if and only if we can write a function (in a pure calculus, no undefined) of the corresponding type
* the function is called a witness for the type (and thus for the corresponding formula)
* app :: (a, ab) b
app (x, g) = g x

* swap :: (a,b) (b,a)
swap (x,y) = (y,x)

* ifapp :: (a | b) (ab) b
ifapp (Left x) g = g x
ifapp (Right y) g = y

* oops :: (a | b, ac) c [not a tautology!]
oops (Left x) g = g y
ifapp (Right y) g = … [uh-oh, what to do here?]

V some notes
* the use of type variables is important: these functions have to be polymorphic, i.e., they must work at all types
V note that we don't really need to have an implication/function, but without it, values of variable types don't exist in general
* why not? because we don't have undefined values, and thus nothing of a universal/arbitrary type
* adding "undefined :: a" is like adding a proof of any arbitrary proposition/formula
* just as adding unbounded recursion risks the abyss, so it opens up the logic to inconsistency (every formula is provable)
V the program or function we write constitutes a kind of constructive proof of the formula: we show how to:
* construct a value of the result type from values of the argument types
* and thus how to build a proof of the consequent formula from proofs of the antecedent ones
* understanding this correspondence can reinforce both programming and proving skills with intuitions from the "other side"
V the given proofs may not be the only ones possible; consider the type of Church numerals in lambda calculus
* zero, one, two, three :: ((aa) a) a
zero f x = x
one f x = f x
two f x = f (f x)
three f x = f (f (f x))

V we can think of these same functions another way just by changing the way we write their type
* zero, one, two, three :: (aa) (a a) [now as "one-ingredient" transformers]
zero f = id
one f = f
two f = f ○ f
three f = f ○ f ○ f

V this idea can be used to develop a system which automatically writes functions of a desired type
* see Lennart Augustsson's Djinn program
* www.haskell.org—017055.html