During compile, the require is added by the compiler, based on the require definition of the alias type:
method x(p: Percentage);
//Require section is added by the compiler based on the definition in the Alias
Require
p.IsNaN = false;
0 <= p <= 100;
begin
//Some code
end;
When the type is used as a const or byval parameter for a method, the require that is defined for the type is added to the method for that parameter.
When the type is used as an var or out parameter for a method, the require and ensure that are defined for the type are added to the method for that parameter.
when the type is used as return value for a method, only the ensure is added to the method for the return value.
when the type is used as a field or property in a class, the ensure is added as private invariant for the field.
First: I use aliases to make the code more readable; by example var Weight: Kg; instead of var Weight Double; This way I just use a double, but it is completely clear that it represents a Kg.
Second: Each unit or object type that I use this way (as an alias) has it’s own specific properties; by example, the Kg can not be negative as we did not invent anti-gravity yet.
Third: To prevent bugs and flaws it is handy to enforce those specific properties; when an object of type Kg gets a negative value it means there is a bug in the code - And I want to know that as soon as possible.
Fourth: Enforcement during debug, without performance impact on the release build is done using the class contracts. parameters are checked using require, output parameters and return values are checked using ensure and class fields are checked using invariants.
Fifth: Rules and requirements can change. If I program those checks on each and every method and class that use a specific alias, I have to check and change them all when a rule or requirement changes - with a big chance that some are forgotten.
Those points together gave me the idea to centralize the checks.
The alias definition itself defines the checks - this is very readable and maintainable; it is very clear what rules are valid for the specific alias and when rules or requirements change they can be changed at one place.
These rules (require and ensure) on the alias level are only compiler hints to generate the appropriate checks in all methods and classes that use this specific alias during compile time.
right. i just don’t see how this would be technically feasible. at runtime, Kg is just a double. you can pass it to any system function that processes (and manipulates) doubles. who would enforce the invariant?
The checks are only in own code.
If used as parameter in own code, the require checks it before it is used in the routine.
If used as output (parameter or returnvalue) in own code, ensure checks if it is stiil valid before leaving the routine. If a system function within that method did not obey my rules, ensure will fail my routine.
If used as class field, private invariant will check if the field is still valid after a method call in that class.
So it does not matter what a method does - system functions can do what they want, because it is all checked within my own code, at entry and - if needed - at exit.
The whole point is that all require and ensure sections of a method is generated based on the type of the used parameters and return value, and private invariant sections are generated based on the type of the used fields in the class.
This means that if I have a class with 10 fields of type Kg, 40 methods with a total of 60 parameters of type Kg and 20 return values of Kg would require me to write:
10 private invariant rules
60 require rules
20 ensure rules
instead of
1 require rule
1 ensure rule
And when a require rule changes, I have to change it on 60 places instead of 1 place.
And maintain it in 1 place is readable, understandable, maintainable and less error-prone.
Edit: But indeed, when Kg is used as local variable in within a function, nothing will be checked or ensured.
Maybe another extension to the method check: Maybe to allow the local checks, the Compiler could create i think silently from this:
type Percentage = double
requires
not self.IsNaN;
0 <= self <= 100;
ensure
0 <= self <= 100;
end;
this:
type
Percentage = record Extension(Double)
method require: bool;
begin
result := not self.IsNaN and
0 <= self<= 100;
end;
method ensure: bool;
begin
result := 0 <= self<= 100;
end;
end;
and now I think the Compiler can treat it as wished, I think if im not wrong here.
So the require is called at the beginning when the Compiler detects the first use of the local KG, and the ensure is called when the Compiler detects the last use of the local KG.
in my fantasie this should be like:
method DoWork;
begin
var lKG: Percentage := 24;
//now the Compiler calls silently lkg.require ! <code-injection>
writeln(lgk);
//now the Compiler calls silently lg.ensure ! <code-injection>
end;
Problem with your solution is that this is injected in Debug aswell in Release builds. And that will give performance problems.
My request was to use the already available class contract constructs - all tests available during development, debug and test phase, but absolutely no load on the release build, ensuring performance for the customers.
but again, feel free to call me stupid, but that is what my Purpose was actually for, to solve this Limitation?.
You can inject the Code just in debug mode (internally by the Compiler I mean) and then you can have your orignal function request + it works now when you use local variables?
So this (theortically) would solve this.
so you would have working: class contracts for type aliases as Parameters/out vars and local variables
But it makes it about 1000% more complex for the compiler guys to build it and it adds almost nothing. If you don’t do anything with a local variable (I mean calling any of your own implementations), it won’t be that important.
type
CheckedPercentage = public sealed class
public
property Value : Double read private write; // to avoid changing the value externally.
// define what you want to check, with the operators, ...
end;
{$if Debug}
Percentage = CheckedPercentage;
{$else}
Percentage = Double;
{$endif}