overturetool / language

Overture Language Board issue tracking
2 stars 0 forks source link

Equality and Order Clauses for Type Definitions #39

Closed paulch42 closed 7 years ago

paulch42 commented 8 years ago
Identification of the Originator

Paul Chisholm

Target of the request, defining the affected components of the language definition

VDM-SL order relation

Motivation for the request

All types in VDM-SL admit equality via a primitive (i.e. built-in) equality relation. Numeric types in VDM-SL are the only types that admit an order relation. A general purpose order relation can be defined for most types in VDM-SL, in a similar manner to equality. We need only define the < relation; equality plus < allows the derivation of <=, > and >=.

Order is ubiquitous. After equality, order is the most commonly used relation in computing. When specifying a data type, we very often require an order relation on the type. In VDM-SL we must define a truth valued function that implements the order relation. If we want the familiar complement of operators (<, <=, >, >=) then we must define four functions. Comparisons in a specification then employ the functions, but using function application notation rather than the familiar symbolic infix notation.

The Overture VDM-SL example ISO8601 specifies types for date, time, date/time and duration. The effective use of these types in specifications requires an order relation on each of the four types. Sixteen functions are specified: <, <=, >, >= on each of the four types. In every case the order relation is the ‘obvious’ or ‘natural’ ordering one would expect.

Description of the request

The attached VDM-SL module ‘Order.vdmsl’ contains a model of VDM-SL values and defines an order relation over those values. A notion of ‘comparable’ values is defined, with the order relation being total with respect to comparability. The primitive VDM-SL order relation on numeric values could be extended to the order relation defined in ‘Order.vdmsl’.

Notes:

description of the modification
benefits from the modification
possible side effects

None - existing specifications are semantically unchanged

A test suite for validation of the modification in the executable models (if appropriate).

None

The following is the specification of the order relation as a VDM-SL model.

Order.vdmsl.txt

nickbattle commented 7 years ago

PJ, your first "ordering not being inherited" example was just a bug. That is now fixed I think, so both useTn functions return true (because 1>2 for this type!). However, in fixing this, you bang up against what you should do if both types have ordering clauses. Currently, I'm using "overriding" semantics - so if you define a type with an order and then define a second type (using the first) with a different order, the different order wins when you are talking about the second type.

Presumably we should use an order on a union to override any orders from the members. But if there is no override, then I can't see what we can sensibly do with the orders from the subtypes that may conflict. Is this just a TC error?

Let me know if you want a jar update.

peterwvj commented 7 years ago

I think the "overriding" approach is the most reasonable approach . If we take that approach then I can't think of any particularly complex cases that we need to address.

peterwvj commented 7 years ago

I would like you to upload a new JAR file whenever you have time -- thanks!

nickbattle commented 7 years ago

OK, here's the latest. This includes a fix for Paul, who's also testing.

vdmj-4.0.0-170427.zip

peterwvj commented 7 years ago

I can think of a case involving union types that puzzles me -- see below.

types

  N = nat
  eq t1 = t2 == t1 <> t2;

  T = nat
  eq t1 = t2 == t1 = t2;

functions

  run: () -> bool
  run() ==
  let x : N | T = 1,
      y : N | T = 2
  in
    x = y;

So what is x = y suppose to do? Either the interpreter uses the equality definition of N or T or that of nat. For some reason, it uses the equality definition of N. Thoughts?

λ java -jar vdmj-4.0.0-170427.jar -vdmsl test.vdmsl -i
Parsed 1 module in 0.132 secs. No syntax errors
Type checked 1 module in 0.25 secs. No type errors
Initialized 1 module in 0.137 secs. 
Interpreter started
> p run()
= true
Executed in 0.009 secs. 
nickbattle commented 7 years ago

I'll debug and find out what's happening here.

nickbattle commented 7 years ago

