Wednesday, September 14, 2011

Karel The robot (chapter 2)


Chapter 2
Programming Karel


In its new object-oriented implementation, the simplest style of Karel program consists of a definition of a new Karel class that specifies a sequence of built-in commands that should be executed when the program is run. A very simple Karel program is shown in Figure 1.

The program in Figure 1 is composed of several parts. The first part consists of the following lines:

/*
* File: BeeperPickingKarel.java
* -----------------------------
* The BeeperPickingKarel class extends the basic Karel class
* by defining a "run" method with three commands.  These
* commands cause Karel to move forward one block, pick up
* a beeper, and then move ahead to the next corner.
*/

These lines are an example of a comment, which is simply text designed to explain the operation of the program to human readers. Comments in both Karel and Java begin with the characters /* and end with the characters */. Here, the comment begins on the first line and ends several lines later. The stars on the individual lines that make up the text of the comment are not required, but make it easier for human readers to see the extent of the comment. In a simple program, extensive comments may seem silly because the effect of the program is obvious, but they are extremely important as a means of documenting the design of larger, more complex programs.

The second part of the program is the line

import stanford.karel.*;

This line requests the inclusion of all definitions from the stanford.karel library. This


Figure 1.  Simple Karel example to pick up a single beeper

/*
* File: BeeperPickingKarel.java
* -----------------------------
* The BeeperPickingKarel class extends the basic Karel class
* by defining a "run" method with three commands.  These
* commands cause Karel to move forward one block, pick up
* a beeper, and then move ahead to the next corner.
*/

import stanford.karel.*;

public class BeeperPickingKarel extends Karel {
public void run() {
move();
pickBeeper();
move();
}
}




library contains the basic definitions necessary for writing Karel programs, such as the definitions of the standard operations move() and pickBeeper(). Because you always need access to these operations, every Karel program you write will include this import command before you write the actual program.

The final part of the Karel program consists of the following class definition:

public class BeeperPickingKarel extends Karel {
public void run() {
move();
pickBeeper();
move();
}
}

To understand this definition, it is useful to look more carefully at its structure. The definition  of  the  BeeperPickingKarel class consists of the line beginning with public class and encompasses everything between the curly brace at the end of that line and the corresponding closing brace on the last line of the program. The single line that introduces the new class is called the header of the definition; the code between the braces is called the body.

In programming, it is often very useful to think about a particular definition and its body as separable ideas. In this example, the definition of BeeperPickingKarel has the following form, where the entire body of the definition has been replaced by a box that you can put out of your mind for the moment:

public class BeeperPickingKarel extends Karel {

body of the class definition

}

The header line at the top tells you quite a bit about the BeeperPickingKarel class, even before you have looked to see what the body contains. The key new concept in the class header is embodied in the word extends, which is used in both Karel and Java to indicate that a new class is an extension of an existing one. Here, the class header line indicates that BeeperPickingKarel is an extension of the standard Karel class imported from the stanford.karel library.

In object-oriented languages, defining a new class by extension means that the new class (here, BeeperPickingKarel) builds on the facilities provided by the existing class (in this case, Karel). In particular, the fact that it extends Karel guarantees that the new BeeperPickingKarel class will have the following properties :

1.   Any instance of the class BeeperPickingKarel is also an instance of the class Karel.
Any instance of the class Karel represents a robot that lives in a world of streets,
avenues, beepers, and walls whose state consists of its location, direction, and the
number of beepers in its bag. Because BeeperPickingKarel is an extension of
Karel, you know that an instance of BeeperPickingKarel will also be a robot that
lives in the same type of world and has the same state properties.
2.   Any instance of the BeeperPickingKarel class will automatically respond to the same commands as an instance of the Karel class. Because every robot in the Karel class knows how to respond to the commands move(), turnLeft(), pickBeeper(), and putBeeper(), it follows that a instance of BeeperPickingKarel will understand that same set of commands.




In other words, the new BeeperPickingKarel class automatically acquires the state attributes and the behavior of the Karel class from which it is derived. The process of taking on the structure and behavior of the parent class is called inheritance.

