Download COMP 356 Programming Language Structures Notes for Chapter 15

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Falcon (programming language) wikipedia , lookup

Curry–Howard correspondence wikipedia , lookup

Tail call wikipedia , lookup

Currying wikipedia , lookup

Closure (computer programming) wikipedia , lookup

Anonymous function wikipedia , lookup

Combinatory logic wikipedia , lookup

Standard ML wikipedia , lookup

Lambda calculus wikipedia , lookup

Lambda calculus definition wikipedia , lookup

Lambda lifting wikipedia , lookup

Transcript
COMP 356
Programming Language Structures
Notes for Chapter 15 of Concepts of Programming Languages
Functional Programming
Application domains for functional programming:
• prototyping
• AI research
– symbolic computation
– list processing
• parallel programming
Key feature – no mutation:
• “variables” don’t change their values
• no side effects
Consequences of no mutation:
• no assignment statements
• no loops
• all parameters are passed by value
• referential transparency – the same expression always evaluates to the same value. Note that this
is not true in imperative languages such as C:
int x = 2;
int add(int y) {
x++;
return (x + y);
}
The call add(3) will return 6 the first time, 7 the second time, ... This can’t happen in a functional
programming language, which makes reasoning about programs much simpler.
Other key features:
• garbage collection
• functions as first class citizens – functions can do anything that any other data type can do:
– be passed as an argument to another function
– be stored in a data structure
– be returned from a function
1
1
Running Scheme
• Mac OS X:
– to start the interpreter, enter:
DrScheme &
in a terminal window, or use the Finder to go to /CS Applications/PLT v204/ and then select
DrScheme.
– the first time you run the interpreter, it will ask you to choose a language. Choose: Essentials of
Programming Languages (2nd ed).
– the DrScheme application is divided into upper and lower windows.
∗ enter function definitions in the upper window.
∗ you can use: File→Save Definitions As... to save your function definitions to a file (use a
.scm extension), and File→Open to open an existing file in a new window.
∗ when you are ready to test your function(s), click the Execute button, and then enter function
calls in the lower window. You must reclick the Execute button each time that you modify
your function definitions.
• Windows:
– download the self-installing executable from the course web page and run it. This installs the
interpreter, compiler and full documentation.
– by default, the load function will look for .scm files in C:\
– use (exit) to quit
2
Programming in Scheme
2.1
Functions
An anonymous (unnamed) function is defined using lambda.
Syntax: (lambda (parameters) body)
Example:
(lambda (x y) (+ x y))
The result of the function (when it is called) is the value of the body. All functions are prefix (including
arithmetic operators), and parameters are not comma separated.
A function (or any other value) can be given a name using define.
Example:
(define add (lambda (x y) (+ x y)))
to call this function:
(add 3 4)
which returns 7.
Note the parentheses around the function name and the arguments.
A function does not have to be named to be called:
((lambda (x y) (+ x y)) 3 4)
returns 7.
2
2.2
Conditionals and Local Bindings
The keyword if is used for if expressions.
Syntax: (if <c1 > <e1 > <e2 >)
Meaning: if condition <c1 > is true, the value of the if expression is the value of the expression <e1 >.
Otherwise, the value of the if expression is the value of the expression <e2 >.
Example: an absolute value function.
(define absval (lambda (x) (if (< x 0) (- x) x)))
Example: an integer exponentiation function, i.e. mn .
(define pow (lambda (m n)
(if (= n 0) 1 (* m (pow m (- n 1))))))
A tail recursive version of pow:
(define powtr (lambda (m n a)
(if (= n 0) a (powtr m (- n 1) (* m a)))))
(define pow2 (lambda (m n) (powtr m n 1)))
The Scheme standard specifies that all Scheme implementations must be properly tail recursive, which
means that tail recursive functions are evaluated iteratively (no stack growth).
The keyword cond is used for n-way conditionals (similar to switch). Syntax:
(cond (<c1 > <e1 >)
(<c2 > <e2 >)
...
(<cn > <en >)
)
The <ci >’s (the guards) are tried in order, and for the first one that is true, the value of the associated
<ei > becomes the value of the cond expression.
Often, the last <ci > is #T (true), which acts as an else. If all <ci >’s are false, the value of the cond
expression is false (#F or ()).
Example: absolute value again.
(define absval (lambda (x)
(cond ((< x 0) (- x))
(#T
x))))
Note that: (if <c1 > <e1 > <e2 >) ≡ (cond (<c1 > <e1 >) (#T <e2 >))
Example: a function to return letter grades on a 10 point quiz.
(define grade (lambda (score)
(cond ((> score 8) "A")
((> score 7) "B")
((> score 6) "C")
(#T
"U"))))
As in denotational semantics, a let expression can be used to bind local names to expression results.
Syntax: (let ((a1 <e1 >) (a2 <e2 >) ... (an <en >)) <body>)
Each <ei > is evaluated and its value bound to ai . The ai ’s can be used in the body, and the value of the
let expression is the value of the body.
Example:
(let ((a (+ 2 3)) (b (* 2 3)))
(- b a))
3
evaluates to 1.
The keyword letrec is used for recursive local bindings - i.e. for defining local functions.
Example: hiding powtr from outside use.
(define pow3 (lambda (m n)
(letrec ((powtr (lambda (m n a)
(if (= n 0) a (powtr m (- n 1) (* m a))))))
(powtr m n 1))))
2.3
Data Types
Scheme has only one data type - the S-expression (symbolic expression). However, there are several kinds
of S-expressions:
• atomic symbols - numbers, strings and and atoms. An atom is just an identifier that is quoted to
distinguish it from a variable name. The atom foo can be constructed in two ways:
(quote foo)
’foo
• dotted pairs - if <e1 > and <e2 > are S-expressions, then (<e1 > . <e2 >) is an S-expression. Dotted
pairs are used to build lists (including trees, programs, ...)
List notations:
• usual: (1 a 3)
• as dotted pairs: (1 . (a . (3 . ())))
where () is nil (the empty list).
Lists can be constructed in 3 ways:
1. using function list, i.e.:
(list 1 ’a 3)
2. using a quote
’(1 a 3)
The ’ distinguishes the list from a function call.
3. using cons – a function that takes an element and a list, and returns a new list with the argument
element as its first element and the argument list as the rest of the list.
Examples:
(cons 3 ())
returns (3).
(cons 1 (cons ’a (cons 3 ())))
returns (1 a 3).
List functions:
• null? tests if a list is null (empty). It is built-in, but could be defined as:
4
(define null? (lambda (x) (eq? x ())))
• car returns the first element of a list. For example:
(car ’(1 2 3)) returns 1
car is an acronym standing for contents of the address register
• cdr returns all of a list except for the first element. For example:
(cdr ’(1 2 3)) returns (2 3)
cdr is an acronym standing for contents of the decrement register
Examples:
• a function to add up the elements of a list of numbers:
(define sumlist (lambda (lst)
(if (null? lst) 0
(+ (car lst) (sumlist (cdr lst))))))
• a function to add 1 to each element of a list of numbers:
(define inclist (lambda (lst)
(if (null? lst) ()
(cons (+ (car lst) 1) (inclist (cdr lst))))))
cons actually builds a dotted pair (S-expression).
• car returns the first element of the pair
• cdr returns the second element of the pair
A “chain” of dotted pairs with the last element () is a list. For example:
’(1 2 3)
= (cons 1 (cons 2 (cons 3 ())))
= (1 . ( 2 . (3 . ())))
A dotted pair is usually implemented as a record containing two pointer fields (one for the car, one for
the cdr). For example, the list ’(1 2 3) is represented by:
1
2
3
5
()
List elements can also be lists – lists can be nested. For example, the list ’(1 (2 3) (4 (5))) is represented by:
1
2
()
3
()
4
()
5
()
A Scheme function is just a list (of lists and atoms).
2.4
Higher Order Functions
Higher order functions are functions that take other functions as arguments, return functions as their
results, or both. For example, the built-in function map takes a 1-argument function and a list as arguments,
and returns a list constructed by applying the function to each element of the argument list.
(define inc (lambda (x) (+ x 1)))
(map inc ’(1 2 3))
returns the list: (2 3 4)
lambda can be used to construct the function argument “on the fly”:
(map (lambda (x) (+ x 1)) ’(1 2 3))
map can be implemented as follows:
(define map (lambda (f l)
(if (null? l) ()
(cons (f (car l)) (map f (cdr l))))))
Another example – using map to find all first elements of a list of lists:
(map car ’((1 2) (2 3) (4 5)))
6
returns the list: (1 2 4)
Tracing the evaluation of (map inc ’(1 2 3)):
=
=
=
=
=
=
=
=
(map inc ’(1 2 3))
(cons (inc 1) (map inc (2 3)))
(cons 2 (map inc (2 3)))
(cons 2 (cons (inc 2) (map inc (3))))
(cons 2 (cons 3 (map inc (3))))
(cons 2 (cons 3 (cons (inc 3) (map inc ()))))
(cons 2 (cons 3 (cons 4 (map inc ()))))
(cons 2 (cons 3 (cons 4 ())))
(2 3 4)
Another built-in higher order function is reduce:
• arguments are a 2 argument function, a “zero” and a list
• returns the result of “accumulating” the function over the list, i.e. reducing + over a list of numbers
returns the sum
• the zero is the result of the reduction for an empty list
Examples:
(reduce + 0 ’(1 2 3 4)) returns 10
(reduce * 1 ’(1 2 3 4)) returns 24
Implementation:
(define reduce (lambda (f z l)
(if (null? l) z
(f (car l) (reduce f z (cdr l))))))
Trace:
=
=
=
=
=
(reduce + 0 ’(1 2 3))
(+ 1 (reduce + 0 (2 3)))
(+ 1 (+ 2 (reduce + 0 (3))))
(+ 1 (+ 2 (+ 3 (reduce + 0 ()))))
(+ 1 (+ 2 (+ 3 0)))
6
Function make_incr (defined below) takes an integer argument n, and returns a function that increments
by n. For example:
(define inc2 (make_incr 2))
(inc2 3)
returns 5.
(define make_incr (lambda (n) (lambda (x) (+ x n))))
trace:
((make_incr 2) 3)
= ((lambda (x) (+ x 2)) 3)
= 5
i.e. make_incr is a function that returns a function.
A curried function is a function that takes multiple arguments one at a time (by returning a function
to take the next argument each time).
A function to curry a two argument function:
7
(define curry2 (lambda (f)
(lambda (x)
(lambda (y) (f x y)))))
For example:
(define curryplus (curry2 +))
(define inc2 (curryplus 2))
(inc2 3)
returns 5. This can also be done all in one step:
(((curry2 +) 2) 3)
Function compose (defined below) takes two one-argument functions and composes them. For example,
composing a squaring function with itself yields a function that takes its argument to the fourth power.
(define compose (lambda (f g) (lambda (x) (f (g x)))))
(define square (lambda (x) (* x x)))
(define inc (lambda (x) (+ x 1)))
(define f1 (compose square square))
(define f2 (compose square inc))
After these definitions, (f1 3) returns 81, and (f2 3) returns 16.
3
Implementation of Scheme
In Scheme, activation records are allocated on the heap because the lifetime of local variables and parameters
can be longer than one activation.
Example:
(define make_incr (lambda (n) (lambda (x) (+ x n))))
(define inc3 (make_incr (+ 1 2)))
(inc3 4)
Observations:
• the value of (+ 1 2) is stored in formal parameter n in the activation record of make_incr
• this value is needed as long as inc3 can be called
• this activation record of make_incr must be kept as long as inc3 can be called
• in Scheme, activation records are allocated on the heap and only freed by garbage collection. In fact,
Scheme interpreters use no stack.
Scheme (and most “standard” programming languages) use strict evaluation - all actual parameters in
a function call are evaluated before the function is invoked. Some functional languages use lazy evaluation
- actual parameters are evaluated after the function is called, and then only as needed.
8
Comparisons:
• strict evaluation
– is easier to implement
– is more efficient in many cases
• lazy evaluation
– is more efficient in some cases (if some parameters are never used)
– permits infinite data structures
Scheme programmers can define lazy functions using macros or thunks (as in pass-by-name).
Other implementation notes:
• Scheme implementations are required to be properly tail recursive
• Scheme interpreters use garbage collection techniques similar to those used by JVMs. Garbage in
Scheme consists of:
– dotted pairs (cons cells) that are no longer referenced
– activation records whose bindings are no longer referenced
4
Functional and Parallel Programming
A call (f <e1 > <e2 > . . . <en >) can be evaluated on a parallel machine by:
• assigning one processor to f and one to each <ei >
• each processor evaluating an <ei > returns its result to the processor evaluating f
• f is evaluated
This works because each ei has no side effects, so the order they are evaluated in doesn’t matter. Additionally, all processors can share the same memory because it is only being read (not written).
For a conditional expression: (if <c> <e1 > <e2 >)
• all of <c>, <e1 > and <e2 > can be evaluated simultaneously (on different processors)
• the unneeded result can be discarded
• to mimic lazy evaluation:
– any error can be ignored if the result of the expression that generated it isn’t needed
– computation of an <ei > can be terminated if its result isn’t needed (to prevent infinite recursion)
The problem with this approach is granularity:
• the computation is broken into many small pieces
• the overhead of communication between processors may outweigh the benefits of parallelism
9