Lecture Notes on Programming Languages: A Brief History and Lambda Calculus - Prof. Carlos, Study notes of Programming Languages

An overview of the history of programming languages, focusing on imperative, object-oriented, actor-oriented, functional, and logic languages. It also covers the concept of lambda calculus, which is fundamental to functional programming. Explanations of function composition, free and bound variables, and the order of evaluation.

Typology: Study notes

Pre 2010

Uploaded on 08/09/2009

koofers-user-8u9
koofers-user-8u9 🇺🇸

10 documents

1 / 6

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
CSCI.4430/6969 Programming Languages
Lecture Notes
January 20, 2005
1 Brief History of Programming Languages
Ada Augusta, the Countess of Lovelace, the daughter of the poet Lord Byron, is attributed as being the
first “programmer” ever, using Babbage’s Analytical Engine, circa 1843.
Imperative programming languages include: Fortran (Backus 1954) as one of the first “high-level”
(compiled) programming languages created at IBM and used for numerical computing, Cobol (Hopper
1960) for business applications, Algol (Naur 1960), BASIC (Kemeny and Kurtz 1963), Pascal (Wirth
1970), C (Kernighan and Ritchie 1972) used to program the UNIX operating system at Bell Labs, and Ada
(Whitaker 1979).
Object-oriented programming languages include: Simula (Dahl and Nygaard 1967), Smalltalk (Kay
1980), C++ (Stroustrop 1980), and Java (Gosling 1994) initially created to program appliances and later
used and introduced to program dynamic web content.
Actor-oriented programming languages include PLASMA (Hewitt 1975), Scheme (Sussman and Steele
1975), Act (Lieberman 1981), ABCL (Yonezawa 1988), Actalk (Briot 1989), Erlang (Armstrong 1990), E
(Miller et al 1998) and SALSA (Varela and Agha 1999).
Functional programming languages include: Lisp (McCarthy 1962), ML (Milner 1980), and Haskell
(1990). The most well-known logic programming language is Prolog (Colmerauer and Roussel 1972) created
to process natural language (French). Oz (Smolka 1995) is a declarative programming language that can be
used to combine multiple paradigms, e.g., functional, object-oriented, and logical programming. Scripting
languages include Python (van Rossum 1985), Perl (Wall 1987), and Tcl (Ousterhout 1988).
2 Lambda Calculus
The lambda calculus created by Church and Kleene in the 1930’s is at the heart of functional programming
languages. It is Turing-complete, that is, any computable function can be expressed and evaluated using
the calculus. It is useful to study programming language concepts because of its high level of abstraction.
We will briefly motivate the calculus and introduce its syntax and semantics.
The mathematical notation for defining a function is with a statement such as
f(x) = x2, f :ZZ,
where Zis the set of all integers. The first Zis called the domain of the function, or the set of values x
can take. The second Zis called the range of the function, or the set containing all possible values of f(x).
Suppose f(x) = x2and g(x) = x+ 1. Traditional function composition is defined as
fg=f(g(x)) .
With our functions fand g,
fg=f(g(x)) = f(x+ 1) = x2+ 2x+ 1.
1
pf3
pf4
pf5

Partial preview of the text

Download Lecture Notes on Programming Languages: A Brief History and Lambda Calculus - Prof. Carlos and more Study notes Programming Languages in PDF only on Docsity!

CSCI.4430/6969 Programming Languages

Lecture Notes

January 20, 2005

1 Brief History of Programming Languages