When a class is defined by extension, the new class is said to be a subclass of the original. In this example, BeeperPickingKarel is therefore a subclass of Karel. Symmetrically, Karel is said to be a superclass of BeeperPickingKarel. Unfortunately, this terminology can be confusing for new programmers, who are likely to make the intuitive inference that a subclass is somehow less powerful that its superclass when in fact the opposite is true. A subclass inherits the behavior of its superclass and can therefore respond to the entire set of commands available to that superclass. A subclass, however, usually defines additional commands that are unavailable to the superclass. Thus, the typical subclass actually has more functionality than the class from which it was derived. This idea is expressed much more clearly by the notion of extension: a subclass extends its superclass and can therefore add new capabilities to it.

Now that you have some idea about what class extension means, it now makes sense to look at the body of the BeeperPickingKarel class. That body consists of the following lines:

public void run() { move(); pickBeeper(); move();
}

These lines represent the definition of a new method, which specifies the sequence of steps necessary to respond to a command. As in the case of the BeeperPickingKarel class itself, the method definition consists of two parts that can be considered separately: The first line constitutes the method header and the code between the curly braces is the method body. If you ignore the body for now, the method definition looks like this:

public void run() {

body of the method definition

}

The first two words in the method header, public and void, are part of Java’s syntactic structure, and you should pretty much feel free to ignore them at this point. The next word on the header line specifies the name of the new method, which in this case is the method run. Defining a method means that the new Karel subclass can respond to a new command with that name. The built-in Karel class responds to the commands move(), turnLeft(), pickBeeper(), and putBeeper(); a BeeperPickingKarel responds to that same set of commands plus a new command called run.  The run command plays a special role in a Karel program. When you start a Karel program in the Eclipse environment, it creates a new Karel instance of the appropriate subclass, adds that Karel to a world that you specify, and then issues the run command. The effect of issuing that command is defined by the body of the run method, which is a sequence of commands that the robot will execute in order.  For example, the body of the run method for the BeeperPickingKarel class is

move(); pickBeeper(); move();




Thus, if the initial state of the world matches the example given in Chapter 1, Karel first moves forward into the corner containing the beeper, picks up that beeper, and finally moves forward to the corner just before the wall, as shown in the following before-and- after diagram:



Before


After



4                                                                                                      4

3                                                                                                      3







 




 
2                                                                                                      2

1                                                                                                      1



1          2         3          4          5         6

Solving a more interesting problem


1          2         3          4          5         6


The BeeperPickingKarel class defined in Figure 1 doesn’t do very much as yet. Let’s try to make it a little more interesting. Suppose that the goal is not simply to get Karel to pick up the beeper but to move the beeper from its initial position on 2nd Avenue and 1st Street to the center of the ledge at 5th Avenue and 2nd Street. Thus, your next assignment is to define a new Karel subclass that accomplishes the task illustrated in this diagram:



Before


After



4                                                                                                      4

3                                                                                                      3







 




 
2                                                                                                      2

1                                                                                                      1



1          2         3          4          5         6


1          2         3          4          5         6



The first three commands in the new program—the ones that move forward, pick up the beeper, and then move up to the ledge—are the same as before:

move(); pickBeeper(); move();

From here, the next step is to turn left to begin climbing the ledge. That operation is easy, because Karel has a turnLeft command in its standard repertoire. Executing a turnLeft command at the end of the preceding sequence of commands leaves Karel facing north on the corner of 3rd Avenue and 1st Street. If Karel then executes a move command, it will move north to reach the following position:





4

3

2

1

1          2          3         4          5          6

From here, the next thing you need to do is get Karel to turn right so that it is again facing east. While this operation is conceptually just as easy as getting Karel to turn left, there is a slight problem: Karel’s language includes a turnLeft command, but no turnRight command. It’s as if you bought the economy model and have now discovered that it is missing some important features.

