Skip to content

Math with Number Literal Type #26382

Description

@AnyhowStep

Search Terms

number literal type, math

If there's already another such proposal, I apologize; I couldn't find it and have been asking on Gitter if such a proposal was already brought up every now and then.

Suggestion

We can have number literals as types,

constx : 32=34;//Errorconsty : 5=5;//OK

If possible, math should be enabled with these number literals,

constx : 32+5=38;//Errorconsty : 42+10=52;//OKconstz : 10-22=-12;//OK

And comparisons,

constx : 32>=3 ? true : false=true;//OKconsty : 32>=3 ? true : false=false;//Error//Along with >, <, <=, ==, != maybe?

And ways to convert between string literals and number literals,

//Not too sure about syntaxconstw : (string)32="hello";//Errorconstx : (string)10="10";//OKconsty : (number)"45"=54;//Errorconstz : (number)"67"=67;//OK

Use Cases

One such use case (probably the most convincing one?) is using it to implement tuple operations.
Below, you'll find the types Add<>, Subtract<>, NumberToString<>, StringToNumber<>.

They have been implemented with... Copy-pasting code until the desired length.
Then, using those four types, the tuple operations are implemented.

While this works, having to copy-paste isn't ideal and shows there's something lacking in the language.
I've found that I've had to increase the number of copy-pastes every few days/weeks as I realize I'm working with larger and larger tuples over time.

The below implementation also ignores negative numbers for simplicity but supporting negative numbers would be good.

/*function gen (max) {	const base = [];	const result = [];	for (let i=0; i<max; ++i) { if (i == max-1) { base.push(`${i}: number;`); } else { base.push(`${i}: ${i+1};`); } if (i>=2) { result.push(`${i}: Add<Add<T, ${i-1}>, 1>;`); }	}	const a = base.join("\n ");	const b = result.join("\n ");	return `${a}\n }[T];\n ${b}`}gen(100)*/exporttypeAdd<Textendsnumber,Uextendsnumber>={[index: number]: number;0: T;1: {[index: number]: number;0: 1;1: 2;2: 3;3: 4;4: 5;5: 6;6: 7;7: 8;8: 9;9: 10;10: 11;11: 12;12: 13;13: 14;14: 15;15: 16;16: 17;17: 18;18: 19;19: 20;20: 21;21: 22;22: 23;23: 24;24: number;}[T];2: Add<Add<T,1>,1>;3: Add<Add<T,2>,1>;4: Add<Add<T,3>,1>;5: Add<Add<T,4>,1>;6: Add<Add<T,5>,1>;7: Add<Add<T,6>,1>;8: Add<Add<T,7>,1>;9: Add<Add<T,8>,1>;10: Add<Add<T,9>,1>;11: Add<Add<T,10>,1>;12: Add<Add<T,11>,1>;13: Add<Add<T,12>,1>;14: Add<Add<T,13>,1>;15: Add<Add<T,14>,1>;16: Add<Add<T,15>,1>;17: Add<Add<T,16>,1>;18: Add<Add<T,17>,1>;19: Add<Add<T,18>,1>;20: Add<Add<T,19>,1>;21: Add<Add<T,20>,1>;22: Add<Add<T,21>,1>;23: Add<Add<T,22>,1>;24: Add<Add<T,23>,1>;}[U];/*function gen (max) {	const base = [];	const result = [];	for (let i=1; i<=max; ++i) { base.push(`${i}: ${i-1};`); if (i>=2) { result.push(`${i}: Subtract<Subtract<T, ${i-1}>, 1>;`); }	}	const a = base.join("\n ");	const b = result.join("\n ");	return `${a}\n }[T];\n ${b}`}gen(100)*/exporttypeSubtract<Textendsnumber,Uextendsnumber>={[index: number]: number;0: T;1: {[index: number]: number;0: number;1: 0;2: 1;3: 2;4: 3;5: 4;6: 5;7: 6;8: 7;9: 8;10: 9;11: 10;12: 11;13: 12;14: 13;15: 14;16: 15;17: 16;18: 17;19: 18;20: 19;21: 20;22: 21;23: 22;24: 23;25: 24;}[T];2: Subtract<Subtract<T,1>,1>;3: Subtract<Subtract<T,2>,1>;4: Subtract<Subtract<T,3>,1>;5: Subtract<Subtract<T,4>,1>;6: Subtract<Subtract<T,5>,1>;7: Subtract<Subtract<T,6>,1>;8: Subtract<Subtract<T,7>,1>;9: Subtract<Subtract<T,8>,1>;10: Subtract<Subtract<T,9>,1>;11: Subtract<Subtract<T,10>,1>;12: Subtract<Subtract<T,11>,1>;13: Subtract<Subtract<T,12>,1>;14: Subtract<Subtract<T,13>,1>;15: Subtract<Subtract<T,14>,1>;16: Subtract<Subtract<T,15>,1>;17: Subtract<Subtract<T,16>,1>;18: Subtract<Subtract<T,17>,1>;19: Subtract<Subtract<T,18>,1>;20: Subtract<Subtract<T,19>,1>;21: Subtract<Subtract<T,20>,1>;22: Subtract<Subtract<T,21>,1>;23: Subtract<Subtract<T,22>,1>;24: Subtract<Subtract<T,23>,1>;25: Subtract<Subtract<T,24>,1>;}[U];/*function gen (max) {	const base = [];	for (let i=0; i<max; ++i) { base.push(`${i}: "${i}";`);	}	return base.join("\n ");}gen(101)*/exporttypeNumberToString<Nextendsnumber>=({0: "0";1: "1";2: "2";3: "3";4: "4";5: "5";6: "6";7: "7";8: "8";9: "9";10: "10";11: "11";12: "12";13: "13";14: "14";15: "15";16: "16";17: "17";18: "18";19: "19";20: "20";21: "21";22: "22";23: "23";24: "24";25: "25";26: "26";27: "27";28: "28";29: "29";30: "30";}&{[index : number] : never})[N];/*function gen (max) {	const base = [];	for (let i=0; i<max; ++i) { base.push(`"${i}": ${i};`);	}	return base.join("\n ");}gen(101)*/exporttypeStringToNumber<Sextendsstring>=({"0": 0;"1": 1;"2": 2;"3": 3;"4": 4;"5": 5;"6": 6;"7": 7;"8": 8;"9": 9;"10": 10;"11": 11;"12": 12;"13": 13;"14": 14;"15": 15;"16": 16;"17": 17;"18": 18;"19": 19;"20": 20;"21": 21;"22": 22;"23": 23;"24": 24;"25": 25;"26": 26;"27": 27;"28": 28;"29": 29;"30": 30;}&{[index: string]: never})[S];typeLastIndex<ArrTextendsany[]>=(Subtract<ArrT["length"],1>);typeIndicesOf<ArrT>=(Extract<Exclude<keyofArrT,keyofany[]>,string>);typeElementsOf<ArrT>=({[indexinIndicesOf<ArrT>] : ArrT[index]}[IndicesOf<ArrT>]);typeGtEq<Xextendsnumber,Yextendsnumber>=(numberextendsX ?
boolean :
numberextendsY ?
boolean :
numberextendsSubtract<X,Y> ?
//Subtracted too muchfalse :
true);typeKeepGtEq<Xextendsnumber,Yextendsnumber>=({[ninX]: (trueextendsGtEq<n,Y>?
n : never)}[X])typeSliceImpl<ArrTextendsany[],OffsetTextendsnumber>=({[indexinSubtract<KeepGtEq<StringToNumber<IndicesOf<ArrT>>,OffsetT>,OffsetT>]: (ArrT[Extract<Add<index,OffsetT>,keyofArrT>])});typeSlice<ArrTextendsany[],OffsetTextendsnumber>=(SliceImpl<ArrT,OffsetT>&ElementsOf<SliceImpl<ArrT,OffsetT>>[]&{length : Subtract<ArrT["length"],OffsetT>});declareconstsliced0: Slice<["x","y","z"],0>;constsliced0Assignment: ["x","y","z"]=sliced0;//OKdeclareconstsliced1: Slice<["x","y","z"],1>;constsliced1Assignment: ["y","z"]=sliced1;//OKdeclareconstsliced2: Slice<["x","y","z"],2>;constsliced2Assignment: ["z"]=sliced2;//OKdeclareconstsliced3: Slice<["x","y","z"],3>;constsliced3Assignment: []=sliced3;//OK//Pop FronttypePopFrontImpl<ArrTextendsany[]>=({[indexinExclude<IndicesOf<ArrT>,NumberToString<LastIndex<ArrT>>>]: (ArrT[Extract<Add<StringToNumber<index>,1>,keyofArrT>])});typePopFront<ArrTextendsany[]>=(PopFrontImpl<ArrT>&ElementsOf<PopFrontImpl<ArrT>>[]&{length: Subtract<ArrT["length"],1>});//Kind of like Slice<["x", "y", "z"], 1>declareconstpopped: PopFront<["x","y","z"]>;constpoppedAssignment: ["y","z"]=popped;//OK//ConcattypeConcatImpl<ArrTextendsany[],ArrUextendsany[]>=({[indexinIndicesOf<ArrT>] : ArrT[index]}&{[indexinNumberToString<Add<StringToNumber<IndicesOf<ArrU>>,ArrT["length"]>>]: (ArrU[Subtract<index,ArrT["length"]>])});typeConcat<ArrTextendsany[],ArrUextendsany[]>=(ConcatImpl<ArrT,ArrU>&ElementsOf<ConcatImpl<ArrT,ArrU>>[]&{length : Add<ArrT["length"],ArrU["length"]>});declareconstconcat0: Concat<[],["x","y"]>;constconcat0Assignment: ["x","y"]=concat0;declareconstconcat1: Concat<[],["x"]>;constconcat1Assignment: ["x"]=concat1;declareconstconcat2: Concat<[],[]>;constconcat2Assignment: []=concat2;declareconstconcat3: Concat<["a"],["x"]>;constconcat3Assignment: ["a","x"]=concat3;declareconstconcat4: Concat<["a"],[]>;constconcat4Assignment: ["a"]=concat4;declareconstconcat5: Concat<["a","b"],[]>;constconcat5Assignment: ["a","b"]=concat5;declareconstconcat6: Concat<["a","b"],["x","y"]>;constconcat6Assignment: ["a","b","x","y"]=concat6;typePushBackImpl<ArrTextendsany[],ElementT>=({[indexinIndicesOf<ArrT>] : ArrT[index]}&{[indexinNumberToString<ArrT["length"]>] : ElementT});typePushBack<ArrTextendsany[],ElementT>=(PushBackImpl<ArrT,ElementT>&ElementsOf<PushBackImpl<ArrT,ElementT>>[]&{length : Add<ArrT["length"],1>});declareconstpushBack0: PushBack<[],true>;constpushBack0Assignment: [true]=pushBack0;declareconstpushBack1: PushBack<[true],"a">;constpushBack1Assignment: [true,"a"]=pushBack1;declareconstpushBack2: PushBack<[true,"a"],"c">;constpushBack2Assignment: [true,"a","c"]=pushBack2;typeIndexOf<ArrTextendsany[],ElementT>=({[indexinIndicesOf<ArrT>]: (ElementTextendsArrT[index] ?
(ArrT[index]extendsElementT ? index : never) :
never);}[IndicesOf<ArrT>]);//Can use StringToNumber<> to get a numberdeclareconstindexOf0: IndexOf<["a","b","c"],"a">;//"0"declareconstindexOf1: IndexOf<["a","b","c"],"b">;//"1"declareconstindexOf2: IndexOf<["a","b","c"],"c">;//"2"declareconstindexOf3: IndexOf<["a","b","c"],"d">;//NeverdeclareconstindexOf4: IndexOf<["a","b","a"],"a">;//"0"|"2"declareconstindexOf5: IndexOf<["a","b","c"],"a"|"b">;//"0"|"1"//Splice//Pop Back//Push Front//And other tuple operations?//Implementing Map<> is even worse, you basically have to copy-paste some boilerplate code//for each kind of Map<> operation you want to implement because we//can't have generic types as type parameters

