This homework has three types of problems:
Self Check: You are strongly encouraged to think about and work through these questions, but you will not submit answers to them.
Problems: You will turn in answers to these questions.
Programming: This part involves writing Scala code. You are strongly encouraged to work with a partner on it.
(Required) Mitchell, Chapter 12.
(Required) Mitchell, Chapter 13.
(As needed) Java and Scala Resources as needed for the programming.
Mitchell, Problem 13.5
Mitchell, Problem 13.3
In C++, a derived class may have multiple base classes. The designers of Java chose not to allow multiple inheritance. Therefore, a Java derived class may only have one base class. However, a Java class may be declared to implement more than one interface. This question asks you to compare these two language designs when consider the following kinds of movies:
Please answer the following:
Draw a C++ class hierarchy with multiple inheritance for the above set of classes.
If you were to implement these classes in C++ for some kind of movie database, what kind of potential conflicts associated with multiple inheritance might you have to resolve?
If you were to represent this hierarchy in Java, what interfaces and classes would you use? Write your answer by carefully drawing a class/interface hierarchy, identifying which nodes are classes and which are interfaces. Note that there must be a class for each of the movie genres, but you may use any interfaces you require to preserve the relationships between genres. For example, one way of doing this would be to have the Comedy and RomanticComedy genres both implement some kind of IComedy interface.
Give an advantage of C++ multiple inheritance over Java classes and interfaces and one advantage of the Java design over C++.
Assuming you will need to create objects of every movie type
(other than Movie
), do Scala traits offer a better solution in
this case? Why or why not?
Java does not have general pointer types. More specifically, Java only has primitive types (Booleans, integers, floating point numbers, ...) and reference types (objects and arrays). If a variable has a primitive type, then the variable is associated with a location, and a value of that type is stored in that location. When a variable of primitive type is assigned a new value, a new value is copied into the location. In contrast, variables of reference types are implemented as pointers. When a reference-type variable is assigned, Java copies a pointer to the appropriate object into the location associated with the variable.
Imagine that you were part of the Java design team and you believe
strongly in pointers. You want to add pointers to Java, so that for
every type A
, there is a type A*
of pointers to values of type
A
. Gosling is strongly opposed to adding an "address of" operator
(like &
in C
), but you think there is a useful way of adding
pointers without adding address-of.
One way of designing a pointer type for Java is to consider A*
equivalent to the following class:
class A* {
private A data;
A*() {
data = null;
}
public void assign(A x) {
data=x;
}
public A deref() {
return data;
}
}
Intuitively, a pointer is an object with two methods, one assigning
a value to the pointer and the other dereferencing a pointer to get
the object it points to. One pointer, p
, can be assigned the
object reached by another, q
, by writing p.assign(q.deref())
.
If A
is a reference type, do A*
objects seem like pointers
to you? More specifically, suppose A
is a Java class with
method m
that has a side effect on the object. Consider the
following code:
A x = new A(...);
A* p = new A*();
p.assign(x);
(p.deref()).m();
Here, pointer p
points to the object named by x
and p
is
used to invoke a method. Does this modify the object named by
x
? Answer in one or two sentences.
What if A
is a primitive type, such as int
? Do A*
objects
seem like pointers to you? (Hint: Think about code similar to
that in part (a).) Answer in one or two sentences.
If A <: B
, should A* <: B*
? Answer this question by
completing the following chart and explaining the relationship:
Method Type
------------ ----
A*.assign
B*.assign
A*.deref
B*.deref
d. Can you generalize the issue discussed in part (c) to Java
generics? More specifically, What might happen if you had a
pointer generic? Based on the Ptr
example, do you think it is
correct to assume that for every generic class T
, if A <: B
then T<A> <: T<B>
? Explain briefly.
Type parameters in Scala (and Java) can be given upper and lower
bounds to restrict how they can be instantiated. Specifically, the
type List[_ <: C]
describes a list that stores some data of some
type that is a subtype of of class C
. In other words, the type
parameter has an upper bound C
. For example, an object of
List[_ <: Point]
is a list that contains objects which extend the
Point
class. For example, the list could be List[Point]
or
List[ColorPoint]
, etc. Reading an element from such a list is
guaranteed to return a Point
, but writing to the list is not
generally allowed. This sort of bounded type is often called an
existential type because it can be interpreted as "there exists some
type T such that T <: C
."
Existential types can also have lower bound constraints. A
constraint [_ >: C]
means that the existential type must be a
supertype of class C
. For example, an object of List[_ >: Point]
could be a List[Point]
or List[Any]
. (Any
is the supertype of
all types in Scala, and serves similar purposes as Object
in
Java). Reading from such a list returns objects of type Any
, but
any object of type Point
can be added to the list.
This question asks about generic versions of a simple function that reads elements from one list and adds them to another. Here is sample non-generic code, in case this is useful reference in looking at the generic code below.
def addAllNonGeneric(src: MutableList, dest: MutableList) : Unit = {
for (o <- src) {
dest += o;
}
}
val listOfPoints = new MutableList();
val listOfColorPoints = new MutableList();
...
addAllNonGeneric(listOfColorPoints, listOfPoints);
It will not compile in Scala, but gives you an idea of what we're trying to do.
The simplest generic version of the addAll
method uses an
unconstrained type parameter and no bounds.
def addAll0[T](src: MutableList[T], dest: MutableList[T]) : Unit = {
for (o <- src) {
dest += o;
}
}
Suppose that we declare
val listOfPoints : MutableList[Point] = new MutableList[Point]();
val listOfColorPoints : MutableList[ColorPoint] = new MutableList[ColorPoint]();
and call
addAll0(listOfColorPoints, listOfPoints).
Will this call compile or will a type error be reported at compile time? Explain briefly.
With listOfColorPoints
and listOfPoints
defined as in the
previous part of this question, will the call
addAll1(listOfColorPoints, listOfPoints)
compile, where addAll1
is defined as follows:
def addAll1[T](src: MutableList[_ <: T], dest: MutableList[T]) : Unit = {
for (o <- src) {
dest += o;
}
}
Explain briefly.
With listOfColorPoints
and listOfPoints
defined as in the
previous part of this question, will the call
addAll2[ColorPoint](listOfColorPoints, listOfPoints)
compile, where addAll2
is defined as follows:
def addAll2[T](src: MutableList[T], dest: MutableList[_ >: T]) : Unit = {
for (o <- src) {
dest += o;
}
}
Explain briefly. (The explicit instantation of T
in the call
to addAll2
is needed because of how Scala infers types.)
Suppose that your friend comes across the following general programming suggestion on the web and asks you to explain. What can you tell your friend to explain the connection between the programming advice and principles of subtyping, showing off your understanding gained from CS 334?
Get and Put Principle: If you pass a generic structure to a function:
Use an existential type with an upper bound (ie,
[_ <: T])
when you only GET values out of the structure.
Use an existential type with a lower bound (ie,
[_ >: T])
when you only PUT values into the structure.
Don't use an existential type when you both GET and PUT values out of / into the structure.
The goal of this problem is to implement the core data structures of a text editor using the Command Design Pattern.
In essence, a text editor manages a character sequence that can be changed in response to commands issued by the user, such as inserting new text or deleting text. Typically, these commands operate on the underlying character buffer at the current position of the cursor. Thus, if the cursor is positioned at the beginning of the buffer, typing the string "moo" will cause those letters to be inserted at the start of the buffer, and so on. This question explores the internal design of a simple editor.
Most text editors involve a GUI and the user issues commands to the
editor by keyboard and mouse events. For us, however, the most
interesting part of a text editor's is what happens behind the
scenes. Therefore, our text editor will just be a simple command
line program that prompts you for edit commands. The program will
print the contents of the text editor's buffer, including a ^
to
indicate the current cursor position, print the prompt "?", and then
wait for you to enter a command. At one point in time, this was in
fact how many text editors worked --- look up ed text editor in
Wikipedia, for example (or run it on our lab machines...). The
following shows one run of our editor:
Here is a summary of all available editor commands (including some described below). The term [num] indicates an optional number.
Command | Description |
---|---|
I text |
Insert text at the current cursor, moving cursor to after the new text. |
D [num] |
Delete num characters to the right of cursor position. (If num is missing, delete 1 character.) |
< [num] |
Move the cursor num characters to the left. (If num is missing, move 1 character.) |
> [num] |
Move the cursor num characters to the right. (If num is missing, move 1 character.) |
Q |
Quit |
U |
Undo the previous edit command |
P |
Print the history of edit commands |
R |
Redo an undone edit command |
I have provided a working program for all but the last three
commands. Your job is to change TextEditor
to support multiple
levels of undo and redo using the Command Design Pattern.
The figure below
shows an example that uses "U
" (undo) and "P
" (print history).
(We'll look at "R
" (redo) at the very end of the problem.) Notice
that you can undo multiple edits, not simply the last one. To
support this, the text editor must keep track of an edit command
history that permits you to undo as many commands from the history
as you like. Undoing all commands will lead you all the way back to
the original empty buffer.
The starter code for this problem is divided into two classes:
Buffer
: This class manages the internal state of the editor's
buffer (ie, character sequence and current cursor location), and
it supports commands for getting/setting the cursor location and
for inserting/deleting text. Refer to the javadoc on the
handouts page for more details. You should not change this
class.
TextEditor
: This class stores a Buffer
named buffer
. The
processOneCommand()
method reads in a command from the user
and performs the appropriate operation on buffer
by invoking
one of the following methods:
protected def setCursor(loc: Int): Unit
protected def insert(text: String): Unit
protected def delete(count: Int): Unit
protected def undo(): Unit
protected def redo(): Unit
protected def printHistory(): Unit
These methods are all quite simple. For example, the insert
method simply inserts the text into buffer
and repositions the
cursor:
protected def insert(text: String) = {
buffer.insert(text);
buffer.setCursor(buffer.getCursor() + text.length());
}
EditCommand
ClassTo support undo, we first change the way the TextEditor
operates
on the underlying buffer
. Rather than changing it directly, the
TextEditor
constructs EditCommand
objects that know how to
perform the desired operations and --- more importantly --- know how
to undo those operations. All EditCommand
objects will be derived
from the EditCommand
abstract class:
abstract class EditCommand(val target: Buffer) {
/** Perform the command on the target buffer */
def execute(): Unit;
/** Undo the command on the target buffer */
def undo(): Unit;
/** Print out what this command represents */
def toString(): String;
}
Here, the execute()
method carries out the desired operation on
the target
buffer, and undo()
would perform the inverse
operation. For example, to make insert undoable, the first step
would be to define an InsertCommand
class in a new file
InsertCommand.scala
:
class InsertCommand(b: Buffer, val text: String) extends EditCommand(b) {
override def execute(): Unit = { ... }
override def undo(): Unit = { ... }
override def toString(): String = { ... }
}
The TextEditor
would then perform code like the following inside
insert
:
protected def insert(text: String) = {
val command = new InsertCommand(buffer, text);
command.execute();
...
}
Assuming InsertCommand
is implemented properly, the insertion
would happen as before. However, the TextEditor
can now remember
that the last operation performed was the InsertCommand
we
created, and we can undo it simply by calling that object's undo()
method. In essence, an EditCommand
object describes one
modification to a Buffer
's state and how to undo that
modification. Supporting undo is then as simple as writing a new
kind of EditCommand
object for each type of buffer modification
you support.
And of course, to implement multiple levels of undo, you need to keep track of more than just the last command object created...
I suggest tackling the implementation the following steps:
Download the starter code from the handouts page. Compile the
Scala files with the command fsc *.scala
as usual. I have
added some assert
statements to the Buffer
class to aid in
debugging. The general form is
assert(condition, { "message" })
You may find it useful to add similar asserts to your own code as well.
Implement InsertCommand
, DeleteCommand
, and MoveCommand
subclasses of EditCommand
. For each one, you must define: 1)
execute()
, (2) undo()
, and (3) toString()
. I recommend
holding off on undo()
for the moment. Change TextEditor
to
create and execute
edit command objects appropriately.
Extend TextEditor
to remember the last command executed, and
change TextEditor
's undo()
method to undo that command. Go
back and implement undo
for each type of EditCommand
.
Once a single level of undo is working, extend TextEditor
to
support undoing multiple previous commands. Specifically, change
TextEditor
to maintain a history of commands that have been
executed and not undone. Also implement the printHistory()
method to aid in debugging. Your program should simply ignore
undo requests if there are no commands are in the history. You
are free to use any Scala libraries you like in your
implementation (ie, any immutable or mutable collection class).
The last task is to implement redo. Specifically, if you undo one or more commands but have not yet performed any new operations on the buffer, you can redo the commands you undid:
Note that redoing undone commands is no longer possible if the buffer is changed in any way. For example, if you insert text after undoing some command E, you should no longer be able to redo command E:
Also, redone commands should be able to be subsequently undone:
Extend TextEditor
to support multiple levels of redo. You
should not need to change any class other than TextEditor
to
implement this feature.
There are many extensions that would make our editor more "realistic". One idea is listed below. It should not require more than a few additional lines of code and really highlights the elegance and simplicity of adopting this design pattern.
You will want to test as you go on small inputs. I have also given you some test files that you may run as follows:
scala TextEditor < ex1.scala
The test files will all produce readily identifiable prose once you have properly implemented the different commands.
The autograder will also run a number of tests. If you pass them all, you should be in good shape.
Here is one interesting extension to the basic Text Editor. (No need to hand in a separate version of the code for this part if you choose to work on it -- simply change your solution to P1. But be sure to commit your earlier version in case you wish to revert back to it!)
Most of the time, two consecutive commands of the same type are lumped together into a single command. Thus, if I type "hello" followed immediately by " there" into an editor (such as emacs), the editor lumps them together into a single insertion command that removes all of "hello there" from the buffer when undone. Similarly, if I perform two cursor movement commands in a row, that is recorded in the undo history as a single command. Here is an example:
Implement composable commands. A good way to start is to extend the
EditCommand
class and its subclasses to define the following
method:
def compose(other : EditCommand) : Option[EditCommand]
This method either:
returns None
if the current command cannot be composed with
other
.
returns a new command if the current command can be composed
with other
, because, for example, they are both insert
commands.
For example,
val c1 = new InsertCommand(target, "hel");
val c2 = new InsertCommand(target, "lo"));
c1.compose(c2) match {
case None => // can't combine them
case Some(c3) => c3.execute();
}
would create the command c3
that inserts "hello" into the target.
If we changed c2
to be a DeleteCommand
, the compose
operation
would return None
. You may find it useful to test whether an
object has a certain type, which can be done in Scala with pattern
matching, as in:
x match {
case i : InsertCommand => ... // x is an InsertCommand, now bound to i
case i : DeleteCommand => ... // x is an DeleteCommand, now bound to i
case i => ... // match all other types
}
Submit your homework via GradeScope by the beginning of class on the due date.
Submit your answers to the Gradescope assignment named, for example, "HW 1". It should:
You will be asked to resubmit homework not satisfying these requirements. Please select the pages for each question when you submit.
If this homework includes programming problems, submit your code to the Gradescope assignment named, for example, "HW 1 Programming". Also:
Autograding: For most programming questions, Gradescope will run an autograder on your code that performs some simple tests. Be sure to look at the autograder output to verify your code works as expected. We will run more extensive tests on your code after the deadline. If you encounter difficulties or unexpected results from the autograder, please let us know.