(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)
(define fold (lambda (fct identity-value sequence) (if (null? sequence) identity-value ; e.g., 0 for +, 1 for * (fct (car sequence) (fold fct identity-value (cdr sequence)))))) (fold * 1 '(2 4 6)) ==> 48fold 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 fold as follows:
(fold + 0 (map * row column))
(define curried-plus (lambda (a) (lambda (b) (+ a b)))) ((curried-plus 3) 4) ==> ((lambda (b) (+ 3 b)) 4) ==> 7
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.