At this point, you have your first opportunity to begin thinking like a programmer. You have one set of commands, but not exactly the set you need. What can you do?  Can you accomplish the effect of a turnRight command using only the capabilities you have? The answer, of course, is yes. You can accomplish the effect of turning right by turning left three times. After three left turns, Karel will be facing in the desired direction. From here, all you need to do is program Karel to move over to the center of the ledge, drop the beeper and then move forward to the final position. A complete implementation of a BeeperTotingKarel class that accomplishes the entire task is shown in Figure 2.





Figure 2.  Program to carry a beeper to the top of a ledge

/*
* File: BeeperTotingKarel.java
* ----------------------------
* The BeeperTotingKarel class extends the basic Karel class
* so that Karel picks up a beeper from 1st Street and then
* carries that beeper to the center of a ledge on 2nd Street.
*/

import stanford.karel.*;

public class BeeperTotingKarel extends Karel {
public void run() {
move();
pickBeeper();
move();
turnLeft();
move();
turnLeft();
turnLeft();
turnLeft();
move();
move();
putBeeper();
move();
}
}




Defining new methods
Even though the BeeperTotingKarel class in Figure 2 demonstrates that it is possible to perform the turnRight operation using only Karel’s built-in commands, the resulting program is not particularly clear conceptually. In your mental design of the program, Karel turns right when it reaches the top of the ledge. The fact that you have to use three turnLeft commands to do so is annoying. It would be much simpler if you could simply say turnRight and have Karel understand this command. The resulting program would not only be shorter and easier to write, but also significantly easier to read.

Fortunately, the Karel programming language makes it possible to define new commands simply by including new method definitions. Whenever you have a sequence of Karel commands that performs some useful task—such as turning right—you can define a new method that executes that sequence of commands. The format for defining a new Karel method has much the same as the definition of run in the preceding examples, which is a method definition in its own right. A typical method definition looks like this:

private void name() {
commands that make up the body of the method
}

In this pattern, namrepresents the name you have chosen for the new method. To complete the definition, all you have to do is provide the sequence of commands in the lines between the curly braces. For example, you can define turnRight as follows:

private void turnRight() {
turnLeft();
turnLeft();
turnLeft();
}

Similarly, you could define a new turnAround method like this:

private void turnAround() {
turnLeft();
turnLeft();
}

You can use the name of a new method just like any of Karel’s built-in commands. For example, once you have defined turnRight, you could replace the three turnLeft commands in the BeeperTotingKarel class with a single call to the turnRight method. A revised implementation of the program that uses turnRight is shown in Figure 3.

There is, of course, one obvious difference between the definitions of the run and turnRight methods shown in Figure 3: the run method is marked as public in contrast to turnRightwhicimarkeas private. The difference between these two designations is that public methods can be invoked from outside the class, while private methods cannot. The run method needs to be public because the Karel environment needs to be able to issue a run command to get things going. By contrast, turnRight is used only inside the other code appearing within this class. That definition, therefore, can be private, and it is generally good programming practice to keep definitions private whenever possible. The reasons for this rule are difficult to appreciate until you have had a chance to work with larger programs, but the basic idea is that classes should try as much as possible to encapsulate information, which means not only to gather it together but also to restrict access to that information if possible. Large programs quickly become very complex in terms of the volume of detail that they encompass. If a class is well designed, it will seek to reduce that complexity by hiding as much extraneous detail as it




Figure 3.  Revised implementation of BeeperTotingKarel that includes a turnRight method

/*
* File: BeeperTotingKarel.java
* ----------------------------
* The BeeperTotingKarel class extends the basic Karel class
* so that Karel picks up a beeper from 1st Street and then
* carries that beeper to the center of a ledge on 2nd Street.
*/

import stanford.karel.*;

public class BeeperTotingKarel extends Karel {
public void run() {
move();
pickBeeper();
move();
turnLeft();
move();
turnRight();
move();
move();
putBeeper();
move();
}

/**
* Turns Karel 90 degrees to the right.
*/
private void turnRight() {
turnLeft();
turnLeft();
turnLeft();
}
}

can. This property is known as information hiding and is a cornerstone of the object- oriented philosophy.

