Closed alzibab closed 1 year ago
@alzibab Thank you for posting this RC, and my apologies for the late response. The Language Board has accepted this RC. We are looking into this.
I asked the developers of VDMTools. Like Overture, VDMTools also allow read access to state variables and operation calls in all of define expressions, define statements, let expressions and let statements. A developer of VDMTools says that the main difference between define
and let
is the sequential evaluation of bindings. The define evaluates bindings in the order separated by ;
whereas the semantics of let
keeps a room for re-ordering although VDMTools does not re-order for now.
I've compared the behaviour of VDMJ and VDMTools (we can assume Overture is the same as VDMJ), using the attached test spec. The results are recorded in the spreadsheet also attached, which I hope we can use to decide what we want (since the tools differ!).
https://docs.google.com/spreadsheets/d/1slikOuxMJyNYmh7flTOexbZnkVNY2vvOCORtClS2uaM/edit?usp=drive_web issue49.vdm.txt
(from Tomo-san email)
VDMTools differentiate let and def by accessibility to the state/operations while VDMJ basically treats them samely except operation calls in let/define statements. It looks the LRM is closer to VDMTools' semantics than VDMJ's. I think it's rather from the classic VDM. In my personal opinion, I think VDMJ's semantics is more conformant to the rest parts of VDM10.
One interesting point of VDMJ's semantics is that it allows any (including a non-pure one) operation call in let expression in an operational context but it prohibits a non-pure operation call in let statement in an operational context. It looks a little odd to me that let expression is more permissive to the stateful access than let statement. Nick, do you know why it is defined in that way?
@tomooda The rules were so unclear in the LRM/manuals when VDMJ was written that any difference is almost certainly confusion and/or bugs, rather than a definite decision. So I'd be very happy to clear it up and make the interpretation the same.
I've updated the table to add extra columns with the behaviour (in VDMJ/Overture) for operation calls and state variable access from the <body>
of let/def expressions and statements.
If we make cell D8 "Y" (meaning you're allowed to call operations from the <expression>
in a let statement's bind), then the table seems consistent and symmetrical. The principle would then be, "You can do anything from an operational context, but from a functional context you can't access state and can only use pure operations".
I haven't checked what VDMTools allows.
I've now filled in the VDMTools tab. It looks like it allows anything except state variable access from a functional context.
Thank you for the table, @nickbattle. It surely gives a good overview. The new semantics tab in the table says def expression/statement is almost equivalent to let expression/statement except for static typing and concrete syntax. The remaining differentiator between let and def is re-ordering, and that's what the current VDMTools' semantics is a little stricter about (8D) than the new semantics. We need to decide whether or not interpreters reserve the freedom to re-order let bindings. My proposal is "interpreters may re-order evaluations of right-hand sides of let statements only when the side effects of non-pure operation calls do not influence the evaluations of right-hand sides. Interpreters must evaluate the local definitions of def expression/statement in order."
Did you mean re-order evaluation of left-hand sides? Meaning the order of the bindings? If so, then I like the proposal, though I think it would be clearer to talk about re-ordering bindings.
Thank you for the clarification. Yes, I meant the order of bindings.
This is now available in the "development" branch of VDMJ (4.5.0-SNAPSHOT). The only change is regarding operation calls within the definitions of a let clause in an operation - I've updated the spreadsheet to show the changed cell in green. Before the change, the test spec attached above used to give the following warning:
Warning 5033: Impure operation 'impOp' cannot be called from here in 'ExprStmtClarification' (test.vdm) at line 71:16
With the correction in place, there are no errors or warnings in the specification.
I made a draft of the modification in the RC49 branch. https://github.com/overturetool/documentation/commit/f1df37b1e39dcf2542f70b4c41ff0e10d1fe72ca Please check https://github.com/overturetool/documentation/blob/RC49/documentation/VDM10LangMan/VDM10_lang_man.pdf . The changes are Section 6.1, 6.2, and 12.2. If this looks OK, I'll merge it to the editing branch.
That looks good, Tomo. Would it help to add something explicitly to 6.1 (lets) to say that the en
expression can only involve state or impure operations calls when the let is in the context of an operation?
Yes, that would be a reasonable supplementary comment. I'll work on that.
I added a couple of sentences in Section 6.1 to explain that a right hand side of a def expression may contain stateful expressions, such as operation calls and state variables. (https://github.com/overturetool/documentation/commit/d2993e66dc1968de345668a598389020d8734f8f)
OK, let's go with that :)
updated the LRM with version info. Can we close this issue?
Have we addressed the last two points in the original request? We've just been focussing on the let/def subtleties only (which I think are now clarified by the spreadsheet, and hopefully the LRM).
It's true that the tools do allow operation calls in assignments, and function applies as "direct" return values.
Nick, I think VDMJ raises a type error to function application as a statement except the case that the function returns ()
.
Seem to work here? Do you have an example?
functions
f: nat -> nat
f(a) == a + 1;
operations
op: () ==> nat
op() == f(123);
Parsed 1 module in 0.085 secs. No syntax errors
Type checked 1 module in 0.281 secs. No type errors
Initialized 1 module in 0.179 secs.
Interpreter started
> p op()
= 124
Executed in 0.011 secs.
functions
f: nat->nat
f(a) == a + 1;
operations
op1: () ==> ()
op1() == skip;
op2: () ==> ()
op2() == if 1 = 2 then op1() else f(1);
raises
Error 3312: Void operation returns non-void value in 'DEFAULT' (t.vdmsl) at line 7:5
Actual: (() | nat)
Expected: ()
But this type error also looks incorrect because the body of op2
does not have a return statement and therefore returns ()
implicitly.
An easy way to fix it is to add a type check rule on the operation call that the callee's type should be an operation type (==>
).
I also think an operation/function call as a statement should be typed ()
, and the operation/function call as an expression should be typed as its return type.
The return statement return exp
should be typed as the exp
's type.
But this change might break existing specs (which are, in my opinion, not actually well-formed).
Ah OK, now I understand what you mean. But we did do it this way deliberately, and after some considerable discussion (at least with PGL and MV, as I recall, a decade or more ago). We thought that all paths through an operation must return the right type, so you would get the same behaviour here even if "f" was an operation. Is VDMTools different? (No time to try, sorry!)
Interesting! I tried on VDMTools and it behaves just the same as vdmj. So, the interpreter has been expected to handle a function call as an operation call since the age of VDM toolbox!? Hmm... I think we need to discuss which semantics we choose, the de-facto interpreters of ages, or the LRM. š®
I'm glad they're the same! But I do remember discussing this at length with people at the time and changing VDMJ as a result. So this is by design. Perhaps the LRM should be updated, if it is unclear?
Okay, I now understand that it is designed in that way although I still don't get why. Anyway, it's the working semantics and we need to update the LRM to explain the same semantics. As far as I understand, the LRM needs to be updated in the following two points.
Are these what you changed VDMJ to?
Yes, that's right: in a block, VDMJ treats operation and function "call statements" the same way - except we know a function will always return a value.
Okay, I think the implicit return statement on a call statement with a return value and the function call as a statement can be considered an implementation extension to the language definition. I mean, they are not a part of the language semantics, but the interpreters may do those extensions for convenience. How about modifying the description that @alzibab pointed out into the following?
Examples: In the following example
OpCall()
is an operation call whereasFunCall()
is a function call. As the if statement only accepts statements in the two branches,FunCall
is āconvertedā to a statement by using the return statement, i.e.return FunCall()
. This "conversion" may be carried out explicitly by the specifier, or implicitly by the interpreter.
solved by 1f13bec5877aba78890c4a110426215cd6b31c93
Some areas of Expressions and statements that may require some clarification for VDM-SL.
A VDM-SL file is attached showing examples.
Define Expressions
The Language manual states define expressions can only be used in operations however Overture allows define expressions to be used in function definitions.
Overture allows Define Expressions called in operations can call operations that modify state. The language manual states: "The define statement corresponds to a define expression except that it is also allowed to use operation calls on the right-hand sides. Thus, operations that change the state can also be used here." This would suggest that a define expression cannot call operations?
Let Expressions
Let Statements
Apply Expression
Return Statement Page 127 Example
if test then opCall() else return FunCall()
ExpStmtClarification.vdmsl.zip