Problem
- There is no straight way to encode a predicate (some checking) about a value in its type.
Details
There are situations when a value has to pass some sort of check/validation prior to being used. For example: a min/max functions can only operate on a non-empty array so there must be a check if a given array has any elements. If we pass a plain array that might as well be empty, then we need to account for such case inside the min/max functions, by doing one of the following:
- crashing
- returning undefined
- returning a given default value
This way the calling side has to deal with the consequences of min/max being called yet not being able to deliver.
functionmin<a>(values: a[],isGreater: (one: a,another: a)=>boolean) : a{if(values.length<1){throwError('Array is empty.');}// ...}varfound;try{found=min([]);}catch(e){found=undefined;}Solution
A better idea is to leverage the type system to rule out a possibility of the min function being called with an empty array. In order to do so we might consider so called tag types.
A tag type is a qualifier type that indicates that some predicate about a value it is associated with holds true.
constenumAsNonEmpty{}// a tag type that encodes that a check for being non-empty was passedfunctionmin<a>(values: a[]&AsNonEmpty) : a{// only values that are tagged with AsNonEmpty can be passed to this function// the type system is responsible for enforcing this constraint// a guarantee by the type system that only non-empty array will be passed makes// implementation of this function simpler because only one main case needs be considered// leaving empty-case situations outside of this function}min([]);// <-- compile error, tag is missing, the argument is not known to be non-emptymin([1,2]);// <-- compile error, tag is missing again, the argument is not know to be non-emptyit's up to the developer in what circumstances an array gets its AsNonEmpty tag, which can be something like:
// guarntee is given by the server, so we always trust the data that comes from itinterfaceGuaranteedByServer{values: number[]&AsNonEmpty;}Also tags can be assigned at runtime:
functionasNonEmpty(values: a[]) : (a[]&AsNonEmpty)|void{returnvalues.length>0 ? <a[]&AsNonEmpty> : undefined;}functionisVoid<a>(value: a|void) : value is void{returnvalue==null;}varvalues=asNonEmpty(Math.random()>0.5 ? [1,2,3] : []);if(isVoid(values)){// there are no elements in the array, so we can't call minvarniceTry=min(values);// <-- compile error; }else{varfound=min(values);// <-- ok, tag is there, safe to call}As was shown in the current version (1.6) an empty const enum type can be used as a marker type (AsNonEmpty in the above example), because
- enums might not have any members and yet be different from the empty type
- enums are branded (not assignable to one another)
However enums have their limitations:
- enum is assignable by numbers
- enum cannot hold a type parameter
- enum cannot have members
A few more examples of what tag type can encode:
string & AsTrimmed & AsLowerCased & AsAtLeast3CharLongnumber & AsNonNegative & AsEvendate & AsInWinter & AsFirstDayOfMonth
Custom types can also be augmented with tags. This is especially useful when the types are defined outside of the project and developers can't alter them.
ALSO NOTE: In a way tag types are similar to boolean properties (flags), BUT they get type-erased and carry no rutime overhead whatsoever being a good example of a zero-cost abstraction.
UPDATED:
Also tag types can be used as units of measure in a way:
string & AsEmail, string & AsFirstName:
varemail=<string&AsEmail>'aleksey.bykov@yahoo.com';varfirstName=<string&AsFirstName>'Aleksey';firstName=email;// <-- compile error
number & In<Mhz>, number & In<Px>:
varfreq=<number&In<Mhz>>12000;varwidth=<number&In<Px>>768;freq=width;// <-- compile errorfunctiondivide<a,b>(left: number&In<a>,right: number&In<b>&AsNonZero) : number&In<Ratio<a,b>>{return<number&In<Ratio<a,b>>>left/right;}varratio=divide(freq,width);// <-- number & In<Ratio<Mhz, Px>>
Problem
Details
There are situations when a value has to pass some sort of check/validation prior to being used. For example: a min/max functions can only operate on a non-empty array so there must be a check if a given array has any elements. If we pass a plain array that might as well be empty, then we need to account for such case inside the min/max functions, by doing one of the following:
This way the calling side has to deal with the consequences of min/max being called yet not being able to deliver.
Solution
A better idea is to leverage the type system to rule out a possibility of the min function being called with an empty array. In order to do so we might consider so called tag types.
A tag type is a qualifier type that indicates that some predicate about a value it is associated with holds true.
it's up to the developer in what circumstances an array gets its AsNonEmpty tag, which can be something like:
Also tags can be assigned at runtime:
As was shown in the current version (1.6) an empty const enum type can be used as a marker type (AsNonEmpty in the above example), because
However enums have their limitations:
A few more examples of what tag type can encode:
string & AsTrimmed & AsLowerCased & AsAtLeast3CharLongnumber & AsNonNegative & AsEvendate & AsInWinter & AsFirstDayOfMonthCustom types can also be augmented with tags. This is especially useful when the types are defined outside of the project and developers can't alter them.
User & AsHavingClearanceALSO NOTE: In a way tag types are similar to boolean properties (flags), BUT they get type-erased and carry no rutime overhead whatsoever being a good example of a zero-cost abstraction.
UPDATED:
Also tag types can be used as units of measure in a way:
string & AsEmail,string & AsFirstName:number & In<Mhz>,number & In<Px>: