A Smarter Way To Type Your JavaScript
JSOS's type system is quite different from how TypeScript does types. It does not require any compiler or additional build steps. That's because JSOS's type system works its magic at runtime.
TypeScript vs. JSOS
While TypeScript utilizes structural typing almost exclusively, JSOS's type system is mostly nominal.
To put this difference in simpler terms:
In TypeScript, two objects are considered equal when they have the same shape.
In JSOS, two objects are only considered equal when they have an internal reference to the same type or a subtype thereof.
A nominal type system is arguably far stricter than a structural type system. This leads to less bugs in your software. And checking whether two things have the same type is also trivial in a nominative type system, since all that needs to be compared is the internal type tag of the two things, whereas you have to check every property and values of an object in a structural type system.
You might think that a nominal type system runs counter to the extensibility JSOS promises with its multimethods. But that's actually not the case at all.
JSOS solves this seeming contradiction by having a hierarchy of abstract types (types that cannot be instantiated) and concrete types (that can be instantiated) as subtypes of these abstract types.
Overview
TypeScript | JS+JSOS |
---|---|
structural typing | nominal typing |
compiler | no compiler |
TypeScript | Equivalent JS+JSOS |
---|---|
typed functions | multimethods |
interfaces | structs |
typed classes | structs + multimethods |
union types | union types |
intersection types | intersection types |
generics | parametric structs |
JSOS | Equivalent TypeScript |
---|---|
multimethods | -- |
dependent types | -- |
Typed Functions (Multimethods)
function add(a: number, b: number): number {
return a + b;
}
const { add } = Generic({expect: JSNumber});
def(add, [JSNumber, JSNumber], (a, b) => a + b);
Union Types
type key = string | number;
type day = "Mo" | "Tu" | "We" | "Th" | "Fr" | "Sa" | "Su";
const Key = Union(JSString, JSNumber);
const Day = JSString.where(
s => ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].includes(s)
);
Enums
enum Day {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
const { Day } = Enum;
const { Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday } = Day;
Interfaces (Structs)
interface User {
id: number;
name: string;
}
const { User } = Struct({
id: JSNumber,
name: JSString
});
Parametric Interfaces (Structs)
interface Line<T extends Point> {
start: T;
end: T;
}
const line: Line<Point2D> = {
start: new Point2D(5, 10),
end: new Point2D(13, 18)
};
const { Line } = Struct(Point)(T => ({
start: T,
end: T
}));
const line = Line(Point2D).new({
start: Point2D.new(5, 10),
end: Point2D.new(13, 18)
});
Classes (Structs+Multimethods)
class Song {
public name: string;
public artist: string;
public album: string;
public year: number;
constructor(name: string, artist: string, album: string, year: number) {
this.name = name;
this.artist = artist;
this.album = album;
this.year = year;
}
toString(): string {
return `Song: '${ this.name }' by '${ this.artist }' ` +
`(${ this.album }, ${ this.year })`;
}
}
const blackwaterPark: Song = new Song(
"Blackwater Park",
"Opeth",
"Blackwater Park",
2001
);
console.log(blackwaterPark.toString());
const { Song } = MStruct({
name: JSString,
artist: JSString,
album: JSString,
year: JSNumber
});
def(
Song.new, [JSString, JSString, JSString, JSNumber],
(name, artist, album, year) => Song.new({name, artist, album, year})
);
def(stringify, [Song], s =>
`Song: '${ s.name }' by '${ s.artist }' (${ s.album }, ${ s.year })`);
const blackwaterPark = Song.new(
"Blackwater Park",
"Opeth",
"Blackwater Park",
2001
);
console.log(stringify(blackwaterPark));