A short tour of program evolution with StrongScript
We start from the following dynamically typed program:
| var p:any = { x=3; z=4 }
var f:any = func (p) {
if (p.x < 10) return 10
else return p.distance() }
f(p)
|
p
and the argument of f
with the optional type
Point
:
|
class Point {
constructor(public x, public y){}
dist(p) { return ... }
}
var p:Point = <any> { x=3; z=4 } //Correct
var f:any = func (p:Point) {
if (p.x < 10) return 0
else return p.distance(p) } //Wrong
|
StrongScript
compiler tsc
is invoked on the example, we get:
$ tsc example.ts
example.ts(8,14): error TS2339: Property 'distance' does not exist on type 'Point'.
f
enables local type checking, catching type errors
such as the call to distance
.
The programmer can also create instances of class Point
, which are
concretely typed as !Point
, and pass them to f
:
|
var s:!Point = new Point(5,6);
f(s); // evaluates to 10
|
f
has been type checked assuming that its argument is a
Point
, we known it's body will manipulate the argument as a
Point
. However, whenever an object which is an instance of a class is
passed to an optionally or dynamically typed context, it protects its own
abstractions at runtime.
Consider a new class definition, where the
x
and y
fields have been strengthened as !number
and as
such can only refer to instances of class number
:
|
class TypedPoint {
constructor(public x:!number, public y:!number){}
dist(p) { return ... :!number }}
var t:!TypedPoint = new TypedPoint(1,2);
( |
dist
function dynamically typed, so that it is correct to invoke it
with an arbitrary object as in t.dist({x=1;y=2})
.
StrongScript strategy for program evolution is to first add optional types, catching and fixing unexpected local type errors; the programmer can then identify the parts of the code that obey to a stricter type discipline, and replace optional types with concrete types. Optional types act as a bridge to move values into the concrete world:
|
var fact = func(x:!number) {return ...:!number}
var u:TypedPoint = { dist = func(p) {...} }
var n:!number = fact(u.dist(p))
|
p
has type any
, and u
points to a
dynamic object with a method dist
typed any -> any
. However, u
has been typed as TypedPoint
; the
runtime will ensure that the method dist
respects the
TypedPoint.dist
signature any -> !number
and
will dynamically check that the returned value is an instance of class
number
. As a consequence, fact(u.dist(p))
is well-typed
(the concretely typed function fact
is guaranteed to receive a
value of type !number
) and the programmer, by specifying just
one optional type, can invoke the concretely-typed function
fact
with a value that has been computed from the dynamic
world.
The ability to have fine grained control over typing
guarantees is one of the main benefits of StrongScript
.
Last update: