SciELO - Scientific Electronic Library Online

 
vol.5 issue1Letter from the guest editorsLabeled Families in Modular Software Development author indexsubject indexarticles search
Home Pagealphabetic serial listing  

Services on Demand

Journal

Article

Indicators

Related links

Share


Journal of the Brazilian Computer Society

Print version ISSN 0104-6500On-line version ISSN 1678-4804

J. Braz. Comp. Soc. vol. 5 n. 1 Campinas July 1998

http://dx.doi.org/10.1590/S0104-65001998000200002 

Procedures and Recursion in the Refinement Calculus

 

Ana Cavalcanti and Augusto Sampaio
Departamento de Informática
UFPE
Caixa Postal 7851
50740-540 Recife PE Brazil
{alcc,acas}@di.ufpe.br

Jim Woodcock
Oxford University Computing Laboratory
Wolfson Building, Parks Road
Oxford OX1 3QD England - UK
Jim.Woodcock@comlab.ox.ac.uk

 

 

Abstract Morgan's approach to program development is a refinement calculus: using this method, programs are developed from specifications through the successive application of refinement laws. This programming technique has been widely accepted, but it has been shown that Morgan's approach to procedures and parameters has an inconsistency. Back presents an alternative formalisation of procedures and parameters which is consistent, but does not propose any refinement law. In this paper, we present refinement laws that allow the development of (possibly parametrised and recursive) procedures in the style of Morgan, and derive these laws based on Back's formalism. Some of the refinement laws we present have a counterpart in Morgan's calculus, but some are new laws that support his approach to the development of recursive procedures.
Keywords: formal methods, program development, refinement laws.

 

 

1 Introduction

Three formalisations of the stepwise refinement technique of program development have been presented by Back [2], Morgan [12, 11], and Morris [13, 15]. In these works, Dijkstra's weakest preconditions (wp) [5] are used to define the semantics of a unified language of specification and programming and to define a notion of refinement that reflects the concept of total correctness. In this context, a specification is regarded as a special form of program, a term used to refer to both specifications and programs themselves. The development of a program typically starts from a specification and can be proved correct by showing that the wp semantics is preserved.

In particular, Morgan presents a refinement calculus; in [11] developing a program consists of repeatedly applying refinement laws to a specification until an adequate program is obtained. Refinement laws establish program transformations that preserve total correctness (the wp semantics), and typically yield programs that are better (more deterministic, executable, or more efficient, for instance) than those to which the transformations have been applied.

The benefits of calculating as opposed to verifying programs are manifold. Morgan's style of program refinement, in particular, produces developments that can be presented uniformly as sequences of small refinement steps. Each step is the result of the application of a refinement law and may involve the discharge of corresponding proof-obligations. The refinement laws provide guidance in the development of programs.

Despite these advantages, Morgan's approach to procedures and parameters [8] has an inconsistency, as reported in [4]. The problem emerged as a consequence of na unfortunate design decision. In Morgan's work, parameters are regarded as global (rather than local) variables; as a consequence, the attempt to define parameter passing mechanisms based on substitutions led to unexpected results, as explained in detail in [4]. Back's formalism does not suffer from this problem. Nevertheless, Back's approach is not appealing to practical use since no refinement law is presented to help in the development process.

The main contribution of this paper is to integrate Morgan's and Back's approaches and provide a solution to the problem reported in [4]. We use Back's formalism as a consistent model to derive Morgan's refinement laws. Moreover, we investigate how Morgan's approach to the development of recursive (parametrised) procedures can be formalised in this framework. In this way, we benefit from two of the major approaches to the formalisation of procedures and parameters. Morris's approach [14] is not considered here since it is directly based on that of Back.

In Section 2 we introduce the notation we use to write and develop parametrised and recursive procedures. In Section 3, we define the semantics of these constructs and in Section 4 we present refinement laws. Section 5 develops a case study presented in [11]: we show how to derive a recursive program that calculates the area of the largest rectangle under a given histogram using the refinement laws we propose here. Section 6 summarises our results. Appendix A presents the derivation of some of the refinement laws; and, finally, Appendix B presents the laws of Morgan's calculus that we use in the case study.

 

2 Procedures, Parameters, and Recursion

As in [1] and [11], we declare procedures in blocks as shown in the following example.

|[proc Inc v5n1a2f183.gif (839 bytes)x := x + 1 l Inc; Inc]|

This very simple program declares and uses the procedure Inc to increase the value of x by 2. The program x := x+1 is the body of Inc, and Inc is the main program (the scope of the procedure).

The notation we employ to write parametrised procedures is based on Back's parametrised statements. We consider parametrised statements of the form (val vl l p), (res vl l p), and (val-res vl l p), which correspond to the traditional mechanisms of parameter passing known as call-by-value, call-by-result, and call-by-value-result, respectively. In each case, vl ranges over lists of variables standing for the formal parameters, and p over programs. These conventions are assumed in subsequent definitions, where we also use l to range over lists of variables. Moreover, we use subscripts to extend the set of metavariables, so that and , for instance, also stand for lists of variables.

As opposed to assignments, for instance, parametrised statements are not programs by themselves. However, a parametrised statement (or the name of a procedure whose body is a parametrised statement) can be applied to a suitable list of actual parameters to yield a program. The resulting program acts as that obtained by passing the actual parameters to the program in the body of the parametrised statement. The number of actual parameters must be the same as the number of formal parameters and the correspondence between them is positional.

By way of illustration, we consider the program that assigns 0 to the variables x and y by using a parametrised procedure Zero.

|[proc Zerov5n1a2f183.gif (839 bytes) (res n l n:= 0) l Zero(x) ; Zero(y)]|

This program is equivalent to the sequential composition (res n l n := 0)(x) ; (res n l n := 0)(y), which is itself equivalent to x := 0 ; y := 0, as expected. A formal definition of each form of parametrised statement is given in the next section.

In order to simplify the presentation and derivation of refinement laws, we establish that a parametrised statement may declare no formal parameters. These special parametrised statements are actually programs themselves and we define that, for every program p, (l p) = p.

The general form of a procedure block is shown below, where fpd ranges over formal parameter declarations.

|[proc pn v5n1a2f183.gif (839 bytes) (fpd l p1) l p2]|

If pn is not parametrised, fpd is empty and so (fpd l p1) is simply p1, as noted above.

The development of recursive procedures requires the use of variants. Recursion may be used if the refinement of a program (parametrised statement) p leads to another program (parametrised statement) that contains p itself as a component. Due to termination concerns, however, the introduction of a recursive procedure requires the definition of a variant: an integer expression whose value must be decreased by each recursive call, but cannot assume negative values. As a matter of fact, from a theoretical point of view, the type of a variant can be any well-founded set, but in practice it is enough to consider that the variant is an integer bounded below by 0.

In our notation, these expressions are declared in a variant block, as suggested in [11]. This novel kind of block is a procedure block that also introduces a variant. The form of a variant block is as shown below, where v and e range over variant names (identifiers) and integer expressions, respectively.

|[proc pn v5n1a2f183.gif (839 bytes)(fpd l p1) variant v is e l p2]|

In this program v is a name for the variant expression e whose value must be decreased. In the refinement process, v is used as a logical constant that gives a name to the initial value of e. As an example, we consider the development of the very simple program that assigns to the variable x the factorial of y.

In Morgan's refinement calculus (and here), specifications are written using specification statements. These have the form w:[pre,post], where w (the frame) ranges over lists of variables, and pre (the precondition) and post (the postcondition) over predicates. This program can change only the value of the variables in w and, when executed from a state that satisfies pre, terminates in a state that satisfies post. The program that assigns the factorial of y to x can be written as x :[true, x = y!].

If, when refining this program, we decide that we want to develop a recursive procedure Fact that implements the factorial function, we have to introduce the variant block below.

|[proc Fact v5n1a2f183.gif (839 bytes) (val n l {N = n} x : [true,x = n!])
variant N is n l x :[true,x = y!] ]|

At this point, we can refine the body of Fact to obtain an implementation for this procedure and refine the main program to introduce the appropriate procedure call.

In the body of Fact, N is a logical constant that records the initial value of the variant n. Following Morgan [11], we call the program {N = n} an assumption: {N = n} x : [true,x = n!] aborts if N = n does not hold and, otherwise, behaves like x : [true, x = n!].

