The Role of the Study of Programming Languages in the Education of a Programmer Dan Friedman Indiana University Why study programming languages? • First: To teach one to avoid bad ideas – A little history about dynamic scope – A small discussion about types – Improper implementation of tailcalls 2 Why study programming languages? • Second: To embrace good ideas in a representationally-independent fashion – Resolving tail-calls until something better comes along! 3 Why study programming languages? • Other roles: Three qoutes from students: – Jon Rossie: The life of a programming language expert in a world that does not know what one is and does not understand what one does... 4 Every program is an interpreter! • • • • • Programs are data All data are programs The hardware is an interpreter A compiler is an interpreter A type checker is an interpreter! 5 Data must be interpreted • What is the interpretation of “5” • What is the interpretation of “V” – A character? – A number? • What is the interpretation of a function that takes “XIV” and produces XV”? – The successor function? – The predecessor function? 6 Essentials of Programming Languages • Programmers like several PL • Programmers are upset if things are difficult to do in their favorite PL • Studying PL alleviates this problem • Programmers are expected to learn PL • But there are certain language design issues that are essential... 7 Why study programming languages? • Other roles: Three quotes from students: – Anurag Mendhekar: I believe in the power of abstraction in software development ... 8 Why study programming languages? • Separate what you want to do from how are you going to do it! • Details are for later, after the ideas are developed and prototyped • Details can be the implementation of the language design or of the language itself 9 What do we mean by the study? • The application of one’s mental faculties to the acquisition of knowledge in a particular field or to a specific subject • One acquires such knowledge by developing a firm foundation of concepts to absorb knowledge • Modeling the artifact one is studying 10 What do we mean by the study? • For instance: knowing that a language passes its parameters by value is better than a description of what call-by-value is • If one knows call-by-value -calculus one only needs to know what items in the language are values! 11 What do we mean by the study of programming languages? • The semi-formal analysis of programming language concepts and their underlying principles that have lasted beyond a decade of their discovery! • Syntax should not be the focus! 12 A programming languages concept: Lexical Scope • Free variables are bound to values when procedures are created • Around since Algol 60 • Logicians have used it for much longer, as quantifiers and rely on lexical scope 13 Lexical Scope > let a = 3 in let p = proc (x) +(x, a) a=5 in *(a, p(2)) *(5, +(2, 3)) = 25 14 The power of -abstraction • Relational database system operators (relying on lexical scope) (x E Tuple*)== (andmap( (x)E)Tuple*) and ( x E Tuple*)== (ormap( (x)E)Tuple*) 15 Why study programming languages? • To teach one to avoid bad ideas – A little history about dynamic scope – A small discussion about types – Improper implementation of tailcalls 16 Dynamic Scope > let a = 3 in let p = proc (x) +(x, a) a=5 in *(a, p(2)) *(5, +(2, 5)) = 35 17 When you study scoping... • You are likely to discover dynamic scope first • You need to know why dynamic scope is a mistake! • You need to know about the choice of a name for a bound variable 18 -substitution ( (x) x) = ( (y) y) To change the x to y you need to use a name that is not used in the expression ( (y) ( (x)(y x))) = ( (z) ( (x)(z x))) 19 Consider the definition of map (define map ( (f ls) (if (null? ls) ’() (cons (f (first ls)) (map f (rest ls)))))) 20 What happens when -rule works with map, with lexical scope? (let ((ls ’(1 2 3 4))) (map ( (x) (cons x ls)) ls)) > ((1 1 2 3 4) (2 1 2 3 4) (3 1 2 3 4) (4 1 2 3 4)) 21 What does this return if let and are dynamically scoped? It starts out the same: ( (1 1 2 3 4) . . . But, on the second recursion, ls gets smaller, so it affects the ls inside the definition of map! Isn’t that a surprise? 22 Should any language designer be allowed to inflict such horrifying thoughts on a language user? >((1 1 2 3 4) (2 2 3 4) (3 3 4) (4 4)) 23 Equations for Lexical and Dynamic Binding E[( (x)M)]env =( (arg)( (env)E[M]env[x arg])) versus =((arg)((enw)E[M]env[x arg])) 24 Why study programming languages? • To teach one to avoid bad ideas – A little history about dynamic scope – A small discussion about types – A disaster in the implementation of tail-calls 25 What is wrong with this Scheme program? (if (= n 0) (+ n 5) (not (= (length ls) 4))) Nothing? 26 How about this one? ((if (= n 0) 5 ( (x) (+ x 7))) 6) Nothing? 27 What is wrong with this Scheme program? (if (= n 0) (+ n 5) (not (= (length ls) 4))) The value of the conditional is either a number or a boolean! 28 How about this one? ((if (= n 0) 5 ( (x) (+ x 7))) 6) The value of the conditional is either a number or a function! So, sometimes we will apply a function to a number, but other times we will apply the number five to a number! 29 Why study programming languages? • To teach one to avoid bad ideas – A little history about dynamic scope – A small discussion about types – Improper implementation of tailcalls 30 The problem • Most implementations of Java and C do not handle tail-calls properly; the problem is not just recursive calls, but also method calls! • Java does not encourage the writing of recursive programs • But Java can be ok... assume that Scheme (Lisp) code can be easily converted to Java-code 31 The solution • Use correctness-preserving transformations • Transformations are simple and logical, • Should be used when the opportunity arises • Solve the problem of language 32 implementation... for Java and C! (define E ( (e r) (cases expression e (lit-exp (datum) datum) (var-exp (id) (r id)) (primapp-exp (prim rands) (prim (map ( (x) (E x r)) rands))) (if-exp (test-e true-e false-e) (if (E test-e r) (E true-e r) (E false-e r))) (proc-exp (ids body) ( (args) (E body ( (id) (if (memq id ids) (lookup id ids args) (r id)))))) (app-exp (rator rands) 33 ((E rator r) (map ( (x) (E x r)) rands)))))) DO NOT PANIC WHILE READING THE CODE THAT FOLLOWS DETAILS ARE FOR LATER 34 Nontail calls: embedded calls sum program with a non-tail call (define sum ( (n) (if (= n 0) 0 (+ n (sum (- n 1)))))) The call: > (sum 1000000) 35 Programs in Tail form Implementation using an accumulator so that it would contain no non-tail calls. (define sum ( (n acc) (if (= n 0) acc (sum (- n 1)(+ n acc))))) The call: > (sum 1000000 0) 36 The problem • Running these programs in Java would not give the right answer: almost guaranteed! • We are relying on a control stack, which is not very deep: Java assumes that most of your programs have a lot of while loops and assignment statements! • The two Java programs for sum will produce the same wrong result: an exception! 37 Why study programming languages? • Second: To embrace good ideas in a representationally-independent fashion – Resolving tail-calls until something better comes along! 38 Transforming the tail form into register form Substitute the call for assignment statements: (define n) (define acc) (define sum ( () (if (= n 0) acc {( acc (+ n acc)) ;; (begin(set! ( n (- n 1)) (sum)}))) 39 Transforming a Java program to make it work! The execution: 1. Set the registers 2. Make the call! > {( acc 0) ( n 1000000) (sum)} ;assigment ;goto But, In Java, this program still blows up! 40 Next: we transform the program into suspended-goto form (define sum ( () (if (= n 0) acc {( acc (+ n acc)) ( n (- n 1)) ( ()(sum))} > {( acc 0) ( n 1000000) ( ()(sum))} 41 But( ()( f )) = f , in -calculus -rule restricted to variables: the -suspended-goto form. (define sum ( () (if (= n 0) acc {( acc (+ n acc)) ( n (- n 1)) sum}))) > {( acc 0) ( n 1000000) 42 sum} Executing a program in suspended form • In Java, we are required to use a while loop, which is primitive in Java and does not grow the stack • We need to define a variable false instead of acc to terminate the while loop • The accumulator is de-referenced at the end of the computation 43 The new program (define sum ( () (if (= n 0) false {( acc (+ n acc)) ( n (- n 1)) sum}))) 44 The new program The control loop: (define run ( () {(while (sum) ’no-op) acc})) The call: > {( acc 0) ( n 1000000) (run)} 45 Independence of parameters • We introduce a new register action to avoid reliance on the “return” facilities • The value returned from sum is placed in action • No reliance on arguments being passed nor the value being returned, leading to trampoline form 46 Independence of parameters (define action) (define sum ( () (if (= n 0) ( action false) {( acc (+ n acc)) ( n (- n 1)) ( action sum)}))) 47 Independence of parameters The control loop: (define run ( () {(while action (action)) acc})) The call: > {( acc 0) ( n 1000000) ( action sum) (run)} 48 Can every program be written in tail form? • The two initial programs for sum (tail and non-tail forms) return the same result but compute differently; The first numbers added in the non-tail form were 1 and 0, but in the tail form, the first two numbers added were n and 0 (we relied on associative and commutative properties of sum) • We cannot always do that! 49 Putting programs into tail form • An interpreter can be put into tail-form (Essentials, Chap. 7) • An interpreter can also be put into register form! • But we need to transform any program to one that can be written and run in any host language 50 Putting programs into tail form • There is an algorithm in: “Call-by-value, call-by-name and the lambda calculus” by Gordon Plotkin, 1975. • Fate lent a hand: Amr Sabry and Matthias Felleisen discovered a new algorithm (Chapter 8, of the second edition of “Essentials...”) 51 Putting programs into tail form • First, let’s transform the sum program into preregister-tail form without using the associative and commutative properties • Then, transform the program into trampoline form • This kind of code is said to be in continuation-passing style 52 The original sum program... again The program: (define sum ( (n) (if (= n 0) 0 (+ n (sum (- n 1)))))) The call: > (sum 1000000) 53 Continuations • Programs passed as arguments to other programs indicating what to do next • We need to add such an argument to every program that will be put into continuationpassing style • This has to be done for every -expression of the original program! 54 The sum in continuation-passing style (define id ( (acc) acc)) (define sum ( (n cont) (cont (if (= n 0) 0 (+ n (sum (- n 1) id)))))) > (sum 1000000 id) 55 The sum in continuation-passing style Let’s push the continuation through the branches of the if-expression: (define sum ( (n cont) (if (= n 0) (cont 0) (cont (+ n (sum (- n 1) id)))))) The call: > (sum 1000000 id) 56 The sum in continuation-passing style Let’s push the continuation through the embedded sum: (define sum ( (n cont) (if (= n 0) (cont 0) (sum (- n 1) ( (acc) (cont (+ n acc))))))) 57 Continuations • The program has been transformed into tailform • Next: we must dereference every free variable used in the continuation 58 The continuation with the free variables: ( (acc) (cont (+ n acc))) The two free variables are n and cont, so we replace the entire expression by (let ((n n)(cont cont)) ( (acc) (cont (+ n acc)))) 59 The preregistered tail-form: (define sum ( (n cont) (if (= n 0) (cont 0) (sum (- n 1) (let ((n n)(cont cont )) ( (acc) (cont (+ n acc)))))))) 60 But we might be in a language that does not directly support higher-order functions such as: ( (acc) acc) or ( (acc) (cont (+ n acc))) We can still use continuation-passing style, but we need to change the representation of continuations 61 Solution: Replace calls of the form (cont s) with (apply-cont cont s): (define sum ( (n cont) (if (= n 0) (apply-cont cont 0) (sum (- n 1) (let ((n n) (cont cont)) ( (acc) (apply-cont cont (+ n acc))) ))))) 62 Solution: Replace calls of the form (cont s) with (apply-cont cont s). Where: (define apply-cont ( (cont acc) (cont acc))) we come to the representationWith this, independent-preregister-tail form, as follows: 63 (define id ’()) (define sum ( (n cont) (if (= n 0) (apply-cont cont 0) (sum (- n 1) (cons n cont))))) (define apply-cont ( (cont acc) (if (null? cont) acc (apply-cont(cdr cont) (+ (car cont) acc))))) > (sum 1000000 id) 64 Where are we? • All calls are tail-calls • The dereferencing of free variables happens automatically when cons is invoked • So, we can proceed as we did in the tail form definitions of sum leading to the trampoline form, as follows: 65 Trampoline form (define sum ( () (if (= n 0) {( cont cont) ( acc 0) ( action apply-cont)} {( cont (cons n cont)) ( n (- n 1)) ( action sum)}))) 66 (define apply-cont ( () (if (null? cont) ( action false) {( acc (+ (car cont) acc)) ( cont (cdr cont)) ( action apply-cont)}))) > {( cont id) ( n 1000000) ( action sum) 67 (run)} Modeling the apply-cont dispatch • Using the abstract method apply-cont by inheriting from the associated abstract class of continuation types (A Little Java, A Few Patterns, Chap. 1) • There will be two subclasses – For continuation modeled by the empty list – For continuations modeled by cons 68 Continuations as procedures • Alternatively, we can go back to the version of continuations represented as procedures • We can treat those procedures as actions, as follows: 69 (define id ( () action false))) ( (define sum ( () (if (= n 0) {( acc 0) ( action cont) } {( cont (let ((n n)(cont cont)) ( () {( acc (+ n acc)) ( action cont)}))) ( n (- n 1)) ( action sum)}))) 70 The moral... • A problem with poor implementation technology can be solved with good correctness-preserving transformations • Our approach relies heavily on such transformations • You will learn to develop your own perspective on how to implement things elegantly! 71 Why study programming languages? • Other roles: Three quotes from students: – Jonathan Sobel: “That was the amazing part: I had produced a program that I could not have written, and in any case would not have wanted to write.” 72 Conclusion • The study of programming languages yields general-purpose tools that allow you to do things that are too hard to do without them. • Learning these tools is the standard fare for researchers in programming languages 73 Conclusion • A final quote from Christopher Strachey: – I always worked with programming languages because it seemed to me that until you could understand those, you really couldn’t understand computers. Understanding them doesn’t really mean only being able to use them. A lot of people can use them without understanding them. 74 For those who wish to run this code in Scheme instead of Java or C, here is the definition of while. (define-syntax while (syntax-rules () ((while exp stmts ...) (let loop () (if exp (begin stmts ...(loop))) )))) 75