(define add (lambda (x) (+ x 20))) (define min (lambda (x y) (if (< x y) x y))) (trace add) (min (add 5) (add 20)) [Entering #[compound-procedure 4 add] Args: 20] [40 <== #[compound-procedure 4 add] Args: 20] [Entering #[compound-procedure 4 add] Args: 5] [25 <== #[compound-procedure 4 add] Args: 5] ;Value: 25
(and (not (= y 0)) (/ x y))Normal-order evaluation will allow us to short-circuit the evaluation as soon as one of the conditional expressions causes the outcome to become known
(define double (lambda (x) (+ x x ))) (double (* 3 4))Under applicative order evaluation we have:
(double (* 3 4)) ==> (double 12) ==> (+ 12 12) ==> 24Under normal order evaluation we have:
(double (* 3 4)) ==> (+ (* 3 4) (* 3 4)) ==> (+ 12 (* 3 4)) ==> (+ 12 12) ==> 24and hence we perform twice the work.
(define expr (delay (+ a 10))) (define a 15) (force expr) ==> 25delay binds an expression to a name and force forces the evaluation of that expression. Scheme uses the memoization technique described below to save the value of the evaluated expression, so if force is called again on it, it simply returns the cached value.
a10 = b10 + c10 b10 = 3 * b9 c10 = 8 * c9 b9 = 5 c9 = 10then a spreadsheet will cache the results of evaluating the three formulas for a10, b10, and c10. If the user changes the value of a cell, such as b9, then the spreadsheet will use a depth-first traversal to find all formulas that depend directly or indirectly on this changed cell, and mark these formulas and their related cells out-of-date. When a cell's value is requested, the spreadsheet checks the cell's out-of-date flag. If it is set to false, the spreadsheet returns the cached value. If the flag is set to true, the spreadsheet evaluates the cell's formula, caches the result, sets the out-of-date flag to false, and returns the newly computed value. Note that this evaluation could recursively trigger the evaluation of other out-of-date formulas.
(map * '(2 4 6) '(3 5 7)) ==> (6 20 42)map can be applied to any number of lists. Here is an example where it adds 1 to each element in a single list:
> (map (lambda (x) (+ x 1)) '(3 6 9 10 12)) (4 7 10 11 13)If you are struggling to understand how map is implemented, here is pseudo-code showing how the pair-wise list multiplication would be implemented using an imperative procedure and assuming that the lists are implemented as arrays:
map(list1, list2, function) { newlist = [] for i = 0 to list1.length-1 { newlist.append(function(list1[i], list2[i])); } return newlist }
(define reduce (lambda (fct identity-value sequence) (if (null? sequence) identity-value ; e.g., 0 for +, 1 for * (fct (car sequence) (reduce fct identity-value (cdr sequence)))))) (reduce * 1 '(2 4 6)) ==> 48reduce and map are frequently used in tandem. For example, if I am doing a matrix multiply, each element in the newly computed matrix is the dot product of some row and column. The dot product is obtained by doing a pair-wise multiplication of the corresponding elements in the row and column, and then summing the resulting products. You can express this operation elegantly using map and reduce as follows:
(reduce + 0 (map * row column))If you are struggling to understand how reduce works, here is the pseudo-code for implementing an imperative version of reduce. The pseudo-code assumes the list is implemented as an array:
reduce(fct identity-value sequence) { result = identity-value for i = 0 to sequence.length-1 { result = fct(result, sequence[i]) } return result }As a more concrete example, here is a reduce function for adding a list of numbers:
result = 0; // 0 is the identity value for i = 0 to sequence.length - 1 { result = result + sequence[i] } return result
(define curried-plus (lambda (a) (lambda (b) (+ a b)))) ((curried-plus 3) 4) ==> ((lambda (b) (+ 3 b)) 4) ; curried-plus replaced by its lambda function ==> 7Here's a more useful example of currying a reduce function so that it produces a function that performs the indicated operation on a list without having to always provide the function and identity element. We are actually currying 2 arguments in this example, the function to be used in the "reduce" operation and the identity element used to initialize the result we are computing:
(define reduce (lambda (fct identity-value sequence) (if (null? sequence) identity-value ; e.g., 0 for +, 1 for * (fct (car sequence) (reduce fct identity-value (cdr sequence)))))) (define curry_reduce (lambda (fct identity) (lambda (L) (reduce fct identity L)))) (define total (curry_reduce + 0)) (define product (curry_reduce * 1)) (total '(1 2 3 4 5)) ;; produces 15 (product '(2 3 5)) ;; produces 30Notice how nice it is to just type the function we want, such as total or product, instead of having to write out:
(reduce + 0 '(1 2 3 4 5)) (reduce * 1 '(2 3 5))
Various compiler techniques and programmer annotations have been developed to handle these situations and make the performance hit to a functional program much less pronounced.
The greatest tragedy of all history is the murder of a beautiful theory by a gang of brutal facts.I feel this quotation applies to functional programming. In theory it is elegant, and its lack of side-effects can lead to reduced development times because of fewer errors. In practice though the world is a messy place that constantly demands side-effects (input/output, the update problems mentioned above), and in my experience, functional programming falls apart in the presence of these side-effects. As one example, we have seen in class how elegant looking functional programs get transformed into messy looking, obscure code when we have to convert the original versions to more efficient, tail-recursive forms. In practice, I have seen very few situations that benefit from a purely functional approach (tree and graph traversals come to mind, as do some recurrence relations).
Another knock against functional programming from my perspective is that we are brought up from birth in an imperative-oriented world of "do this" and "do that". It takes considerable training to re-orient most programmer's thinking to the more functional thinking of mathematicians, and most programmers either will not, or can not, master it.
Finally, many of the most useful ideas from functional programming, such as garbage collection, anonymously created on the fly functions (i.e., lambda functions), and higher-order functions, such as map and reduce (fold), have been finding their way into imperative languages, especially scripting languages.
In fact, the one very valuable thing that I think is evolving into imperative languages from functional languages is the concept of higher-order functions that can do a tremendous amount of computation with just one or two lines of code. As a post-doc I spent two years programming in Lisp, and its higher-order functions were the biggest asset to me in programming. In the end I had to use side-effects all the time, because I was programming for graphical user interfaces. It took me about 6 months to become comfortable with this "pidgin" form of functional programming, and I never really became comfortable with Lisp's prefix notation. I am also not comfortable with the more mathematical notation that later functional languages developed to denote functions.
For those of you with a natural mathematical bent, I encourage you to examine functional languages in more depth. However, I suspect that the world at large will remain a largely imperative shop, with the more desirable characteristics of functional languages mixed in.