In refining the body of Fact (using, for instance, Morgan's refinement laws and some of the laws we present here), we can obtain the program below.

if n = 0v5n1a2f159.gif (848 bytes)x := 1

[] n > 0 v5n1a2f160.gif (846 bytes)

(val n l {0 v5n1a2f161.gif (839 bytes)n < N} x:[true,x = n!]) (n - 1);

x := x v5n1a2f162.gif (838 bytes)n

fi

The parametrised statement that appears in this program is the same as that initially defined as the body of Fact, except for the assumption in its body. The new assumption could be introduced only because the variant, n, has been decreased and its presence actually implies that the body of the parametrised statement is expected to behave properly only in the presence of a parameter which is strictly less than N. The parametrised statement as a whole can be replaced with a recursive call to Fact resulting in Fact(n - 1). Recursive calls can be introduced only at points where the variant has been strictly decreased.

Since variant names play the role of logical constants, variant blocks are not executable. They have to be refined away in the development. Altogether, using the laws we present here (and some laws of Morgan's calculus) we can derive the program in Figure 1 by the successive application of refinement laws, as recommended in [11].

 

v5n1a2f163.gif (2525 bytes)

Figure 1: Implementation of Factorial

 

3 Semantics

Parametrised statements, or more precisely, the programs obtained by applying them to lists of actual parameters, are defined in terms of variable blocks. The general form of a variable block is |[ var vl l p ]|; this program declares a list vl of variables that are local to its body, p.

In the definition of call-by-value, which is shown below, el is a metavariable that ranges over lists of expressions.

Call-by-value

(val vl l p )(el) = |[var l l l := el ; p[vl \ l] ]|

provided the variables of l are fresh: not free in p or el, and not in vl.

The variable block above implements call-by-value using the well-known technique of assignment to a local variable.

The variable block that corresponds to a call-by-result, which uses the technique of assignment from a local variable, is shown below.

Call-by-result

(res vl1 l  p)(vl2) = |[var l l p[vl1 \ l] ; vl2:= l]|

provided the variables of l are fresh: not free in p and not in vl1 or vl2 .

For call-by-value-result, we have the definition that follows.

Call-by-value-result

(val-res vl1 l p)(vl2) = |[var l l l := vl2 ; p[vl1 \ l] ; vl2:= l]|

provided the variables of l are not free in p, and are not in vl1 or vl2.

We assume that in the case of a call-by-result or a call-by-value-result, the list of actual parameters is a duplicate-free list of variables.

Parametrised statements that combine different forms of parameter transmission are defined below by composition. The metavariable par used in this definition ranges over mechanisms of parameter passing: val, res, and val-res.

Multiple parametrisation mechanisms

(par vl1; fpd l p)(el1, el2 ) = (par vl1 l (fpd l p)(el2) (el1)

provided the variables of are not free in .

This definition expresses in a general way the definitions of [1].

A refinement relation between programs, , is defined in both [1] and [8] in terms of weakest preconditions. We rephrase the definitions there below, where y is a metavariable that stands for predicates and the application of wp to a program p and a postcondition y is denoted wp.p. y.

Definition 1 For programs p1 and p2 , p1   p2 if and only if wp. p1. yv5n1a2f173.gif (847 bytes)wp.p2 y, for all postconditions y.

The function wp determines, when applied to a program p and to a postcondition y, the weakest precondition that guarantees that p terminates in a state that satisfies y. In other words, wp determines the predicate that identifies all states in which the execution of p will lead to a state that satisfies y. Intuitively, p1   p2 holds exactly when p2 terminates whenever p1 does, and produces only results that are acceptable to p1. Therefore, if  p1  p2, then p2 is always satisfactory as a substitute for p1. A more formal justification of Definition 1 can be found in [2, 9].

For parametrised statements, the definition of refinement that we adopt is that of [1].

Definition 2 For parametrised statements (fpd l p1) and (fpd l p2), with the same formal parameter declaration, (fpd l p1 ) (fpd l p2 ) if and only if, for all lists (al) of actual parameters, (fpd l p1 ) (al) (fpd l p2 ) (al).

Surprisingly, maybe  p1   p2, is not equivalent to (fpd l p1 ) (fpd l p2 ). As established in [1], parametrised statements are monotonic with respect to , so that p1   p2 implies (fpd l p1 ) (fpd l p2). Nevertheless, there are cases in which the relationship (fpd l p1 ) (fpd l p2) holds, but  p1 p2) does not hold. For instance, by applying the definition of call-by-value, it is not difficult to prove that the programs (val n l n := n + 1)(m) and (val n l n := n + 2)(m) are equivalent to skip. Therefore, since m is arbitrary, we deduce that (val n l n := n + 1) (val n l n := n + 2). Nevertheless, n := n + 1 is not refined by n := n + 2.

The main program of a procedure block and, in the case of a recursive procedure, its body as well, may refer to the procedure name, so they are not conventional programs. From the semantic point of view, they are contexts: functions from programs (or parametrised statements) to programs (or parametrised statements). If c is the main program of a procedure block or the body of a recursive procedure, then it corresponds to the function that, when applied to a program (or parametrised statement) p, yields the result of substituting p for the free occurrences of the procedure name in c, where c is a metavariable ranging over programs and parametrised statements.

The semantics of a procedure block, which we define below, is based on the copy rule of Algo l60 and on least fixed points, as adopted, for example, in [1, 12, 11]. The copy rule says that a program containing a procedure call is equivalent to that obtained by substituting the procedure body for the procedure name.

Procedure block

|[ proc pn v5n1a2f183.gif (839 bytes)(fpd l p1 )(pn) l p2 (pn)]| = p2 (m (fpd l p1 ) )

This definition states that a procedure block is equivalent to the program obtained by applying its main program to the least fixed point of the procedure body. A more usual notation for the least fixed point of a function (fpd l p1 ) is m pn l (fpd l p1 )(pn), but for the sake of brevity, we will adopt the more concise notation m (fpd l p1 ).

The existence of m (fpd l p1 ) has to be justified. According to Knaster-Tarski [16], we can establish that m (fpd l p1 ) exists by showing that the set of programs and the sets of parametrised statements with the same formal parameter declaration are complete lattices, and that (fpd l p1 ) is monotonic. As expected, the partial order of interest is refinement.

In the unified languages of [1, 12] programs are modelled as monotonic predicate transformers, which, as shown in [3], form a complete lattice. For parametrised statements, the lattice is somehow left implicit in [1]; here we present an explicit justification for its construction. The bottom element of the set of parametrised statements with formal parameter declaration fpd is (fpd l abort), where abort is the program that is never guaranteed to terminate and, even if it does, its behaviour is unpredictable. The least upper bound of a set of parametrised statements {i l.(fpd l pi )} can be defined as (u{i l (fpd l pi)}) (al) = i{i l (fpd l pi)(al)}, where al is a metavariable that stands for lists of actual parameters. Finally, the program constructors are monotonic with respect to _ [1,12] and, consequently, (fpd l p1) is a monotonic function, as required.

In the case where (fpd l p1 ) does not contain free occurrences of pn or, in other words, pn is not a recursive procedure, m (fpd l p1) is (fpd l p1 ). Therefore, |[proc pn v5n1a2f183.gif (839 bytes)(fpd l p1 ) l  p2 (pn)]| is the program obtained by substituting (fpd l p1 ) for the calls to pn in p1,   p2, (fpd l p1 ), as expected.

The variant name introduced by a variant block is a logical constant whose scope is restricted to the procedure body. This logical constant is supposed to assume ever decreasing values in the successive recursive procedure calls. Accordingly, the semantics of a variant block is as follows.

Variant block

|[proc pn v5n1a2f183.gif (839 bytes) (fpd l p1 )(pn) variant n is e l  p2 (pn)]| =  p2 (m (fpd l |[con n : Z l p1]|))

The block |[con n : Z l p1 ]| declares the logical constant n which is local to p1.

In the next section, we present some refinement laws. An example that illustrates the use of the constructs presented here and of the laws presented in the sequel is given in Section 5.

 

4 Refinement Laws

In view of the semantics of procedure blocks, variant blocks, and parametrised statements, and of the definition of refinement, we can derive a set of refinement laws to support the development of procedures. In particular, we propose laws that can be used to introduce procedure and variant blocks, and procedure calls (Laws 1 to 5 in the sequel). These laws support Morgan's technique of procedure development and, to our knowledge, are an original contribution to the literature on formal program development. Furthermore, we present refinement laws that can be used to introduce parametrised statements. These correspond to the laws of [10] that introduce substitutions.

4.1 Procedures and Recursion

The refinement law that can be employed to introduce a procedure block is presented below.

Law 1 Introduce procedure block.

p2 =  |[proc pnv5n1a2f183.gif (839 bytes)(fpd l p1) l  p2]|

provided pn is not free in  p2.

This refinement law allows any program p2 to be transformed into a procedure block that declares a procedure pn and whose main program is p2. Calls to pn can be introduced subsequently in p2 using the Law 4 presented later on. As mentioned in the previous section, a parametrised statement may have an empty formal parameter declaration. Therefore, Law 1 can also be used to introduce a block that declares a non-parametrised procedure. This also holds for the other laws that we present below.

The introduction of a variant block can be achieved by the following refinement law.

Law 2 Introduce a variant block.

p2 = |[proc pn v5n1a2f183.gif (839 bytes) (fpd l {n = e} p1) variant n is e l p2]|

provided

l pn and n are not free in e or in p2;

l n is different from pn.

Recursion can be introduced by refining (fpd l {n = e} p1) and subsequently replacing occurrences of (fpd l {0 v5n1a2f161.gif (839 bytes) n < e} p1) in the resulting program by procedure calls.

The introduction of procedure calls in the main program of a procedure block that declares a non-recursive procedure can be achieved by applying the very simple refinement law below.

Law 3 Introduce call to a non-recursive procedure in the main program of a procedure block.

|[proc pn v5n1a2f183.gif (839 bytes) (fpd l p1) l p2) l [(fpd l p1 )] ]| = |[proc  pn v5n1a2f183.gif (839 bytes) (fpd l p1 ) l p2 [pn] ]|

provided pn is not recursive.

We identify an occurrence of a program (parametrised statement) p1 in a context c by writing c [p1]. Subsequent references to c [p2] denote the context obtained by substituting p2 for that particular occurrence of p1 in c. The program c [p2] should not be confused with c (p2). As already explained, the latter, is the result of substituting p2 for the free occurrences of the procedure name, as opposed to a particular occurrence of p1, in c.

The Laws 1 - 3 are simple consequences of the definitions of procedure and variant blocks. For conciseness, we do not present a derivation for them here. In Appendix A, we derive the Laws 4, 5, 7, and 8. A derivation for Law 6 is presented in the next section as an example.

The introduction of a procedure call in the main program of a variant block can be accomplished by the following law.

Law 4 Introduce call to a procedure in the main program of a variant block.

|[proc pn v5n1a2f183.gif (839 bytes) (fpd l p1 ) variant n is e l p2 [(fpd l p3 )]]|

|[proc pn v5n1a2f183.gif (839 bytes) (fpd l p1 ) variant n is e l p2 [pn] ]|

provided

l {n = e} p3 v5n1a2f177.gif (851 bytes) p1;

l pn is not recursive;

l n is not free in e or p3.

The first proviso of this law, {n = e } p3 v5n1a2f177.gif (851 bytes) p1, could be replaced by the simpler condition p3 v5n1a2f177.gif (851 bytes) p1, since the refinement achieved by the law is also valid in this case. Nonetheless, in the strategy of development we support (that in [11]), pn is introduced using Law 2 and refined using only the laws presented here and those of Morgan's calculus. In this case, p1 is either a program of the form {n = e } p or is obtained by refining a program like this. In this case, p1 can have free occurrences of n and we would rather compare {n = e } p3 and p1. The idea is that {n = e } p3 is actually the initial specification of pn, so that the application of Law 2 does not generate aditional proof-obligations as, in this case, {n = e } p3 _   p1 can be deduced from the history of development.

Recursive calls can be introduced with the use of Law 5 which follows.

Law 5 Introduce recursive call.

|[proc pn v5n1a2f183.gif (839 bytes) (fpd l p1 [(fpd l {0 v5n1a2f161.gif (839 bytes)e < n} l  p3)])
variant n is e l p2 ]|
_

|[proc pn (fpd v5n1a2f183.gif (839 bytes) (fpd l p1 [pn]) l p2]|

provided

l {n = e } p3 _  p1 [(fpd l {0 v5n1a2f161.gif (839 bytes)e < n} p3 )];

l n is not free in p3 or p1 [pn].

As in the case of Law 2, {n = e } p3 is supposed to be the initial specification of pn. The first proviso requires that p1 [(fpd l {0 v5n1a2f161.gif (839 bytes)e < n} p3)] can be obtained by refining this specification. The introduction of a recursive call is justified by the fact that  p3 appears in its own refinement in a context where the variant has been decreased.

4.2 Parametrised Statements

The Laws 3, 4 and 5 allow the substitution of parametrised procedure names for appropriate parametrised statements. We still need, however, laws that introduce parametrised statements. In this section, we present three such laws, which account for value, result, and value-result parametrised statements.

The law that introduces a call-by-value is as follows.

Law 6 Call-by-value.

w :[pre[vl \ el], post[vl \ el] ] = (val vl l w :[pre, post])(el)

provided the variables of vl are not in w, and the variables of w are not free in el.

In order to exemplify the derivation of refinement laws, we present a proof for this particular law. As mentioned before, the derivation of some other laws can be found in Appendix A, where we also present the wp definitions used in these derivations.

wp.w : [pre[vl \ el], post[vl \ el] ].y

= pre[vl \ el] v5n1a2f190.gif (848 bytes)(v5n1a2f191.gif (847 bytes)w l post[vl \ el] v5n1a2f173.gif (847 bytes)y) [by definition of wp]

= pre[vl \ l][l \ el] v5n1a2f190.gif (848 bytes)(v5n1a2f191.gif (847 bytes)w l post[vl \ l][l \ el] v5n1a2f173.gif (847 bytes)y) [by the variables of l are fresh]

= pre[vl \ l] v5n1a2f190.gif (848 bytes)(v5n1a2f191.gif (847 bytes)w l post[vl \ l] v5n1a2f173.gif (847 bytes)y))[l \ el] [by the variables of l are fresh and the proviso]

= v5n1a2f191.gif (847 bytes)l l (pre[vl \ l] v5n1a2f190.gif (848 bytes)(v5n1a2f191.gif (847 bytes)w l post[vl \ l] v5n1a2f173.gif (847 bytes)y))[l \ el] [by predicate calculus]

= v5n1a2f191.gif (847 bytes)l l (wp. :[pre, post])[vl \ l].y)[l \el] [by the proviso]

