11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-09-07

ES6のサブクラス化仕様

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

class MyNumber extends Number{
	//...
}

ただし、これをやるためにはランタイム側がサブクラス化に対応している必要があり(Babelなどでは対応できない)、現時点(2015-09-08)では対応しているものも多くない

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

Intクラスの作成

コードはシンプルだ。

"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クラスなども作れる。

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のインスタンス扱いにすらならない)。

まあこうなるよね
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ではできないので以下のような使い方は(親クラスとはことなり)できない。

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

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

TypeScript

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

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

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

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型を定義してみてはどうだろう。

かわらない…
"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()の戻り値の型までは見てくれないらしい。現状では演算子の両辺で以下のようにするしかない。

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

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

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

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

IntNumberを継承しているので代入してもエラーにならない。

ガチガチにやるなら

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

なお、ちょうど今現在面白い提案がされている。これが実装されると以下のようなことができるかもしれない。

//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を継承しない方がいいかも
11
11
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?