Examples

Addition and subtraction should only allow integers

typeexample0=1+1;//2typeexample1=1+number;//numbertypeexample2=number+1;//numbertypeexample3=number+number;//numbertypeexample4=1.0+3.0;//4typeexample5=1-1;//0typeexample6=1-number;//numbertypeexample7=number-1;//numbertypeexample8=number-number;//numbertypeexample9=1.0-3.0;//-2

If we did allow 5.1 - 3.2 as a type, we would get 1.8999999999999995 as a type.

typeinvalidSub=5.1-3.2;//Error, 5.1 not allowed; must be integer; 3.2 not allowed; must be integertypeinvalidAdd=5.1+3.2;//Error, 5.1 not allowed; must be integer; 3.2 not allowed; must be integer

Maybe throw a compiler error on overflow with concrete numeric types substituted in,

//Number.MAX_SAFE_INTEGER + 1typeoverflow=9007199254740992+1;//Should throw compiler error; overflow//Number.MIN_SAFE_INTEGER - 1000000typeoverflow2=-9007199254740991-1000000;//Should throw compiler error; overflowtypeOverflowIfGreaterThanZero<Nextendsnumber>=(9007199254740992+N);typeokay0=OverflowIfGreaterThanZero<0>;//Will be Number.MAX_SAFE_INTEGER, so no errortypeokay1=OverflowIfGreaterThanZero<number>;//No error because type is numbertypeerr=OverflowIfGreaterThanZero<1>;//Overflow; error

Comparisons should work kind of like extends

typegt=3>2 ? "Yes" : "No";//"Yes"typegteq=3>=2 ? "Yes" : "No";//"Yes"typelt=3<2 ? "Yes" : "No";//"No"typelteq=3<=2 ? "Yes" : "No";//"No"typeeq=3==3 ? "Yes" : "No";//"Yes"typeneq=3!=3 ? "Yes" : "No";//"No"

If either operand is number, the result should distribute

typegt0=number>2 ? "Yes" : "No";//"Yes"|"No"typegt1=2>number ? "Yes" : "No";//"Yes"|"No"typegt2=number>number ? "Yes" : "No";//"Yes"|"No"

Don't think floating-point comparison should be allowed.
Possible to have too many decimal places to represent accurately.

typeprecisionError=3.141592653589793<3.141592653589793238 ?
"Yes" : "No";//Ends up being "No" even though it should be "Yes" because precision

Converting between string and number literals is mostly for working with tuple indices,

typeexample0=(string)1;//"1"typeexample1=(string)1|2;//"1"|"2"typeexample2=(number)"1"|"2";//1|2

Converting from integer string literals to number literals should be allowed, as long as within MIN_SAFE_INTEGER and MAX_SAFE_INTEGER,
but floating point should not be allowed, as it's possible that the string can be a floating point number that cannot be accurately represented.

For the same reason, converting floating point number literals to string literals shouldn't be allowed.

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. new expression-level syntax)

Since this suggestion is purely about the type system, it shouldn't change any run-time behaviour, or cause any JS code to be emitted.

I'm pretty sure I've overlooked a million things in this proposal...

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions