Open pjljvandelaar opened 4 years ago
@pjljvandelaar Pragma Test_Case
allows you to specify properties that should hold on entry to the test (the optional Requires
part) and properties that should hold on exit to the test (the optional Ensures
part).
Currently, the proof tool called GNATprove which analyzes SPARK code ignores Test_Case
pragmas/aspects. But this is really a tool issue more than a language issue. You should contact AdaCore if you want GNATprove to support Test_Case
.
Regarding more complex properties, I think we routinely specify and prove such properties with ghost code in SPARK. The way to do that is to state a lemma, which is a procedure with a precondition stating the conditions for the lemma to apply, and a postcondition stating the property you want to prove. Then, you can use GNATprove to prove that lemma, using ghost code for more complex lemmas. See this section of the SPARK User's Guide about that process: http://docs.adacore.com/live/wave/spark2014/html/spark2014_ug/gnatprove_by_example/manual_proof.html#manual-proof-examples
Some of the other requirements you had looked like tool enhancements, which you should report to AdaCore. Can you clarify in which case you would need a language feature instead, given my answers above?
@pjljvandelaar do you have an opinion on Yannick's answers?
I consider Yannick's answer quite tool related due to statement such as
I would like properties to be really tool independent: to be really a language feature!
I don't want to add Pre and Post conditions to use just
SPARK.
I want to document my code with Properties, thus at least examples, standard Test cases, Pre and Post conditions, and Invariants.
And as a consequence be able to
If I specify the property x == reverse(reverse(x))
,
I want that any tool can use this information to help me ensure the quality of the software.
x
and check that the property indeed holds for all those values.Similar if I specify "Pre => f(x)"
To make my request even clearer, I describe my envisioned specification of the function Add
:
function Add(X,Y : Integer) return Integer
with
Example 'Simple' => Add(3,4) = 7
Example 'Negative Value' => Add(3,-4) = -1
Post => Add'Result = X + Y
Invariant 'Identity Element' => for all I : Integer => Add(I,0) = I
Invariant 'Inverse' => for all I : Integer => Add(I,-I) = 0
Invariant 'Commutative'=> for all A,B : Integer => Add(A,B) = Add(B,A)
Invariant 'Associative'=> for all A,B,C : Integer => Add(Add(A,B),C) = Add(A, Add(B,C))
I leave it up to the tooling to decide what to do with this additional information!
Maybe a symbolic code rewriter can change Add(Add(X,Y),-X)
to just Y
based on this information!
Hi @pjljvandelaar, what you're describing can readily be described by lemmas today:
procedure Add_Identity_Element (I : Integer)
with Ghost,
Post => Add (I, 0) = I and then Add (0, I) = I;
procedure Add_Commutative (A, B : Integer)
with Ghost,
Post => Add (A, B) = Add (B, A);
procedure Add_Associative (A, B, C : Integer)
with Ghost,
Post => Add (Add (A, B), C) = Add (A, Add (B, C));
This is the extrinsic expression of a property instead of the intrinsic expression used in a postcondition. Here you're suggesting to use an aspect for this extrinsic expression of the property, that does not seem to bring such advantages to me compared to the use of lemmas as above (which reduce the use of quantifiers).
Hi @yannickmoy,
First of all, I was not aware of the extrinsic expression of a property for GNATprove. I like it, and I will definitely going use it.
However, this solution distributes information about the Add
function.
For some tools this is irrelevant. E.g. a test case generator will work perfectly.
Yet for others, this has undesired consequences: a documentation generator, like GNATdoc, will be unable to provide me and my users with all the properties of the Add
function.
This is the extrinsic expression of a property instead of the intrinsic expression used in a postcondition. Here you're suggesting to use an aspect for this extrinsic expression of the property, that does not seem to bring such advantages to me compared to the use of lemmas as above (which reduce the use of quantifiers).
And works even when the function takes as parameters complex structures on which quantification is not possible.
-- Claire Dross Senior Software Engineer, AdaCore
@pjljvandelaar For documentation, you need in any case to do something special to recover this information in a readable format. Just showing the aspect is not the best result. So here I would recommend using some conventions to help group the information. Exactly like you would do in a development in the Coq (or others) proof assistant, where you state lemmas after some definitions.
We all seem to agree that users of Ada would like to specify pre and post-conditions and invariants of a function. We seem to disagree what is the best solution. One alternative is to add these features with minimal changes to the existing language. Another alternative is to add these features that leads to the best language in the long term.
The current situation (i.e. the use of lemmas) does imho not lead to the best language in the long term since
@clairedross about the quantifiers: Comparable alternative is of course
function Add(X,Y : Integer) return Integer
with
Example 'Simple' => Add(3,4) = 7
Example 'Negative Value' => Add(3,-4) = -1
Post => Add'Result = X + Y
Invariant 'Identity Element' => VALUE I : Integer BEGIN Add(I,0) = I END
Invariant 'Inverse' => VALUE I : Integer BEGIN Add(I,-I) = 0 END
Invariant 'Commutative'=> VALUE A,B : Integer BEGIN Add(A,B) = Add(B,A) END
Invariant 'Associative'=> VALUE A,B,C : Integer BEGIN Add(Add(A,B),C) = Add(A, Add(B,C)) END
And to make Ada even less verbose: one could even define rules about the introduction of values in Invariants, since the types of these values are of course already specified in the declaration of the procedure/function.
Or even:
function Add(X,Y : Integer) return Integer
with
Example Simple => Add(3,4) = 7
Example Negative_Value => Add(3,-4) = -1
Post => Add'Result = X + Y
Invariant Identity_Element(I : Integer) => Add(I,0) = I
Invariant Inverse(I : Integer) => Add(I,-I) = 0
Invariant Commutative(A,B : Integer) => Add(A,B) = Add(B,A)
Invariant Associative(A,B,C : Integer) => Add(Add(A,B),C) = Add(A, Add(B,C))
All these alternatives can of course be mechanically transformed in the other representations.
Hi @pjljvandelaar, I'm personally not convinced that this is more readable, rather the contrary. Also, this is not how it is done in languages like Coq or Isabelle, where people are used to describing such groups of theorems and lemmas separately from the definition of the function.
Regarding the absence of body, in fact we do provide a body for such lemmas, which may be an empty body (begin null; end;
) if you don't need to prove or don't want to prove the property using the auto-active proof method (where you guide automatic provers with ghost code, see https://blog.adacore.com/research-corner-auto-active-verification-in-spark)
On Fri, Jan 29, 2021 at 8:55 AM pjljvandelaar notifications@github.com wrote:
Or even:
function Add(X,Y : Integer) return Integer with Example Simple => Add(3,4) = 7 Example Negative_Value => Add(3,-4) = -1 Post => Add'Result = X + Y Invariant Identity_Element(I : Integer) => Add(I,0) = I Invariant Inverse(I : Integer) => Add(I,-I) Invariant Commutative(A,B : Integer) => Add(A,B) = Add(B,A) Invariant Associative(A,B,C : Integer) => Add(Add(A,B),C) = Add(A, Add(B,C))
All these alternatives can of course be mechanically transformed in the other representations.
I am not sure why you call these lemmas 'invariants'. I think the usual meaning of an invariant is a property which is preserved by the subprogram, you might have a look at the stable properties of Ada202X. With respect to readability, it can seem like a good idea on such a small and simple example, but I am not sure it would work for bigger lemmas. Also, a lemma can very well speak about several subprograms, like distributivity for example: Mul (Add (A, B), C) = Add (Mul (A, C), Mul (B, C)). Would you put it as an invariant of Mul, Add, both? -- Claire Dross Senior Software Engineer, AdaCore
@clairedross Looking back in this thread, I originally called them properties
. So indeed Property
or Lemma
might be a better name than invariant
.
@clairedross Some properties are indeed not uniquely coupled to a single function/procedure. Still I would like that a property is linked to the relevant functions, so decode(encode(x)) == x
should link to decode
and encode
. Yet, abs(x) == max(x,-x)
should not link to max
!
During a meeting with AdaCore, Nexperia and ESI, we were asked by AdaCore to submit this issue to trigger a discussion to determine whether a RFC is justified.
Support for
Properties
in Ada and SPARKCurrent Situation
Replace_Prefix
function are specified for GNATTest and can't be proven by SPARK to hold.Add(3,4) = 7
Add(3,-4) = -1
andReplace_Prefix("bla", "", "new") = "newbla"
for all I : Integer => Add(I,0) = I
andfor all p,n,r : String => Replace_Prefix(p&r, p,n) = n&r
Desired situation