doc/hacking.texi (Typescript Migration): New section
parent
16409a014f
commit
b81022b568
188
doc/hacking.texi
188
doc/hacking.texi
|
@ -50,6 +50,7 @@ References for these topics and others are provided in
|
||||||
* Source Files:: Conventions for project files
|
* Source Files:: Conventions for project files
|
||||||
* Libraries:: The few libraries used by Liza
|
* Libraries:: The few libraries used by Liza
|
||||||
* Developer Resources:: Where to look for more information
|
* Developer Resources:: Where to look for more information
|
||||||
|
* TypeScript Migration:: Information on migrating to TypeScript
|
||||||
@end menu
|
@end menu
|
||||||
|
|
||||||
|
|
||||||
|
@ -183,6 +184,8 @@ They further introduce maintenance obligations for keeping up with
|
||||||
|
|
||||||
@subsection System Libraries
|
@subsection System Libraries
|
||||||
@dnindex GNU ease.js
|
@dnindex GNU ease.js
|
||||||
|
Liza was originally developed using JavaScript
|
||||||
|
(first ECMAScript@tie{}3, and then ECMAScript@tie{}5).
|
||||||
JavaScript does not natively support the classical object-oriented
|
JavaScript does not natively support the classical object-oriented
|
||||||
model familiar to users of more traditional classical
|
model familiar to users of more traditional classical
|
||||||
object-oriented languages like Java, C++, C#, and@tie{}PHP.
|
object-oriented languages like Java, C++, C#, and@tie{}PHP.
|
||||||
|
@ -201,6 +204,14 @@ The @code{class} keyword introduced in ECMAScript@tie{} is largely
|
||||||
primary concerns of ease.js,
|
primary concerns of ease.js,
|
||||||
nor does it provide traits.
|
nor does it provide traits.
|
||||||
|
|
||||||
|
@dnindex TypeScript
|
||||||
|
@emph{The project is now migrating toward TypeScript},
|
||||||
|
so new code should not use ease.js unless required
|
||||||
|
and an effort should be made to move existing code away from
|
||||||
|
ease.js.
|
||||||
|
For more information on this migration,
|
||||||
|
see @xref{TypeScript Migration}.
|
||||||
|
|
||||||
|
|
||||||
@subsection Testing Libraries
|
@subsection Testing Libraries
|
||||||
@dnindex Mocha
|
@dnindex Mocha
|
||||||
|
@ -214,6 +225,9 @@ Chai offers a few different styles of assertions (``should'',
|
||||||
``expect'', and ``assert'');
|
``expect'', and ``assert'');
|
||||||
Liza uses @url{http://www.chaijs.com/guide/styles/#expect,``expect''}.
|
Liza uses @url{http://www.chaijs.com/guide/styles/#expect,``expect''}.
|
||||||
|
|
||||||
|
@devnotice{A library to aid in mocking TypeScript classes needs to be
|
||||||
|
researched.}
|
||||||
|
|
||||||
|
|
||||||
@subsection UI Libraries
|
@subsection UI Libraries
|
||||||
@dnindex jQuery
|
@dnindex jQuery
|
||||||
|
@ -253,6 +267,18 @@ All developers should familiarize themselves with the resources
|
||||||
available on MDN so that they understand what type of information is
|
available on MDN so that they understand what type of information is
|
||||||
readily accessible for future reference.
|
readily accessible for future reference.
|
||||||
|
|
||||||
|
@dnindex TypeScript
|
||||||
|
An overview of TypeScript can be found in its
|
||||||
|
@url{https://www.typescriptlang.org/docs/handbook/basic-types.html,Handbook}.
|
||||||
|
The language borrows concepts from a number of others,
|
||||||
|
so many concepts may be familiar to you.
|
||||||
|
TypeScript uses structural typing (duck typing).
|
||||||
|
In Liza,
|
||||||
|
we also choose to implement nominal typing using ``branding''
|
||||||
|
(@srcrefraw{src/types/misc.d.ts}).
|
||||||
|
A @url{https://github.com/microsoft/TypeScript/blob/master/doc/spec.md,language specification}
|
||||||
|
is also available.
|
||||||
|
|
||||||
@dnindex Node.js
|
@dnindex Node.js
|
||||||
The Server (@pxref{Server}) uses Node.js.
|
The Server (@pxref{Server}) uses Node.js.
|
||||||
Although it's largely abstracted away,
|
Although it's largely abstracted away,
|
||||||
|
@ -285,3 +311,165 @@ Database operations in Liza are abstracted away,
|
||||||
|
|
||||||
For information on specific libraries used by Liza,
|
For information on specific libraries used by Liza,
|
||||||
@pxref{Libraries}.
|
@pxref{Libraries}.
|
||||||
|
|
||||||
|
|
||||||
|
@node TypeScript Migration
|
||||||
|
@section TypeScript Migration
|
||||||
|
@dnindex TypeScript
|
||||||
|
@helpwanted{}
|
||||||
|
|
||||||
|
This section contains notes regarding a migration to TypeScript.
|
||||||
|
It is intended to serve as a guide@mdash{
|
||||||
|
}it is not prescriptive.
|
||||||
|
|
||||||
|
|
||||||
|
@subsection Migrating Away From GNU ease.js
|
||||||
|
Liza was originally written in @easejs.
|
||||||
|
TypeScript now provides many features that ease.js was written to address,
|
||||||
|
though not all (most notably traits).
|
||||||
|
|
||||||
|
Since ease.js was designed with JavaScript interoperability in mind,
|
||||||
|
and TypeScript generates prototypes from classes,
|
||||||
|
TypeScript classes serve as drop-in replacements under most
|
||||||
|
circumstances.
|
||||||
|
However,
|
||||||
|
subtypes must be migrated at the same time as their parents,
|
||||||
|
otherwise type checking in TypeScript cannot properly be performed.
|
||||||
|
If this is a concern,
|
||||||
|
@url{https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-type-assertions,type assertions}
|
||||||
|
can potentially be used to coerce types during a transition period
|
||||||
|
in conjunction with ease.js'
|
||||||
|
@url{https://www.gnu.org/software/easejs/manual/easejs.html#Type-Checks-and-Polymorphism,@samp{Class.isA}}.
|
||||||
|
|
||||||
|
Interfaces do not exist at runtime in TypeScript,
|
||||||
|
but they do in easejs.
|
||||||
|
Consequently,
|
||||||
|
you can continue to export an ease.js interface while also exporting
|
||||||
|
a TypeScript interface.
|
||||||
|
To do this,
|
||||||
|
continue to export using @samp{module.exports} rather than
|
||||||
|
TypeScript's @samp{export =}.
|
||||||
|
|
||||||
|
ease.js implements stackable Scala-like traits.
|
||||||
|
Traits are @emph{not} provided by TypeScript.
|
||||||
|
Traits will therefore have to be refactored into,
|
||||||
|
for example,
|
||||||
|
decorators or strategies.
|
||||||
|
|
||||||
|
|
||||||
|
@subsection Structural Typing
|
||||||
|
@dnindex Typing, Duck
|
||||||
|
@dnindex Typing, Structural
|
||||||
|
TypeScript implements
|
||||||
|
@url{https://en.wikipedia.org/wiki/Structural_typing,structural typing},
|
||||||
|
also called duck typing.
|
||||||
|
This means that any two types sharing the same ``shape'' are
|
||||||
|
compatible with one-another.
|
||||||
|
|
||||||
|
For classes,
|
||||||
|
this can be mitigated by defining private members,
|
||||||
|
which then ensures that compatible types are indeed subtypes.
|
||||||
|
|
||||||
|
Interfaces can be used in either the traditional OOP sense,
|
||||||
|
or as a means to define the shape of some arbitrary object.
|
||||||
|
Since interfaces do not define implementation details,
|
||||||
|
the distinction isn't important@mdash{
|
||||||
|
}it does not matter if we receive an instance of an object
|
||||||
|
implementing an interface,
|
||||||
|
or some object arbitrary that just happens to adhere to it.
|
||||||
|
|
||||||
|
In other instances where we want to distinguish between two values
|
||||||
|
with otherwise compatible APIs,
|
||||||
|
Nominal Typing below.
|
||||||
|
|
||||||
|
|
||||||
|
@subsection Nominal Typing
|
||||||
|
@dnindex Typing, Nominal
|
||||||
|
It is sometimes desirable to distinguish between two otherwise
|
||||||
|
compatible types.
|
||||||
|
Consider, for example, a user@tie{}id and a Unix timestamp.
|
||||||
|
Both are of type @code{number},
|
||||||
|
but it's desirable to ensure that one is not used where another is
|
||||||
|
expected.
|
||||||
|
|
||||||
|
TypeScript doesn't directly support
|
||||||
|
@url{https://en.wikipedia.org/wiki/Nominal_typing,nominal typing},
|
||||||
|
where compatibility of data types are determined by name.
|
||||||
|
Liza uses a convention called ``branding'',
|
||||||
|
abstracted behind a @code{NominalType} generic
|
||||||
|
(defined in @srcrefraw{src/types/misc.d.ts}).
|
||||||
|
|
||||||
|
@float Figure, f:nom-type
|
||||||
|
@verbatim
|
||||||
|
type UnixTimestamp = NominalType<number, 'UnixTimestamp'>;
|
||||||
|
type Milliseconds = NominalType<number, 'Milliseconds'>;
|
||||||
|
|
||||||
|
function timeElapsed( start: UnixTimestamp, end: UnixTimestamp ): Milliseconds
|
||||||
|
{
|
||||||
|
return end - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = <UnixTimestamp>1571325570000;
|
||||||
|
const end = <UnixTimestamp>1571514320000;
|
||||||
|
|
||||||
|
// this is okay
|
||||||
|
const elapsed = timeElapsed( start, end );
|
||||||
|
|
||||||
|
// this is not, since elapsed is of type Milliseconds
|
||||||
|
timeElapsed( start, elapsed );
|
||||||
|
|
||||||
|
@end verbatim
|
||||||
|
@caption{Example of nominal typing}
|
||||||
|
@end float
|
||||||
|
|
||||||
|
Consider the example in @ref{f:nom-type}.
|
||||||
|
Both @code{UnixTimestamp} and @code{Milliseconds} are a @code{number} type,
|
||||||
|
but they have been defined in such a way that their names are part
|
||||||
|
of the type definition.
|
||||||
|
Not only does the compiler prevent bugs caused from mixing data,
|
||||||
|
but nominal types also help to make the code self-documenting.
|
||||||
|
|
||||||
|
If you want to have self-documenting types @emph{without} employing
|
||||||
|
nominal typing,
|
||||||
|
use type aliases.
|
||||||
|
|
||||||
|
There are no prescriptive rules for whether a type should be defined
|
||||||
|
nominally.
|
||||||
|
|
||||||
|
In some cases,
|
||||||
|
it's useful to use nominal types after having validated data,
|
||||||
|
so that the compiler can enforce that assumption from that point forward.
|
||||||
|
This can be done using
|
||||||
|
@url{https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-type-assertions,type assertions}.
|
||||||
|
|
||||||
|
@float Figure, f:nom-assert
|
||||||
|
@verbatim
|
||||||
|
type PositiveInteger = NominalType<number, 'PositiveInteger'>;
|
||||||
|
|
||||||
|
const isPositiveInteger = ( x: number ): n is PositiveInteger => n > 0;
|
||||||
|
|
||||||
|
const lookupIndex<T>( arr: T[], i: PositiveInteger ): T => arr[ i ];
|
||||||
|
|
||||||
|
// untrusted input from the user
|
||||||
|
const user_input = readSomeValue();
|
||||||
|
|
||||||
|
if ( isPositiveInteger( user_input ) )
|
||||||
|
{
|
||||||
|
// user_input is now of type PositiveInteger
|
||||||
|
return lookupIndex( data, user_input );
|
||||||
|
}
|
||||||
|
@end verbatim
|
||||||
|
@caption{Validating nominal types}
|
||||||
|
@end float
|
||||||
|
|
||||||
|
In @ref{f:nom-assert} above,
|
||||||
|
we only assume something to be a @code{PositiveInteger} after having
|
||||||
|
checked its value.
|
||||||
|
After that point,
|
||||||
|
we can use TypeScript's type system to ensure at compile time that
|
||||||
|
we are only using positive integers in certain contexts.
|
||||||
|
|
||||||
|
@devnotice{Never cast values
|
||||||
|
(e.g. using @samp{<PositiveInteger>user_input})
|
||||||
|
when type predicates are provided,
|
||||||
|
since that undermines type safety.}
|
||||||
|
|
Loading…
Reference in New Issue