Ada Augusta, the Countess of Lovelace, the daughter of the poet Lord Byron, is attributed as being the first “programmer” ever, using Babbage’s Analytical Engine, circa 1843. Imperative programming languages include: Fortran (Backus 1954) as one of the first “high-level” (compiled) programming languages created at IBM and used for numerical computing, Cobol (Hopper

  1. for business applications, Algol (Naur 1960), BASIC (Kemeny and Kurtz 1963), Pascal (Wirth 1970), C (Kernighan and Ritchie 1972) used to program the UNIX operating system at Bell Labs, and Ada (Whitaker 1979). Object-oriented programming languages include: Simula (Dahl and Nygaard 1967), Smalltalk (Kay 1980), C++ (Stroustrop 1980), and Java (Gosling 1994) initially created to program appliances and later used and introduced to program dynamic web content. Actor-oriented programming languages include PLASMA (Hewitt 1975), Scheme (Sussman and Steele 1975), Act (Lieberman 1981), ABCL (Yonezawa 1988), Actalk (Briot 1989), Erlang (Armstrong 1990), E (Miller et al 1998) and SALSA (Varela and Agha 1999). Functional programming languages include: Lisp (McCarthy 1962), ML (Milner 1980), and Haskell (1990). The most well-known logic programming language is Prolog (Colmerauer and Roussel 1972) created to process natural language (French). Oz (Smolka 1995) is a declarative programming language that can be used to combine multiple paradigms, e.g., functional, object-oriented, and logical programming. Scripting languages include Python (van Rossum 1985), Perl (Wall 1987), and Tcl (Ousterhout 1988).

2 Lambda Calculus

The lambda calculus created by Church and Kleene in the 1930’s is at the heart of functional programming languages. It is Turing-complete, that is, any computable function can be expressed and evaluated using the calculus. It is useful to study programming language concepts because of its high level of abstraction. We will briefly motivate the calculus and introduce its syntax and semantics. The mathematical notation for defining a function is with a statement such as

f (x) = x^2 , f : Z → Z,

where Z is the set of all integers. The first Z is called the domain of the function, or the set of values x can take. The second Z is called the range of the function, or the set containing all possible values of f (x). Suppose f (x) = x^2 and g(x) = x + 1. Traditional function composition is defined as

f ◦ g = f (g(x)).

With our functions f and g,

f ◦ g = f (g(x)) = f (x + 1) = x^2 + 2x + 1.

Similarly g ◦ f = g (f (x)) = g(x^2 ) = x^2 + 1. Therefore, function composition is not commutative. In lambda (λ) calculus, we can use a different notation to define these same concepts. To define a function f (x) = x^2 , we instead write λx.x^2.

Similarly for g(x) = x + 1 we instead write λx.x + 1.

To describe a function application such as f (2) = 4, we write

(λx.x^2 2) ⇒ 22 ⇒ 4.

The syntax for lambda calculus expressions is

e ::= v – variable | λv.e – lambda expression | (e e) – procedure call

The semantics of the lambda calculus, or the way of evaluating or simplifying expressions, is defined by the rule (λx.E M ) ⇒ E{M/x}.

The new expression E{M/x} can be read as “replace ‘fresh’ x’s in E with M ”. Informally, a “fresh” x is an x that is not nested inside another lambda expression. We will cover free and bound variable occurrences in detail later on. For example, in the expression (λx.x^2 2),

E = x^2 and M = 2. To evaluate the expression, we replace x’s in E with M , to obtain

(λx.x^2 2) ⇒ 22 ⇒ 4.

In lambda calculus, all functions may only have one variable. Functions with more than one variable may be expressed as a function of one variable through currying. Suppose we have a function of two variables expressed in the normal way

h(x, y) = x + y, h : (Z × Z) → Z.

With currying, we can input one variable at a time into separate functions. The first function will take the first argument, x, and return a function that will take the second variable, y, and will in turn provide the desired output. To create the same function with currying, let

f : Z → (Z → Z)

and g : Z → Z.

That is, f maps integers to a function, and g maps integers to integers. The function f (x) returns a function gx that provides the appropriate result when supplied with y. For example,

f (2) = g 2 , where g 2 (y) = 2 + y.

So f (2)(3) = g 2 (3) = 2 + 3 = 5.

int f(int x) { return x*x; }

int main() { int x; ... x = x + 1; return f(x); }

In this example, the x in f could have been substituted for y or any other variable name without changing the output of the program. In the same way, the lambda expression

(λx.x^2 x + 1)

is identical to the expression (λy.y^2 x + 1),

since the final x is unbound, or free. To simplify the expression

(λx.(λx.x^2 x + 1) 2)

You could let E = (λx.x^2 x + 1) and M = 2. The only free x in E is the final one so the correct reduction is (λx.x^2 2 + 1).

The x in x^2 is bound, so it is not replaced. However, things get more complicated. It is possible when performing β -reduction to inadvertently change a free variable into a bound variable, which changes the meaning of the expression. In the statement

(λx.λy.(x y) (y w)),

the second y is bound to λy and the final y is free. Taking E = λy.(x y) and M = (y w), we could mistakenly arrive at the simplified expression

λy.((y w) y).

But now both the second and third y’s are bound, because they are both a part of of the λy function definition. This is wrong because one of the y’s should remain free as it was in the original expression. To get around this, we can change the λy expression to a λz expression

(λx.λz.(x z) (y w)),

which again does not change the meaning of the expression. This process is called α -renaming. Now when we perform the β -reduction, the original two y variables are not confused. The result is

λz.((y w) z).

Here, the free y remains free.

2.2 Order of Evaluation

There are different ways to evaluate lambda expressions. The first method is to always fully evaluate the arguments of a function before evaluating the function itself. This order is called applicative order. In the expression (λx.x^2 (λx.x + 1 2)),

the argument (λx.x + 1 2) should be simplified first. The result is

⇒ (λx.x^2 2 + 1) ⇒ (λx.x^2 3) ⇒ 32 ⇒ 9.

Another method is to evaluate the left-most redex first. A redex is an expression of the form (λx.E M ), on which β -reduction can be performed. This order is called normal order. The same expression would be reduced from the outside in, with E = x^2 and M = (λx.x + 1 2). In this case the result is

⇒ (λx.x + 1 2)^2 ⇒ (2 + 1)^2 ⇒ 9. As you can see, both orders produced the same result. But is this always the case? It turns out that the answer is “no” for certain expressions whose simplification does not terminate. Consider the expression

(λx.(x x) λx.(x x)).

It is easy to see that reducing this expression gives the same expression back, creating an infinite loop. If we consider the expanded expression

(λx. 3 (λx.(x x) λx.(x x))),

we find that the two evaluation orders are not equivalent. Using applicative order, the (λx.(x x) λx.(x x)) expression must be evaluated first, but this never terminates. If we use normal order, however, we evaluate the entire expression first, with E = 3 and M = (λx.(x x) λx.(x x)). Since there are no x’s in E to replace, the result is simply 3. It turns out that it is only in these particular non-terminating cases that the two orders may give different results. The Church-Rosser theorem (also called the confluence property or the diamond property) states that if a lambda calculus expression can be evaluated in two different ways and both ways terminate, both ways will yield the same result. Also, if there is a way for an expression to terminate, using normal order will cause the termination. In other words, normal order is the best if you want to avoid infinite loops. Take as another example the C program

int loop() { return loop(); }

int f(int x, int y) { return x; }

int main() { return f(3, loop()); }

In this case, using applicative order will cause the program to hang, because the second argument loop() will be evaluated. Using normal order will terminate because the unneeded y variable will never be evaluated. Though normal order is better in this respect, applicative order is the one used by most programming languages. Why? Consider the function f (x) = x + x. To find f (4/2) using normal order, we hold off on evaluating the argument until after placing the argument in the function, so it yields

f (4/2) = 4/2 + 4/2 = 2 + 2 = 4,

and the division needs to be done twice. If we use applicative order, we get

f (4/2) = f (2) = 2 + 2 = 4,

which only requires one division. Since applicative order avoids repetitive computations, it is the preferred method of evaluation in most programming languages, where short execution time is critical.