= wp.|[var l l l := el ; (w :[pre, post])[vl \ l] ]| .y [by definition of wp]

= wp.(val vl l w :[pre, post])(el ).y [by definition]

The introduction of a call-by-result can be achieved with the refinement law below.

Law 7 Call-by-result .

w, vl2,:[pre,post]=(res vl1 l w, vl1 : [pre, post[vl2 \ vl1] ])(vl2)

provided the variables of vl1 are not in w, and are not free in pre or post.

For a call-by-value-result we have the following law.

Law 8 Call-by-value-result.

w, vl2 : [pre[vl1 \ vl2], post]= (val-res vl1 l w, vl1 : [pre, post[vl2 \ vl1] ])(vl2)

provided the variables of vl1 are not in w and are not free in post.

These laws correspond to the laws of [10] that introduce substitutions that apply to specification statements. The laws of [11] combine these simpler laws with an appropriate law that introduces procedure calls. Morgan [10] also presents laws that transform assignments into substitutions. Since assignments can be written as specification statements, we do not present the corresponding laws here. Some laws of Morgan's calculus that are used in Section 5 are presented in Appendix B.

 

5 The Largest Rectangle Under a Histogram

In [11] Morgan develops a program that calculates the area of the largest rectangle under a histogram using a recursive procedure. Since Morgan does not present laws to introduce procedures, variants, and recursive procedure calls, several refinement steps in his development cannot be justified in terms of law applications. In this section we develop the program Morgan presents in [11] in a completely formal way using the refinement laws we proposed in the previous section.

