Actually there is a way to achieve what you want to achieve, but it's a bit tricky, has some limitations and could be completely unreasonable to a person who sees that code for the first time, so treat it as a curiosity rather than actual implementation ;)
Okay, so let's go. First, we need to create a "subclass" of Number
. The problem is, that lib.d.ts actually declares Number
as an interface, not a class (which is reasonable - no need to implement methods, browser takes care of that). So we have to implement all the methods declared by the interface, thankfully we can use existing implementation of declared var Number
.
class WrappedNumber implements Number {
//this will serve as a storage for actual number
private value: number;
constructor(arg?: number) {
this.value = arg;
}
//and these are the methods needed by Number interface
toString(radix?: number): string {
return Number.prototype.toString.apply(this.value, arguments);
}
toFixed(fractionDigits?: number): string {
return Number.prototype.toFixed.apply(this.value, arguments);
}
toExponential(fractionDigits?: number): string {
return Number.prototype.toExponential.apply(this.value, arguments);
}
toPrecision(precision: number): string {
return Number.prototype.toPrecision.apply(this.value, arguments);
}
//this method isn't actually declared by Number interface but it can be useful - we'll get to that
valueOf(): number {
return this.value;
}
}
There you go, we created a type WrappedNumber
which behaves just like number type. You can even add two WrappedNumber
s - thanks to the valueOf()
method. 2 limitations here, however: first, you need to cast variables to perform this operation. Second: the result will be a regular number
, so it should be again wrapped afterwards. Let's look at an example of addition.
var x = new WrappedNumber(5);
var y = new WrappedNumber(7);
//We need to cast x and y to <any>, otherwise compiler
//won't allow you to add them
var z = <any>x + <any>y;
//Also, compiler now recognizes z as of type any.
//During runtime z would be a regular number, as
//we added two numbers. So instead, we have to wrap it again
var z = new WrappedNumber(<any>x + <any>y); //z is a WrappedNumber, which holds value 12 under the hood
And here comes the most, in my opinion, tricky part. We now create 2 classes, Latitude
and Longitude
which will inherit from WrappedNumber
(so that they behave as numbers)
class Latitude extends WrappedNumber {
private $;
}
class Longitude extends WrappedNumber {
private $;
}
What the heck? Well, TypeScript uses duck typing when comparing types. Which means that two different types are considered to be "compatible" (and therefore assignable to itselves, i.e you can assign variable of one type to a value of other) when they have the same set of properties. And the solution is really simple: add a private member. This private member is pure virtual, it's not used anywhere and won't be compiled. But it makes TypeScript think that Latitude
and Longitude
are completely different types and, which we are interested in more, won't allow to assign variable of type Longitude
to that of type Latitude
.
var a = new Latitude(4);
var b: Longitude;
b = a; //error! Cannot convert type 'Latitude' to 'Longitude'
Voila! That's what we wanted. But the code is messy and you need to remember to cast types, which is really inconvenient, so don't use that actually. However, as you see, it's possible.