Jura Home | John Winn, December 2003 |
A restricted type is one where there are one or more restrictions on the
valid instances of that type. An example of a restricted type is an Integer
which is restricted to have values from 1 to 10 inclusive. Restricted types are
another form of generics and are defined just like generic types. For example,
here is how such a range-restricted Integer type would be defined in Jura:
Class:Range{extends=IntegerPrimitive doc={"A number which is restricted to be in a given range of values."} Property:min{type=Long doc={"The minimum valid value of an instance of this type"}} Property:max{type=Long doc={"The maximum valid value of an instance of this type"}} Property:base{type=IntegerPrimitive doc={"The base type whose range is being restricted"}} Template{ doc={"This creates a primitive which extends and restricts the base type."} extends=$base implements={Restricted{type=$base}} Method:isValidInstance{ doc={"Checks to ensure that the supplied instance is in range."} Par:i{type=$base} throws={InvalidValueException} // If{test=((i<$min) || (i>$max)) // Throw{ new InvalidValueException{msg="Value is not in the range "+$min+".."+$max} } } } } }
An integer field whose values are restricted to be in the range 1 to 10 can then be defined using
Field:x{type=Range{base=Integer min=1 max=10}}
Now suppose we wish to assign an instance of an unrestricted integer to x
.
Local:y{type=Integer} y=7 x=y {* This will fail at compile time *} x=(x.TYPE)y {* A cast is required from an unrestricted integer to a restricted integer. *} y=x {* This is always allowed. *}
In this case, y
is 7 and the cast will succeed. If y
was 11 then the cast would fail and an InvalidValueException
would
be thrown.
isAssignableTo()
and isCastableTo()
To make using restricted types more intuitive, they should override the
default implementations of isAssignableTo()
and isCastableTo()
.
In the Range
example above, isAssignableTo()
should be
overridden to return true
for any Range
instance which
is a sub-range of the current instance. Then statements like:
Local:x{type=Range{base=Integer min=1 max=10}} Local:y{type=Range{base=Integer min=1 max=100}} y=x {* isAssignableTo() should be overridden so that this will succeed at compile-time *}
Note: It follows that any method on a class that implements Type
may be called by the compiler.
Such methods should therefore ensure that they execute quickly and do not use
excessive system resources. For security, the compiler should execute such
methods in a sandbox preventing all access to external resources (e.g. file
system, network etc).
Given that we can now restrict the values of primitive types, it makes sense to define more meaningful primitives with predefined restrictions. For example, we could define a percentage type whose value is limited to be between 0 and 100:
IntegerPrimitive:Percentage{extends=Range{base=Integer min=0 max=100}}
We can then define a field like:
Field:score{type=Percentage}
The field score
will then behave as follows:
score=110 {* This will fail at compile time. *} score=(Percentage)110 {* This will succeed at compile time but always throw an exception at run time *} score=(Percentage)70 {* This will succeed at both compile time and run time *}
The restricted type should define restricted return types for operations on
that type e.g. arithmetic operations. If this is not done, then any
arithmetic operation on a type will have return a value whose type is
unrestricted. For example, using the basic definition of Range
above, then the expression score+1
will have type Integer
.
Thus, by default, the expression score=score+1
will fail at compile
time and score=(Percentage)(score+1)
would have to be used
instead. Of course, if score
had an initial value of 100 then
the cast would cause an exception to be thrown.
Note: In Jura, the use of direct casts is discouraged in favour of the
IfCast
statement as this will never cause an exception to be
thrown.
A more complete definition of Range
would provide restricted
return types for all arithmetic operations by overriding and restricting the
return types of appropriate methods of IntegerPrimitive
.