Title: Using Templates for Generalization in RESOLVE
1Using Templates for Generalization in RESOLVE
- Annatala Wolf
- 222 Lecture 1
2Templates
- A template is a definition for a type thats
missing a key piece of information the names of
one or more classes it uses. In order to create
objects of a templates type, you have to
instantiate it first, by filling it in to
complete the template. - In this course, we use templates to design
components that will work with many different
types of data. This is what templates are
typically used for in C, and what generics (a
similar paradigm) are used for in Java and C.
3Unconventional Use
- We will also use templates to decouple component
dependencies, for example, by plugging in which
version of a kernel we want to use. - This is not typically how things are done in C.
Usually, you would use something called
polymorphism to accomplish the same task (though
the task is important, however you do it). - The downside to this strategy is complex error
messages that come from mistakes in template
instantiations and from errors involving template
types and/or kernel Representations.
4Instantiation
- When you fill in a template, this is called
instantiating the template. To do this, you
have to plug a class name (a type) into each
template slot. - Until you instantiate a template, you cant use
it to make objects. For example, Set is a
template parameterized by Item. You cant make a
Set object until you fill in what Item type the
Set will hold.
5Instantiation in Resolve/C
- Normally, templates are filled in directly when
they are used to declare objects, like so - // typical usage, but not in Resolve/C
- SetltTextgt myTextSet
- However, in this class we will instantiate
templates in a way that creates a new type you
can use just like any non-template type. We
always do this at the top of the file, after the
include statements.
6Template Expectations
- We dont expect you to write template
declarations, since the way we use them in this
course is unusual and complicated. - However, you do need to be able to instantiate
templates (fill them in). You should know the
syntax for instantiation in case you need to fill
in a template on a test. It will also help you
in Lab 2, where you can instantiate your own
concrete instances.
7Template Declaration (Simplified)
Item a template parameter to Sequence_Kernel_3
- concrete_template lt
- concrete_instance class Item, ...
- / Other template parameters go here too.
There are typically a bunch, since we
use templates to decouple everything, so
an actual declaration is complex. But
you wont ever have to read or write one. / - gt
- class Sequence_Kernel_3
- implements
- abstract_instance Sequence_Kernel ltItemgt,
- encapsulates
- concrete_instance Rep
-
- / the operation bodies go here /
the name of this concrete template class
the abstract class (the specification) this class
implements, with Item plugged in
8Template Instantiation (Client)
- include CT/Set/Set_Kernel_1a_C.h
- concrete_instance
- class Set_Of_Text
- instantiates
- Set_Kernel_1a_C ltTextgt
-
what we want to call the new type were creating
the concrete type were plugging into the
template parameter for Item
the name of the concrete template we are
instantiating
9Using Templates
- Once youve instantiated your Set_Of_Text, you
can use it just like any other type. - object Set_Of_Text myHappySet
- object Text name Double Rainbow
- myHappySet.Add(name)
- The abstract specification in Set_Kernel will
describe what a Set does, in terms that make
sense no matter what type is plugged into Item.
10Four Component Types
- Abstract Instance (e.g. AM_PM_Clock_Kernel)
- Prototype and formal description of contract for
a particular kind of object - Abstract Template (e.g. Set_Kernel)
- Prototype and formal description of contract for
a parameterized kind of object (described in
general) - Concrete Instance (e.g. AM_PM_Clock_Kernel_1)
- Implementation code for an abstract instance or,
the result of instantiating a concrete template
by filling in the template parameters - Concrete Template (e.g. Set_Kernel_1)
- Implementation code for an abstract template
(written in a general way, so it can work with
whatever Item is)
11Combining Instantiations
- concrete instance
- class Set_Of_Integer
- instantiates
- Set_Kernel_1a_C ltIntegergt
-
- // Now that Set_Of_Integer is a concrete
instance, - // it can fill a template just like any other
type! - concrete instance
- class Power_Set
- instantiates
- Set_Kernel_1a_C ltSet_Of_Integergt
12Containers
- Many libraries for C and Java maintain
collection objects designed to hold a dynamic
number of items in some fashion. These
collections are usually designed to hold many
different kinds of items. - In Resolve/C we call our collection objects
containers. Common examples of Resolve/C
containers - Sequence totally ordered, random-access
- Set unordered, named access
- Queue totally ordered, FIFO-based access
13Sequence
- Intuitively, a sequence is like a flexible array.
- You can insert in the middle. Elements to the
right all move down one position, and it gets
longer. - You can remove from the middle elements to the
right move back one position, and it gets
shorter. - You cant go past the end of the sequence.
- Mathematically, sequence is modeled by string of
Item. - The default value is the empty string ltgt
- We describe position from left to right lt0, 1,
2gt - Item is the template parameter. A given
sequence can only hold elements of a single type.
14Sequence Kernel Operations
- Add(pos, x)
- requires 0 pos self
- Remove(pos, x)
- requires 0 pos lt self
- Accessor pos
- requires 0 pos lt self
- Length()
- requires true
- (in other wordsno requires clause)
15SequenceltCharactergt ? Text
- There is one key difference between a Sequence of
Character and a Text object Add(pos, x) consumes
x for Sequence, and preserves x for Text. - The reason why has to do with how we move
information around in Resolve/C, and the fact
that Item could be anything, including something
we cant copy. With Text, we know we can copy
its elements (Character is copyable). But we
want to write Sequence so it will work with any
type, even one that cant be copied. Hence, it
must be consumes.
16Reading Specifications
- global_procedure Smooth (
- preserves Sequence_Of_Integer s1,
- produces Sequence_Of_Integer s2
- )
- /!
- requires
- s1 gt 0
- ensures
- s2 s1 - 1 and
- for all i, j integer,
- a, b string of integer
- where (s1 a ltigt ltjgt b)
- (there exists c, d string of
integer - (s2 c lt(ij)/2gt d and c
a)) - !/
Eep! Im scared!
17Breaking The Specification Down
- Sequence is modeled by string of Item (in this
case, Integer), so s1 and s2 will be type string
of integer in the spec. -
- /!
- s2 s1 - 1 and
- for all i, j integer,
- a, b string of integer
- where (s1 a ltigt ltjgt b)
- (there exists c, d string of
integer - (s2 c lt(ij)/2gt d and c
a)) - !/
Part 1
Part 2
18The easy part
- // requires s1 gt 0
- // ensures s2 s1 - 1 ...
- This tells us s2 will be one item shorter than
s1. It makes sense, since s1 cant be empty. - So if s1 has one item in it, s2 must be the empty
string. But for longer s1 values, we need more
information. - Since s2 is produces-mode, all of its values will
have to come from s1, somehow.
19Quantifiers For All
- When we model strings of items, frequently we
will want to talk about what happens throughout
the entire string. In this case, we are talking
about all possible ways that you can arrange s1,
so that there is an ltigt next to a ltjgt all
consecutive pairs i j. - The strings a and b are merely placeholders. It
doesnt matter what they containtheyre just
there so we can say lets talk about consecutive
pairs of integers in s1. - for all i, j integer,
- a, b string of integer
- where (s1 a ltigt ltjgt b) ...
20Vacuously True
- If there are no integers and strings that make
the statement true, then the spec tells us
nothing. Its already vacuously true because
there are no cases for us to test. - This could only happen when s1 lt 2. But we
already know what happens when s1 1, and s1
cant be 0, so were good. - for all i, j integer,
- a, b string of integer
- where (s1 a ltigt ltjgt b) ...
we need at least two integers in the string for
this case to happen
21Quantifiers There Exists
- With there exists, were usually making some
assertion about the state of an object. Here, we
say what the value of s2 has to be, based on the
value of s1. - there exists c, d string of integer
- (s2 c lt(ij)/2gt d and c a)
this part tells us what we should find in s2
this part tells us where it should be in the
string
22What are the values?
- The values in s2 are just the smoothed (averaged)
values of each pair of integers. This is why s2
is one shorter than s1. For example, if there
are six integers in s1, there are only five
consecutive pairs. - s2 c lt(ij)/2gt d
each integer in s2 is the average of two
consecutive integers from s1
23Where do the values go?
- By adding some information about those
placeholder string variables, we can describe
exactly how the two strings match up. - Can you figure out how they line up? (Hint
Just ignore b and d!) - s1 a ltigt ltjgt b
- s2 c lt(ij)/2gt d
- c a
24The Meaning of Smooth( )
- The procedure simply averages the consecutive
values of s1, and produces s2 with the averaged
values in the same order in which the pairs
appeared in s1. - This spec is a good example because its about
the hardest thing you would be expected to read
and interpret. Dont let quantifiers scare you.
Most of the string variables we create with
quantifiers are only there as placeholders, so we
can talk about multiple cases at the same time.
25Recursively-Defined Specifications
- global_procedure Weird(alters Sequence_Of_Integer
switch) - /! requires
- (switch mod 2) 0
- ensures
- switch SWITCH(switch)
- math_function SWITCH(string of integer s) string
of integer - if s 0 then SWITCH(s) empty string
- else if s 2 then there exists a, b
integer - where (s ltagt ltbgt and SWITCH ltbgt
ltagt) - else
- there exists a, b integer t string of
integer - where (s ltagt ltbgt t and
- SWITCH(s) ltbgt ltagt
SWITCH(t)) - !/
A math_function is just a piece of a
specification. It is used to make specs easier
to read. It is not code. You cant make a
call to SWITCH(s).
26The Specification for Weird( )
- requires
- (switch mod 2) 0
- The sequence must contain an even number of
integer elements. - ensures
- switch SWITCH(switch)
- Weird(switch) will alter the sequence you pass it
by doing whatever SWITCH describes.
27Interpreting SWITCH(s)
- SWITCH changes one string of integer into a
different string of integer. - math_function SWITCH(string of integer s) string
of integer - if s 0 then SWITCH(s) empty string
- else if s 2 then there exists a, b
integer - where (s ltagt ltbgt and SWITCH(s) ltbgt
ltagt) - else
- there exists a, b integer t string of
integer - where (s ltagt ltbgt t and
- SWITCH(s) ltbgt ltagt
SWITCH(t))
case 1 s 0
case 2 s 2
case 3 other
28SWITCH(s) Case 1
- If you SWITCH(lt gt), it returns lt gt. In other
words, it does not change s when s is the empty
string of integer. - if s 0 then
- SWITCH(s) empty string
29SWITCH(s) Case 2
- If s has two elements, SWITCH(s) returns a string
consisting of those same two integer elements, in
reverse order. - else if s 2 then
- there exists a, b integer where
- (s ltagt ltbgt and
- SWITCH(s) ltbgt ltagt)
30Limiting Complexity
- It turns out that SWITCH(s) doesnt make any
sense at all when the length of s is odd. But
thats okay, because were only using it to
describe what happens when the length of s is
even. - In other words, we dont need to bother reasoning
about cases that wont come up.
31SWITCH(s) Case 3
- If s has more than two elements, we call the
first two a and b. We reverse these two, then
recurse to append SWITCH(t), where t is the rest
of the string. - Its safe, since s is even implies s-2 is
even. - else there exists a, b integer
- t string of integer where
- (s ltagt ltbgt t and
- SWITCH(s) ltbgt ltagt SWITCH(t))
32The Meaning of SWITCH(s)
- Given a string of integer of even length, SWITCH
takes every two integers and swaps them, like so - SWITCH(lt 1, 2, 3, 4, 5, 6 gt) lt 2, 1, 4, 3, 6, 5
gt - Recursive descriptions are often the easiest way
to describe things in math. Youll need to read
them to understand the contracts for Lab 1. - Note just because a spec is recursive doesnt
mean you need to use recursion to write code that
implements it. Weird() would be both simpler and
more efficient if written iteratively.
33Set
- Intuitively, a set is like a bunch of different
items in a bag. - A set is only determined by its elementsit
doesnt contain (or more precisely, doesnt
remember or understand) duplicate items. - The items are not ordered.
- Mathematically, a Set is modeled by set of Item.
- The default value is the empty set
- A set is an unordered container. The RESOLVE
Set component also requires that all elements
be of the same type (Item).
34Set Kernel Operations
- Add(x)
- requires x ? self
- Remove(x, x_copy)
- requires x ? self
- Remove_Any(x)
- requires self gt 0
- Is_Member(x)
- requires true
- Size()
- requires true
35Why restrict Add?
- The restriction on Add(x) for Set (you can only
add elements to a set if theyre not in the set
already) seems unusual. From a math standpoint,
it might make more sense to have a set ignore
duplicate entries, than require that the client
never add duplicates. - However, this restriction does make sense from a
design-by-contract perspective. If we prohibit
the addition of duplicates, this allows us to
implement Set Kernel in a lot of different ways,
some of which are much more efficient than the
alternative approach.
36Why does Remove take two Items?
- Set is designed to work with any type (though it
includes a special restriction that Item must
implement Is_Equal_To). - If the type Set holds is very large, you might
have a use for both the key x you pass to
Remove(x, x_copy), and the copy x_copy that gets
yanked out of the Set. It returns the copy to
you just in case you need it, e.g., if copying
Item is expensive or difficult. - However, most of the time youll just want to
make a throw-away variable for x_copy.
37Example of Set Math Operations
- Let A 1, 2 B 2, 3
- Element (the member is in the set)
- 1 ? A 3 ? A
- Subset (all members of one set are in the other)
- ? A 1 ? B 1, 2 ? A
- Union (the combined contents of two sets, like
OR) - A ? B 1, 2, 3
- Intersection (the overlap between two sets, like
AND) - A ? B 2
- Set Difference (elements in one set that arent
in another) - A\B 1 (sometimes written A-B)
- Power Set (the set of all subsets of a set)
- P(A) , 1, 2, 1, 2
38Why are these not part of Set_Kernel?
- The purpose of a kernel is to provide a minimal
set of operations needed to examine and change
the abstract state space of an object. - Kernel operations must provide the ability to
build any possible legal object of that type, and
the ability to identify and change an unknown
objects contents. - Set_Kernel doesnt include Union and Intersection
because you dont need them to create and change
a given set. You can build these operations
later, using the operations the Kernel gives you.
39Iterating With Sets
- The process of iterating through an unordered
collection in Resolve relies on using the Kernel
operations (Remove_Any(x), for Sets) to dump out
the contents into a temporary object, so you can
remember what youve seen and what you havent. - Once youve gone through all the items, the
original Set will be empty, and the temporary Set
will be full. A swap will then restore the
original Set, preserving it.
40Practice Using Sets
- Lets build a function that takes two
Set_Of_Integer objects (instantiated from
SetltIntegergt) and returns a Boolean value for the
subset operator. - (A ? B means all elements of A are also elements
of B.) - Get into teams of two to four.
- When writing code, remember to use object
Set_Of_Integer to create a new empty set that
can hold Integers, if you need one. - Eventually well learn how to do this kind of
extension for ANY type of Set, but for this test
well just use Set_Of_Integer.
41Templating and Prototype
- include CT/Set/Set_Kernel_1a_C.h
- concrete instance
- class Set_Of_Integer
- instantiates
- Set_Kernel_1a_C ltIntegergt
-
- global_function Boolean Is_Subset(
- preserves Set_Of_Integer subset,
- preserves Set_Of_Integer superset
- )
- /!
- ensures
- Is_Subset
- for all i integer
- where i is in subset
- (i is in superset)
- !/
if the name of the function appears in its spec,
this means the value that gets returned
put another way, returns false iff (if and only
if) subset has something superset doesnt
42Is_Subset
- global_function_body Boolean Is_Subset(
- preserves Set_Of_Integer subset,
- preserves Set_Of_Integer superset
- )
-
- object catalyst Set_Of_Integer subset_seen
- object Boolean answer true
- // check each element from subset to see if
its in superset - while (subset.Size() gt 0)
- object catalyst Integer elem
- subset.Remove_Any(elem)
- if (not superset.Is_Member(elem))
- answer false
-
- subset_seen.Add(elem) // hold on to
what weve - // already
iterated through - subset subset_seen // now, we can
restore subset
43Planning in Is_Subset
- Note that we started by setting our answer to
true. This is a good choice, because in the case
that we have nothing to test (because subset is
empty), result is vacuously true. - When we set the answer to false, it has no way to
change back again. As soon as we find a
counterexample, we have our answer. - However, we cant return as soon as we find such
a counterexample! We have to iterate through the
rest of the set so that we can restore it to its
original value. Planning to return at the end of
the function avoids error.
44Queue
- Intuitively, a queue is like data waiting in
line. The data waiting the longest is the next
available. - A queue is useful for handling data in the order
that it arrives or is stored (oldest item first) - Sometimes this is called FIFO (first in, first
out) - Mathematically, a queue is modeled by string of
Item. - The default value is the empty string ltgt
- This is the same model as Sequence! The data is
represented the same way abstractly. - The difference is how you access the data (what
operations you have available to you). - The head (current) is the leftmost member ltA,
Bgt
45Arbitrary Notation
- We model Queue abstractly by setting the head of
the queue to be the left side. - Removing an object x removes the head
- Dequeue(x) ensures (q ltxgt q)
- Inserting an object x concatenates onto the tail
- Enqueue(x) ensures (q q ltxgt).
- This choice was made since English reads from
left to right, but its arbitrary. Queue would
work the same way if we reversed all of its specs.
46Queue Kernel Operations
- Enqueue(x)
- requires true
- Dequeue(x)
- requires self gt 0
- Accessor current
- requires self gt 0
- Length()
- requires true
47Practice Using Queues
- Lets write an operation to find both the min and
max value in a Queue. (Hint you dont need to
make a new Queue!) - global_procedure Find_Min_And_Max (
- preserves Queue_Of_Integer values,
- produces Integer min,
- produces Integer max,
- )
- /!
- requires
- values gt 0
- ensures
- max is in values and
- min is in values and
- for all i integer
- where (i is in values)
- (max i and min i)
- !/
48Find_Min_And_Max
- global_procedure_body Find_Min_And_Max (
- preserves Queue_Of_Integer values,
- produces Integer min,
- produces Integer max )
-
- object Integer counter
- min valuescurrent
- max valuescurrent
- while (counter lt values.Length())
- object catalyst Integer last
- values.Dequeue(last)
- values.Enqueue(last)
- if (max lt valuescurrent)
- max valuescurrent
-
- if (min gt valuescurrent)
- min valuescurrent
-
- counter
49Planning in Find_Min_And_Max
- Notice that we copy a value from the queue (which
must exist, since its required to be nonempty)
into min and max at the outset. This ensures we
dont accidentally produce a number that isnt in
the queue. - A common approach might involve checking the
dequeued item, rather than using current.
Theres nothing wrong with this approach, but
its helpful to see how useful current can be.
With some operations, it can provide significant
clarity, and save time.
50Queue Rotation
- I like to call a Dequeue of an element, followed
by an Enqueue of the same element, a rotation of
a Queue. - Since Queue is last-in, first-out, this preserves
order. If you rotate a Queue a number of times
equal to the length of the Queue, it restores the
Queue to its original values - original lt1, 2, 3gt (the queue has
three elements) - one rotation lt2, 3, 1gt
- two rotations lt3, 1, 2gt
- three rotations lt1, 2, 3gt (the original order
is restored) - This lets us easily iterate through a Queue while
preserving it, without the need for a separate
Queue.
51Uses for Sequence, Set, Queue
- Sequence is useful when you need to keep
something in order as you insert new elements
into the middle - an outline, when you need to edit things in the
middle - a list of items you want insertion sort to keep
in order - Set is useful when you need to test a binary
condition over a group of items - a set of keywords, so you can check if a word is
a keyword - a set of filenames, so you know which files are
up-to-date - Queue is useful when you want to process things
in the order you encounter them - a queue of print jobs waiting to print, oldest
job first - a queue of chores that need to be done, oldest
job first
52Record
- Intuitively, Record is like a single row in a
data-base table (Anna Wolf, 123456789,
Gemini) - Mathematically, Record is modeled by n-tuple of
objects, where n is an integer between 1 and 10. - Record is a client template that allows different
types to be stored together in the same object. - Each field of a Record (piece of tuple) has its
own type. - The default value of a Record is a tuple
consisting of the default values of all of its
fields ( d1, d2, d3) - For example, the default value of
- RecordltInteger, Text, Queue_Of_Textgt ( 0, ,
ltgt) - RecordltReal, Boolean, Real, Charactergt ( 0.0,
false, 0.0, \0)
53Interesting Record Facts
- In most cases, its better create a new component
than make a Record. This is because its useful
to have not only data held in a package, but also
the algorithms that handle the data. - Theres a special version of Record found inside
Kernels, called Representation. Its used to
hold the Kernels data members. Representation
works just like Record. - Record is a Resolve/C foundation type(and so
is Representation).
54Instantiating Record
- To instantiate a record, you must fill in the
template AND name the fields - concrete instance
- class Nickname_And_Grade
- instantiates
- Record lt
- Text,
- Integer
- gt
-
-
- field_name (Nickname_And_Grade, 0, Text,
nickname) - field_name (Nickname_And_Grade, 1, Integer,
grade)
our name for the new Record type
the field types you can put from 1 to 10 types
here
a field_name must be declared for each field,
once the Record is instantiated (usually done
right after instatiation)
55Instantiating Record
- Before using a Record, you must name each field.
- The index is the position the field appeared at
in the instantiation, as an integer starting from
0. - The fieldname is the only thing you control.
Everything else is pulled from the instantiation. -
- field_name (Nickname_And_Grade, 0, Text,
nickname) - field_name (Nickname_And_Grade, 1, Integer,
grade)
the fieldname you want to use to access the
field at that index
name of the Record type
index of the field
the type of the field at that index
56Record Details
- Once you instantiate Record and name its fields,
each fieldname has a type permanently associated
with it. - Making a new Record will create one new object
for each of its fields, and those objects will be
of their default types. Similarly, clearing a
Record resets each of its objects to their
default types. - Record is not a collection object! A Record is a
single tuple. It always has something inside it,
and only stores one group of fields.
57Combining Record Instantiations
- concrete instance
- class Full_Name field_name(Full_Name,
... - instantiates
- Record lt
- Text, ... 0, Text,
first_name) - Text ... 1, Text,
last_name) - gt
-
- concrete_instance
- class Name_And_Grade field_name(Name_And_G
rade, ... - instantiates
- Record lt
- Full_Name, ... 0, Full_Name,
name) - Integer ... 1, Integer,
grade) - gt
-
-
58Using Record
- Records only operation is Accessor
- fieldname (returns a reference to named field)
- The only legal thing you can put in Records
Accessor is a fieldname you have already defined. - Since Accessor returns a reference, you can use
it just like its a variable. In other words, if
you create a Full_Name object called student,
then you just created two Text objects, called
studentfirst_name and studentlast_name.
59Examples Using Record
- object Name_And_Grade student
- object Name_And_Grade other_student
- object Set_Of_Name_And_Grade course
- studentnamefirst_name Early
- studentnamelast_name Cuyler
- studentgrade 3
- student other_student
- course.Add(other_student)
60Unrolling Expressions
- Its important that you be able to unroll an
expression to figure out the types of its terms. - Consider a Queue_Of_Name_And_Grade object called
foo. Can you identify all of the types here? - foocurrentnamelast_name
61Identifying All of the Types
Queue_Of_Name_And_Grade
keyword
field_name
Name_And_Grade
Full_Name
Text