310 likes | 425 Views
Formal Methods. Isn’t this really getting old?. Inheritance in Object Z. Before we discuss deferred refinement, we will look at the specification of a small system containing inheritance and some other system specification aspects we have not yet seen.
E N D
Formal Methods Isn’t this really getting old?
Inheritance in Object Z Before we discuss deferred refinement, we will look at the specification of a small system containing inheritance and some other system specification aspects we have not yet seen. We will develop a small set of object structures culminating in the specification of an abstract generic stack.
Inheritance in Object Z Stack [T] |max : N Take a simple object structure such as a stack. We should, by now, be able to specify one using Object Z fairly easily. items: seq T # items ≤ max Note: the operation “Top” and the history invariant have been omitted. INIT items =< > Push • (items) • item?: T #items < max items’ = <item?>^items Pop • (items) • item!: T items ≠ < > items = <item!>^items’
Inheritance in Object Z IndexedStack [T] |max : N Now let us define an “IndexedStack”. This is a stack with a curser. items: seq T index : N1 # items ≤ max items≠ < >index dom items INIT items =< > could be written with a “;” instead of “”, or on two different lines SetIndex • (index) • n?: N1 “pop” and “top” can also be similarly written and are omitted as is the history invariant. n? dom items index’ = n? Push • (items,index) • item?: T #items < max items =<item!>^items’items≠ < >index’=index+1
Inheritance in Object Z IndexedStack [T] Or, we could do the following: Stack[T] index : N1 items≠ < >index dom items INIT items =< > SetIndex • (index) • n?: N1 n? dom items index’ = n? Push • (index) items ≠ < >index’=index+1
Inheritance in Object Z Here is an example of a “DoublePushStack”. This is possible but inadvisable from a software engineering principles of good design perspective, as this is an example of “Specification” inheritance (not “generalization” DoublePushStack[T] Stack[T] redef Push Push • (items) • item? :T #items < max-1 items’ = <item?,item?>^items
Deferred Refinement Deferred refinement was the second approach to refinement that we had set out to examine. You will recall that in direct refinement the specification is refined into their corresponding program design in a single step; as we saw with the library example so far. In deferred refinement, the first refinement cycle will result in a new – more explicit – specification, which is then further refined and so on, resulting in a related and provably consequent sequence of documents finally yielding an implementable design.
Deferred Refinement A database check-pointing system A database, in the most generic, is defined as a function from addresses to pages of data. We shall use this definition to commence our work. We introduce ADDR and PAGE as types. They may be classes developed or to be developed elsewhere or basic types (probably the former). [ADDR, PAGE] We define DATABASE as the abbreviation for all the functions from ADDR to PAGE. DATABASE ==ADDR PAGE
Deferred Refinement Let us compose a system that, from the user’s viewpoint, has two versions of the database. The state space will be something like: CheckSys working: DATABASE backup : DATABASE This says that the two observations working and backup may be any databases at all and need not be related in any way. Of course we will build some relationship between them later. Most operations affect only the working database. For example:
Deferred Refinement Access ΞCheckSys a?: ADDR p! : PAGE p! = working(a?) This operation takes an address a? as input and produces as its output p! the page stored at that address. Neither version of the database changes.
Deferred Refinement It is also possible to update the working database with a new page: Update CheckSys a?: ADDR p? : PAGE working’= working {a? p?} backup’ = backup Note: Now the two versions are no longer identical – if they were at all
Deferred Refinement We can “Synch” the two database copies. This is a Check Point CheckPoint CheckSys working’= working backup’ = working We can also restore the working database to its state at the last check point. Restore CheckSys working’= backup backup’ = backup
Deferred Refinement This completes the specification! Now we commence the refinement process. At first we might want to keep two copies of the database, so implementing the specification directly. Experience however tells us that copying the entire database is a very expensive operation particularly if check points are taken frequently. A better idea might be to keep one complete copy and a “delta”, a record of changes since the creation of this master copy.
Deferred Refinement The master copy is thus only a single database now: Master master: DATABASE The record changes made since last checkpoint is a partial function from addresses to pages: it is partial because not every page will have been updated since last checkpoint. Changes changes: ADDR PAGE The concrete state space is described by putting these two parts together:
Deferred Refinement CheckSys1 Master Changes How does this concrete state space mirror our original work? Basically, the master database is what we described as the backup and the working database is master changes, the result of updating the master copy with the recorded changes. Let us record this in a schema:
Deferred Refinement Link CheckSys Checksys1 backup = master working= master changes To remind you, the notation: means that a function (in this case working) agrees with master everywhere except in the domain of changes, where it agrees with changes. master changes
Deferred Refinement Now let us attempt to implement some operations: Accessing a page at address a? should return a page from the working copy according to: working (a?)= (master changes)(a?) Recall that working is a function, and from Link we had: working = master changes So from here to a valid specification is a small step.
Deferred Refinement Access1 ΞCheckSys1 a?: ADDR p! : PAGE p!= (master changes)(a?) But we can do a little better: if a? dom changes, then: And if a? dom changes, then (master changes)(a?) = changes(a?) (master changes)(a?) = master(a?)
Deferred Refinement So we now know that we have a conditional: If there are no changes, we return the master, if there are changes, we update the master and then return it. Access2 ΞCheckSys1 a?: ADDR p! : PAGE r! : REPORT (a? dom changes p!= changes(a?) r! = ok) (a? dom changes p!= master(a?) r! = not_present)
Deferred Refinement We could of course write the specification slightly differently as: Access2 ΞCheckSys1 a?: ADDR p! : PAGE r!: REPORT ((a? dom changes)((p!= changes(a?) r! = ok)) ((a? dom changes) ((p!= master(a?) r! = not_present)) The second specification is closer to an implementation, the first closer to the logic we need to prove the correctness of our derivations later in the process. Either one would be fine.
Deferred Refinement Observing the symmetry, we could write: Access2 ΞCheckSys1 a?: ADDR p! : PAGE r :== REPORT GetChanges == ((((a? dom changes)) ((p!= changes)(a?)) (r! = ok )) (r! = not_present)) ReadMaster == ((r! = not_present) ( p!=master(a?)))
Deferred Refinement Now we can implement: procedure Access(a:ADDR; p:PAGE); var r:REPORT begin GetChange(a,p,r); if r != ok then ReadMaster(a,p); end; Of course we now need to write GetChanges and ReadMaster, but we basically have them.
Deferred Refinement Directly from slide 18; GetChanges ΞChanges a?: ADDR p! : PAGE r!: REPORT (((a? dom changes)) ( ((p!= changes)(a?)) (r! = ok )) (r! = not_present))
Deferred Refinement ReadMaster is simply: ReadMaster ΞMaster a?: ADDR p! : PAGE p!= master(a?) procedure ReadMaster(a: ADDR; p: PAGE); begin p := master(a); end;
Deferred Refinement For Update, we need backup’ = backup, so master’ = backup’ = backup = master, and working’= working {a? p?}; so we want: Luckily, (f g) h =f (g h) So, if we let changes’ = changes {a? p?}, then: (master changes){a? p?} master’ changes’ = working’= working {a? p?} [from Update] = (master changes){a? p?} [from Link] = master (changes {a? p?}) [associativity] = master changes’ [from Update1]
Deferred Refinement So, update1 must be: Update1 CheckSys1 a?: ADDR p! : PAGE changes’= changes {a? p!} master’ = master Implementation left as exercise
Deferred Refinement Similarly and without proof (left as an exercise); CheckPoint1 CheckSys1 master’ = master changes changes’= We can of course implement this in two functions, one perhaps called WritetoMaster(changes) and one as ResettoNull(changes)
Deferred Refinement What about the Restore function? Let us recall its specification: Restore CheckSys working’= backup backup’ = backup We want backup’=backup; so we need master’ = master; so as in Update1, again we want: This time, because working’ = backup and changes’ must be null, master’ changes’ =master’
Deferred Refinement The condition is as before: Restore1 CheckSys1 master’ = master changes’= We can establish this by a simple call to ResettoNull(changes) The next phase of the development (next refinement iteration) would commence in order to decide on the concrete implementation of WritetoMaster(changes); ResettoNull(changes); GetChanges(a,p,r); etc. One set of these operate on the master part, another on changes
Deferred Refinement These could form two independent modules developed independently, perhaps even by different teams. The next iteration would require selection of data and object structures to represent the various operations, perhaps for instance for master, we choose an array, or maybe a linked list of pages stored on secondary storage, and changes may be a hash table held in main memory. Of course these accesses need to be written. They are not as hard as they seem.