OK, I see what's happening. It's to do with how types are assigned to values. When you say x:N | T, you're saying that that the value is either of type N or T. When the assignment happens, it attempts to find out which type in the union the value actually belongs to, and finds N first. So both x and y are of type N. The equality is then true (since = is defined as <>).

If you introduce type U = N | T, and then declare types of that named union U, it regards both variables to be of type U, which has no equality defined. You therefore get the default behaviour as things stand, which says that the simple numeric equality is false.

I think the problem ought to be raised when U = N | T is type checked, though I'm not sure what it should really say. A warning that the eqs will be ignored? An error because there are eqs? Or should the union try to inherit eqs from the members? How??

peterwvj commented 7 years ago

Thanks for the explanation. I would prefer raising an error because there are multiple "eqs" defined. It's non-sense anyway :) can't think of a situation where this would be useful.

nickbattle commented 7 years ago

I think I agree, and the same for ordering clauses. Though we have to be careful not to prevent people from composing unions from several types (that they did not write), in the case where they do not care about equality or ordering - though equality is very common, so perhaps that's nonsense. But you see my concern? If we make unions of types with eq/ord illegal, then it severely limits what you can do with such types.

You can't even really say that the eq/ord must all be "the same". It's very difficult even to test that all eqs are the same in the union - in general it amounts to comparing arbitrary typed expressions for equivalence.

Perhaps we should be less severe, go for a warning ("eq/ord will be ignored in union members"), and then allow the union type itself to provide eq/ord semantics, defaulting to the standard behaviour. The eq/ord expressions can then use "is_" type tests to decide what to do with the elements.

tomooda commented 7 years ago

Maybe we should automatically compose implicit eq_N|T and ord_N|T (we won't need these nonsense names though) to apply in x = y and x < y where x, y : N|T. It would be something like

lambda x, y : N|T & 
    if is_(x, N) and is_(y, N) then eq_N(x, y) 
    elseif is_(x, T) and is_(y, T) then eq_T(x, y) 
    else x = y -- the default equality

and

lambda x, y : N|T & 
    if is_(x, N) and is_(y, N) then ord_N(x, y) 
    elseif is_(x, T) and is_(y, T) then ord_T(x, y) 
    else error <TYPE_MISMATCH>

What do you think?

tomooda commented 7 years ago

I mean, operator overloading should be resolved at compilation time based on static types, not dynamic types. So, we will need to automatically compose anonymous eq/ord functions to resolve the eq/ord operators for anonymous union types (and maybe for anonymous option types).

nickbattle commented 7 years ago

You might be on to something here, Tomo. I think we're talking about whether the orderings were defining on the types are partial or total. When you add an "ord" clause to a type, you're effectively giving it a total order (because the rule applies to all pairs of that type, and one assumes the order relation would satisfy the other aspects of a total order). But when we combine types that have total orders into a union, you get a partial order or perhaps a more complex total order, depending on what the member types have.

I'm not sure that this helps, but it is a way of looking at what happens when you create a union. But what should the semantics of comparison be in that case?

peterwvj commented 7 years ago

The problem with composing an anonymous function, like Tomo suggests, is that it is the order of the if-elseif clauses that determine which (say) eq function that might get called, right?

nickbattle commented 7 years ago

I think that would be OK PJ. A value in the union can only be of subtype N or T?

peterwvj commented 7 years ago

Sorry for not being clear about this. My point is that it's not clear to the user which "eq" function is being called. Really it could be any of them? I mean both is_(x, N) and is_(y, N) (the if-clause) and is_(x, T) and is_(y, T) (the elseif clause) match.

tomooda commented 7 years ago

You are right, PJ. We need to prioritize component types. The order of appearance in the union definition, e.g. N then T for N | T, might be a natural choice. (coincidentally or not, it's the same order as Nick's implementation :-)

In order to compose a total order, may be we can wildly say like

lambda x, y : N|T & 
    if is_(x, N) and is_(y, N) then ord_N(x, y) 
    elseif is_(x, T) and is_(y, T) then ord_T(x, y) 
    else is_(x, N) -- N's value precedes T's when they belong to different types

I know this is rough and naive, but is one possible stuffing to make it total.

By the way, the default equality can't be true when the two values have no common type. So, the composed eq function would be like

lambda x, y : N|T & 
    if is_(x, N) and is_(y, N) then eq_N(x, y) 
    elseif is_(x, T) and is_(y, T) then eq_T(x, y) 
    else false
peterwvj commented 7 years ago

Assuming that we allow union types that define order and equality (which I think we need to do in order not to limit the usage of this feature too much, ref Nick's comment) then I guess the order of appearance is a natural choice. However, I'm not sure .. else is_(x, N) is the best choice when defining order. Can't we raise a runtime error instead for this particular case?

tomooda commented 7 years ago

I think we have the following choices for the last else clause of ord.

The first one is pessimistic. It makes a partial order. The second one looks natural to me, but violates the law of total order: a < b ⇔ not(b ≤ a). The third one is tricky. It satisfies a < b ⇔ not(b ≤ a), but may break transitivity.

peterwvj commented 7 years ago

I'm a bit worried about the second and third cases because they define orderings that are not intuitive. Like you said, this leads to violation of certain properties that you by intuition would expect to hold. Does it severely limit the usage of this feature to make the order underfined (case 1)? Other views on this?

paulch42 commented 7 years ago

I think the primary use of these clauses would be for composite types so I don't see a great limitation with respect to unions. All the examples I had come across that prompted the RM were composite types.

One thing I am not clear on is the discussion concerning automatically creating these ord functions. Don't we explicitly define the ord functions in the ord clause so if I want to say treat elements of N before elements of T in N|T then I must specify that in my ord clause, it is not implicit (the 'is_' expressions would appear in my ord clause).

nickbattle commented 7 years ago

It's avoiding the problem to some extent, but make progress with this we could insist that if you compose a union of types where at least one of them defines an ord clause, then we insist that you define an order for the union explicitly. That would override the member orderings. That order could then be any of the possibilities we are discussing, but it puts the ball back in the specifier's court and is far more explicit (which is usually a good thing).

paulch42 commented 7 years ago

I have been migrating the ISO8601 module to use eq/ord clauses as it is an example that significantly benefits from the new features. An issue has cropped up in the supporting Seq module. There exists a function

  ascending[@a]: (@a * @a +> bool) +> seq of @a +> bool
  ascending(o)(s) == forall i in set {1,...,len s - 1} & s(i) = s(i+1) or o(s(i), s(i+1));

that determines if a sequence is ascending with respect to an order relation. I tried to redefine in the new context as

  ascending[@a]: seq of @a +> bool
  ascending(s) == forall i in set {1,...,len s - 1} & s(i) <= s(i+1);

Rather than pass around an order relation it uses the relation defined in the ord clause. This results in the error

Error 3139: Left hand of <= is not ordered in 'DEFAULT' (Ord.vdmsl) at line 5:60
Actual: @a
Error 3140: Right hand of <= is not ordered in 'DEFAULT' (Ord.vdmsl) at line 5:60
Actual: @a

I understand this, it is saying @a is an arbitrary type so we don't know if it has an order relation. Unfortunately that means we can't write generic functions, other examples being descending, insert, and sort, without passing round relations explicitly. What we really want to do here is add a precondition to the function that says "type @a has an ord clause defined", but such is not expressible (though it is similar in nature to expressions of the form is_(v,T)). At present the type checker fails because it rejects a<b it it can't guarantee a and b are of a type that has an ord clause. Could this be weakened to a<b is rejected if the type checker can guarantee a and b are of a type that does not have an ord clause. In the case above the second definition of ascending would be valid but fail at runtime if passed values of a type that does not have an ord clause.

paulch42 commented 7 years ago

Re Nick's comment, in general it is good to be more explicit, but I am not so sure in this instance. It seems overbearing to force me to define an order relation on a union type if I have no interest in ordering the elements of that type. I will just end up creating some arbitrary order relation to get round a technicality of the system, such as ord x < y == false.

nickbattle commented 7 years ago

Fair point. Then how about we say that the ordering of a union is undefined (unless explicitly defined) and if you attempt to compare union members without having defined an order, you get a runtime exception? That way, if you're not interested there is no problem; if you are interested but forget to define it, you get a sharp reminder :-)

paulch42 commented 7 years ago

That sounds a reasonable approach. In some way a little related to what I was saying in the comment concerning the ascending function.

tomooda commented 7 years ago

I think a type checker should raise a type error when it failed to find ordering on the static types of the both hands of a comparison operator.

I agree that explicit and conservative way is good.

An explicit definition of ordering function would be complicated; it would require combinations of type discriminations of the member types. For convenience, maybe we can consider something like deriving clause in Haskell, like

peterwvj commented 7 years ago

Nick: Fair point. Then how about we say that the ordering of a union is undefined (unless explicitly defined) and if you attempt to compare union members without having defined an order, you get a runtime exception? That way, if you're not interested there is no problem; if you are interested but forget to define it, you get a sharp reminder :-)

Yes, I would be OK with that.

paulch42 commented 7 years ago

I have migrated the ISO8601 example and associated modules to employ eq and ord clauses. The result is attached. A good test of the implemenation since it features eq and ord clauses on a number of types with three levels of nesting. It compiles and type checks, though I have not as yet run any tests.

Some comments relating to the benefits accrued:

Archive.zip

nickbattle commented 7 years ago

So firstly, regarding unions, it turns out we can't naively do as I was suggesting - silently treating unions as unordered - because there are lots of existing tests in the language test suite that legitimately create unions of ordered and non-ordered types, and then make comparisons of the members (which are carefully arranged to be ordered only, of course). So we can't easily change union ordering semantics without potentially changing the meaning of existing specifications. So for now, I've left the test for union ordering as being true if any of the member types are ordered (or if you define a type for the union with an ord clause). There should be a runtime error if you end up comparing incomparable values, and a proof obligation should cover that.

Regarding the polymorphic function point... yes, this is tricky. For now I've relaxed the ordering assumption on polymorphic types, so that they are assumed to be ordered. That should allow us to do more testing, but the essential problem remains. We could perhaps introduce is_ord(val), meaning a symbol is an ordered type? And again, we will get a runtime error (and need a proof obligation) if the values are not ordered.

I'll do a few more tests with what I have, then I'll attach an updated jar.

nickbattle commented 7 years ago

OK, try the following:

vdmj-4.0.0-170430.zip

tomooda commented 7 years ago

Nick, thank you for the update. In the following spec,

types
    OneTwoThree = <ONE> | <TWO> | <THREE> ord x < y == x <> y and cases mk_(x, y):
        mk_(<ONE>, -) -> true,
        mk_(<TWO>, <THREE>) -> true,
        others -> false
        end;
    OneTwo = <ONE> | <TWO>;

values
    unionOne:OneTwoThree|OneTwo = <ONE>;
    unionTwo:OneTwoThree = <TWO>;
    unionThree:OneTwoThree = <THREE>;

unionOne < unionTwo causes a runtime error, which means vdmj failed to find the common static type <OneTwoThree>.

nickbattle commented 7 years ago

Tomo, Stepping through that with a debugger, what happens is that at runtime, the test expression's left hand side is considered to be of type OneTwo. This is because you've declared that the type is either OneTwoThree or OneTwo, and at runtime the value is arbitrarily matched to one type or the other (since it is a member of both).

I don't know the correct semantics here. But I think this is similar to an example PJ had earlier. If you say that a static type is A | B, the type checker will understand the union, but at runtime any value you assign will be either A or B, and thereafter behave as an A or a B. It can't delay that decision until later and flip its identity to one of the other alternatives. It is different if you declare a new type C = A | B; in that case all value assignments behave like type C (which may have an invariant).

Have you tried similar experiments with VDMTools?

tomooda commented 7 years ago

Nick, that's why I believe an overloaded operators should be resolved at compile type by a type checker. Because a VDM value can be of multiple types, one dynamic type is not enough to choose the right body of the function. Resolved at compilation time, the ord should be equivalent to

functions
    order : OneTwoThree * OneTwoThree -> bool
    order(x, y) == x <> y and cases mk_(x, y):
        mk_(<ONE>, -) -> true,
        mk_(<TWO>, <THREE>) -> true,
        others -> false
        end

and order(unionOne, unionTwo) evaluates true.

nickbattle commented 7 years ago

Well, I see your point Tomo. But unless you think this is a bug, we have to be careful about changing more fundamental aspects of the language semantics. What does VDMTools do in a similar situation?

tomooda commented 7 years ago

The eq/ord clauses are the first "overloading function" feature in the VDM world at the language level. So, Nick's VDMJ preview is the only interpreter that deals with overloaded operators and thus there's no "similar situation".

The reason why I insist resolving at compile time is to limit the impact on the language semantics. By resolving at compile time, we can have an equivalent VDM spec without overloading by translating from a spec with eq/ord clauses. Any occurrences of comparison operators should be replaced with eq_T, ord_T, the built-in operators and their compound expressions. That'll ensure that we are NOT changing the foundation of the language semantics.

nickbattle commented 7 years ago

Well, how about invariant functions? If I change your example to the following:

types
    OneTwoThree = <ONE> | <TWO> | <THREE> inv x == cases x:
        <ONE> -> true,
        others -> false
        end;
    OneTwo = <ONE> | <TWO>;

values
    unionTwo: OneTwoThree | OneTwo = <TWO>;

Both VDMTools and VDMJ are happy to create the unionTwo value, even though it violates the invariant for the OneTwoThree type. I think that's because at runtime, the value is only one type or the other, not both. If more than one type in the union matches a value, then the value is assigned one of the matching types arbitrarily (at least in VDMJ; I don't have the VDMTools source).

You would expect this to be an error, because one of the types in the union will not admit the value?

nickbattle commented 7 years ago

Actually, looking at the VDMJ implementation, values that are converted to a union type are converted to the first type of the union (in an arbitrary order) that accepts the value. If no member type accepts the value, then obviously the union assignment is invalid. So in the case above, the OneTwo type accepts the value.

tomooda commented 7 years ago

Nick, I DO agree that the unionTwo: OneTwoThree | OneTwo = <TWO> is well typed. It is simply because unionTwo is not a value of the OneTwoThree type as is_(unionTwo, OneTwoThree) evaluates false.

However, in the case of resolving the overloaded < operator in unionOne < unionTwo, the first type that accepts the unionOne is not enough. All types of unionOne and also all the types of unionTwo matters. The static type, namely OneTwoThree|OneTwo is the important clue to get all candidate types that carry with ord/eq clauses.

nickbattle commented 7 years ago

I've asked PGL to comment on this.

I'm worried that, in general, you're suggesting that unions form a sort of "behavioural union" whose members act according to the combined behaviour of their member types. To me, a union just describes a collection of values, each of which come from one of a set of types, and whose individual behaviour is determined by that one type. The runtime assignment an untyped value to a union chooses the type of that value (albeit arbitrarily, if several match, which I agree may be an issue).

nickbattle commented 7 years ago

If there was a union of class types, and several of those classes defined "compareTo" and "equals" functions, surely they would act independently? Isn't that the VDM++ equivalent of what is happening here with ord/eq?

pglvdm commented 7 years ago

Well I have been thinking about this and in general my view is that union types gives raise to challenges for many different aspects of tool analysis. If I should design a VDM-like language with the knowledge I have today I am sure that I would limit the way unions could be used. So if you look for example at RSL they essentially only have unions of records. If one was having just that all these analysis challenges would go away. I think that this may be slightly to limited since I would like for example also to make unions of disjoint basic types (e.g. bool ı nat). Thus, I would like to suggest that when we make changes to VDM in a VDM-10 setting we target to make it easier to use "good practice". What I recommend all my students is to only make unions of either records or disjoint basic types. Thus, I think that we should say that if users haven't followed this principle the "compareTo" and "equals" is simply undefined! It makes it simpler from a tool development perspective and we strongly encourage more VDM users to follow good modelling principles.

tomooda commented 7 years ago

I agree. I think we already demonstrated that the combination of non-disjoint union and overloading is really complex. How about raising a type error when

I don't think this would harm existing specifications without eq/ord nor Paul's ISO8601 spec.

P.S. Nick,

If there was a union of class types, and several of those classes defined "compareTo" and "equals" functions, surely they would act independently? Isn't that the VDM++ equivalent of what is happening here with ord/eq?

Interesting. Functions are static members of classes that should be resolved by static types. So, for this case, finding the first matching type from members of union type and get the static method is the right behavior of the interpreter. The difference is that a static method is resolved by only the static type of the object value, while an overloaded operator is resolved by the entire signature (the static types of the both arguments because the return type is always bool in the case of ord/eq operators). Comparison operators needs the possible types of the both arguments to find out the right definition with a matched signature.

paulch42 commented 7 years ago

Interesting comments on union types. As it happens I had some issues in this direction when creating a specification for an aviation domain. The problem was I wanted to take the union of two types, both based on seq of char, which were distinct but not disjoint. A simple example

A = seq of char
inv a == len a < 4;
B = seq of char
inv b == len b > 2;

so any three character sequence satisfies both is_A and is_B. At first I started out changing to

A1:: a:seq of char
inv a == len a.a < 4;
B1:: b:seq of char
inv b == len b.b > 2;

effectively making them disjoint. However that made my specifications awkward because it was messy to have to handle the field tags when I was using A1 or B1 independently (which was the majority of uses). What I ended up doing was sticking with the simpler definitions of A and B but instead of the union A|B I had

AB = compose A' of a:A end
   | compose B' of b:B end;

While the definition of AB looks a little clumsy, its use in specifications is actually quite nice (function definitions look similar to those over A1|B1 while retaining the benefits of the simple A and B). Example

  lenab: AB +> nat
  lenab(ab) == cases ab:
                 mk_A'(a) -> len a,
                 mk_B'(b) -> len b
               end;

I recall discussing with Nick some time back and he pointed out the a and b tags in the definition can be dropped (I have yet to come across a situation where I wanted to use them so it seems a good general rule). This was the simplest way I could think of being able to handle the situation where the immediately obvious solution is a union of non-disjoint types. I think this is an example of Peter's "good practice". Of course, if anyone has a better approach...

nickbattle commented 7 years ago

I'll look at adding a TC warning for comparisons that use union types with (potentially) different orderings. It should be easy, though I need to make sure we don't get warnings for implicitly ordered numeric types.

I don't want to get dragged off into VDM++ OO semantics again! (We never really resolved whether bindings to statically defined functions/operations were static in the typed sense, though that is what most OO languages do). But I don't really understand why "a < b" is not effectively just a static binding to A.compareTo(b), where A is a's actual type. Why is that different to the object case?

nickbattle commented 7 years ago

PS - sorry Tomo, I see you said raise an error rather than a warning. But in my mind, I am starting to think of these situations as potential runtime errors that would have proof obligations raised to defend against them. It's similar to comparing "nat | char" with "<". That should produce an is_nat obligation, but you don't get a warning about the possible error (or a TC error - which is more life "definite semantics").

tomooda commented 7 years ago

Nick, I think giving warning message would be good. But in that case, we need to give a certain semantics on the overlapped ordered types. (Implementation dependent, aka undefined behavior, might be a choice.)

I'd like to avoid further OO-like semantics, too. Maybe your current implementation is like bool T::operator<(const T& rhs), while I'm thinking of something like bool operator<(const T& lhs, const T& rhs) if I explain in C++. What I'm saying is the difference between "overriding" and "overloading".

The reason why I prefer "overloading" semantics over "overriding" semantics is to handle cases like https://github.com/overturetool/language/issues/39#issuecomment-298264341 . Even if we give warnings and POG generates PO to ensure unionOne and unionTwo have only one ordered type, the "overriding" semantics can't find the right ordering function. The "overloading" semantics can handle it. Maybe it's rare cases in practice. Can the interpreter/POG detect such cases at compile time?

nickbattle commented 7 years ago

OK, I've added warnings for any ordering comparison with union arguments that define multiple ord clauses. And you also get a warning when you define a type as a union with an ord clause that overrides member ord clauses. ZIP attached below.

vdmj-4.0.0-170503.zip

As for semantics with overlapping types, the easiest thing to implement is for the ordering to reflect the ordering of the actual type of the argument (with the caveat that it may not be obvious what the actual type is). In the example below, f(1) returns true because the argument 1 is bound to the type A in the union; if it had bound to B, then it would have returned false. And this is just the sort of mess that the warnings are telling you about!

types
    A = nat
    ord a < b == a < b;

    B = real
    ord a < b == a > b;

    C = A | B    -- warnings about ignoring members' ord clauses
    ord a < b == a < b;   -- warning on this "<"

functions
    f: A | B -> bool
    f(x) == x < 10;    -- warning on this "<"

Warning 5021: Multiple union members define order clauses, (A, B) in 'DEFAULT' (test.vdm) at line 9:20
Warning 5019: Order of union member A will be overridden in 'DEFAULT' (test.vdm) at line 8:5
Warning 5019: Order of union member B will be overridden in 'DEFAULT' (test.vdm) at line 8:5
Warning 5021: Multiple union members define order clauses, (A, B) in 'DEFAULT' (test.vdm) at line 13:15
Type checked 1 module in 0.257 secs. No type errors and 4 warnings

> p f(1)
= true
Executed in 0.016 secs. 
> p f(11)
= false
Executed in 0.002 secs. 
> 
nickbattle commented 7 years ago

So what do we need for new proof obligations here?

I've created a new PO type called an ordered obligation. If the left or right of a comparison operator is not completely ordered (ie. some part is not numeric and doesn't define an ord clause) then the obligation creates "and not is_(a, T)" clauses for every part of the type that is not comparable.