At this point in your study of programming, you are not likely to find these arguments about encapsulation particularly convincing. Defining turnRight and turnAround in every program is certainly a bit of a pain, particularly in light of the fact that they are so useful. The difficulty, however, in making them more easily available lies in figuring out where to put those definitions. Declaring turnRight to be public in the definition of BeeperTotingKarel would not be much help. In an object-oriented language, the methods that specify the behavior of a class are encapsulated within that class. The turnRight method that appears within that class knows how to turn an instance of BeeperTotingKarel 90 degrees to the right, but that method cannot be applied to an instance of the Karel class or any its subclasses.

In some sense, what you really want to do is add turnRight and turnAround to the Karel class so that all subclasses will be able to use these undeniably useful commands. The problem with that strategy is that you don’t necessarily have the access to the Karel class necessary to make such a change. The Karel class is part of the Stanford library, which is used by all students in this CS106A. If you were to go and make changes to it, you might end up breaking someone else’s program, which would not endear you to the other students. Similarly, if you at some point later in the quarter decide that you really want to add something to one of the standard Java classes, you won’t actually be able to




change that class because it is under the control of Sun Microsystems. What you can do, however, is define a new class that includes your new features as extensions. Thus, if you want to use turnRight and turnAround in several different Karel programs, you could define a new class that included these method definitions and then create your programs by extending that class. This technique is illustrated in Figure 4, which consists of two program files. The first contains a class definition called NewImprovedKarel that includes the turnRight and turnAround definitions as public methods so that other classes can use them. The second is yet another implementation of BeeperTotingKarel that extends NewImprovedKarel, thereby giving itself access to these methods.

The stanford.karel package does not include the NewImprovedKarel class as it appears here but does include a SuperKarel class that includes the methods turnRight and turnAround along with several other extensions that will make it possible for you to write much more exciting programs. The examples that follow extend SuperKarel to ensure that these methods are available. The other extensions are described in Chapter 6.

Decomposition
As a way of illustrating more of the power that comes with being able to define new methods, it’s useful to have Karel do something a little more practical than move a beeper from one place to another. The roadways around Palo Alto often seem to be in need of repair, and it might be fun to see if Karel can fill potholes in its abstract world. For example, imagine that Karel is standing on the “roadshown in the left-hand figure, one corner to the left of a pothole in the road. Karel’s job is to fill the hole with a beeper and proceed to the next corner. The diagram on the right illustrates how the world should look after the program execution.

Before                                                                     After





 







 
4

3

2

1



1          2          3         4          5          6


1          2         3          4          5         6



If you are limited to the four predefined commands, the run method to solve this problem would look like this:

public void run() { move(); turnLeft(); turnLeft(); turnLeft(); move(); putBeeper(); turnLeft(); turnLeft(); move(); turnLeft(); turnLeft(); turnLeft(); move();
}




Figure 4.  Defining a NewImprovedKarel class that understands turnRight and turnAround

/*
* File: NewImprovedKarel.java
* ---------------------------
* The NewImprovedKarel class extends the basic Karel class
* so that any subclasses have access to the turnRight and
* turnAround methods.  It does not define any run method
* of its own.
*/

import stanford.karel.*;

public class NewImprovedKarel extends Karel {

/**
* Turns Karel 90 degrees to the right.
*/
public void turnRight() {
turnLeft();
turnLeft();
turnLeft();
}

/**
* Turns Karel around 180 degrees.
*/
public void turnAround() {
turnLeft();
turnLeft();
}
}


/*
* File: BeeperTotingKarel.java
* ----------------------------
* The BeeperTotingKarel class extends the basic Karel class
* so that Karel picks up a beeper from 1st Street and then
* carries that beeper to the center of a ledge on 2nd Street.
*/

import stanford.karel.*;

public class BeeperTotingKarel extends NewImprovedKarel {
public void run() {
move();
pickBeeper();
move();
turnLeft();
move();
turnRight();
move();
move();
putBeeper();
move();
}
}




You can, however, make the main program easier to read by extending SuperKarel and then making use of the turnAround and turnRight methods. This version of the program appears in Figure 5.

