Anda di halaman 1dari 28

MODULE 3

Abstract data types, Encapsulation by subprogram - Type definition, storage management - Sequence Control - Implicit and Explicit sequence control, sequencing with arithmetic expressions, sequence control between statements.

ABSTRACT DATA TYPE abstract data type (ADT) is a mathematical model for a certain class of data structures that have similar behavior; or for certain data types of one or more programming languages that have similar semantics. An abstract data type is defined indirectly, only by the operations that may be performed on it and by mathematical constraints on the effects (and possibly cost) of those operations.[1] For example, an abstract stack data structure could be defined by three operations: push, that inserts some data item onto the structure, pop, that extracts an item from it (with the constraint that each pop always returns the most recently pushed item that has not been popped yet), and peek, that allows data on top of the structure to be examined without removal. When analyzing the efficiency of algorithms that use stacks, one may also specify that all operations take the same time no matter how many items have been pushed into the stack, and that the stack uses a constant amount of storage for each element. Abstract data types are purely theoretical entities, used (among other things) to simplify the description of abstract algorithms, to classify and evaluate data structures, and to formally describe the type systems of programming languages. However, an ADT may be implemented by specific data types or data structures, in many ways and in many programming languages; or described in a formal specification language. ADTs are often implemented as modules: the module's interface declares procedures that correspond to the ADT operations, sometimes with comments that describe the constraints. This information hiding strategy allows the implementation of the module to be changed without disturbing the client programs. The term abstract data type can also be regarded as a generalised approach of a number of algebraic structures, such as lattices, groups, and rings.[2] This can be treated as part of subject area of artificial intelligence. The notion of abstract data types is related to the concept of data abstraction, important in object-oriented programming and design by contract methodologies for software development[c An abstract data type is defined as a mathematical model of the data objects that make up a data type as well as the functions that operate on these objects. There are no standard conventions for defining them. A broad division may be drawn between "imperative" and "functional" definition styles. Imperative abstract data type definitions
1

In the "imperative" view, which is closer to the philosophy of imperative programming languages, an abstract data structure is conceived as an entity that is mutable meaning that it may be in different states at different times. Some operations may change the state of the ADT; therefore, the order in which operations are evaluated is important, and the same operation on the same entities may have different effects if executed at different times just like the instructions of a computer, or the commands and procedures of an imperative language. To underscore this view, it is customary to say that the operations are executed or applied, rather than evaluated. The imperative style is often used when describing abstract algorithms. This is of Computer Programming]]. Abstract variable Imperative ADT definitions often depend on the concept of an abstract variable, which may be regarded as the simplest non-trivial ADT. An abstract variable V is a mutable entity that admits two operations:

store(V,x) where x is a value of unspecified nature; and fetch(V), that yields a value;

with the constraint that

fetch(V) always returns the value x used in the most recent store(V,x) operation on the same variable V.

As in so many programming languages, the operation store(V,x) is often written V x (or some similar notation), and fetch(V) is implied whenever a variable V is used in a context where a value is required. Thus, for example, V V + 1 is commonly understood to be a shorthand for store(V,fetch(V) + 1). In this definition, it is implicitly assumed that storing a value into a variable U has no effect on the state of a distinct variable V. To make this assumption explicit, one could add the constraint that

if U and V are distinct variables, the sequence { store(U,x); store(V,y) } is equivalent to { store(V,y); store(U,x) }.

More generally, ADT definitions often assume that any operation that changes the state of one ADT instance has no effect on the state of any other instance (including other instances of the same ADT) unless the ADT axioms imply that the two instances are connected (aliased) in that sense. For example, when extending the definition of abstract variable to include abstract records, the operation that selects a field from a record variable R must yield a variable V that is aliased to that part of R. The definition of an abstract variable V may also restrict the stored values x to members of a specific set X, called the range or type of V. As in programming languages, such restrictions may simplify the description and analysis of algorithms, and improve their readability.
2

Note that this definition does not imply anything about the result of evaluating fetch(V) when V is un-initialized, that is, before performing any store operation on V. An algorithm that does so is usually considered invalid, because its effect is not defined. (However, there are some important algorithms whose efficiency strongly depends on the assumption that such a fetch is legal, and returns some arbitrary value in the variable's range.[citation needed]) Instance creation Some algorithms need to create new instances of some ADT (such as new variables, or new stacks). To describe such algorithms, one usually includes in the ADT definition a create() operation that yields an instance of the ADT, usually with axioms equivalent to

the result of create() is distinct from any instance S in use by the algorithm.

This axiom may be strengthened to exclude also partial aliasing with other instances. On the other hand, this axiom still allows implementations of create() to yield a previously created instance that has become inaccessible to the program. Preconditions, postconditions, and invariants In imperative-style definitions, the axioms are often expressed by preconditions, that specify when an operation may be executed; postconditions, that relate the states of the ADT before and after the execution of each operation; and invariants, that specify properties of the ADT that are not changed by the operations. Example: abstract stack (imperative) As another example, an imperative definition of an abstract stack could specify that the state of a stack S can be modified only by the operations

push(S,x), where x is some value of unspecified nature; and pop(S), that yields a value as a result;

with the constraint that

For any value x and any abstract variable V, the sequence of operations { push(S,x); V pop(S) } is equivalent to { V x };

Since the assignment { V x }, by definition, cannot change the state of S, this condition implies that { V pop(S) } restores S to the state it had before the { push(S,x) }. From this condition and from the properties of abstract variables, it follows, for example, that the sequence { push(S,x); push(S,y); U pop(S); push(S,z); V pop(S); W pop(S); } where x,y, and z are any values, and U, V, W are pairwise distinct variables, is equivalent to

{ U y; V z; W x } Here it is implicitly assumed that operations on a stack instance do not modify the state of any other ADT instance, including other stacks; that is,

For any values x,y, and any distinct stacks S and T, the sequence { push(S,x); push(T,y) } is equivalent to { push(T,y); push(S,x) }.

A stack ADT definition usually includes also a Boolean-valued function empty(S) and a create() operation that returns a stack instance, with axioms equivalent to

create() S for any stack S (a newly created stack is distinct from all previous stacks) empty(create()) (a newly created stack is empty) not empty(push(S,x)) (pushing something into a stack makes it non-empty)

Single-instance style Sometimes an ADT is defined as if only one instance of it existed during the execution of the algorithm, and all operations were applied to that instance, which is not explicitly notated. For example, the abstract stack above could have been defined with operations push(x) and pop(), that operate on "the" only existing stack. ADT definitions in this style can be easily rewritten to admit multiple coexisting instances of the ADT, by adding an explicit instance parameter (like S in the previous example) to every operation that uses or modifies the implicit instance. On the other hand, some ADTs cannot be meaningfully defined without assuming multiple instances. This is the case when a single operation takes two distinct instances of the ADT as parameters. For an example, consider augmenting the definition of the stack ADT with an operation compare(S,T) that checks whether the stacks S and T contain the same items in the same order. Functional ADT definitions Another way to define an ADT, closer to the spirit of functional programming, is to consider each state of the structure as a separate entity. In this view, any operation that modifies the ADT is modeled as a mathematical function that takes the old state as an argument, and returns the new state as part of the result. Unlike the "imperative" operations, these functions have no side effects. Therefore, the order in which they are evaluated is immaterial, and the same operation applied to the same arguments (including the same input states) will always return the same results (and output states). In the functional view, in particular, there is no way (or need) to define an "abstract variable" with the semantics of imperative variables (namely, with fetch and store operations). Instead of storing values into variables, one passes them as arguments to functions. Example: abstract stack (functional)

For example, a complete functional-style definition of a stack ADT could use the three operations:

push: takes a stack state and an arbitrary value, returns a stack state; top: takes a stack state, returns a value; pop: takes a stack state, returns a stack state;

with the following axioms:


top(push(s,x)) = x (pushing an item onto a stack leaves it at the top) pop(push(s,x)) = s (pop undoes the effect of push)

In a functional-style definition there is no need for a create operation. Indeed, there is no notion of "stack instance". The stack states can be thought of as being potential states of a single stack structure, and two stack states that contain the same values in the same order are considered to be identical states. This view actually mirrors the behavior of some concrete implementations, such as linked lists with hash cons. Instead of create(), a functional definition of a stack ADT may assume the existence of a special stack state, the empty stack, designated by a special symbol like or "()"; or define a bottom() operation that takes no aguments and returns this special stack state. Note that the axioms imply that

push(,x)

In a functional definition of a stack one does not need an empty predicate: instead, one can test whether a stack is empty by testing whether it is equal to . Note that these axioms do not define the effect of top(s) or pop(s), unless s is a stack state returned by a push. Since push leaves the stack non-empty, those two operations are undefined (hence invalid) when s = . On the other hand, the axioms (and the lack of side effects) imply that push(s,x) = push(t,y) if and only if x = y and s = t. As in some other branches of mathematics, it is customary to assume also that the stack states are only those whose existence can be proved from the axioms in a finite number of steps. In the stack ADT example above, this rule means that every stack is a finite sequence of values, that becomes the empty stack () after a finite number of pops. By themselves, the axioms above do not exclude the existence of infinite stacks (that can be poped forever, each time yielding a different state) or circular stacks (that return to the same state after a finite number of pops). In particular, they do not exclude states s such that pop(s) = s or push(s,x) = s for some x. However, since one cannot obtain such stack states with the given operations, they are assumed "not to exist". Data abstraction refers to, providing only essential information to the outside word and hiding their background details ie. to represent the needed information in program without presenting the details.
5

Data abstraction is a programming (and design) technique that relies on the separation of interface and implementation. Let's take one real life example of a TV which you can turn on and off, change the channel, adjust the volume, and add external components such as speakers, VCRs, and DVD players BUT you do not know it's internal detail that is, you do not know how it receives signals over the air or through a cable, how it translates them, and finally displays them on the screen. Thus we can say, a television clearly separates its internal implementation from its external interface and you can play with its interfaces like the power button, channel changer, and volume control without having zero knowledge of its internals. Now if we talk in terms of C++ Programming, C++ classes provides great level of data abstraction. They provide sufficient public methods to the outside world to play with the functionality of the object and to manipulate object data ie. state without actually knowing how class has been implemented internally. For example, your program can make a call to the sort() function without knowing what algorithm the function actually uses to sort the given values. In fact, the underlying implementation of the sorting functionality could change between releases of the library, and as long as the interface stays the same, your function call will still work. In C++ we use classes to define our own abstract data types (ADT). You can use the cout object of class ostream to stream data to standard output like this: 1. include <iostream> using namespace std; int main( ) { cout << "Hello C++" <<endl; return 0; } Here you don't need to understand how cout displays the text on the user's screen. You need only know the public interface and the underlying implementation of cout is free to change. Access Labels Enforce Abstraction: In C++ we use access labels to define the abstract interface to the class. A class may contain zero or more access labels: Members defined with a public label are accessible to all parts of the program. The dataabstraction view of a type is defined by its public members. Members defined with a private label are not accessible to code that uses the class. The private sections hides the implementation from code that uses the type.

There are no restrictions on how often an access label may appear. Each access label specifies the access level of the succeeding member definitions. The specified access level remains in effect until the next access label is encountered or the closing right brace of the class body is seen. Benefits of Data Abstraction: Data abstraction provide two important advantages: Class internals are protected from inadvertent user-level errors, which might corrupt the state of the object. The class implementation may evolve over time in response to changing requirements or bug reports without requiring change in user-level code. By defining data members only in the private section of the class, the class author is free to make changes in the data. If the implementation changes, only the class code needs to be examined to see what affect the change may have. If data are public, then any function that directly accesses the data members of the old representation might be broken. Data Abstraction Example: Any C++ program where you implement a class with public and private members is an example of data abstraction. Consider the following example: 1. include <iostream> using namespace std; class Adder{ public: // constructor Adder(int i = 0) { total = i; } // interface to outside world void addNum(int number) { total += number; } // interface to outside world int getTotal() { return total; }; private: // hidden data from outside world int total;
7

}; int main( ) { Adder a; a.addNum(10); a.addNum(20); a.addNum(30); cout << "Total " << a.getTotal() <<endl; return 0; } When the above code is compiled and executed, it produces following result: Total 60 Above class adds numbers together, and returns the sum. The public members addNum and getTotal are the interfaces to the outside world and a user needs to know them to use the class. The private member total is something that the user doesn't need to know about, but is needed for the class to operate properly. Designing Strategy: Abstraction separates code into interface and implementation. So while designing your component, you must keep interface independent of the implementation so that if you change underlying implementation then interface would remain intact. In this case whatever programs are using these interfaces, they would not be impacted and would just need a recompilation with the latest implementation. Typical operations Some operations that are often specified for ADTs (possibly under other names) are

compare(s,t), that tests whether two structures are equivalent in some sense; hash(s), that computes some standard hash function from the instance's state; print(s) or show(s), that produces a human-readable representation of the structure's state.

In imperative-style ADT definitions, one often finds also


create(), that yields a new instance of the ADT; initialize(s), that prepares a newly-created instance s for further operations, or resets it to some "initial state"; copy(s,t), that puts instance s in a state equivalent to that of t; clone(t), that performs s new(), copy(s,t), and returns s; free(s) or destroy(s), that reclaims the memory and other resources used by s;

The free operation is not normally relevant or meaningful, since ADTs are theoretical entities that do not "use memory". However, it may be necessary when one needs to analyze the storage used by an algorithm that uses the ADT. In that case one needs additional axioms that specify how much memory each ADT instance uses, as a function of its state, and how much of it is returned to the pool by free.
8

Examples Some common ADTs, which have proved useful in a great variety of applications, are

Container Deque List Map Multimap Multiset Priority queue Queue Set Stack String Tree

Each of these ADTs may be defined in many ways and variants, not necessarily equivalent. For example, a stack ADT may or may not have a count operation that tells how many items have been pushed and not yet popped. This choice makes a difference not only for its clients but also for the implementation. Implementation Implementing an ADT means providing one procedure or function for each abstract operation. The ADT instances are represented by some concrete data structure that is manipulated by those procedures, according to the ADT's specifications. Usually there are many ways to implement the same ADT, using several different concrete data structures. Thus, for example, an abstract stack can be implemented by a linked list or by an array. An ADT implementation is often packaged as one or more modules, whose interface contains only the signature (number and types of the parameters and results) of the operations. The implementation of the module namely, the bodies of the procedures and the concrete data structure used can then be hidden from most clients of the module. This makes it possible to change the implementation without affecting the clients. When implementing an ADT, each instance (in imperative-style definitions) or each state (in functional-style definitions) is usually represented by a handle of some sort.[3] Modern object-oriented languages, such as C++ and Java, support a form of abstract data types. When a class is used as a type, it is an abstract type that refers to a hidden representation. In this model an ADT is typically implemented as a class, and each instance of the ADT is an object of that class. The module's interface typically declares the constructors as ordinary procedures, and most of the other ADT operations as methods of that class. However, such an approach does not
9

easily encapsulate multiple representational variants found in an ADT. It also can undermine the extensibility of object-oriented programs. In a pure object-oriented program that uses interfaces as types, types refer to behaviors not representations. Example: implementation of the stack ADT As an example, here is an implementation of the stack ADT above in the C programming language. Imperative-style interface An imperative-style interface might be: typedef struct stack_Rep stack_Rep; /* Type: instance representation (an opaque record). */ typedef stack_Rep *stack_T; /* Type: handle to a stack instance (an opaque pointer). */ typedef void *stack_Item; /* Type: value that can be stored in stack (arbitrary address). */ stack_T stack_create(void); /* Create new stack instance, initially empty. */ void stack_push(stack_T s, stack_Item e); /* Add an item at the top of the stack. */ stack_Item stack_pop(stack_T s); /* Remove the top item from the stack and return it . */ int stack_empty(stack_T ts); /* Check whether stack is empty. */ This implementation could be used in the following manner: #include <stack.h> /* Include the stack interface. */ stack_T t = stack_create(); /* Create a stack instance. */ int foo = 17; /* An arbitrary datum. */ t = stack_push(t, &foo); /* Push the address of 'foo' onto the stack. */ void *e = stack_pop(t); /* Get the top item and delete it from the stack. */ if (stack_empty(t)) { } /* Do something if stack is empty. */ This interface can be implemented in many ways. The implementation may be arbitrarily inefficient, since the formal definition of the ADT, above, does not specify how much space the stack may use, nor how long each operation should take. It also does not specify whether the stack state t continues to exist after a call s pop(t). In practice the formal definition should specify that the space is proportional to the number of items pushed and not yet popped; and that every one of the operations above must finish in a constant amount of time, independently of that number. To comply with these additional specifications, the implementation could use a linked list, or an array (with dynamic resizing) together with two integers (an item count and the array size) Functional-style interface
10

Functional-style ADT definitions are more appropriate for functional programming languages, and vice-versa. However, one can provide a functional style interface even in an imperative language like C. For example: typedef struct stack_Rep stack_Rep; /* Type: stack state representation (an opaque record). */ typedef stack_Rep *stack_T; /* Type: handle to a stack state (an opaque pointer). */ typedef void *stack_Item; /* Type: item (arbitrary address). */ stack_T stack_empty(void); /* Returns the empty stack state. */ stack_T stack_push(stack_T s, stack_Item x); /* Adds x at the top of s, returns the resulting state. */ stack_Item stack_top(stack_T s); /* Returns the item currently at the top of s. */ stack_T stack_pop(stack_T s); /* Remove the top item from s, returns the resulting state. */ The main problem is that C lacks garbage collection, and this makes this style of programming impractical; moreover, memory allocation routines in C are slower than allocation in a typical garbage collector, thus the performance impact of so many allocations is even greater. ADT libraries Many modern programming languages,such as C++ and Java, come with standard libraries that implement several common ADTs, such as those listed above. Built-in abstract data types The specification of some programming languages is intentionally vague about the representation of certain built-in data types, defining only the operations that can be done on them. Therefore, those types can be viewed as "built-in ADTs". Examples are the arrays in many scripting languages, such as Awk, Lua, and Perl, which can be regarded as an implementation of the Map or Table ADT. ENCAPSULATION BY SUBPROGRAMS 1.Subprograms as abstract operations Subprograms can be viewed as abstract operations on a predefined data set. A subprogram represents a mathematical function that maps each particular set of arguments into a particular set of results. Specification of a subprogram (same as that for a primitive operation): the name of the subprogram

11

the signature of the subprogram - gives the number of arguments, their order, and the data type of each, as well as the number of results, their order, and the data type of each the action performed by the subprogram

Some problems in attempting to describe precisely the function computed by a subprogram: 1. Implicit arguments in the form of nonlocal variables. 2. Implicit results (side effects) returned as changes to nonlocal variables or as changes in the subprogram's arguments. 3. Using exception handlers in case the arguments are not of the required type. 4. History sensitiveness - the results may depend on previous executions. Implementation of a subprogram: Uses the data structures and operations provided by the language Defined by the subprogram body

Local data declarationsStatements defining the actions over the data. The body is encapsulated, its components cannot be accessed separately by the user of the subprogram. The interface with the user (the calling program) is accomplished by means of arguments and returned results. Type checking: similar to type checking for primitive operations. Difference: types of operands and results are explicitly stated in the program 2.Subprogram definition and invocation 2. 1. Subprogram definitions and subprogram activations Subprogram definition: the set of statements constituting the body of the subprogram. It is a static property of the program, and it is the only information available during translation. Subprogram activation: a data structure (record) created upon invoking the subprogram. It exists while the subprogram is being executed. After execution the activation record is destroyed. 2. 2. Implementation of subprogram definition and invocation A simple (but not efficient) approach: Each time the subprogram is invoked, a copy of its executable statements, constants and local variables is created.

12

A better approach: The executable statements and constants are invariant part of the subprogram they do not need to be copied for each execution of the subprogram. A single copy is used for all activations of the subprogram. This copy is called code segment. This is the static part. The activation record contains only the parameters, results and local data. This is the dynamic part. It has same structure, but different values for the variables. Below is Figure 2.1

On the left is the subprogram definition. On the right is the activation record created during execution. It contains the types and number of variables used by the subprogram, and the assigned memory locations at each execution of the subprogram. The definition serves as a template to create the activation record (the use of the word template is different from the keyword template in class definitions in C++, though its generic meaning is the same - a pattern, a frame to be filled in with particular values. In class definitions the binding refers to the data types and it is performed at compilation time,

13

while here the binding refers to memory locations and data values, and it is performed at execution time.) Generic subprograms: have a single name but several different definitions overloaded. Subprogram definitions as data objects In compiled languages subprogram definition is separate from subprogram execution. C, C++, Java In interpreted languages there is no difference - definitions are treated as run-time data objects Prolog, LISP, Perl. Interpreted languages use an operation to invoke translation at run-time consult in Prolog, define in LISP. TYPE DEFINITIONS 1.Basics Type definitions are used to define new data types. Note, that they do not define a complete abstract data type, because the definitions of the operations are not included. Format: typedef definiion name Actually we have a substitution of name for the definition. Examples: typedef int key_type; key_type key1, key2; These statements will be processed at translation time and the type of key1 and key2 will be set to integer.

struct rational_number {int numerator, denominator;}; typedef rational_number rational; rational r1, r2; Here r1 and r2 will be of type rational_number 2. Type equivalence and equality of data objects Two questions to be answered:
14

When are two types the same? When do two objects have the same value?

Name equivalence: two data types are considered equivalent only if they have the same name. Issues Every object must have an assigned type, there can be no anonymous types. A singe type definition must serve all or large parts of a program. Structural equivalence: two data types are considered equivalent if they define data objects that have the same internal components. Issues Do components need to be exact duplicates? Can field order be different in records? Can field sizes vary? Data object equality We can consider two objects to be equal if each member in one object is identical to the corresponding member of the other object. However there still may be a problem. Consider for example the rational numbers 1/2 and 3/6. Are they equal according to the above definition? In general, the compiler has no way to know how to compare data values of user-defined type. It is the task of the programmer that has defined that particular data type to define also the operations with the objects of that type. 3.Type definition with parameters Parameters allow the user to prescribe the size of data types needed array sizes. Implementation: The type definition with parameters is used as a template as any other type definition during compilation. SEQUENCE CONTROL Sequence control refers to user actions and computer logic that initiate, interrupt, or terminate transactions. Sequence control governs the transition from one transaction to the next. General design objectives include consistency of control actions, minimized need for control actions, minimized memory load on the user, with flexibility of sequence control to adapt to different user needs. Methods of sequence control require explicit attention in interface design, and many published guidelines deal with this topic.

15

The importance of good design for controlling user interaction with a computer system has been emphasized by Brown, Brown, Burkleo, Mangelsdorf, Olsen and Perkins (1983, page 4-1): One of the critical determinants of user satisfaction and acceptance of a computer system is the extent to which the user feels in control of an interactive session. If users cannot control the direction and pace of the interaction sequence, they are likely to feel frustrated, intimidated, or threatened by the computer system. Their productivity may suffer, or they may avoid using the system at all. Complete user control of the interaction sequence and its pacing is not always possible, of course, particularly in applications where computer aids are used for monitoring and process control. The actions of an air traffic controller, for example, are necessarily paced in some degree by the job to be done. As a general principle, however, it is the user who should decide what needs doing and when to do it. A fundamental decision in user interface design is selection of the dialogue type(s) that will be used to implement sequence control. Here "dialogue" refers to the sequence of transactions that mediate user-system interaction. Interface design will often involve a mixture of two or more dialogue types, since different dialogues are appropriate to different jobs and different kinds of users. Recognition of appropriate dialogue types at the outset of system development will facilitate the design of user interface software and help ensure the effectiveness of system operation. The selection of dialogue types based on anticipated task requirements and user skills seems straightforward, at least for simple cases. Computer-initiated question-and-answer dialogues are suited to routine data entry tasks, where data items are known and their ordering can be constrained; this type of dialogue provides explicit prompting for unskilled, occasional users. Form-filling dialogues permit somewhat greater flexibility in data entry, but may require user training. When data entries must be made in arbitrary order, perhaps mixed with queries as in making airline reservations, then some mixture of function keys and coded command language will be required for effective operation, implying a moderate to high level of user training. One important aspect of dialogue choice is that different types of dialogue imply differences in system response time for effective operation. In a repetitive form-filling dialogue, for example, users may accept relatively slow computer processing of a completed form. If the computer should take several seconds to respond, a user probably can take that time to set one data sheet aside and prepare another. But several seconds delay in a menu selection dialogue may prove intolerable, especially when a user must make an extended sequence of selections in order to complete an action. The general requirements estimated in this table may vary, of course, with any specific system design application. As an example, graphic interaction is judged here to require a high degree of user training. That would surely be true of systems providing a full range of graphic functions. But in other applications some simple forms of graphic interaction, such as iconic representation of menu options, might actually serve to reduce the need for user training.

16

This categorization of dialogue types has been adopted from that proposed by Ramsey and Atwood (1979). Each of these dialogue types is considered in the guidelines presented here. But much pertinent material will be found elsewhere in these guidelines. Thus form filling is considered here as a dialogue type for sequence control, but is treated elsewhere as a means of data entry (Section 1.4) and of data display (Section 2.2). Graphic interaction, considered here as a dialogue type for sequence control, is also treated extensively as a means of data entry and data display One might speculate whether some other type of sequence control dialogue, beyond those listed here, could become commonplace in the future. Imagine an application where a user interacts with a so-called "expert" computer system, with the locus of control in query and reply shifting back and forth. Would that represent merely a combination of the various dialogue types considered here? Or should we define some new type of interaction called "mutual consultation" to deal more effectively with that situation? This remains an open question. Regardless of the dialogue type(s) chosen, providing context, consistency and flexibility will be important for sequence control as it is for other aspects of user interface design. Several guidelines proposed here deal explicitly with the need to define and maintain context for users. With regard to consistency of sequence control, it should be emphasized that users of information systems regard their computer as a tool necessary to perform a job. As a tool, they expect the computer to perform efficiently, reliably, and predictably. They will not regard the computer as an intriguingly unpredictable toy with which to play games. Elements of surprise that might be entertaining in a game will be frustrating in a tool. Neither will users want to regard their computer as a puzzle to be solved, a challenging device whose intricacies promise pleasure in mastery. Where a programmer might be intrigued by the problems of instructing a computer to perform a difficult task, an ordinary user of the system may merely be irritated by the complexity of a computer tool. Where smart shortcuts are provided to perform particular tasks in particular ways, the ordinary system user may resent the extra learning involved, and the extra memory load, rather than appreciate the elegance of saving keystrokes. This argument for consistent control rather than smart shortcuts has been made elsewhere (e.g., Reisner, 1981) but merits continual repetition. Perhaps the most frequent mistake made by designers of user interface software is to provide smart shortcuts instead of consistent control procedures. In every instance, the designer's intent is to help users -- by shortening a particular command, by saving a logically redundant keystroke, or by making sequence control more efficient for a knowledgeable user with perfect memory. But no real users fit that description. Real users depend upon consistent interface design to set practical limits on what they must learn and remember about their computer tools. In accord with this argument, many of the guidelines proposed here deal in some way with the need to provide consistent logic for sequence control. A consistent interface design -- where actions are all taken in the same way, where displayed control options are all formatted in the same way, where error messages are all worded in the same way, and so on -- may seem dull to
17

its designers. It may even seem dull to some of its users. But it should prove easy to learn. Smart shortcuts, i.e., the special design features that can make particular control actions more efficient, should be provided only as optional extras, not needed by novice users but offering some flexibility for experienced users. Ideal flexibility would permit experienced users to undertake whatever task or transaction is needed, at any time. Although this may not always prove feasible, the interface designer should try to provide the maximum possible user control of the on-line transaction sequence. As a simple example, a user who is scanning a multipage data display should be able to go either forward or back at will. If interface software only permits stepping forward, so that users must cycle through the entire display set to reach a previous page, that design is inefficient. Users should also be able to interrupt display scanning at any point to initiate some other transaction. Such simple flexibility is relatively easy for the designer to achieve, and indeed is commonly provided. More difficult are transactions that involve potential change to stored data. Here again users will need flexibility in sequence control, perhaps wishing to back up in a data entry sequence to change previous items, or to cancel and restart the sequence, or to end the sequence altogether and escape to some other task. The interface designer can provide such flexibility through use of suspense files and other special programmed features. That flexibility may require extra effort from the software programmer. But that extra effort is made only once, and is a worthwhile investment on behalf of future users who may interact with their computer system for months or even years. Of course, flexibility of sequence control has pitfalls. Just as users can make mistakes in data entry, so also will users make mistakes in sequence control. The interface designer must try to anticipate user errors and ensure that potentially damaging actions are difficult to take. In most data entry tasks, for example, simple keying of data items should not in itself initiate computer processing. The user should have to take some further, explicit action to ENTER the data. The interface logic should be designed to protect the user from the consequences of inadvertently destructive actions. Any large-scale erasure or deletion of data, for example, should require some sort of explicit user confirmation, being accomplished as a two-step process rather than by a single keystroke. (This provides a software analogy to the physical barriers sometimes used to protect critical hardware controls from accidental activation.) Some well-designed systems go a step further and permit the user to reverse (UNDO) a mistaken action already taken. One form of flexibility frequently recommended is the provision of alternate modes of sequence control for experienced and inexperienced users. In a command-language dialogue, optional guidance might be provided to prompt a beginner step by step in the composition of commands, whereas an experienced user might enter a complete command as a single complex input. Some such flexibility in the user interface is surely desirable -- so that the computer can interpret halting, stepwise control inputs, as well as fluent, coherent commands. More generally, however, it may be desirable to include redundant modes of sequence control in user interface design, perhaps involving combinations of different dialogue types. As an example, menu selection might be incorporated to provide easy sequence control for beginners,
18

but every display frame might also be formatted to include a standard field where an experienced user could enter complete commands more efficiently. Examples of that approach have been provided by Palme (1979). Another way to provide flexibility in sequence control is through specific tailoring of display formats. Consider, for example, a menu selection dialogue in which sequence control is exercised through lightpen selection among displayed control options. For any particular display frame it might be possible to display just three or four options most likely to be selected by a user at that point in the task sequence, plus a general purpose OPTIONS selection that could be used to call out a display of other (less likely) commands. Thus, on the first page of a two-page display set, one of the likely commands would be NEXT PAGE; but on the second page that command would be replaced by its more likely complement, PREV PAGE. This approach illustrates two design ideas. The first comes close to being a general principle for sequence control: make the user's most frequent transactions the easiest to accomplish. The second idea is the reliance on context to improve flexibility. SEQUENCING WITH ARITHMETIC EXPRESSION Introduction Various arithmetic expression sequencing exist. Here Algebraic notation is still the most desirable.Postfix and prefix provide some simplification for machine evaluation. Assignments := in Pascal is a standalone statement = in C/C++/Java is a function or operator (right associative)

a = b = c+1; while(ch=getchar()) putchar(c);

Operator Categories Arity


Unary - operates on a single operand Binary - operates on two operands Ternary - three operands N-ary

Infix - binary operator is found between its operands Prefix - operator precedes its operands Postfix - operator follows its operands
19

Examples of expressions -x+y*z: : an infix expression; - is unary and + and * are binary (- as unary determined by context) x-yz*+ : a corresponding postfix expression ( - denotes negation to not confuse with - as subtraction) +-x*yz : a corresponding prefix expression A syntax tree of the expression: Ambiguity is a problem with infix expressions when operations have different precedence. What different interpretations are there in the above infix expressions? Prefix is often couched in functional syntax: add((neg(x),mult(y,z)) as in C or Java or in LISP or Scheme: (+ (neg x) (* y z)) Evaluation Order Applicative Order Evaluate the operands of the function or operation then apply the operator or function. This corresponds to a bottom up evaluation of the expression tree. "Function or procedure order" Delayed Evaluation (Normal evaluation) Don't evaluate an argument or operand until it is really needed. float function sq(float x); begin sq = x*x; return sq end; Applicative order for the call sq(a+b) Normal order for the call sq(a+b) would compute would compute temp := a+b; sq := temp*temp; sq := (a+b) * (a+b);

When is the normal order approach more efficient than applicative evaluation? and vice versa?

20

Short Circuit Evaluation Typically found in boolean expressions. Not all subexpressions of a boolean expression need to be evaluated to determine its final value. 0 and x = 0 (don't evaluate x) 1 or y = 1 (don't evaluate y) In C/C++/Java: && and || are short circuit evaluations; & and | force both left and right subexpressions to be evaluated before and-ing or or-ing. In Ada, 'or else' and 'and then' are explicit short circuit boolean operations. Postfix Infix notation: Operator appears between operands Prefix notation: Operator precedes operands Postfix notation: Operator follows operands Evaluation of postfix 1. If argument is an operand, stack it. 2. If argument is an n-ary operator, then the n arguments are already onthe stack. Pop the n arguments from the stack and replace by the value of the operator applied to the arguments. Example: 2 3 4 + 5 * + 1. 2. 3. 4. 5. 6. 7. 2 - stack push 3 - stack push 4 - stack push + - replace 3 and 4 on stack by 7 5 - stack push * - replace 5 and 7 on stack by 35 + - replace 35 and 2 on stack by 37

Importance of Postfix to Compilers Code generation same as expression evaluation. To generate code for 2 3 4 + 5 * +, do: 1. 2 - stack L-value of 2
21

2. 3 - stack L-value of 3 3. 4 - stack L-value of 4 4. + - generate code to take R-value of top stack element (L-value of 4) and add to R-value of next stack element (L-value of 3) and place L-value of result on stack 5. 5 - stack L-value of 5 6. * - generate code to take R-value of top stack element (L-value of 5) and multiply to Rvalue of next stack element (L-value of 7) and place L-value of result on stack 7. + - generate code to take R-value of top stack element (L-value of 35) and add to R-value of next stack element (L-value of 2) and place L-value of result (37) on stack Forth - A language based on postfix Postfix source language leads to an efficient execution model, even though generally interpreted. System runs on two stacks - a subroutine return stack and an expression evaluation stack. Runtime model very small making it useful on small embedded computers. Forth was developed by Charles Moore around 1970. The name was a contraction of "Fourth Generation Programming Language" with the program name limited to five characters. The language was a replacement for FORTRAN on small minicomputers in the 1970s where space was at a premium and the only input-output device was often a very slow and cumbersome paper tape. Having a resident translator/interpreter made for easy program development on the target system. Example Forth program Program to compute: 12+22+ ... +92+102$ [Notation: a,b,c is expression stack where c is stack(top)] Precedence of operators Assumed order of evaluation for arithmetic expressions: 2*3+4*5 assumed to be 26 since assumed ordering is (2*3)+(4*5). This is specified by precedence of operators. In any expression, the highest precedence operations are evaluated first, and so on. Most languages have an implied algebraic precedence. APL and Smalltalk do not. Neither language emphasizes arithmetic data, so it is not clear what precedence means in this case. C and its descendents have 17 levels of precedence.

22

C precedence levels C++ permits overloading but precedence is fixed. For example, << and >> are seen with I/O, but they have precedence level 11. Java, Perl and other similar languages adopt the same precedence. Precedence 17 16 Operators tokens, a[k], f() . , -> ++, -++, -~ , -, sizeof ! &,* (typename) *, /, % +,<<, >> <,>,<=, >= ==, != & ^ | && || Operator names Literals, subscripting, function call Selection (dot and arrow) Postfix increment/decrement Prefix inc/dec Unary operators, storage size Logical negation indirection TypeCasts Multiplicative operators Additive operators Shift Relational Equality Bitwise and Bitwise xor Bitwise or Logical and Logical or

15

14 13 12 11 10 9 8 7 6 5 4

23

?:

Conditional

=, +=, -=, *=, /=, %=, <<=, >>=, &=, Assignment ^ =, |= , Sequential evaluation (comma)

SEQUENCING CONTROL BETWEEN STATEMENTS Review sequencing simple expressions (post-, pre-, in- fix, precedence, problems with infix, short circuits) sequencing complex expressions (pattern matching, term rewriting, unification, substitution) Synopsis goto Selection Iteration

Sequence Control Between Statements Forms of Statement-Level Control composition (collecting statements into a structure) selection iteration Explicit Sequence Control Most languages provide unconditional "GOTO label", and conditional "if A=0 then GOTO label", GOTO statements. They equate to a machine code instruction that transfers control to another memory location Early languages mostly provided direct analogues of these. However it was soon recognised that they are a prime cause of poorly structured code. One of the key ideas behind structured programming is that each section of code should have one entry point and one exit point,
24

GOTO's break this. Most modern languages provide many control alternatives so GOTO statements are not needed, and some do not provide GOTOs at all. Many languages provide a special command (Ada - exit, C - break) that will jump from the inside of a loop to just past its end. C also provides the continue command that jumps to the end of the loop (but still inside it). These structures are still considered structured as they have only one exit. Structured Sequence Control Most structured imperative languages have some sort of compound statement that collects terms together: C family {}, Algol family begin .. end, Smalltalk []. Conditional Statements The common if statements. if test then statements endif and if test then statements else statements2 endif are implemented by simple machine code statements that check a value, then dependent on whether it is true or false, jump to the appropriate locationCASE statements are more complicated as often multiple tests must be performed. case check is when 1 => .. when 2 => ... when 3 => .. when others => end case; In some languages the following approach is used GET value of check jump to location L0+check
25

L0

Jump to L1 Jump to L2 jump to L3

L1 L2 L3

code for when check = 0 code for when check = 1 code for when check = 2

Iteration Statements Simple Repetition: The simplest loop is of the form perform body K times but even it leads to questions What if K is 0 or negative? What if the value of K changes whilst the loop is running?

To answer these we must know: When is the test done (at the start or end) ? Are the values used to control the loop reevaluated each time the loop is executed?

Repetition while condition holds: while test do body (pretest loop) repeat body until test (posttest loop) loop body (infinite loop - hopefully contains an exit statement!) Fairly consistent across languages

26

Repetion while increasing a counter: for I:=1 step 2 until 30 do body or more generally For (K=3, L=2 ; (K<7 && L<11) ; K++ , L+=2) Raises similar questions about when exactly the test is checked, and if values can be reevaluated. Implemented as a simple test that checks whether or not to continue the loop, it may also be necessary to allocate memory for the loop counter. Problems for Structured Sequence Control The following cases are not easily satisfied by the pure structured commands. They can be solved either by complicated combinations of the above commands, some sort of alternate structure, the use of exit/breaks or with GOTOs. Multiple exit loops: If an algorithm has multiple tests that may indicate a loop must finish, eg loop X = read_character(file); Exit when EOF(file); Exit when EOLN(file); Exit when X = ' '; end loop; do_while_do: It is sometimes desirable to do the loop test in the middle of the loop. loop read (X)

27

if X is acceptable then goto labela give error message end loop labela This 'n and a half loop' construct can also be built with an exit or break statement. Exception conditions: When an exception occurs, i.e about to divide by 0, encountered end of file, it is sometimes desirable to transfer control to a separate section of code that deals with exceptional circumstances. More on exceptions later Review GOTO conditional and case statements repetition (when checked) problems with structures (multiple exit, do_while_do, exception conditions) .

28

Anda mungkin juga menyukai