The Role of the Study of Programming Languages in Dan Friedman

advertisement
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
Download