The initial motivation for defining the turnRight method was that it was cumbersome to keep repeating three turnLeft commands to accomplish a right turn. Defining new methods has another important purpose beyond allowing you to avoid repeating the same command sequences every time you want to perform a particular task. The power to define methods unlocks the most important strategy in programming—the process of breaking a large problem down into smaller pieces that are easier to solve. The process of breaking a program down into smaller pieces is called decomposition, and the component parts of a large problem are called subproblems.

As an example, the problem of filling the hole in the roadway can be decomposed into the following subproblems:

1.   Move up to the hole
2.   Fill the hole by dropping a beeper into it
3.   Move on to the next corner

If you think about the problem in this way, you can use method definitions to create a program that reflects your conception of the program structure. The run method would look like this:

public void run() { move(); fillPothole(); move();
}



Figure 5.  Karel program to fill a single pothole

/*
* File: PotholeFillingKarel.java
* ------------------------------
* The PotholeFillingKarel class puts a beeper into a pothole
* on 2nd Avenue.  This version of the program uses no
* decomposition other than turnRight and turnAround,
* which are inherited from SuperKarel.
*/

import stanford.karel.*;

public class PotholeFillingKarel extends SuperKarel {
public void run() {
move();
turnRight();
move();
putBeeper();
turnAround();
move();
turnRight();
move();
}
}




The correspondence with the outline is immediately clear, and everything would be great if only you could get Karel to understand what you mean by fillPothole. Given the power to define methods, implementing fillPothole is extremely simple. All you have to do is define a fillPothole method whose body consists of the commands you have already written to do the job, like this:

private void fillPothole() {
turnRight();
move();
putBeeper();
turnAround();
move();
turnRight();
}

The complete program is shown in Figure 6.







Figure 6.  Program to fill a single pothole using a fillPothole method for decomposition

/*
* File: PotholeFillingKarel.java
* ------------------------------
* The PotholeFillingKarel class puts a beeper into a pothole
* on 2nd Avenue.  This version of the program decomposes
* the problem so that it makes use of a fillPothole method.
*/

import stanford.karel.*;

public class PotholeFillingKarel extends SuperKarel {
public void run() {
move();
fillPothole();
move();
}

/**
* Fills the pothole beneath Karel's current position by
* placing a beeper on that corner.  For this method to
* work correctly, Karel must be facing east immediately
* above the pothole.  When execution is complete, Karel
* will have returned to the same square and will again
* be facing east.
*/
private void fillPothole() {
turnRight();
move();
putBeeper();
turnAround();
move();
turnRight();
}
}




Choosing the correct decomposition
There are, however, other decomposition strategies you might have tried. For example, you could have written the program as

public void run() { approachAndFillPothole(); move();
}

where the approachAndFillPothole method is simply

private void approachAndFillPothole() {
move();
turnRight();
move();
putBeeper();
turnAround();
move();
turnRight();
}

Alternatively, you might have written the program as

public void run() { move(); turnRight(); move();
fillPotholeYouAreStandingIn();
turnAround();
move();
turnRight();
move();
}

where the body of fillPotholeYouAreStandingIn consists of a single putBeeper command. Each of these programs represents a possible decomposition. Each program correctly solves the problem. Given that all three versions of this program work, what makes one choice of breaking up the problem better than another?

In general, deciding how to decompose a program is not easy. In fact, as the problems become more complex, choosing an appropriate decomposition will turn out to be one of the more difficult aspects of programming. You can, however, rely to some extent on the following guidelines:

1.   Each subproblem should perform a conceptually simple task. The solution of a subproblem may require many commands and may be quite complex in terms of its internal operation. Even so, it should end up accomplishing some conceptual task that is itself easy to describe. A good indication of whether you have succeeded in identifying a reasonable task comes from the name you give to the method. If you can accurately define its effect with a simple descriptive name, you have probably chosen a good decomposition. On the other hand, if you end up with complex names such as approachAndFillPothole, the decomposition does not seem as promising.
2.   Each subproblem should perform a task that is as general as possible, so that it can be used in several different situations. If one decomposition results in a program that is only useful in the exact situation at hand and another would work equally well in a variety of related situations, you should probably choose the more general one 

0 comments: