角待ちは対空

おもむろガウェイン

「劇場版 魔法科高校の劣等生 星を呼ぶ少女」見た

僕はこのアニメ好きで結構な回数リピートしている。このアニメ何が良いかっていうとお兄様が本当に強い。俺TUEEEEという言葉があるけど、そこらの俺強いとは一線を画していると思う。キリトくんは本当の強さがなにか思い悩む洒落臭い部分があるけどお兄様はそういうのないのが良い。

TVシリーズは良かった。もっと強いお兄様を望む視聴者をちゃんと満足させてくれた。最終的に核兵器扱いですからね。とてもよかった。翻って映画はどうだったかっていうといまいちだったと言わざるをえない。もっと強いお兄様を提供できていない。伝説になるっていうキャッチコピーをなしていない。言っとくけどお兄様、TVシリーズですでに歴史に名を刻んでますからね。ちゃんと伝説になってくれないと困るわけですよ。我々はお兄様のジャンプアップを、飛翔を期待していたわけですよ。山場が衛生破壊ってのが良くなかった。だって開始10分で当たり前のように隕石破壊してるんですよ。ジャンプアップが少ないって。

とは言いつつまぁそんなに悪い映画でもなかった。個人的に良かったのは当たり前のように軍を敵に回すことを良しとしているところとか、当たり前のように宇宙に行くとことか、だんだん常識のタガが外れ始めているとことですね。

ちなみにゲームにしか出てこないキャラが結構重要な立ち位置で出て来る。許されるのかそんなことが。

魔法科高校の劣等生 Out of Order - PS Vita

魔法科高校の劣等生 Out of Order - PS Vita

TypeScript 2.4のSafer callback parameter checkingについて

TypeScript 2.4 RCがリリースされました。

Announcing TypeScript 2.4 RC | TypeScript

いくつか変更点があるのですがこのエントリではSafer callback parameter checkingについて解説します。公式ドキュメントでいうとFAQ · Microsoft/TypeScript Wiki · GitHubあたりの話に関連します。あるいはなぜ TypeScript の型システムが健全性を諦めているか - Qiitaとも関連します。

Dog[]Animal[] のサブタイプか

TSでは DogAnimal のサブタイプである時、Dog[]Animal[] のサブタイプです。型システムとして健全かどうかは置いといて便利なのでこうなっています。

さて、Dog[]Animal[] に代入可能かどうかを判定する際コンパイラは最終的に、(x: Dog) => number(x: Animal) => number に代入可能かを調べることになります。ではこれをどうやって判定するのでしょうか?答えは「DogAnimalに代入可能もしくはAnimalDogに代入可能」かで判定します。

つまり「DogAnimalに代入可能もしくはAnimalDogに代入可能 ならば(x: Dog) => number(x: Animal) => numberに代入可能」ということです。

閑話

本筋とはズレますが、Dog[]Animal[] のサブタイプとしたときに、型システムとして健全性が崩れる例です。

class Animal {
}

class Dog extends Animal {
    bark(): void {}
}

let a = [new Animal]
let d = [new Dog]

a = d;


a[0] = new Animal

for ( let item of d) {
    item.bark()
}

// => Uncaught TypeError: item.bark is not a function

この仕様の問題点

Dog[]Animal[] のサブタイプであることの問題点はさておき、「DogAnimalに代入可能もしくはAnimalDogに代入可能 ならば(x: Dog) => number(x: Animal) => numberに代入可能」となることが問題になります。

アナウンスブログからの引用でいうと

interface Animal { animalStuff: any }
interface Dog extends Animal { bark(): void }

interface BasicCollection<T> {
    forEach(callback: (value: T) => void): void;
}

declare let animalCollection: BasicCollection<Animal>;
declare let dogCollection: BasicCollection<Dog>;

// This should be an error, but TypeScript 2.3 and below allow it.
dogCollection = animalCollection;

dogCollectionanimalCollectionが代入可能かどうかは最終的にはインターフェースBasicCollectionforEachの引数になっているcallback部分が代入可能化どうかで判定されます。つまりcallback: (value: Dog) => voidcallback: (value: Animal) => voidが代入可能かどうかですが、配列の例で見た時と同じロジックで代入可能と判断されdogCollection = animalCollectionはエラーになりません。

具体的にcallback

dogCollection.forEach((value: Dog) => {value.bark()});

だと想定すると実行時にエラーになるのがわかると思います。

これはPromise<T> におけるthenにも当てはまりますので、Promise<Animal>Promise<Dog> に代入可能なことになってしまいます。

2.4からどうなるのか

callback関数の判定時は特別に(x: Dog) => void(x: Animal) => void に代入可能かの判定がAnimalDogに代入可能かで判定されるようになります。

というわけでdogCollection = animalCollectionはエラーとなり、animalCollection = dogCollectionは(引き続き)エラーになりません。またPromise<Animal>Promise<Dog>に代入することもできなくなります。

TS 2.4以前(playdroundがアップデートされるまではエラーが出ない様子が見れると思います)。

2.4以降は以下。

interface Animal {

}
interface Dog extends Animal {
    someProperty: string
}

let a: Promise<Animal>;
let d: Promise<Dog>;

d = a;

// =>
// a.ts(11,1): error TS2322: Type 'Promise<Animal>' is not assignable to type 'Promise<Dog>'.
//   Type 'Animal' is not assignable to type 'Dog'.
//     Property 'someProperty' is missing in type 'Animal'.

まとめ

Dog[]Animal[] のサブタイプにするためにPromise<Animal>Promise<Dog>に代入可能でしたが、2.4からはPromise<Animal>Promise<Dog>に代入できなくなります。Dog[]Animal[] のサブタイプであることはそのままです。