Genericsを使ったクラスのコンストラクタの引数の制約
以下の条件を満たすItem<V>
型を受け入れるGeneric Typeを持つクラスを定義するつもりで、以下のクラス Generator
を定義しました。
Generator
クラスはItem<unknown>
型を受け入れるGeneric Typeを持つ(Generic constraints)Item<V>
の Generic Type(V
)の型は制限しないこと(=unknown
type)としたい- コンストラクタで受け取る
T
型 (=extends Item<unknown>
) とProcessor<T>
型において、同一のT
であるという制約を設けたい
interface Item<V> {
value: V
clone: () => Item<V>
}
class Processor<U extends Item<unknown>> {
setObject(obj: U) { }
}
class Generator<T extends Item<unknown>> {
constructor(private obj: T, private processor: Processor<T>) {}
process() {
const cloned = this.obj.clone()
this.processor.setObject(cloned)
}
}
しかし、上記のような定義では、process()
のthis.processor.setObject(cloned)
においてType Errorとなります。
cloned
の型はItem<unknown>
と評価されている状態(this.obj
はItem<unknown>
として評価されるため、Item<unknown> = {value: unknown, clone: () => Item<unknown>
と評価される)- (わたしはここ
cloned
の型はT
と評価されるべきと勘違いしてしまっていたのだった)
- (わたしはここ
- 一方で
this.processor.setObject
の第一引数はT
型を要求している
T
型よりもItem<unknown>
のほうが取りうる型の範囲が広いので、以下のType Errorが表示されます。
Argument of type 'Item<unknown>' is not assignable to parameter of type 'T'.
'Item<unknown>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Item<unknown>'.
よく考えてみれば、制約をしたいのは Item<V>
そのものではなく、V
なので、以下のように修正してみます。
class Generator<T> {
constructor(private obj: Item<T>, private processor: Processor<Item<T>>) {}
process() {
const cloned = this.obj.clone()
this.processor.setObject(cloned)
}
}
以下のようなItemを実装したクラスを定義したとき
class NumberItem implements Item<number> {
constructor(private _value: number) {}
get value() { return this._value }
clone() { return new NumberItem(this._value) }
}
class StringItem implements Item<string> {
constructor(private _value: string) {}
get value() { return this._value }
clone() { return new StringItem(this._value) }
}
以下のように記述すると、期待通りの評価結果となりました。コンストラクタに渡した引数に対して型推論が行われるので、 Generator<T>
の T
型を明示的に与えなくても型による制約を得られます。
// T型(= NumberItem) が一致するので型チェックはパスする
const testB1 = new GeneratorB(new NumberItem(1), new Processor<NumberItem>)
// Type Error (T型が一致しない)
const testB2 = new GeneratorB(new NumberItem(1), new Processor<StringItem>)
気が付けば「たしかにそれはそう」となりそうなことですが、なかなか気づけずにはまってしまったのでここにメモを残しておきます。
ではでは。
このカウンタは @piyoppi/counter-tools を使っています。
クリックすると匿名でいいねできます。