- Consider a new EBNF form A ^ B which
denotes A | ABA | ABABA | ....
Such a form makes it convenient to write rules involving
separators, such as
IDLIST → ID ^ ","
This form can also be used to model a construct representing
"one or more" A's, rather than using A+
or AA* or
A*A. Show how to do this.
- Consider an extension of Iki that features
if and while statements,
variable declarations, and a large set of arithmetic, relational
and logical operators. Variables in this language may
only have
type integer, but expressions in
if and while statements
are to be Boolean-valued. Give a syntax for this
language that enforces typing. (In other words, type errors are to
be syntactic, rather than static semantic, errors.)
- Suppose we wanted to modify Iki by (1) making assignments
be expressions, as they are in C, rather than statements, (2)
adding function declarations and function calls, and (3)
allowing four types: integers, booleans, doubles and strings.
- Give the macrosyntax (for the whole modified language, not
just the parts that have changed). Note that the addition
of types means you will need literals for each type as
well as additional operators to make use of them.
Type checking does not need to be done
syntactically.
- Write an abstract syntax for the new language.
- Write a natural semantics for the new langauge.
- Write a denotational semantics for the new langauge.
- In the Ada langauge comments are started with "--"
and go to the end of the line. Therefore the designers decided
not to make the unary negation operator have the highest precedence.
Instead, expressions are defined as follows:
EXP → EXP1 (and EXP1)* | EXP1 (or EXP1)*
EXP1 → EXP2 (RELOP EXP2)?
EXP2 → -? EXP3 (ADDOP EXP3)*
EXP3 → EXP4 (MULOP EXP4)*
EXP4 → EXPR5 (** EXPR5)? | not EXPR5 | abs EXPR5
Explain why this choice was made. Also, give an abstract syntax
tree for the expression -8 * 5 and explain how this is
similar to and how it is different from the alternative of
dropping the negation from EXPR2 and adding - EXPR5
to EXP4.
- Explain the need for elsif in Perl and Ada.
Then propose changes to the Perl and Ada syntax that preserve
the syntactic prohibition on the dangling else by means of
"blocking" the statement components, but avoids the funny word.
Hint: you only have to change the syntax clause for
the if-statement; you do not have to add any new clauses.
- Give regular expressions for the following languages.
You may not use a not operator; use
only concatenation, alternation, Kleene star and Kleene plus.
- A number from the set {34,36,37,59,57,365}.
- The set of all strings over
{a,b,c} that do not contain two consecutive a's anywhere
in the string.
- The set of all strings over {a,b} that do not
have two consecutive a's nor two consecutive b's
anywhere in the string.
- The set of all strings over
{a,b,c} that do not contain three consecutive a's anywhere
in the string.
- The set of all strings over {a,b} that contain neither
the substring "aba" nor "babb" anywhere in the string.
- C, does not allow structures (i.e., non-atomic objects)
to be tested for equality. Ada allows this. How exactly does this
complicate the compiler or the runtime system?
- If possible, write a program in Modula 3 that makes a variable point
to itself. That is, for some designator X, make it so that
X^ = X. If this is not possible, state why it is not possible.
- If possible, write a program in Ada that makes a variable point
to itself. That is, for some designator X, make it so
that X.all = X. If this is not possible, state why it is not
possible.
- Give the abstract syntax tree for the following C++ expression
(a = 3) >= m >= ! & 4 * ~ 6 || y %= 7 ^ 6 & p
- Give an abstract syntax tree for the following Ada code fragment:
if x > 2 or not F(F(X)) then
Put (-3 * Q);
elsif not Here or There then
loop
while Close loop Try_Harder; end loop;
X := X ** 3 mod 2*X;
end loop;
Q.all(4).G(6) := Person.List(2);
else
raise Hell;
end if;
- Give the abstract syntax for the following C fragment:
int f(int x, int y) {
return y,x?a.p[5]+=7&!y<x<y+++x||+x|*&y-~--y*x^y:0;
}
- Give the abstract syntax for the following C fragment:
printf("%08X %#16.7e\n",x---a.p[9]^x|*&p&1,*((float*)&x));
- Give the abstract syntax tree for the following Perl fragment
(die and split are both list operators):
die "\n">>1,~\$$x^split @a,3|x**$;=>%p&&&p;
- Give the abstract syntax tree for the following Perl fragment:
sub f {
print $x[0] unless q/pig =~ m!^r$!/ =~ m!^\((4)+!x;
push @_, sort reverse keys %$shift, g A 1, 2
}
- What can't you do with a Perl package named m, s,
or y?
- In C++ you can say (x += 7) *= z but you can't say this
in C. Explain the reason why, using precise, technical terminology.
See if this same phenomenon holds for conditional expressions, too.
What other languages behave like C++ in this respect?
- Identify the following errors as syntactic, static semantic, or
dynamic semantic (runtime).
- Redeclaration of an identifier in Ada.
- Unbalanced parentheses.
- Applying an operator to an element of the wrong type in Ada.
- Array index out of bounds in Ada.
- Integer division by zero in Ada.
- Semicolon after a class body in Java.
- Wrong number of arguments supplied to a call in Java.
- Assignment of a variable of type T to a variable of type subtype of
T where the first variable is out of the range of the second in Ada.
- An unwanted infinite loop.
- Dereference of a null pointer in Ada.
- Application of the "." to an identifier which is not a field of the
record (or struct) in Ada.
- Use of an uninitialized variable in Ada.
- for I in A'Range loop Put("*"); X := X+1; end loop; (Ada).
- Identify each of the following C90 code fragments
as either (a) a lexical error, (b) a syntax error
(c) a static semantic error, (d) a dynamic semantic error, or
(e) not an error. Again, these are code fragments, not complete
programs (translation units, as they call them in C).
not complete programs.
- {int x; int x;}
- {int x; {int x;}}
- {int x; struct x {};}
- void f() {return 5;}
- x +. 2;
- x = (((int)x)-*0);
- x---!-+~*&x
- x = (((int)x)-5));
- x>&x
- if (x == 2) {return 1;}; else {return 2;};
- int a[5]; a[5]=10;
- float y; double x = y / 0.0;
- Hawai`i = 2;
- int y = 0; *y = 7;
- main(5, 0, 0, 0, 0, 0); /* a call to the usual main() function */
- printf("%d\n", x, y, z);
- class C {}
- struct {int x;} p; p.y;
- int f() {int* p = p;}
- int main() {main();}
- int f() {int p; return p;}
- Identify each of the following as either (a) a
lexical error, (b) a syntax error (c) a static semantic
error, (d) a dynamic semantic error, or (e) not an error.
All items refer to the ML programming language and
are considered to be "whole programs" - that is, they
have no dependence on external bindings.
- let val x = 2 val x = 3 in x end;
- let val x = 5; val x = "dog" in x end;
- let val x = print 5 in x end;
- let val x = print "5" in x end;
- let val y = ref 2; val x = while (!y < 1) do print "*" in x end;
- let val y = ref 2; val x = while (!y != 1) do print "*" in x end;
- let val x = 3 in () end;
- let val p = *^$q in () end;
- Give a grammars for the languages
- {w in {a,b,c}* | w has at most one occurence of any symbol}
- {ambncm+n | m,n >= 1 }
- Give grammars for the languages
- {anbncn | n >= 0 }
- {aibjck | i = j or j = k }
- {ww | w in {a,b}* }
- The language Ada does not require the parameters to a
subprogram call to be evaluated in any particular order. Is it
possible that different evaluation orders can lead to different
arguments being passed? If so, give an example to illustrate
this point, and if not, prove that no such event could occur.
- It is a well-known irritation that Ada does not allow you to write
array aggregates for zero- or one-element arrays, e.g., A := (3)
gives a static semantic error when A is a one-element array of
Integer. Why is this so? Propose a (trivial) syntactic extension to
Ada that would remove this irritation.
- What exactly must be the case for a
subprogram to not need a static link in its stack frame?
Think up as many cases as possible.
- In Ada, the declarations
X: Integer := X + 1;
Foo: Foo;
Bar: Real := Bar(Foo);
(where global declarations of X, Foo and Bar are visible) are
all illegal, since a declaration of an identifier hides global
declarations of the same name immediately at the point it
appears in the text, but the identifier may not be used until its
declaration is complete. Give an alternate interpretation
under which these declarations would be legal and explain the
advantages and disadvantages of it from both the programmer's
and the compiler writer's perspectives.
- In C++ it is not permitted to have two functions that differ only in
return type overload each other. In Ada it is allowed. What is the
reason for this situation? Even though Ada does allow this flexibility
in overloading, the compiler needs some sophistication. What exactly
is involved? Be very precise in your explanation and illustrate it with code
fragments.
- Many programming languages require that in order to have
mututally recursive functions, the programmer first define one
header (name, return types, parameters and parameter types), then
the entire second function, then the entire first function. For
example, in C++:
int f(int x, char y);
void g(int x) {if (x < 0) f(2, 'c');}
int f(int x, char y) {g(randomInteger());}
In C++, when f is finally declared, the names
of the formal parameters don't have to be repeated exactly as they
appeared in the incomplete specification. But in Ada they do.
Explain why the Ada rule makes life much easier for the compiler
writer.
- Here's some code in some language that looks exactly like C++. It is
defining two mutually recursive types, A and B.
struct A {B* x; int y;};
struct B {A* x; int y;};
Suppose the rules for this language stated that this language used
structural equivalence for types. How would you feel if you were a
compiler and had to typecheck an expression in which an A was used
as a B? What problem might you run into?
- Here's a variation of M-J. Dominus' Spectacular Example.
local
fun split [] = ([],[])
| split [h] = ([h], [])
| split (x::y::t) = let val (s1,s2) = split t in (x::s1,y::s2) end
fun merge c ([], x) = x
| merge c (x, []) = x
| merge c (h1::t1, h2::t2) =
if c(h1,h2)<0 then h1::merge c(t1,h2::t2) else h2::merge c(h1::t1,t2);
in
fun sort c [] = []
| sort c x = let val (p, q) = split x
in merge c(sort c p, sort c q)
end;
end;
- During type inference, give the types assigned to
- the parameter c within sort
- the function split
- the function merge
- the y in the third clause of split?
- Give the type of sort and explain why it is not what you would
expect.
- How do you rewrite the function to make it actually do a
mergesort?
- Here is a cool little functional language:
PROGRAM → (DECL ";")* EXPR
DECL → val ID "=" EXPR
| fun ID "(" PARAMS? ")" "=" EXPR
EXPR → NUMLIT | ID | UOP EXPR | EXPR BOP EXPR
| EXPR "?" EXPR ":" EXPR | ID "(" ARGS? ")" | "(" EXPR ")"
PARAMS → ID ("," ID)*
ARGS → EXPR ("," EXPR)*
UOP → "-" | "abs" | "not"
BOP → "+" | "-" | "*" | "/" | "mod" | "and" | "or" | "==" | "<"
- Why is this called a functional language?
- Is the grammar ambiguous? Why or why not?
- Write a Greatest Common Denominator function in this language.
- Give three examples of syntax errors and three examples of
static semantic errors in this language. Make sure to write
down all your assumptions; I did not give you any semantics so
you will have to make up something reasonable.
- Here is the description of a language. Programs in this
language are made up of a non-empty sequence of function
declarations, terminated by semicolons, followed by a
single expression. Each function declaration starts with the
keyword fun followed by the function's name
(an identifier), then a parenthesized list of zero
or more parameters (also identifiers) separated by commas,
then an equals sign, then the body which is a single expression.
Expressions can be numeric literals, string literals,
identifiers, function calls, or can be made up of
other expressions with the usual binary arithmetic
operators (plus, minus, times, divide) and a unary
prefix negation and a unary postfix factorial ("!").
There is also an infix binary operator called then
which indicates that both of its expressions be evaluated in
order from left to right, with the value of the right expression
being the value of the entire then-expression. There's
a conditional expression that looks just like the one
in Java, C, and Perl (with the question mark and colon).
Factorial has the highest precedence, followed by negation,
the multiplicative operators, the additive operators, conditional
and finally the then operator. Parentheses
are used, as in most other languages, to group subexpressions.
Numeric literals are non-empty sequences of decimal
digits with an optional fractional part and an optional
exponent part, as in Ada but without the fancy "#" notation.
String literals are as in C (you did these for Homework #1).
Identifiers are those non-empty sequences of letters, decimal
digits, underscores, at-signs, and dollar signs, beginning
with a letter or dollar sign, that are not also reserved
words. Function calls are as in C, with the arguments
in a comma-separated list of expressions bracketed by
parentheses. There are no comments in this language, and
whitespace can be used liberally between tokens.
- Write an example program in this language that
shows off everything described above.
- Write the microsyntax of this language.
Use my EBNF extensions for Unicode categories and
subtraction.
- Write the macrosyntax of this language.
- Write a program in this language that consists
of the declaration of the GCD function followed by
a call to this function with the arguments 99 and 66.
- Show the abstract syntax tree for your
GCD program
- Many languages have a syntax rule
DESIGNATOR → DESIGNATOR "." ID
for specifying variables made up from a record and a field of
the record. But sometimes it can have the additional interpretation
that the DESIGNATOR
to the left of the dot was the name of a (visible) subprogram and
the ID was an object declared immediately inside that
subprogram. Show how to rearchitect the semantic object
class hierarchy to support this.
- In Ada, C and C++ arrays and records can be allocated on the stack,
not just on the heap. When making assigments of aggregates
to variables, compilers usually generate code to deposit the values
in temporary storage. Why is this necessary in general? After all, in
Weekdays := Day_Set(False, True, True, True, True, True, False);
we could construct the aggregate directly in the variable
Weekdays. Give an example of an assignment statement that
illustrates the necessity of constructing an aggregate in temporary
storage (before copying to the target variable).
- Write a Perl "class" for machine parts that have an identification
number (a positive integer divisible by 5), a weight (a positive
floating-point number) and a name (which must consist entirely and
exclusively of alphabetic characters). Provide a "constructor" that
takes in a string consisting of the id, weight, and name, respectively
in which
- the string may have leading and trailing spaces
- the three fields are separated by a vertical bar
- the weight is not expressed in scientific notation: it can have
an integral value, but if it does have a decimal point, then
it is followed by a non-empty fractional part. There is no
"E" part, ever. It's simple.
The constructor will check for a valid argument by matching
against a regex, and if all is cool, will split the string
to assign to its fields.
- Find some old "procedural" code you have written and rewrite
in an object-oriented fashion. I don't mean that you have to
use inheritance or polymorphism; all I am really looking for is
that you wrap some functions up in a sensible class.
- Write a three page paper on the nature of identity in
object oriented philosophy. Include some code fragments to illustrate
your main points.
- In Java, you generally implement "callbacks" via registration of
listeners that implement a known interface, rather than using
method pointers. Create a Swing component called AngleReader
which displays a picture of a circle and notifies all its listeners
of the angle, in degrees, that the mouse cursor makes with the horizontal
axis of the circle as the mouse moves over it.
- A common pattern that comes up a lot is the need to assign unique
identifiers to objects of a given class,
for example:
class Item {
private int id;
private static int numberOfItemsCreated = 0;
public Item() {id = numberOfItemsCreated++;}
// pretend that there are more members here...
};
As you can see, every item that gets created will get a unique id.
Because this pattern occurs frequently, it might be nice to generalize
this and make make something reusable out of it so we don't have to
write this code inside every class that needs ids. Perhaps we
need an interface or abstract class. Tell me why these two suggestions
won't work with a detailed, technical answer. Then tell me something
that will work. (Note: there is nothing wrong with the access
modifiers above; the problems with my two suggestions have to
do with the nature of interfaces and abstract classes.)
- Given a utility class, can you always rewrite it as
a singleton? Given a singleton, can you always rewrite it
as a utility class? If so, when would you choose one over
the other?
- One of the most important ways in which object oriented
programming helps us to manage complexity is through the ability
to group related classes into a hierarchy of subclasses and
superclasses. Dynamic binding (run-time polymorphism) allows
us to operate on collections of objects from different classes
in a hierarchy safely. Furthermore, systems which are
programmed using dynamic binding are more easily extendible.
- What construct is used in non-object oriented programming
languages to simulate class hierarchies, and why do we say
that it is unsafe?
- Why are inheritance-like structures in non-OOPLs harder to extend?
- Why is it said that implementation inheritance is at
odds with encapsulation?
- Inheritance is not always appropriate. Discuss the
reasons why a design with a superclass Person and
subclasses for different jobs (e.g., programmer, manager,
ticket agent, flight attendant, supervisor, student, etc.)
is a lousy design. Give an alternative.
- In designing a class hierarchy in C++, when should you make
an operation virtual and when should you make an operation
non-virtual? Give examples.
- Write C++, Java, Smalltalk, and Ada declarations corresponding
to the following class diagram:
- Explain how, in C++, you can get access to, and indeed modify, a
protected component of an object that someone else declared.
As a concrete example, let's say someone has declared
class C {protected: int x; ...};
C c;
then your job is to assign a new value to c.x.
Assume there are no public operations of C that
modify x that you know of. Also, do not use
any preprocessor tricks (like #define protected
public).
- What makes more sense, to inherit a list from a stack
or a stack from a list?
- Why did the designers of the C++ standard library containers
emphatically reject an inheritance hierarchy of containers?
- In C++ you can write
class C {int x;};
C c;
C* p = &c;
cout << p;
and there is no compile-time nor link time error, despite the
fact that operator<<(C*) is not a member of ostream
(since that class was declared before you declared C), nor for
that matter did anyone declare the global function
ostream& operator<<(ostream&, C*);
So why does it all work? Explain exactly what gets printed and why.
- Write a function that takes in a function f and
a list [a0, a1, ..., an-1]
and returns the list
[a0, f(a1), f(f(a2)),
f(f(f(a3))), ...].
For example, if you pass as arguments the function that
doubles its inputs, and the list [4, 3, 1, 2, 2], then
the return value would be [4, 6, 4, 16, 32].
Hints: Do the f(f(f...)) as a separate function. Also
you do NOT have to make your function tail recursive.
- Write a pair of functions, f and g, such that every time you
call f, you get back 5 less than the result of the previous call
to f or g, and every time you call g, you get back double the
absolute value of the result of the last call to f or g. The
"initial value" is 0. Hint: I'd use Perl for this one! It is
possible to do this in one line of Perl.
- Write the following function in Standard ML, where your
implementation MUST BE tail recursive.
Given a list [a0, a1, ..., an-1]
return a0*a1 + a2*a3
+ a4*a5 + ....
For example, if given [3, 5, 0, 28, 4, 7] we return
15 + 0 + 28 = 43. If there are an odd number of
elements in the list, assume there is an extra "1"
for padding.
Because I am in a good mood, here is a NON tail recursive
formulation:
fun sum_of_prods [] = 0
| sum_of_prods (x::nil) = x
| sum_of_prods (x::y::t) = x * y + sum_of_prods t;
- Write some JavaScript that adds a new method to arrays so that
if I call this method on an array with two parameters f and g,
I get back a new function which, when called with one argument k,
returns the composition of f and g applied to the kth element
of the original array.
Hint: If we defined the functions square and
addSix the "obvious" way, and we called this new method
weird, then:
[4, 6, 7, 3, 5, 2, 4].weird(addSix, square)
would return the function z such that
z(2) == 55
because the element at index 2 within the array is 7
and 7^2 + 6 = 55.
- Complete the following definition of a dot product function in ML:
val dot =
let
fun transpose ... =
in
....
end;
The transpose function should work like this
transpose ([x1,...,xn],[y1,..,yn]) = [(x1,y1),...,(xn,yn)]
raising Domain if the arrays have different lengths. The body
of the definition of dot (between the "in" and "end") should
contain only instances of the functions transpose, o, foldr,
map, op*, op+, and the value 0.
- What philosophers call a "class" mathematicians call a "set"
(i.e., a collection of unordered, unique, values). So, a philosopher
says that every member of a subclass is also a member of the superclass.
But a C++ programmer says that every member of a superclass is also a
member of its subclass! What is going on here?
- This fragment of Java code illustrates something about
scope. Or does it? Relate it to other similar problems we've seen
regarding scope. (Don't forget to come across as being
articulate and intelligent in your discussion.)
public void fail() {
class Failure extends RuntimeException {}
throw new Failure();
}
- What would this program output under static scope rules?
Under dynamic scope rules?
declare x = 2;
sub f() {print x;}
sub g() {declare x = 5; f(); print x;}
g();
print x;
- Explain what is printed under (a) call by value, (b) call by
value-result, (c) call by reference, (d) call by name.
x = 1;
array y = [2, 3, 4];
sub f(a, b) {b++; a = x + 1;}
f(y[x], x);
print x, y;
- What's wrong with this, if anything, and why?
class Pair implements Cloneable {
private Object first, second;
public Pair(Object x, Object y) {first = x; second = y;}
public getFirst() {return first;}
public getSecond() {return second;}
}
- Give an example which shows that default parameters are unnecessary
in C++ because you can always get the desired effect with overloading.
- What does the following program output?
with Ada.Text_IO, Ada.Integer_Text_IO;
use Ada.Text_IO, Ada.Integer_Text_IO;
procedure P is
A: Integer := 4;
type T is access Integer;
B: T := new Integer'(4);
C: T := new Integer'(4);
procedure Q (X: in out Integer; Y: T; Z: in out T) is
begin
X := 5;
Y.all := 5;
Z.all := 5;
end Q;
begin
Q (A, B, C);
Put (A);
Put (B.all);
Put (C.all);
end P;
- If possible, give Ada type and
object declarations to make a variable point to itself, that is,
make it so that P = P.all. If it is not possible to do so, state
why no such variable can be defined.
- If possible, give C++ type and
object declarations to make a variable point to itself, that is,
make it so that p == *p.
If it is not possible to do so, state why no such variable can be
defined.
- Consider the implementation of a Container class framework
with the following abstract base class Container:
template <class Item>
class Container {
public:
unsigned numberOfItems() {return currentSize;}
bool isEmpty() {return currentSize == 0;};
virtual void flush() = 0;
~Container() {flush();}
private:
unsigned currentSize;
};
Here the idea is that each particular (derived) container class shall
implement its own flush() operation (which makes sense because
different containers are flushed in different ways: there may be
arrays, linked lists, rings or hashtables used in the representation),
and when a container is destroyed its flush() operation will
be automatically invoked. However, the idea is flawed and the
code as written causes a terrible thing to happen. What happens?
- It is possible to make a Person class, then subclasses of
Person for different jobs, like Manager, Employee, Student,
Monitor, Advisor, Teacher, Officer and so on. This is a bad
idea, even though the IS-A test passes. Why is this a bad
idea and how should this society of classes be built?
- Why has no (major) language combined static scope and shallow
binding?
- I couldn't find in the defintion of C whether the language employs
shallow or deep binding. Why?
- Make a Perl module with a function called nextOdd. The first time
you call this subroutine you get the value 1. The next time, you get
a 3, then 5, then 7, and so on. Show a snippet of code that uses this
subroutine from outside the module. Is it possible to
make this module hack-proof? In other words, once you compile this module,
can you be sure that malicious code can't do something to disrupt the
sequence of values resulting from successive calls to this function?
- Write Perl subroutines that takes in a file handle
and returns a reference to a hash that maps an integer x to
the number of lines
in that file that contains x characters (not including the
newline), up until the point in the file that contains two
blank lines. For example, if the file contains
Blah
dog dog 123
Four
Zero
1234567890*
More stuff!
1234
Then you should return the hash reference {4=>3,11=>2,0=>1} because, before
the part in the file with two blank lines, there are three lines with
four characters, two lines with 11 characters, and 1 line with zero
characters. The lines after the two blank line sequence are
not considered, nor are the lines in the two blank line
sequence. If there is no place in the file with two blank lines,
then all lines will be counted.
- Implement a priority queue data
type in Ada, using a server task to provide synchronization.
- Implement a priority queue data
type in Ada, where each priority queue object is a protected object.
- In the example Ada package
implementing the Set data type with a guardian task, there is a
serious problem with the package design: errors in insertion are not
handled well! What happens if we run out of memory? (Answer in
terms of the system interfaces.) Show how to add robust error
handling to the package and comment on the amount of parallelism
permitted with your solution.
- Discuss the difficulties of
implementing a secure Post Office object in Ada that meets the
following requirements. The post office is to maintain a collection
of P.O. boxes, each belonging to some task. Any task can put a
letter into another task's box, but only the owner of a particular
box can open it and read the letters.
- A relay is an agent task
created by one task to relay a message to another. For example, if
a calling task wishes to send a message to another but does not wish
to wait for a rendezvous, the caller can create a relay task to send
the message. (Note that relays are only appropriate to use when
there are no out
parameters in the called entry.) Sketch in detail a body for an Ada
task T that uses a relay R to call entry E of
task U, passing message X.
- In Ada, if two tasks are executing
a Put
procedure at the same time, their outputs may be interleaved. (This
could produce amusing and even distasteful results, e.g. writing
"sole" in parallel with "ash") Show how to set
things up so only one task is writing at a time.
- Why do Java programmers not have
to worry about the situation in the previous problem (interleaving
of text output written to a stream)?
- One of the nice features of
Quicksort is that it allows a great deal of parallelism in an
implementation. After partitioning, the slices on either side of
the pivot can be sorted in parallel. It is very easy to set things
up to do this in languages such as occam, but tedious in Ada and
Java. Code up a parallel version of Quicksort in Ada or Java
and explain why it is messy.
- In Ada, what happens when you try
to call an entry in a task that has terminated? Comment on the
following code fragment as a possible approach to calling entry E of
task T only if T has not terminated.
if not T'Terminated then
T.E;
end if;
- In Java, what happens if you
invoke a method on a thread that has completed?