What & WhyWhat is Hegel?Why Hegel?Why actually do we need static analysis?Benefits and disadvantages over TypeScriptBenefits and disadvantages over Flow.js
Setup
Type Annotations
Type System
ConfigurationLibraries
For Potential Contributors
Index

Introduction

Edit

What is Hegel?

Hegel is a static type checker for JavaScript. It means that it is not a new language which compiles to JavaScript. No, it's only a tool which analyzes your code ahead of time and shows you results of the analysis. Also, it means that you don't need to learn a new language to use a static strong type system - JavaScript is enough. But, additionally, this tool provides a type syntax for your variables and function arguments, analysis of your types (even if you don't use type syntax), which gives you an ability to find bugs faster without actually running your program.

Playground
// Example from real life 😭
class DataBase {
reconnect() {
/* ... reconnect logic */
}
dropDatabase() {}
}
class Fixture {
reconnect() {
this.dropDatabase(); /* ... reconnect logic */
}
dropDatabase() {}
}
function setupE2E(db) {
db.reconnect();
}
// And your teammate forgot to change something after debugging
const db = new Fixture();
debugger;
setupE2E(db);
// Congratulations, you became a DropDatabase Engineer

But if you have added only one thing in your code with Hegel, you would not have dropped the DataBase.

Playground
// ...
function setupE2E(db: DataBase) {
// ...

You can see the result in the Hegel Playground - you would be notified before the incident.

Why Hegel?

First of all, let's explore the main goals of Hegel:

  • Minimalistic type syntax
  • Strong type system
  • Powerful type inference

But we need to answer this question differently for different audiences. So, choose the list item which describes your experience:

Why actually do we need static analysis?

First of all, static analysis means that your code will be analyzed without actually running. So, static type analysis will analyze your code for any type of errors.

Actually, there are 2 major benefits of static type analysis:

  1. Static type analysis finds and provides to you information about a type error that exists in your code during code writing. It not only gives you guarantees that your code will work without runtime type errors but in addition, it really saves a lot of time for finding and fixing errors, especially in big projects that contain a really long build step.
  2. When you use an instrument that provides type information of your variables, functions, methods, classes and etc - you have got a documentation of method usage without any additional effort.
Playground
function deserializeUser(stringifiedUserJson) {
const maybeUser = JSON.parse(stringifiedUserJson);
if (
typeof maybeUser === "object" &&
maybeUser !== null &&
"name" in maybeUser &&
typeof maybeUser.name === "string"
) {
return maybeUser;
}
throw new TypeError("Provided serialized user is invalid!");
}
const user = deserializeUser("42");

If you open this example in Hegel Playground and hover at deserializeUser function invocation, then you will see the argument types of the function, return type of the function and which error this function may throw. And you've got it free.

Benefits and disadvantages over TypeScript

If you familiar with TypeScript you may know the benefits of static typing, so let's explore benefits and disadvantages of Hegel over TypeScript

Benefits

  1. Ability to skip type annotation

Hegel is targeting at really powerful type inference which gives an ability to write fewer type annotations. For example:

Playground
// Type of "promisify" function is "<_q, _c>((_c) => _q) => (_c) => Promise<_q>"
const promisify = (fn) => (arg) => Promise.resolve(fn(arg));
// Type of "id" function is "<_c>(_c) => Promise<_c>"
const id = promisify((x) => x);
// Type of "result" variable is "Promise<number>"
const result = id(42).then((x) => x + x);

The same example in TypeScript (tested at version 3.7.5) will show 3 errors and infer Promise<any> type for the "result" variable TypeScript Example.

  1. No unexpected runtime errors

One of the non-goals of TypeScript is: "Apply a sound or "provably correct" type system. Instead, strike a balance between correctness and productivity.". It means that TypeScript never will guarantee that you will not have a Type Error at Runtime. Hegel is on the opposite side. We try to implement a strong type system which will guarantee that your program is valid.

Playground
const doubles: Array<number> = [Math.PI, Math.E];
// Error: Type "Array<number>" is incompatible with type "Array<number | string>"
const numbersToShow: Array<number | string> = doubles;
numbersToShow.push((42).toString(2));
const rounded = doubles.map((double) => double.toFixed());

The same example in TypeScript (tested at version 3.7.5) will be valid, but an uncaught type error will be thrown at runtime TypeScript Example.

  1. Typed Errors

Hegel implements inference and annotation for functions which gives an ability to understand which error type is inside the catch block and which errors will be thrown by a function.

Playground
// Type of "assertIsTrue" function is "(boolean) => undefined throws TypeError"
function assertIsTrue(arg) {
if (!arg) {
throw new TypeError("arg is invalid");
}
}
try {
assertIsTrue(false);
// Type of "e" variable is "TypeError | unknown"
} catch (e) {}

The same example in TypeScript (tested at version 3.7.5) will inference e as "any" type TypeScript Example.

Disadvantages

  1. Minimal changes in JavaScript syntax

TypeScript provides a lot of additional syntax features and syntax sugar with types, but Hegel does not. Hegel is only a JavaScript with types. Let's see the example implemented in TypeScript and Hegel.

Playground
// TypeScript
enum UserStatus {
Active,
Muted,
Banned,
}
class User {
constructor(public name: string, public status: UserStatus) {}
}
const Anatoly = new User("Anatoly", UserStatus.Active);
Playground
// Hegel
const UserStatus = Object.freeze({
Active: "Active",
Muted: "Muted",
Banned: "Banned",
});
class User {
name: string;
status: $Values<$TypeOf<UserStatus>>;
constructor(name, status) {
this.name = name;
this.status = status;
}
}
const Anatoly = new User("Anatoly", UserStatus.Active);
  1. No type coercion and "any" type

As result of attempting to implement a sound type system Hegel doesn't have type coercion and "any" type.

Playground
// Error: There is no "any" type in Hegel.
const something: any = null;
// Error: Type cast does not exist in Hegel
(null: any).call();

Benefits and disadvantages over Flow.js

If you are familiar with Flow.js you may know the benefits of static typing, so let's explore benefits and disadvantages of Hegel over Flow.js

Benefits

  1. Better type inference

As example Flow.js docs says: "Flow does not infer generic types. If you want something to have a generic type, annotate it. Otherwise, Flow may infer a type that is less polymorphic than you expect.". It's because Flow.js infers function types by function usage. Hegel infers function types by function declarations and as result Hegel infers polymorphic types.

Playground
// Type of "id" function is "<_a>(_a) => _a"
const id = (x) => x;
// Type of "num" variable is "number"
let num = id(4);
// Type of "str" variable is "string"
let str = id("4");
// Type of "anotherId" variable is "<_a>(_a) => _a"
let anotherId = id(id);

The same example in Flow.js (tested at version 0.119.0) will infer every variable type as union of all applied types Flow.js Example.

  1. Typed Errors

Hegel implements inference and annotation for functions which gives an ability to understand which error type is inside catch block and which errors will be thrown by a function.

Playground
// Type of "assertIsTrue" function is "(boolean) => undefined throws TypeError"
function assertIsTrue(arg) {
if (!arg) {
throw new TypeError("arg is invalid");
}
}
try {
assertIsTrue(false);
} catch (e) {
// Type of "e" variable is "TypeError | unknown"
}

The same example in Flow.js (tested at version 0.119.0) will infer e as "empty" type Flow.js Example.

  1. No custom library definition language

Flow.js has a custom library definition language and doesn't support the most popular TypeScript "d.ts" format. But for Hegel, the TypeScript "d.ts" it is the only way to create the type definition for a library. So, every library which has TypeScript definitions should work with Hegel.

  1. No OCaml in source code

OCaml is really great language and this language inspired us to implement the same type inference in Hegel, but the problem is that it's not common language (especially for developers who work with a JavaScript stack) and as result it's hard for the JavaScript community to contribute to Flow.js. We decided to implement Hegel in JavaScript.

Disadvantages

  1. No type coercion and "any" type

As a result of attempting to implement a sound type system Hegel doesn't have type coercion and the "any" type.

Playground
// Error: There is no "any" type in Hegel.
const something: any = null;
// Error: Type cast does not exist in Hegel
(null: any).call();