Held: Monday, March 30, 1998
LetAbe an integer-indexed array of references to integers andxbe an integer. If we follow the coercion rules for Algol-68 given in class (dereference the rhs sufficiently many times to obtain an appropriate type to assign to the left-hand side), thenA[x] = x;
uses the r-value of x on the left-hand side and the l-value of
x on the right-hand side. Why? Because the index in the
array is an integer. To get that integer, we need to dereference x
(taking the r-value). However, since the value we want to store is a
reference to an integer, we do not dereference the x on the
right-hand side and therefore use its l-value.
If you gave a similar answer (including explanation), let me know and
you'll get full credit. If you only wrote the code and didn't give an
explanation, you also got 25/25 (although I was seriously considering
giving you 20/25).
Yes, user-definable types may slow down compilation. Note, however, that the slowdown won't be in the type-checking, which should not be more complicated with additional types (in many cases, it's simply a lookup in a table), particularly if we don't allow coercion of user-definable types and use a restricted version of type equivalence.
In addition, it's important to consider why you object to a slower compiler. Note that compilation speed is just one factor in the time of program development, which also includes time for testing, debugging, and development. Since user-definable types have the capability of increasing the readability and modularity of a program, they are likely to slightly decrease writing time (as programmers won't need to continually remind themselves of the different variables they've chosen to use a compound type) and significantly decrease debugging time. Relative to these gains, the slight loss in compilation speed is not a signficant factor.
You are correct that it will be more complicated to build the compiler. However, that is only of concern to implementers. If you are an implementer of the language, you might want to consider some of the advantages you encounter from user-definable types. First, such types are popular, so you increase the salability of your compiler. More importantly, user-definable types give you (the compiler writer) some control over how data are organized within the type, so you can potentially provide more efficient implementations. I realize these don't necessarily ameliorate your additional costs, but you must understand that since your compiler is likely to be used by thousands (if not hundreds of thousands) of programmers, a large increment in compiler complexity is often considered worthwhile even if it gives only a small benefit to programmers, because that small benefit is multiplied by thousands.
In groups of approximately four, write a testing predicate for the quicksort routine that we developed before break. Your predicate should return true if the sorting routine can sort any list of integers up to size fifteen and false if it cannot sort any such list. Your routine should be sufficiently robust that you'd be willing to stake your grade on it (that is, that any sorting routine that passes the predicate will correctly sort any list of integers of size less-than or equal to fifteen and that any sorting routine that fails the predicate will fail on at least one such list).
Turn in your predicate.
(* 3 (- (+ A 4) 5))
(- (+ A 4) 5) is
"multiply 3 by the result".
(+ A 4) is "subtract 5 from the result and then multiply
3 by that new result".
(lambda (x) (* 3 x))
(lambda (y) (- y 5))
((lambda (x) (* 3 x)) ((lambda (y) (- y 5)) (+ A 4)))
ADD A #4 STO TMP SUB TMP #5 STO TMP SUB #3 TMP
((read) - (read)). Which (read)
is done first? It turns out that it's up to the implementer.
If you want the first one done first, you could write
((lambda (first) ((lambda (second) (- first second)) (read))) (read))
You should work on this exercise in groups of approximately four. Turn in a sheet with your answers.
As you may know, one of the criticisms of recursive program design is that it is "inefficient" because of the space required for stack frames and the time required to add and remove those stack frames. One solution to this problem is to use tail-recursive functions in which "nothing" is done with the result of the recursive call (other than returning it). With such functions, it is possible to replace the contents of the stack frame (updating any parameters with their new values) rather than to create a new stack frame.
Consider the factorial function, typically written as
(define (fact n)
(if (= n 0) 1
(* n (fact (- n 1)))))
One way to make this tail-recursive is to define a helper function that
takes a second accumulator as a parameter and have that accumulator
accumulate the multiplication of n*(n-1)*(n-2)*.... Such
a function might look like
(define (tr-fact n)
(tr-fact-helper n 1))
(define (tr-fact-helper n acc)
(if (= n 0) acc
(tr-fact-helper (- n 1) (* n acc))))
There are a few problems with this strategy. First, it is not sufficiently general (not all recursive functions can use a numerical accumulator). Second, it does not do the operations in the same order (the original version multiplied 1*1*2*3*...*n. This multiplies 1*n*(n-1)*...1.).
To develop a more general strategy for making recursive functions tail-recursive, we can rely on continuations. Rather than passing an accumulator in each recursive call, we'll instead pass a continuation that describes what to do with the result. Here's a sketch
(define (cfact n)
(cfact-helper n (lambda (x) x)))
(define (cfact-helper n cont)
(if (= n 0)
(cont 1)
(cfact-helper (- n 1) ???)))
Your goal is to figure out what to plug in for the
???. How can you do so? You might first
determine what happens when you call (cfact 0). Next,
consider what you know about continuations. For example, in the
original definition of factorial, if the continuation for (fact
5) is C, what is the continuation for (fact
4)?
Use your answer to that question to help you fill in the question marks.
Test your answer in the Scheme interpreter.
Run the new, continuation-based (cfact 3) by hand to see
what happens. What do you observe?
Can you use this strategy for other common recursive functions? Why or why not?
Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.
Source text last modified Thu May 7 20:29:41 1998.
This page generated on Thu May 7 20:34:43 1998 by SiteWeaver.
Contact our webmaster at rebelsky@math.grin.edu