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 parametersExamples
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:
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...
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,
If possible, math should be enabled with these number literals,
And comparisons,
And ways to convert between string literals and number literals,
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.
Examples
Addition and subtraction should only allow integers
If we did allow
5.1 - 3.2as a type, we would get1.8999999999999995as a type.Maybe throw a compiler error on overflow with concrete numeric types substituted in,
Comparisons should work kind of like
extendsIf either operand is
number, the result should distributeDon't think floating-point comparison should be allowed.
Possible to have too many decimal places to represent accurately.
Converting between string and number literals is mostly for working with tuple indices,
Converting from integer string literals to number literals should be allowed, as long as within
MIN_SAFE_INTEGERandMAX_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:
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...