Download CS655 Class Notes: A Historical Overview of Programming Languages - Prof. Raphael A. Finke and more Study notes Programming Languages in PDF only on Docsity!
CS655 class notes
Raphael Finkel
October 1, 2009
1 Intro
Lecture 1, 8/27/
- Handout 1 — My names
- Mr. / Dr. / Professor / —
- Raphael / Rafi / Refoyl
- Finkel / Goldstein
- Plagiarism — read aloud from handout 1
- Assignments on web and in handout 1.
- E-mail list: [email protected]; instructor uses to reach students.
- All students will have MultiLab accounts, although you may use any computer you like to do assignments.
- textbook — all homework comes from here
- First assignment (Chapter 1, due Thursday)
2 Software tools
A programming language is an example of a software tool.
Implementation
Use (client) Spec
Usable?
Compilable?
Elegant?
3 McLennan’s Principles (elicit first)
4 Algol-like languages: review
- First generation: Fortran
- constructs based on hardware
- lexical: linear list of statements
- control: sequence, goto , do , subroutines using reference pa- rameter mode
- data: arithmetic, including complex; arrays with a 3-d limit
- name: separate scopes; common area
- Second generation: Algol 60
- lexical: free format; keywords
- control: nested; if , while , for , but baroque; subroutines with value and name parameter modes.
- data: generalized arrays, but no complex
- name: nested scopes with inheritance and local override
- Third generation: Pascal (return to simplicity)
- data: user-defined types; records, enumerations, pointers
- control: subroutines with value and reference parameter modes; case statement
- Fourth generation: Ada (abstract data types)
- lexical: bracketed syntax
- data: modules with controlled export; generic modules
- control: concurrency with rendezvous
- Fifth generation: Other directions
- dataflow
- functional. We will study ML and Lisp.
- object-oriented. We will study Smalltalk.
- declarative (logic). We will study Prolog.
7 Imperative languages
- Imperative languages involve statements that modify the current state by changing the values of variables.
- A variable is an identifier bound (usually statically) to a type, having a value that can change over time. The L-value of a variable is the use of a variable on the left side of an assignment (think of “address”); the R-value of a variable is its use on the right side (think of “current value”).
- A type is a set of values, associated (mostly statically) with opera- tions defined on those values. Type conversion means expressing a value of one type as a value of another type. - coercion: implicit conversion - cast: explicit conversion - non-converting cast: rarely needed. qua operator of Wisconsin Modula, reinterpret cast<> of C++.
- An operation is a function or an operator symbol as shorthand. It can be heterogeneous. - operators have arity (example: unary, binary), precedence, as- sociativity - operators may be infix (+), prefix (unary - ), postfix (->) - operators may have short-circuit (lazy) semantics
- An operation is overloaded if its identifier or operator symbol has multiple visible definitions. Overloading is resolved (usually stati- cally) by arity, operand types, and return type. Overloading resolu- tion can be exponentially expensive.
- A primitive type has no separately accessible components. Exam- ples: integer, character, real, Boolean.
- A structured type has separately accessible components. Examples: pointer (dereference), record (field select), array (subscript), disjoint union (variant select). An associative array is an array whose index type is string.
- A constant is like a variable, but it has no L-value and an unchanging R-value.
8 Parameters
- Nomenclature
- Formal parameter : the identifier that the procedure uses to re- fer to the parameter; it is elaborated when the procedure is in- voked.
- Actual parameter : the expression that computes the value of the parameter; it is evaluated in the environment of the caller.
- Linkage : The machine-oriented mechanism by which the caller A causes control to jump to the called procedure B, including initializing B’s stack frame, passing parameters, passing results back to A, and reclaiming B’s stack frame when it has com- pleted.
- Parameter-passing modes
- value mode : The formal has its own L-value, initialized to the R-value of the actual. The language design may restrict the for- mal to read-only use. Value mode is the only mode available in C.
- result mode : The formal has its own L-value, not initialized. When B returns, the formal’s L-value is copied back to the ac- tual (which must have an L-value, so the actual cannot be an arbitrary expression). Result mode was introduced in Algol-W.
- value-result mode : The formal has its own L-value, initialized to the R-value of the actual. As B returns, its value is copied back to the actual (which must have an L-value). Value-result mode was introduced in Algol-W.
- reference mode : The formal has the same L-value as the ac- tual (which must have an L-value, which might be a temporary location). The language may allow the programmer to specify read-only use of the formal parameter. Reference mode is the only mode available in Fortran.
- name mode : All accesses to the formal parameter re-evaluate the actual (either for L-value or R-value, depending on the ac- cess to the formal). This evaluation is in the RE of the caller, typ- ically by means of a compiled procedure called a thunk. Name mode was invented for Algol-60 and never used again.
def treeNodes(tree): if tree != null: for element in treeNodes(tree.left): yield element yield tree.value for element in treeNodes(tree.right): yield element
- Wouldn’t it be nice to have a yieldall construct:
def treeNodes(tree): if tree != null: yieldall treeNodes(tree.left): yield tree.value yieldall treeNodes(tree.right):
This construct might be able to use shortcuts to improve efficiency.
10 Macro package to embed iterators in C
- Macros are IterSTART , IterFOR , IterDONE , IterSUB , IterYIELD.
- Usage: book 48:
- Implementation
- setjump and longjmp for linkage between for and the con- trolling iterator, between yield and its controlled loop.
- Padding between stack frames to let longjmp() be called with- out harming frames higher on the stack. Three integers is enough in Linux on an i686.
- A Helper routine to actually call the iterator and act as padding.
- The top frame must be willing to assist in creating new frames.
11 General coroutines — Simula 67
- Problem: binary-tree node-equality test in symmetric order
- Solution
- Independently advance in each tree book 38:
- Lecture 4, 9/10/
- Each coroutine has its own stack; main has its own stack.
- All the stacks are joined via static-chain pointers into a cactus stack.
- A scope must not exit until all its children exit, or we must use a reference count. This is an example of the dangling NLRE problem.
- Syntax (Simula 67)
- Records have initialization code that may execute detach.
- Another coroutine may resume it via explicit call.
- Ole-Johann Dahl, designer of Simula 67, got the Turing award in
12 Power loops
- How can you get a dynamic amount of for -loop nesting?
- Application: n queens book 57:
- Usual solution: single for loop with a recursive call.
- Cannot use that solution in Fortran, which does not allow recursion.
- Solution: Power loops. book 57:
- Implementation: Only needs branches, no recursion. book 59:
- How general is this facility?
- Do power loops violate principle 20?
13 IO
- Attempt to strip a programming language of all non-essential ele- ments.
- What’s left: goto with parameters, hence formal-actual bindings.
- Parameters can be integer, anonymous function, or continuation.
- A function is passed by closure : pointer to code, pointer to NLRE.
- The property consists of a set of values.
- Strong typing means the compiler
- knows the type of every R-value and identifier
- enforces type compatibility on assignment and formal-actual binding - compatible means type equivalent, a subtype, or convert- ible
- A subtype consists of a subset of the values
- Assignment and formal-actual binding require a dynamic check.
- Subtype examples: range of integers, subclass of class
- subTypeVar := baseTypeVar
- Type equivalence
- structural equivalence : expand type to a canonical string rep- resentation; equivalence is string equality. - lax: ignore field names, ignore array bounds, ignore index type, flatten records. - pointers require that we handle recursive types (and still build finite strings)
- name equivalence : reduce a type to an instance of a type con- structor - type constructors : array , pointerTo , enum , struct , derived. - lax ( declaration equivalence ): multiple uses of one type constructor are equivalent
17 What does polymorphic mean?
People use the term polymorphic to mean various features.
- Static procedure overloading with compile-time resolution (Ada, Java). Here, the type of the procedure (its signature ) determines whether it is a viable candidate for resolving the overloading.
- Dynamic method binding (Java, Smalltalk, C++ deferred binding). Here, the dynamic type (class) of the value (object) determines which procedure (method) to invoke.
- Types described by type identifiers (perhaps with the compiler infer- ring the types of values) (ML). Here, the dynamic type constraints on the parameter and return value determine the effective type of the function.
- Passing an ADT as a parameter (Russell).
- Generic packages (Ada), templates (C++).
18 Unusual first-class values
- A first-class value can be returned from a function. In languages with variables, a first-class value can be stored in a variable.
- A second-class value can be an actual parameter.
- A third-class value can be used “in its ordinary way”.
- Labels and procedures
- Usually third class values; the “ordinary way” is in a goto statement or a procedure-call statement.
- They could be second class. They must be passed as a closure. For a label, the closure includes the RE of its target, so the stack can be unwound to the right place. For a procedure, the closure includes its NLRE to resolve non-local references.
- To make them first class, we still need to build a closure, which we can then store in a variable. But the lifetime of that variable might exceed the life of the RE stored in the closure. This is the dangling-NLRE problem. book 76:
- To resolve the dangling-NLRE problem
- Let it happen and call the program “erroneous”.
- Prevent it from happening by restricting the language.
- Don’t let labels or procedures be first-class: Pascal
- Don’t let scopes nest, so there is no need for closures (for procedures; labels are still problematic): C
- Only allow top-level procedures (or labels) be first-class: Modula-
- Let it happen and make it work: allocate all frames from the heap and use explicit release (reference counts suffice).
squares :: [Int] squares = [n*n | n <- [1 .. 5] ] fermat :: [Int] fermat = [(a, b, c, n) | a<-[3..], b<-[3..], c<-[3..], n<-[3..], ((aˆn) + (bˆn) == (cˆn))] quicksort :: [Int] -> [Int] quickSort [] = [] quickSort (a:rest) = quickSort [b | b <- rest, b <= a] ++ [a] ++ quickSort [b | b <- rest, b > a]
- List comprehensions give us an easy way to implement Lisp’s mapcar function:
mapcar (list, function) = [function a | a <- list]
In fact, we can generally avoid using mapcar entirely.
- One can combine lazy evaluation, comprehension, and infinite lists to gain memoization (dynamic programming)
cache :: [Int] cache = [fib x | x <- [0 ..]] fib :: Int -> Int fib 0 = 1 fib 1 = 1 fib n = cache!!(n-1) + cache!!(n-2)
- Haskell has interfaces , much like Java, which are like types that pro- vide certain operations.