Services on Demand
Journal
Article
Indicators
- Cited by SciELO
- Access statistics
Related links
- Cited by Google
- Similars in SciELO
- Similars in Google
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 | Jim Woodcock |
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 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 Zero (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 (fpd l p_{1}) l p_{2}]|
If pn is not parametrised, fpd is empty and so (fpd l p_{1}) is simply p_{1}, 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 (fpd l p_{1}) variant v is e l p_{2}]|
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 (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 = 0x := 1
[] n > 0
(val n l {0 n < N} x:[true,x = n!]) (n - 1);
x := x 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].
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 vl_{1} l p)(vl_{2}) = |[var l l p[vl_{1} \ l] ; vl_{2}:= l]|
provided the variables of l are fresh: not free in p and not in vl_{1} or vl_{2} .
For call-by-value-result, we have the definition that follows.
Call-by-value-result
(val-res vl_{1} l p)(vl_{2}) = |[var l l l := vl_{2} ; p[vl_{1 }\ l] ; vl_{2}:= l]|
provided the variables of l are not free in p, and are not in vl_{1} or vl_{2}.
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 vl_{1}; fpd l p)(el_{1}, el_{2} ) = (par vl_{1} l (fpd l p)(el_{2}) (el_{1})
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 p_{1} and p_{2} , p_{1} p_{2} if and only if wp. p_{1}. ywp.p_{2} 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, p_{1} p_{2} holds exactly when p_{2} terminates whenever p_{1} does, and produces only results that are acceptable to p_{1}. Therefore, if p_{1} p_{2}, then p_{2} is always satisfactory as a substitute for p_{1}. 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 p_{1}) and (fpd l p_{2}), with the same formal parameter declaration, (fpd l p_{1} ) (fpd l p_{2} ) if and only if, for all lists (al) of actual parameters, (fpd l p_{1} ) (al) (fpd l p_{2} ) (al).
Surprisingly, maybe p_{1} p_{2}, is not equivalent to (fpd l p_{1} ) (fpd l p_{2} ). As established in [1], parametrised statements are monotonic with respect to , so that p_{1} p_{2} implies (fpd l p_{1} ) (fpd l p_{2}). Nevertheless, there are cases in which the relationship (fpd l p_{1} ) (fpd l p_{2}) holds, but p_{1 } p_{2}) 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 (fpd l p_{1} )(pn) l p_{2} (pn)]| = p_{2} (m (fpd l p_{1} ) )
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 p_{1} ) is m pn l (fpd l p_{1} )(pn), but for the sake of brevity, we will adopt the more concise notation m (fpd l p_{1} ).
The existence of m (fpd l p_{1} ) has to be justified. According to Knaster-Tarski [16], we can establish that m (fpd l p_{1} ) 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 p_{1} ) 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 p_{i} )} can be defined as (u{i l (fpd l p_{i})}) (al) = i{i l (fpd l p_{i})(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 p_{1}) is a monotonic function, as required.
In the case where (fpd l p_{1} ) does not contain free occurrences of pn or, in other words, pn is not a recursive procedure, m (fpd l p_{1}) is (fpd l p_{1} ). Therefore, |[proc pn (fpd l p_{1} ) l p_{2 }(pn)]| is the program obtained by substituting (fpd l p_{1} ) for the calls to pn in p_{1}, p_{2}, (fpd l p_{1} ), 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 (fpd l p_{1} )(pn) variant n is e l p_{2} (pn)]| = p_{2} (m (fpd l |[con n : Z l p_{1}]|))
The block |[con n : Z l p_{1} ]| declares the logical constant n which is local to p_{1}.
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.
p_{2} = |[proc pn(fpd l p_{1}) l p_{2}]|
provided pn is not free in p_{2}.
This refinement law allows any program p_{2} to be transformed into a procedure block that declares a procedure pn and whose main program is p_{2}. Calls to pn can be introduced subsequently in p_{2} 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.
p_{2} = |[proc pn (fpd l {n = e} p_{1}) variant n is e l p_{2}]|
provided
l pn and n are not free in e or in p_{2};
l n is different from pn.
Recursion can be introduced by refining (fpd l {n = e} p_{1}) and subsequently replacing occurrences of (fpd l {0 n < e} p_{1}) 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 (fpd l p_{1}) l p_{2}) l [(fpd l p_{1} )] ]| = |[proc pn (fpd l p_{1} ) l p_{2} [pn] ]|
provided pn is not recursive.
We identify an occurrence of a program (parametrised statement) p_{1} in a context c by writing c [p_{1}]. Subsequent references to c [p_{2}] denote the context obtained by substituting p_{2 }for that particular occurrence of p_{1} in c. The program c [p_{2}] should not be confused with c (p_{2}). As already explained, the latter, is the result of substituting p_{2 }for the free occurrences of the procedure name, as opposed to a particular occurrence of p_{1}, 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 (fpd l p_{1} ) variant n is e l p_{2} [(fpd l p_{3} )]]|
|[proc pn (fpd l p_{1} ) variant n is e l p_{2} [pn] ]|
provided
l {n = e} p_{3} p_{1};
l pn is not recursive;
l n is not free in e or p_{3}.
The first proviso of this law, {n = e } p_{3} p_{1}, could be replaced by the simpler condition p_{3} p_{1}, 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, p_{1} is either a program of the form {n = e } p or is obtained by refining a program like this. In this case, p_{1} can have free occurrences of n and we would rather compare {n = e } p_{3} and p_{1}. The idea is that {n = e } p_{3} 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 } p_{3 }__{ } p_{1} 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 (fpd l p_{1} [(fpd l {0 e < n} l p_{3})])
variant n is e l p_{2} ]|
_
|[proc pn (fpd (fpd l p_{1} [pn]) l p_{2}]|
provided
l {n = e } p_{3 _ }p_{1} [(fpd l {0 e < n} p_{3} )];
l n is not free in p_{3} or p_{1 }[pn].
As in the case of Law 2, {n = e } p_{3} is supposed to be the initial specification of pn. The first proviso requires that p_{1} [(fpd l {0 e < n} p_{3})] can be obtained by refining this specification. The introduction of a recursive call is justified by the fact that p_{3} 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] (w l post[vl \ el] y) [by definition of wp]
= pre[vl \ l][l \ el] (w l post[vl \ l][l \ el] y) [by the variables of l are fresh]
= pre[vl \ l] (w l post[vl \ l] y))[l \ el] [by the variables of l are fresh and the proviso]
= l l (pre[vl \ l] (w l post[vl \ l] y))[l \ el] [by predicate calculus]
= 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, vl_{2},:[pre,post]=(res vl_{1} l w, vl_{1 }: [pre, post[vl_{2} \ vl_{1}] ])(vl_{2})
provided the variables of vl_{1 }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, vl_{2} : [pre[vl_{1 }\ vl_{2}], post]= (val-res vl_{1} l w, vl_{1 }: [pre, post[vl_{2} \ vl_{1}] ])(vl_{2})
provided the variables of vl_{1 }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].
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)
lr(i,min) \ lr(min + 1, j) \(j - i) hs [min]
provided i 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 (val i; res b , j l) variant V is N - i l
a :[true, a = l |r(0,N)]
]|
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 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 j_{1}. 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.
"by Law 9"
|[var j_{1} l a, j_{1}:[true, a = lr(0, N)]
]|
Since N is the number of rectangles of the histogram, we know that -1 < N. Moreover, if -1 < j_{1} and hs[j_{1}] hs[-1], then j_{1} = 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, j_{1} :
With applications of Law 6 and Law 7, we can introduce parametrised statements as follows.
"by Law 6"
(val i l
)(-1)
^{n} "by Law 7"
(res b, j l
)(a, j_{1})
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} p_{3} is the same program as p_{1}. In summary, the main program of the procedure block can be refined to the program |[var j_{1} l Hist(-1,a, j_{1} )]|. 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.
do hs[j]>hs [i]
od
Each step of this iteration increases the value of j as far as possible preserving the properties i < j N, hs[i] l hs[i + 1® j] and b = lr(i + 1 , j). Upon termination, this iteration also establishes hs[i] 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] 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] 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] 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
]|
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] ^{a} hs[i + 1 ® j] and also hs[j] ^{ 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 N, then 0 N - j < V. Furthermore, if hs[j] > hs[i], then, because hs[N] = -1, we can conclude that j 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 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 p_{3} 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.
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 (w · post Þ y )
wp.vl := el. y = y [vl \ el]
wp. (p_{1}; p_{2}) . y = wp. p_{1} . (wp. p_{2} . y)
wp. | [ var vl · p ]| . y (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
[by definition]
[by n not free in e and ]
[by the proviso]
[by pn is not free in ]
[by a property of substitution]
[by pn is not free in ]
= [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 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)
[by assumption]
[by a property of numbers]
[by abort is the least refined program]
[by (fpdabort) is the least refined parametrised statement]
(Case i>0)
[by assumption]
[by induction hypothesis]
[by a property of least upper bounds]
[by a property of contexts]
[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 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 e < n } p . y
= wp . { V { j | j < n · j = e } } p . y [by predicate calculus]
= V { j | j < n · j = e } wp. p. y [by definition of wp]
= V { j | j < n · j = e 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 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.
[by the result above]
[by a property of contexts]
[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
[by definition]
[by Lemma 2 and the proviso]
[by n is not free in [pn]]
[by Lemma 3]
[by definition]
Derivation of Law 7
Derivation of Law 8
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 ; 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
Law 11 Strengthen postcondition.
provided
Law 12 Sequencial composition.
provided
l mid does not have free occurrences of 0-subscripted variables;
l the variables of are not free in post.
where mid is a metavariable ranging over predicates.
Law 13 Introduce assignment.
provided
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.