In this example, a histogram is represented by a sequence hs of non-negative integers. This sequence gives the height of each of the rectangles of the histogram in the order they appear; their width is always 1. For an example, refer to Figure 2, which is extracted from [11]. The sequence hs is indexed from 0; so, in Figure 2, hs[0] = 2 stands for the height of the first rectangle, hs[1] = 1 for the height of the second one, and so on. The length of the histogram is given by a constant N, which in the case of the histogram in Figure 2 takes the value 7. It is assumed that hs[-1] = hs[N] = -1. The largest rectangle under the histogram shown in Figure 2 is marked in Figure 3, which is also extracted from [11].

 

v5n1a2f194.gif (2267 bytes)

Figure 2: Example Histogram

 

 

Figure 3: Largest Rectangle

 

The subsegment of hs that starts at position i and finishes at position j - 1 is denoted by hs[i ® j]. If the height of the shortest rectangle in hs[i ® j] is hs[min], then the largest rectangle under hs[i ® j] is the largest of:

1. the largest under hs[i ® min];

2. the largest under hs[min ® j];

3. the rectangle with base j - i and height hs[min].

Based on this observation, we can define the function lr that determines the largest rectangle under hs[i ® j] as follows.

lr (i , j) v5n1a2f183.gif (839 bytes)
lr(i,min) \ lr(min + 1, j) \(j - i) v5n1a2f162.gif (838 bytes)hs [min]

provided i v5n1a2f161.gif (839 bytes)min < j and hs[min] = \hs[i ® j]. By convention, we assume that lr(i , j) = 0 when i = j.

The program we want to develop can be specified as a :[true, a = lr(0,N)]. To implement this program, we use a (recursive) procedure Hist which takes as argument the starting point i of a subsegment of hs and assigns to b the area of the largest rectangle under hs[i+1 ® j]. The index j, another output of Hist, indicates the first rectangle (after that indicated by i) whose height is less than or equal to h[i]. In order to introduce this procedure, we apply Law 2 to the above specification.

c "by Law 2"

|[proc Hist v5n1a2f183.gif (839 bytes)(val i; res b , j l) variant V is N - i l

wpe1.jpg (5407 bytes)

a :[true, a = l |r(0,N)]

v5n1a2f197.gif (844 bytes)

]|

For clarity, we have stacked the conjuncts of the postcondition of the specification statement in the body of Hist (and do the same with many other predicates that follow). Recursive calls of Hist will take higher and higher values of i as arguments and so the variant of Hist is N - i. The symbol v5n1a2f197.gif (844 bytes) on the right margin indicates the program that is refined subsequently: we refine the main program, instead of the procedure body, first.

We can use Hist to calculate lr(0,N) by passing -1 as argument: in this case, as hs[-1] = -1 and all the elements of hs[0 ® N] are non-negative, Hist returns N in j, because hs[N] = -1, and lr(0, N) in b. In order to supply the third parameter of Hist, we introduce in the main program of the above procedure block a local variable j1. This can be accomplished using a refinement law of Morgan's calculus which we reproduce in Appendix B and name Law 9. In this Appendix we enumerate all laws of Morgan's calculus we use here: Laws 9 - 13.

v5n1a2f177.gif (851 bytes)"by Law 9"

|[var j1 l a, j1:[true, a = lr(0, N)] v5n1a2f197.gif (844 bytes)

]|

Since N is the number of rectangles of the histogram, we know that -1 < N. Moreover, if -1 < j1 and hs[j1] v5n1a2f161.gif (839 bytes) hs[-1], then j1 = N. Consequently, we can rewrite the precondition and strengthen the postcondition of the above specification statement to obtain the specification statement below.

"by Laws 10 and 11"

a, j1 :   v5n1a2f197.gif (844 bytes)

With applications of Law 6 and Law 7, we can introduce parametrised statements as follows.

v5n1a2f177.gif (851 bytes) "by Law 6"

(val i l

)(-1)

n "by Law 7"

(res b, j l

)(a, j1)

Using the definition of multiple parametrisation mechanisms we can tranform the two nested applications of parametrised statements into a single application of a parametrised statement with declaration val i; res b, j. With an application of Law 4, we can replace this parametrised statement with a call to Hist. The proof-obligation generated is trivial because, in this instance, {n = e} p3 is the same program as p1. In summary, the main program of the procedure block can be refined to the program |[var j1 l Hist(-1,a, j1 )]|. At this point, we turn to the refinement of the body of Hist.

Using a routine sequence of law applications, (i) can be refined to the following iteration.

v5n1a2f212.gif (954 bytes)

do hs[j]>hs [i] v5n1a2f160.gif (846 bytes)

od

Each step of this iteration increases the value of j as far as possible preserving the properties i < j v5n1a2f161.gif (839 bytes)N, hs[i] v5n1a2f161.gif (839 bytes) l hs[i + 1® j] and b = lr(i + 1 , j). Upon termination, this iteration also establishes hs[i] v5n1a2f161.gif (839 bytes) hs[j] and so achieves the postcondition of (i) as required. The variable in (ii) refers to the initial value of j.

The program (ii) can be implemented by a recursive call to Hist. We introduce variables c and k to pass as parameters. We want to calculate the area of largest rectangle under hs[i + 1® k], for a k greater than j. The precondition of (ii) records that b has the area of the largest rectangle under hs[i + 1 ® j] and that hs[j] v5n1a2f161.gif (839 bytes) phs[i + 1® j]. So we need to calculate the area of the largest rectangle under hs[j + 1 ® k] for a k such that hs[j] v5n1a2f161.gif (839 bytes) phs[j + 1 ® k]. The greater the value of k satisfying this property, the better in terms of efficiency: longer segments of the histogram will be considered at each iteration. So, we want the greatest, characterised by hs[k] v5n1a2f161.gif (839 bytes) hs[j]. We use c to record the area of the largest rectangle under hs[j + 1 ® k]. To characterise c and k in this way, we split (ii) into a sequential composition.

"by Laws 9,11 and 12"

|[var c,k l

v5n1a2f219.gif (3525 bytes)

v5n1a2f220.gif (3493 bytes)

]|

The precondition of (iv) records the fact that the rectangle whose height is hs[j] is the shortest in the segment hs[i + 1 ® k] (hs[j] v5n1a2f161.gif (839 bytes)a hs[i + 1 ® j] and also hs[j] v5n1a2f161.gif (839 bytes) a hs[j + 1 ® k]) and that b and c are the area of the largest rectangle under the segments hs[i + 1® j] and hs[j + 1 ® k], respectively. In this situation, we can assign k to j and assign the area of the largest rectangle under hs[i + 1 ® k] to b as follows.

This can be formally justified by an application of Law 13.

In the refinement of (iii), we observe that the first five conjuncts of its postcondition are also conjuncts of its precondition. Moreover, if V = N - i and i < j v5n1a2f161.gif (839 bytes)N, then 0 v5n1a2f161.gif (839 bytes)N - j < V. Furthermore, if hs[j] > hs[i], then, because hs[N] = -1, we can conclude that j v5n1a2f223.gif (843 bytes)N and so j < N. As a consequence of all this, we can refine (iv) by simplifying its postcondition and weakening its precondition as follows.

(iv)

_ "by Laws 11 and 10"

If we apply Law 6 and Law 7 to this specification statement and use and the definition of multiple parametrisation mechanisms, much in the same way as we did in the refinement of the main program, we obtain the parametrised statement application below.

(val i; resb, j l

)(j,c,k)

By moving the conjunct 0 v5n1a2f161.gif (839 bytes) N - i < V in the precondition of the specification statement above to an assumption, we obtain a program in a form that is suitable to the application of Law 5. Using this law, we can replace the resulting parametrised statement by a recursive call to Hist. The proof-obligation generated is exactly the result we have just obtained in refining the body of Fact. Therefore, we do not need to provide any additional justification to discharge this proof-obligation.

In general, if we apply Law 5 in this way, with  p3 as the program in the original specification of the procedure, the discharge of the proof-obligation generated is trivial. This strategy of refinement produces developments that follow Morgan's approach to recursion. The collected code for the program we have just derived is presented in Figure 4.

 

v5n1a2f216.gif (2741 bytes)

Figure 4: Collected code and so achieves the postcondition of (i) as required. The variable in (ii) refers to the initial value of j.

 

6 Conclusions

Morgan's calculus has been highly regarded as a pioneering work which presents a novel and practical approach to formal program development based on calculation and with an elegant style of presenting developments. Nonetheless, it has been shown that Morgan's formalisation of procedures and parameters is inconsistent. This problem has been reported in [4], but a solution to it was left open. Back's approach to procedures and parameters does not present this problem, but is not based on refinement laws.

In recognition of the advantages of program calculation, we have proposed and proved refinement laws that correspond to those of Morgan's calculus. Our laws, however, are expressed using Back's formalism. Furthermore, we have proposed additional refinement laws that formalise Morgan's approach to procedures development, including his use of variants and variant declarations. In this way, we provide a solution to the problem documented in [4].

Our refinement laws support the technique of development proposed by Morgan in [11] and are enough to formalise all the examples he presents there. We expect them to be enough for most examples found in practice. The case study we have considered is not large, but is not trivial. The algorithm we have derived is rather intricate (and unusual in the sense that it presents a recursive call in the body of an iteration) and we hope that this example shows the usefulness of our laws.

The development of procedures in the refinement calculus is also considered in [6]. This work concentrates on the methodological aspects (rather than on the formalisation) of the development of procedures. In [6], the suitability of the refinement laws presented in [11] is discussed and an alternative strategy of program refinement, where (non-recursive) procedures are introduced in the final phase of development, is suggested.

It is our intention to develop a tool to support the application of this refinement calculus and, in particular, of the laws presented here. As the use of a refinement calculus tipically involves the manipulation of long programs and proof-obligations, the importance of using a refinement tool is a common sense. The tool will make possible the development of more and longer case studies. This will probably lead to the proposal of new refinement laws that specialise or combine the laws presented here and facilitate the application of the calculus in particular cases.

 

Acknowledgements

The authors are indebted to Paul Gardiner and Bernard Sufrin who have helped us to define the semantics of variant blocks properly. The work of Ana Cavalcanti and Augusto Sampaio are financially supported by CNPq, Brazil, grants 300.765/97-5 and 521.039/95-9, respectively.

 

A Law Derivations

The law derivation that we have presented in Section 4.2 and the law derivations that we present below rely on the weakest precondition semantics of specification statements, assignments, sequential compositions, and variables blocks. For completeness, we present the relevant definitions here.

wp.w : [pre, post]. y = pre v5n1a2f190.gif (848 bytes)(v5n1a2f191.gif (847 bytes)w · post Þ y )

wp.vl := el. y = y [vl \ el]

wp. (p1; p2) . y = wp. p1 . (wp. p2 . y)

wp. | [ var vl ·  p ]| . y (v5n1a2f191.gif (847 bytes)vl · wp . p . y ) provided the variables of vl are not free in y

These definitions are standard and can also be found in [12, 2], for instance.

Derivation of Law 4

v5n1a2f230.gif (1370 bytes)

v5n1a2f231.gif (1275 bytes) [by definition]

v5n1a2f232.gif (1435 bytes) [by n not free in e and v5n1a2f233.gif (863 bytes)]

v5n1a2f234.gif (1378 bytes) [by the proviso]

v5n1a2f235.gif (1356 bytes) [by pn is not free in v5n1a2f236.gif (863 bytes)]

v5n1a2f237.gif (1165 bytes) [by a property of substitution]

v5n1a2f238.gif (1209 bytes) [by pn is not free in v5n1a2f236.gif (863 bytes)]

= v5n1a2f239.gif (1301 bytes) [by definition]

When deriving Law 5, we rely on Lemma 2, which we present below. In [7, p.73], we can find a more restricted version of this lemma, where just programs (parametrised statements with empty formal parameter declarations) are considered. Since [7] outlines a proof for this special case, for the sake of brevity, we consider just parametrised statements with ordinary (non-empty) formal parameter declarations in the proof of Lemma 2. This proof relies on the following additional lemma.

Lemma 1 Let a family v5n1a2f271.gif (866 bytes)of programs be such that, for any , for a non-empty formal parameter declaration fpd, and monotonic c. Then , for all i.

Proof By induction:

(Case i = 0)

v5n1a2f274.gif (869 bytes)

[by assumption]

[by a property of numbers]

v5n1a2f277.gif (1015 bytes) [by abort is the least refined program]

[by (fpdabort) is the least refined parametrised statement]

(Case i>0)

v5n1a2f280.gif (863 bytes)

[by assumption]

[by induction hypothesis]

v5n1a2f283.gif (1087 bytes)[by a property of least upper bounds]

v5n1a2f284.gif (1102 bytes)[by a property of contexts]

v5n1a2f285.gif (1003 bytes)[by a property of fixed points]

Lemma 2 If, for an integer constant n, an integer expression e, a formal parameter declaration fpd, and a program p, we have that {n = e} p _  c(fpd l {0 v5n1a2f161.gif (839 bytes) e < n} p), then we can deduce that (fpd  p) _ µ (fpd  c), provided c is a monotonic context, and n is not free in p and c.

Proof The assumption can be written as , as we show below.

wp. { 0 v5n1a2f161.gif (839 bytes) e < n } p . y

= wp . { V { j | j < n · j = e } } p . y [by predicate calculus]

= V { j | j < n · j = e } v5n1a2f190.gif (848 bytes) wp. p. y [by definition of wp]

= V { j | j < n · j = e v5n1a2f190.gif (848 bytes) wp. p. y [by predicate calculus]

= V { j | j < n · wp. p. { j =e } p. y [by definition of wp]

wp.  { j | j < n · { j =e }p } . y  [by a property of q]

So, by Lemma 1, , for all n. Consequently, as e prove below, .

wp. p. y

= $ n · = e  v5n1a2f190.gif (848 bytes) wp. p. y [by n is not free in wp.p.y]

= $ n · = wp.  { n =e } p. y [by definition of wp]

Þ $ n · = wp. c ( µ (fpd · c )) y [by the conclusion above]

= wp. c ( µ (fpd ·  c )) y [by n is not free in wp. c ( µ (fpd · c )) y]

Using this result, we can get to the required conclusion as follows.

v5n1a2f303.gif (952 bytes)

[by the result above]

v5n1a2f305.gif (1080 bytes) [by a property of contexts]

v5n1a2f306.gif (976 bytes)[by a property of fixed points]

The derivation of Law 5 also uses the lemma that follows.

Lemma 3 For any program context c [pn], µ c c [pn]] _ µ c [pn] .

Proof µ c [pn] is a fixed point of c c [pn]

c c [pn]] ( µ c [pn])

= c [pn] ( µ c [pn]) [by a property of substitution]

= µ c [pn]  [by a property of fixed points]

Therefore, µ c c [pn]] _ µ c [pn] , as required.

Derivation of Law 5

v5n1a2f314.gif (1453 bytes)

v5n1a2f315.gif (1360 bytes)[by definition]

[by Lemma 2 and the proviso]

[by n is not free in [pn]]

[by Lemma 3]

v5n1a2f319.gif (1200 bytes)[by definition]

 

Derivation of Law 7

v5n1a2f320.gif (7343 bytes)

 

Derivation of Law 8

v5n1a2f321.gif (9309 bytes)

 

B Some Laws of Morgan's Calculus

In this appendix we list the refinement laws of Morgan's calculus that are used in Section 5. In some of them we mention a list of varibles v5n1a2f329.gif (869 bytes); this denotes the list obtained by 0-subscripting the variables of list w.

Law 9 Introduce local variable.

provided the variables of vl are not in w and are not free in pre and post.

Law 10 Weaken precondition.

provided pre1 v5n1a2f332.gif (967 bytes)

Law 11 Strengthen postcondition.

provided v5n1a2f334.gif (1110 bytes)

Law 12 Sequencial composition.

provided

l mid does not have free occurrences of 0-subscripted variables;

l the variables of v5n1a2f336.gif (878 bytes) are not free in post.

where mid is a metavariable ranging over predicates.

Law 13 Introduce assignment.

provided v5n1a2f338.gif (1129 bytes)

 

References

[1] R. J. R. Back. Procedural Abstraction in the Refinement Calculus. Technical report, Department of Computer Science, Abo - Finland, 1987. Ser. A No. 55.         [ Links ]

[2] R. J. R. Back. A Calculus of Refinements for Program Derivations. Acta Informatica, 25:593 - 624, 1988.         [ Links ]

[3] R. J. R. Back and J. Wright. Duality in Specification Languages: A Lattice-theoretical Approach. Acta Informatica, 27(7):583 - 625, 1990.         [ Links ]

[4] A. L. C. Cavalcanti, A. Sampaio, and J. C. P. Woodcock. An Inconsistency in Procedures, Parameters, and Substitution in the Refinement Calculus. Accepted for publication in Science of Computer Programming. To appear. A preprint is available at http://www.di.ufpe.br/~alcc/publications.htm.

[5] E. W. Dijkstra. A Discipline of Programming. Prentice-Hall, 1976.         [ Links ]

[6] L. Groves. Procedures in the Refinement Calculus: A New Approach? In H. Jifeng, editor, 7th Refinement Workshop, Bath - UK, July 1996.         [ Links ]

[7] S. King and C. Morgan. Exits in the Refinement Calculus. Formal Aspects of Computing, 7(1):54 - 76, 1995.         [ Links ]

[8] C. C. Morgan. Procedures, parameters, and abstraction: Separate concerns. Science of Computer Programming, 11:17 - 27, 1988.         [ Links ]

[9] C. C. Morgan. The Specification Statement. ACM Transactions on Programming Languages and Systems, 10(3):403 - 419, 1988.         [ Links ]

[10] C. C. Morgan. Programming from Specifications. Prentice-Hall, 1990.         [ Links ]

[11] C. C. Morgan. Programming from Specifications. Prentice-Hall, 2nd edition, 1994.         [ Links ]

[12] C. C. Morgan, K. Robinson, and P. H. B. Gardiner. On the Refinement Calculus. Technical Monograph TM-PRG-70, Oxford University Computing Laboratory, Oxford - UK, October 1988.         [ Links ]

[13] J. M. Morris. A Theoretical Basis for Stepwise Refinement and the Programming Calculus. Science of Computer Programming, 9(3):287 - 306, 1987.         [ Links ]

[14] J. M. Morris. Invariance Theorems for Recursive Procedures. Technical report, Department of Computer Science, University of Glasgow, 1988.         [ Links ]

[15] J. M. Morris. Laws of Data Refinement. Acta Informática, 26:287 - 308, 1989.         [ Links ]

[16] A. Tarski. A Lattice Theoretical Fixed Point Theorem and its Applications. Pacific Journal of Mathematics, 5, 1955.

Creative Commons License All the contents of this journal, except where otherwise noted, is licensed under a Creative Commons Attribution License