CSCI: 4500/6500 Programming Languages

Functional Programming Languages Part 3: Evaluation and Application Cycle

LuisGuillermo.com

Maria Hybinette, UGA 1 Maria Hybinette, UGA 2

Back to the Basics: Steps in Inventing a Language

! Design the grammar ! Meta circular evaluaturs » What strings are in the language? ! Evaluate & » Use BNF to describe all the strings in the language ! Lazy and Aggressive Evaluation ! Make up the evaluation rules » Describe what everything the grammar can produce means ! Build an evaluator » A procedure that evaluates expressions in the language – The evaluator:

! determines the meaning of expressions in the , is just another program.

Maria Hybinette, UGA 3 Maria Hybinette, UGA 4

Programming an Evaluator Definition: A Metacircular Evaluator

! An evaluator that is written in the same ! If a language is just a program, what language language that it evaluates is said to be should we program the language (evaluator) metacircular Sounds like recursion: It's circular recursion. There is no in? termination condition. It's a chicken-and-the-egg kind of thing. (There's actually a hidden termination condition: the bootstrapping process.)

! One more requirement: The language interpreted does not need additional definitions of semantics other than that is defined for the evaluator (sounds circular). » Example: – The C is written is C but is not meta circular because the compiler specifies extremely detailed and precise semantics for each and every construct that it

Maria Hybinette, UGA 5 Maria Hybinette, UGA interprets. 6 Example: Procedural Building Evaluation Basics Blocks

Observation: This is recursive ( define (square x) (* x x) )! To evaluate a combination: » ( square (+ 2 5) ) ! 49! ! Evaluate each element (all the ( define (sum-of-squares x y) !; use square for! subexpressions) of the combination (+ (square x) (square y) ) !; x2 + y2! » ( sum-of-squares 3 4) ! 25! ! Apply the procedure to the value of the left- ( define (f a)! most subexpression (the operator) to the ( sum-of-squares (+ a 1) (* a 2)))! arguments that are the values of the other values of the » (f 5) ! 136 ! subexpressions (the operands) 390! operands percolate upward

26! 15! ! square - is a compound procedure which is given the name square which is represents the operation of multiplying something by itself. Evaluation rule is applied on 4 * ! Evaluating the definition creates the compound procedure and associates it combinations:! 24! with the name square (lookup) + + 2 + 3 5 7 (* (+ 2 (* 4 6))! ! Application: To apply a compound procedure to arguments, evaluate the body (+ 3 5 7) )! of the procedure with each formal parameter replaced by the ‘real’ arguments. (substitution model -- an assignment model <-variable<-env ) Maria Hybinette, UGA * 4 6 7 Maria Hybinette, UGA 8

Environmental Model of Evaluation Core of the Evaluator

1. To evaluate a combination (compound expression) ! Basic cycle in which • evaluate all the subexpressions and then » expressions to be evaluated in environments are • apply the value of the operator subexpression (first » reduced to procedures to be applied to arguments, expression) to the values of the operand subexpressions ! Which in turn are reduced to new expressions (other expressions). » to be evaluated in new environments, and so on, 2. To apply a procedure to a list of arguments, » until we get down to • evaluate the body of the procedure in a new environment – symbols, whose values are looked up in the environment (by a frame) that binds the formal parameters of the procedure to the arguments to which the procedure is – primitive procedures, which are applied directly. applied to. &'(#% &'(#% procedure, procedure, expression, expression, arguments arguments environment environment !""#$% !""#$%

Maria Hybinette, UGA 9 Maria Hybinette, UGA 10

The evaluator - metacircularity ( eval expression environment ) Eval

! Evaluates the the expression relative to the environment (define (eval exp env)! » Examples: environments (returns a specifies for the environment) (cond ((self-evaluating? exp) exp)! – scheme-report-environment version ((variable? exp) (lookup-variable-value exp env))! ((quoted? exp) (text-of-quotation exp))! – null-environment version ((assignment? exp) (eval-assignment exp env))! ! Primitives: ((definition? exp) (eval-definition exp env))! » self-evaluating expressions, such as numbers, eval returns the ((if? exp) (eval-if exp env))! expression itself ((lambda? exp)! (make-procedure (lambda-parameters exp)! » variables, looks up variables in the environment (lambda-body exp)! ! Some special forms (lambda, if, define etc). eval provide direct env))! implementation: ((begin? exp) ! (eval-sequence (begin-actions exp) env))! » Example: quoted: returns expression that was quoted ((cond? exp) (eval (cond->if exp) env))! ! Others lists: ((application? exp)! (apply (eval (operator exp) env)! » eval calls itself recursively on each element and then calls apply, (list-of-values (operands exp) env)))! passing as argument the value of the first element (which must be a (else! function) and a list of the remaining elements. Finally, eval returns what (error "Unknown expression type - EVAL" exp))) apply returned Maria Hybinette, UGA 11 Maria Hybinette, UGA 12 Eval: Example apply

(eval ‘( * 7 3 ) (scheme-report-environment 5)) ! ! !=> 21! ! apply applies its first argument (a function) and applies it to its second argument (a list) (eval (cons '* (list 7 3)) (scheme-report-environment 5)) ! ( apply max '(3 7 2 9) ) => 9 ! !=> 21! ! Primitive function, apply invokes the actual function.

! Non-primitive function ( f ), » Retrieves the referencing environment in which the function’s lambda expression was originally evaluated and Current Scheme doesn’t recognize ‘scheme-report-environment ’ adds the names of the function’s parameters (the list) (call this resulting environment (e) ) » Retrieves the list of expressions that make up the body of f. » Passes the body’s expression together with e one at a time to eval. Finally, apply returns what the eval of the last expression in the body of f returned.

Maria Hybinette, UGA 13 Maria Hybinette, UGA 14

Apply Example: Evaluating ( cadr p )

(define (apply procedure arguments)! ! ( define cadr ( lambda (x) ( car ( cdr x) ) ) ) (cond ((primitive-procedure? procedure)! ! Stored Internally as three element list C: ( E (x) ( car ( cdr (x) ) ) ) (apply-primitive-procedure procedure arguments))! ((compound-procedure? procedure)! – surrounding referencing environment (global) (eval-sequence! – list of parameters (x) (procedure-body procedure)! – list of body expressions (one element: ( car ( cdr x) ) ) (extend-environment! ! Suppose: p is defined to be a list: ( define p ‘(a b) ) (procedure-parameters procedure)! » (cadr p) => b arguments! (procedure-environment procedure))))! ! Evaluating ( cadr p ) scheme executes: (else! » ( eval ‘(cadr p) (scheme-report-environment 5) ) (error! – Note: assumes p is defined in scheme-report-environment 5 "Unknown procedure type - APPLY" procedure))))! 1. Evaluate the car of it’s car of the first argument, » cadr via a recursive call returns function c to which cadr is bound, represented internally as a three element list C. 2. Eval calls itself recursively on ‘p’ returning (a, b) 3. Execute (apply c ‘(a b)) and return results

Maria Hybinette, UGA 15 Maria Hybinette, UGA 16

&'(#% Example: Evaluating ( cadr p ) Summary of Scheme !""#$%

! ( define cadr ( lambda (x) ( car ( cdr x) ) ) ) ! The core of a Scheme evaluator is eval and ! Suppose: p is defined to be a list: ( define p ‘(a b) ) apply, procedures that are defined in terms ! Evaluating ( cadr p ) scheme interpreter executes: 1. ( eval ‘(cadr p) (scheme-report-environment 5) ) of each other. – Note: assumes p is defined in scheme-report-environment 5 » The eval procedure takes an expression and an 2. Evaluate the car of it’s car of the first argument, environment and evaluates to the value of the » cadr via a recursive call returns function c to which cadr is bound, represented internally as a three element list C. expression in the environment; 3. Eval calls itself recursively on ‘p’ returning (a, b) » The apply procedure takes a procedure and its 4. Execute (apply c ‘(a b)) and return results operands and evaluates to the value of applying the 5. Apply then notice the internal list representation cadr, C. ( E (x) ( car ( cdr (x) ) )) and then apply would execute: procedure to its operands. 6. ( eval ‘(car ( cdr (x))) ( cons (cons ‘x ‘(a b)) E )) and return the results

Maria Hybinette, UGA 17 Maria Hybinette, UGA 18 Evaluation Order Example

! Scheme uses applicative order evaluation (as most imperative languages, sometimes called ! (define double (lambda (x) (+ x x))) eager or aggressive evaluation) ! Eager evaluation of ( double (* 3 4) ) ( double 12 ) » Evaluate function arguments before passing them ! to functions ! (+ 12 12) ! 24

Maria Hybinette, UGA 19 Maria Hybinette, UGA 20

Evaluation Order Lazy Evaluation

! Scheme uses applicative order evaluation (as most imperative languages, sometimes called ! Don’t evaluate expressions until their value is eager or aggressive evaluation) really needed. » Evaluate function arguments before passing them » We might save work this way! to functions » We might change the meaning of some ! We can change the evaluator to evaluate expressions, since the order of evaluation matters applications “lazily” instead, by only evaluating the value of an operand when it is needed (also called normal order evaluation, call by need). » Miranda & Haskell evaluates lazily by default, call- by-name in imperative languages is a form of lazy evaluation.

Maria Hybinette, UGA 21 Maria Hybinette, UGA 22

Check: Is being Lazy any Good? Is lazy ever good!

( define switch ( lambda ( x a b c ) ! (define double (lambda (x) (+ x x))) ( cond ! Eager evaluation of ( double (* 3 4) ) ( ( < x 0 ) a ) Eager evaluation of ( switch -1 (+ 1 2) (+2 3) (+ 3 4) ) ! ( double 12 ) ( ( = x 0 ) b ) ! (+ 12 12) ( ( > x 0 ) c ) ) ) ) ! 24 ( switch -1 ( + 1 2 ) ( + 2 3 ) ( + 3 4 ) ) Lazy evaluation ( double (* 3 4) ) – delays computations ! ( switch -1 3 ( + 2 3 ) ( + 3 4 ) ) • ! ( switch -1 3 5 ( + 3 4 ) ) ! ( + ( * 3 4) (* 3 4) ) ! ( switch -1 3 5 7 ) ! ( + 12 ( * 3 4 ) ) ! ( cond ( ( < -1 0 ) 3 ) ! (+ 12 12 ) ( ( = -1 0 ) 5 ) ! 24 ( ( > -1 0 ) 7 ) ) ! QED (Quod Erat Demonstrandum): Proof that lazy is ( cond ( #t 3 ) bad! ( ( = -1 0 ) 5 ) ( ( > -1 0 ) 7 ) ) ! Causes us to evaluate ( * 3 4 ) twice! ! 3

Maria Hybinette, UGA 23 Maria Hybinette, UGA 24 Is lazy ever good! lazy is good!

( define switch ( lambda ( x a b c ) ( switch -1 ( + 1 2 ) ( + 2 3 ) ( + 3 4 ) ) ! ( switch -1 3 ( + 2 3 ) ( + 3 4 ) ) ( cond ! ( switch -1 3 5 ( + 3 4 ) ) ( ( < x 0 ) a ) Lazy evaluation of ( switch -1 (+ 1 2) (+2 3) (+ 3 4) ) ! ( switch -1 3 5 7 ) ! ( cond ( ( = x 0 ) b ) ( ( < -1 0 ) 3 ) ( ( = -1 0 ) 5 ) ( ( > x 0 ) c ) ) ) ) ( ( > -1 0 ) 7 ) ) ( switch -1 ( + 1 2 ) ( + 2 3 ) ( + 3 4 ) ) ( switch -1 ( + 1 2 ) ( + 2 3 ) ( + 3 4 ) ) ! ( cond ( #t 3 ) ! ( cond ! ( cond ( ( = -1 0 ) 5 ) ( ( < -1 0 ) ( + 1 2 ) ) ( ( < -1 0 ) ( + 1 2 ) ) ( ( > -1 0 ) 7 ) ) ( ( = -1 0 ) ( + 2 3 ) ) ( ( = -1 0 ) ( + 2 3 ) ) ! 3 ( ( > -1 0 ) ( + 3 4 ) ) ) ( ( > -1 0 ) ( + 3 4 ) ) ) ! ( ( #t ( + 1 2 ) ) ! ( ( #t ( + 1 2 ) ) ( ( = -1 0 ) ( + 2 3 ) ) ( ( = -1 0 ) ( + 2 3 ) ) ( ( > -1 0 ) ( + 3 4 ) ) ) ( ( > -1 0 ) ( + 3 4 ) ) ) ! ( + 1 2 ) ! ( + 1 2 ) ! 3 ! 3

Lazy evaluation avoids evaluating both (+2 3) and (+ 3 4)

Maria Hybinette, UGA 25 Maria Hybinette, UGA 26

Check Scheme Evaluation Order

! We can also change the evaluator to evaluate ! Secret is out: Scheme does use lazy applications “lazily” instead, by only evaluation for cond evaluating the value of an operand when it is » and special forms (aka macros) needed (also called normal order evaluation, call by need). ! Functions use eager evaluation for functions » In Scheme these can be done with the operator defined with lambda “delay”.

Maria Hybinette, UGA 27 Maria Hybinette, UGA 28

Evaluation Order?

; try with 2 arguments! ! First Review: What does Scheme return (define ( try a a-expression ) ; ( try a ( a-expression ) ) => evaluates! (if (= a 0) 1 a-expression)) ; inner expression first : problem if a = 0 even with if test.! below? ! ( define y 4 ) ; ( try y ( / 1 y ) )! (define ( try a a-expression )! ( define x 0 ) ; ( try x ( / 1 x ) )! !(if (= a 0) 1 a-expression)) ! ! !( define y 4 )! ! !( define x 0 )! ; impact evaluation order by using lazy evaluation 'delay' in scheme! (define (delay-inverse x) (delay (/ 1 x ) ) ) ; (try x (delay-inverse 0))! ! (define (aggressive-inverse x) ( / 1 x ) ) ; (try x (aggressive-inverse 0 ) )! ! ( try y ( / 1 y ) ) ; inverse! ! ( try x ( / 1 x ) ) ! ! ! ( define double ( lambda (x) ( + x x ) ) )! !

Maria Hybinette, UGA 29 Maria Hybinette, UGA 30 Evaluation of Argument Summary

! Lazy by default : ! Applicative Order (“eager evaluation”) » Miranda & Haskell » Evaluate all subexpressions before apply ! Lazy by demand: » The standard Scheme rule, Java » Scheme - using delay ! Normal Order (“lazy evaluation”) » Ocaml – lazy » Evaluate arguments just before the value is needed ! The LAZY Advantage: » Algol60 (sort of), Haskell, Miranda » http://en.wikipedia.org/wiki/Lazy_evaluation

“Normal” Scheme order is not “Normal Order”!

Maria Hybinette, UGA 31 Maria Hybinette, UGA 32

Comparing Functional and Strict and Non-Strict Languages Imperative Languages

! A strict language requires all ! Imperative Languages: arguments to be well-defined, so » Efficient execution applicative (eager) order can be used » Complex semantics » Complex syntax ! A non-strict language does not require » Concurrency is programmer designed all arguments to be well-defined; it ! Functional Languages: requires normal-order (lazy) evaluation » Simple semantics » Simple syntax » Inefficient execution » Programs can automatically be made concurrent

Maria Hybinette, UGA 33 Maria Hybinette, UGA 34

Functional Programming in Functional Programming in Perspective (pros) Perspective (cons)

! Advantages of functional languages ! Advantages of functional languages » lack of side effects makes programs easier to » difficult (but not impossible!) to implement efficiently on understand von Neumann machines » lack of explicit evaluation order (in some – lots of copying of data through parameters languages) offers possibility of parallel evaluation – (apparent) need to create a whole new array in order to (e.g. MultiLisp) change one element » lack of side effects and explicit evaluation order – heavy use of pointers (space/time and locality problem) simplifies some things for a compiler (provided you – frequent procedure calls don't blow it in other ways) – heavy space use for recursion » programs are often surprisingly short – requires garbage collection » language can be extremely small and yet powerful – requires a different mode of thinking by the programmer – difficult to integrate I/O into purely functional model

Maria Hybinette, UGA 35 Maria Hybinette, UGA 36