Introducing types to JS without type annotations

Type Preservation vs Type Annotation

I know this idea is crazy but it feels wonderful.
In the majority of languages we use type annotations to declare the type of an identifier.
Eg:- In TypeScript

let day: string = "monday";

Eg:- C/C++

int day = 2;

But there are several problems with this approach.

  • As the program grows, the code becomes more verbose.
  • The language becomes rigid; JavaScript is an extremely flexible language, A type system with type annotation will reduce its flexibility.
  • Increases development time.

What is Type Preservation ??

It just means that we preserve the type of a variable/identifier.
i.e, if a variable is initialized with an integer, then that variable can only hold integer values.

Experimental Syntax:

//Variables
let <day> = "monday";
     day = "sunday"; //Ok
     day = 3; //Error

//Functions
function <planet>(<name> = "") {
   return name + "";
}
planet("Earth") //Earth
planet("Mars") //Mars
planet(100) //Error

//Objects
let farm = {
<owner>: "Deadpool",
<animals>: ["dog", "cat", "cow"],
<number>: [1, 2, 3],
<total>: 6
}
farm.owner = "Hulk"; //Ok
farm.animals.push("fish"); //Ok
farm.animals.push(true); //Error
farm.total = "7"; //Error

Looks cool right??
Without sacrificing too many stuff we have achieved static typing!!
This idea is not perfect in any means. Please help me improve it further.

3 Likes

What's an "integer"? JS only has Number and BigInt.

What if a variable happens to hold a Promise, but the intention is for it to merely hold a thenable?

1 Like

Sorry Number it is, that was a mistake!!
(I was thinking in terms of other typed language, my bad!)
As for the promises what will happen when we assign them to a variable at present?? The same is going to happen if the type preservation is applied. This proposal is not changing anything in the current language. But aims to provide developers a means to exercise control over typing of identifiers.
It's as safe as private class fields. Nothing will break with its inclusion.

I realize that this would be opt-in, which is fine. It seems like the "type" being preserved is inferred - and what I'm asking is, what happens when the type i want the variable constrained to is not an exact match to what gets inferred? (for example, i assign a Promise but want "thenables", or I assign a plain object but want "a specific interface this object provides")

2 Likes

Reply

You're right!! The type is inferred by the compiler at the initialisation of the identifier, saving the developer from manually asserting the type.

A value assigned to a variable must evaluate to be a primitive(that's how the typeof operator works!!)
Promise is an instanceof Object, here object is the primitive type(i.e, typeof Promise === "object" ), therefore the variable can only hold objects.
Eg:-

let <regex> = new RegExp("(she|he)llo");
//regex is reserved for objects but not specifically for the instance RegExp.
     regex = {} //perfectly valid

If preferred by the language developers, variable preserved for specific object instances can also be implemented.
My suggestion:

let <regex instanceof RegExp> = new RegExp("(she|he)llo");
//or
let <regex in RegExp> = new RegExp("(she|he)llo");
//or
let <regex of RegExp> = new RegExp("(she|he)llo");

    regex = {}; //Error

Variables with object instance preservation can be done likewise by giving a new meaning to any of the membership operators mentioned above within the angle brackets.

  • instanceof is much more readable.
  • in is much more concise.
  • of can also be considered.

However, my initial suggestion is to implement the variable preservation for primitive types only.
The object instance preservation can be set aside for later consideration.
The reason I say this is because:

let <greet in String>;
    greet = "hello";

Does this behavior be endorsed??

  • We should decide whether greet can only hold an object of instance String or a primitive string.
  • what will happen if:
	let <greet String>;
	console.log(greet);//undefined or ""

These decisions along with others should be considered only after rigorous discussions.
The discussion on implementation details is the concern of language implementers. Not the scope of my concern.


This however is an experimental syntax and should be treated as such. Therefore, the syntax should't be taken into account for the evaluation. The idea of variable preservation should be the focus.

A valid concern that I raise is that the current syntax might cause some problems with compiler like Babel, Harp, or Svelte, because they use the angle brackets extensively(like in JSX). Therefore the syntax shouldn't be considered.

In my proposal, type preservation of identifiers works in relation to the typeof operator(which of course has some pitfalls). @Ijharb, your suggestion is that it should also take instancing of objects in consideration(relating the instanceof operator). As I said earlier, the details should be dealt with the language implementers(i.e, if this feature is ever introduced to the language).

Conclusion

This idea has never been introduced to any other language. If JavaScript implements this feature, it will be the first to introduce a revolutionary way of type setting identifiers, and also it will prove to the world that an eighteen year old can make a difference...

Certainly, an 18 year old can make a difference! Regardless of type inference :smile:

1 Like

instaceof in JS does not reliable. If you gets an array from another web page it doesn't "instance of" Array in this page.
It seems like your solution is based on the call structure of the objects.

What if I want to represent a union type?

Perhaps in the place of the type annotation is a function. Every assignment leads to the function being called with a single argument, the value on the right hand side that was assigned. Returning false would throw an exception. Returning true would lead to the assignment succeed.

Implementations could assume that returning false is unusual and optimize for the true case.

function notnull(v) {
  return v !== null;
}
let <foo nonnull> = "test";
foo = "hi"; // ok
foo = null; // throws an exception

If we want implicit checking: the prototype of the value assigned can have a property @@assign, which is read to become the assignment-checking function. For performance, the function would be bound to the declaration and not changeable.

let <regex> = new RegExp("(she|he)llo");
3 Likes