At the moment, there is no "is_ord" way to declare that something is ordered, which means we can't declare that @T polymorphic types are ordered. At the moment, it is assuming @T types are ordered, so no POs are generated for their bodies, though a runtime error would result if @T was not ordered and it was used in a comparison. How do we solve that one?

nickbattle commented 7 years ago

This version of the jar has the POs I mentioned above, as well as some other fixes for nested ord/eq calls (when the ord/eq clause call on to the ord/eq clause of another type).

vdmj-4.0.0-170503.zip

I can't remember whether I mentioned that I will be on holiday for two weeks from this weekend. I'll try to leave the jar in a reasonable state so that you can carry on testing while I'm away :)

ldcouto commented 7 years ago

Guys, I'm not sure I'm in favor of warnings -- that is what the PO is for. There are many places in a spec where a PO is needed to guarantee runtime safety and we don't issue warnings there. I think this introduces inconsistency in the tool.

Regarding the PO Nick suggests, we need a notion of "type is ordered". This needs a higher level construct (for example, Haskell type classes).

If we don't have that, we need to express it with subtype POs, something like the value must be of one of the types in the union with a defined order.

So, for

types
    A = nat
    ord a < b == a < b;

    B = real -- note no ord
functions
    f: A | B -> bool
    f(x) == x < 10;    

We could have forall x: A|B & is_(x,A) -- this cannot be discharged, so the spec is not runtime safe.

Feels clunky to me. Have we considered banning these kinds of comparisons? Paul, have you found the need to compare values of union types?