1 / 18

CS 3304 Comparative Languages

CS 3304 Comparative Languages. Lecture 19: Functional Languages - Perspective 27 March 2012. Evaluation Order Revisited. Applicative order - evaluate function arguments before passing them to a function: Scheme: f unctions use applicative order defined with lambda.

mccalle
Download Presentation

CS 3304 Comparative Languages

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. CS 3304Comparative Languages • Lecture 19:Functional Languages - Perspective • 27 March 2012

  2. Evaluation Order Revisited • Applicative order - evaluate function arguments before passing them to a function: • Scheme: functions use applicative order defined with lambda. • What is usually done in imperative languages. • Usually faster. • Scheme use applicative order in most cases. • Normal order - pass function arguments unevaluated: • Scheme: special forms (hygienic macros) use normal order defined with syntax-rules. • Arises in the macros and call-by-name parameters of imperative languages. • Like call-by-name: don't evaluate argument until you need it. • Sometimes faster. • Terminates if anything will (Church-Rosser theorem).

  3. Example • Function:(define double (lambda (x) (+ x x))) • Applicative order:(double (* 3 4))⇒(double 12)⇒(+ 12 12)⇒24 • Normal order:(double (* 3 4))⇒(+ (* 3 4) (* 3 4))⇒(+ 12 (* 3 4))⇒(+ 12 12)⇒24

  4. Special Forms • Arguments to special forms (such as lambda) are passed unevaluated. • Each special form is free to choose internally when (and if) to evaluate its parameters. • Expression types in Scheme - special forms and functions: • Primitive: built into the language implementation. • Derived: defined in terms of primitive expression types. • lambda: used to create derived functions that can be bound to names with let. • syntax-rules: used to create derived special forms that can be bound to names with define-syntax and let-syntax. • Derived special forms are known as macros in Scheme – hygienic: lexically scoped, integrated into the language’s semantics, and immune from the problems of mistaken grouping an variable capture. • Scheme macros are Turing complete.

  5. Strictness • A (side-effect-free) function is said to be strict if it is undefined (fails to terminate, or encounters an error) when any of its arguments is undefined. • It can safely evaluate all its argument, so its result will not depend on evaluation order. • A strict language requires all arguments to be well-defined, so applicative order can be used: ML and Scheme (with the exception of macros). • A non-strict language does not require all arguments to be well-defined; it requires normal-order evaluation: Miranda and Haskell.

  6. Lazy Evaluation • Lazy evaluation gives the best of both worlds: the advantage of normal-order evaluation while running within a constant factor of the speed of applicative-order evaluation. • Particularly useful for “infinite” data structures. • Scheme: available through explicit use of delay and force: • delay creates a “promise”. • But not good in the presence of side effects. • If an argument contains a reference to a variable that may be modified by an assignment, then the value of the argument will depend on whether it is evaluated before or after the assignment. • If the argument contains an assignment, values elsewhere in the program may depend on when evaluation occurs. • Scheme requires that every use of delay-ed expression be enclosed in force.

  7. Lazy Evaluation Example (define f (lambda () (let ((done #f) (memo ’()) (code (lambda () (* 3 4)))) (if done memo (begin (set! memo (code)) memo))))) … (double (f)) ⇒(+ (f) (f)) ⇒(+ 12 (f)) ⇒(+ 12 12) ⇒24

  8. I/O Streams • Traditional I/O is a major source of side effects: • Scheme: functions read and display. • Model input and output as streams: unbounded-length lists whose elements are generated lazily. • If we model input and output streams, then:(define output (my_prog input)) • When input value is needed, my_prog forces evaluation of the car of input and passes the cdr on to the rest of the program. • Successfully encapsulate the imperative nature of interaction at terminal. • Don’t work well for graphics or random access to files.

  9. Monads • A more general concept (from category theory): • Ability to carry a hidden, structured value of arbitrary complexity from one action to the next. • The pseudorandom number generator example (p. 526): • Passing the state to the function and having it return new state along with the random number. • Monads provide a more general solution to the problem of threading mutable state through a functional program. • In Haskell IO monad serves as the central repository for imperative language features (syntactic sugar): • Additional nomads support partial functions and various container classes. • Coupled with lazy evaluation provides a natural foundation for backtracking search, nodeterminism, and the functional equivalent of iterators.

  10. Higher-Order Functions • Higher-order functions • Take a function as argument, or return a function as a result. • Great for building things. • Map: takes as argument a function and a sequence of lists:(map * '(2 4 6) '(3 5 7)) • One of the most common uses of higher order functions is to build new functions from existing ones:(define fold (lambda (f i l) (if (null? l) i (f (car l) (fold f i (cdr l))))))(define total (lambda (l) (fold + 0 l)))(define total-all (lambda (l) (map total l)))

  11. Implementing Higher-Order Functions • Whey we don’t use higher-order function in imperative programming languages? • Much of the power of the first-class functions depends on the ability to create new functions on the fly - we need a function constructor: a significant departure from the syntax and semantics of traditional imperative languages. • The ability to specify functions as return values, or to store them as variables requires: • Eliminate function nesting: erodes the ability of programs to create functions with desired behaviors on the fly; or • Give local variables unlimited extent: increases the cost of storage management.

  12. Currying • Currying (after Haskell Curry): replace multiargument function with a function that takes a single argument and returns a function that epect the remaining elements:(define curried-plus (lambda (a) (lambda (b) (+ a b))))(plus 3 4)⇒ 7(map (curried-plus 3) ‘(1 2 3))⇒ (4 5 6) • ML, Miranda, and Haskell have especially nice syntax for curried functions. In ML:fun plus (a, b) : int = a + b;==> val plus = fn : int * int -> intfun curried_plus a = fn b : int => a + b;==> val curried_plus = fn : int -> int -> intcurried_plus 3;==> val it = fn : int -> intfun curried_plus a b : int = a + b;==> val curried_plus = fn : int -> int -> int

  13. Theoretical Foundations: Functions • A function is a single-valued mapping: it associates every element in one set (the domain) with (at most) one element in another set (the range):sqrt: R ⟶ R • If a function provides a mapping for every element of the domain, the function is said to be total. Otherwise, it is said to be partial. • It is often useful to characterize functions as sets or, more precisely, as subsets of the Cartesian product of the domain and the range. • One of the limitations of the function-as-set notation is that it is nonconstructive: it doesn’t tell us how to compute the value of a function at a given point (i.e., on a given input).

  14. Theoretical Foundations: Lambda Calculus • Church designed the lambda calculus to address the nonconstructive limitation of function-as-set. In its pure form, lambda calculus represents everything as a function: • Church and Rosser theorem: the simplest forms are unique and that if any evaluation order will terminate, normal order will. • A lambda expression can be defined recursively as: • A name; • A lambda abstraction consisting of the letter λ, a name, a dot, and a lambda expression; • A function application consisting of two adjacent lambda expressions; • A parenthesized lambda expression. To accommodate arithmetic, we will extend this definition to allow numeric literals. • When two expressions appear adjacent to one another, the first is interpreted as a function to be applied to the second.

  15. Naturally Imperative Idioms • There are common programming idioms in which the canonical side effect (assignment) plays a central role, such as I/O and trivial update problem idioms: • Initialization of complex structures: lists are easy to build from old lists but not other data structures, such as multidimensional arrays. • Summarization: the natural way to count occurrences of various item in large data sets is using a dictionary data structure that is repeatedly updated. • In-place mutation: when dealing with large data sets, values should be updated in place rather than copying to a new list or similar. • Solution: a combination of convenient notation (accessing arbitrary elements of a complex structure) and an implementation that can determine when the old version of the structure will never be used gain and can be updated in place.

  16. Perspective: Advantages • Lack of side effects makes programs easier to understand. • Lack of explicit evaluation order (in some languages) offers possibility of parallel evaluation (e.g. MultiLisp). • Lack of side effects and explicit evaluation order simplifies some things for a compiler (provided you don't blow it in other ways). • Programs are often surprisingly short. • Language can be extremely small and yet powerful.

  17. Perspective: Problems • Difficult (but not impossible!) to implement efficiently on von Neumann machines: • Lots of copying of data through parameters. • (Apparent) need to create a whole new array in order to change one element. • Heavy use of pointers (space/time and locality problem). • Frequent procedure calls. • Heavy space use for recursion. • Requires garbage collection. • Requires a different mode of thinking by the programmer. • Difficult to integrate I/O into purely functional model.

  18. Summary • A functional program computes principally through substitution of parameters into functions. • The underlying formal model for functional languages is the lambda calculus. • Many functional languages extend the lambda calculus with additional features, including assignment, I/O, and iteration. • Lists feature prominently in most functional languages.

More Related