ES6(ES2015)とTypeScriptでNumberを継承してIntを作る


# ES6のサブクラス化仕様

ES6(ES2015)ではビルトイン・オブジェクトである`Number`のサブクラスを作る事ができる。

```js
class MyNumber extends Number{
	//...
}
```

ただし、これをやるためにはランタイム側がサブクラス化に対応している必要があり（Babelなどでは対応できない）、現時点(2015-09-08)では[対応しているものも多くない](https://kangax.github.io/compat-table/es6/#miscellaneous_subclassables_Number_is_subclassable)。

数少ない対応環境であるChrome (バージョン 45.0.2454.85 (64-bit))をつかって今回は`Int`を作ってみる。


# Intクラスの作成

コードはシンプルだ。

```js
"use strict";
class Int extends Number{
	constructor(v){
		super(v|0);
	}
}

var a = new Int(12.34);
console.log(a);	// 12
console.log(a+a);	// 24
```

Chrome/Opera/io.jsのV8は`"use strict";`ディレクティブをつけることで`Number`を継承したサブクラスを作ることができる。

コンストラクタの引数の数値を`|0`することで 32bit signed int を作る事が出来る（asm.jsと同じ）。

`+`などの算術演算子も`Number`扱いで計算してくれるのでTypeErrorなどにはならない。


同様に`UInt`クラスなども作れる。

```js:UInt
"use strict";
class UInt extends Number{
	constructor(v){
		super(v>>>0);
	}
}
var u = new UInt(-12.3);
console.log(`u:${u}`);	//u:4294967284
```

# 問題

無事に`Int`クラスができたが、`Int`クラスのインスタンスに普通に数値を代入すると普通の数値になってしまう（`Number`のインスタンス扱いにすらならない）。

```javascript:まあこうなるよね
var a = new Int(12.34);	//12
console.log(a instanceof Int);	//true
console.log(a instanceof Number);//true
a = 56.7;
console.log(a);	//56.7
console.log(a instanceof Int);	//false
console.log(a instanceof Number);//false
```

常に整数値になるように、みたいな、型のある言語のようにはいかない。

また`Number`型は`new`なしの使い方ができるが、staticなコンストラクタ、みたいな定義がES6ではできないので以下のような使い方は（親クラスとはことなり）できない。

```javascript:Number()にはできてもInt()にはできない
var a = Int(12.3);	//Uncaught TypeError: Class constructors cannot be invoked without 'new'
```

`x|0`や`x>>>0`でいいか、となりそう。`x|0`や`x>>>0`を直接書かないメリットは、一種のマジックナンバーの防止、ぐらいだろうか。

# TypeScript

型チェックが欲しいならTypeScript、ということでやってみる。ちょうど出たばかりの1.6betaがビルトインオブジェクトのサブクラス化でエラーが出なくなったのでトライ。`-m "es6"`で直接ES6を出力させる。

```js:typescript1.6.0-beta
"use strict";
class Int extends Number{
	constructor(v:any){
		super(v|0);
	}
}
```

クラス定義は問題ない。ところがもう少し使おうとすると問題がでる。

```js
var a = new Int(12.34);
console.log(a);	// 12
console.log(a+a);	// error TS2365: Operator '+' cannot be applied to types 'Int' and 'Int'.
```

`+`などの算術演算子が`Number`のサブクラスの演算に対応していない。

そこで`valueOf()`メソッドを定義してみる。`valueOf()`メソッドは`+`の時に暗黙的に呼ばれるので、戻り値の型として`number`型を定義してみてはどうだろう。

```js:かわらない…
"use strict";
class Int extends Number{
	constructor(v:any){
		super(v|0);
	}
	
	valueOf():number{
		return super.valueOf();
	}
}

var a = new Int(12.34);
console.log(a+a);	// error TS2365: Operator '+' cannot be applied to types 'Int' and 'Int'.
```

うーむ、`valueOf()`の戻り値の型までは見てくれないらしい。現状では演算子の両辺で以下のようにするしかない。

```js
console.log(<number>a+<number>a)
```

TypeScriptに、型チェックのためだけの演算子オーバーロードが欲しい。

なお、以下の問題はTypeScriptでも起きる。

```js
var a = new Int(12.34);	//12
console.log(a instanceof Int);	//true
console.log(a instanceof Number);//true
a = 56.7;
console.log(a);	//56.7
console.log(a instanceof Int);	//false
console.log(a instanceof Number);//false
```

`Int`が`Number`を継承しているので代入してもエラーにならない。

## ガチガチにやるなら

```js:Numberを継承しない
class IntX{
	private _value:number;
	constructor(v:any){
		this._value = v|0;
	}
	
	valueOf():number{
		return this._value|0;
	}
	
	toString():string{
		return this._value.toString();
	}
}
var a = new IntX(12.3);
console.log(a);	//12
var b = Number(a)+Number(a);
console.log(b);	//24
a = 34.5;	//error TS2322: Type 'number' is not assignable to type 'IntX'. Property '_value' is missing in type 'Number'.
a = new IntX(1);	//ok
```

ガチガチにやるなら、`Number`を継承しないクラスを作る手もある。算術演算子の時のみ`Number()`でキャストする必要があるが、インスタンス定義後に勝手に数値を代入できない（整数であっても、だが）。`valueOf()`や`toString()`を定義しておくことで数値的な扱いもできる。

## Proposal #4639

なお、ちょうど今現在[面白い提案](https://github.com/Microsoft/TypeScript/issues/4639)がされている。これが実装されると以下のようなことができるかもしれない。

```js
//ts
let a = int<32>(x);
//js
var a = x | 0;

//ts
int<8>(x);
//js
(x | 0) << 24 >> 24
```

# まとめ

 * ES6はビルトインオブジェクトを継承できる
  * でも出来る環境は限られる
 * Intっぽいものも作る事ができる
  * けど、型のあるような言語っぽくはならない
 * TypeScriptでも同様にできる
  * 1.6以降
  * 厳格に型チェックしたいならNumberを継承しない方がいいかも