Apple 製品と MS 製品の混在したリストがあったとして、そこから Apple 製品だけ、または MS 製品だけのリストをつくるという例を考える。
そのための事前準備として次の type を定義をする:
type Apple = 'macbook pro' | 'macbook air' | 'mac mini' | 'mac studio'
type MS = 'surface pro' | 'surface go' | 'surface laptop'
そして Apple または MS の両方を表現した type を定義:
type Machine = Apple | MS
さらに今回実験で使う Apple と MS 製品の両方を含んだリスト xs :
const xs: Machine[] = ['surface go', 'mac mini', 'surface laptop']
なお実験では deno を使って typescript を実行します。
$ deno --version deno 2.4.1 (stable, release, x86_64-unknown-linux-gnu) v8 13.7.152.6-rusty typescript 5.8.3
与えられた Machine が Apple か MS かを判別するガード用関数を定義:
const isApple = (v: Machine): boolean => { return v.startsWith('mac') }
const isMS = (v: Machine): boolean => { return v.startsWith('surface') }
これを使えば、リスト xs から Apple 製品だけを取り出したリストをつくるには次のようになります。
const appleMachines = xs.filter((it: Machine)=> isApple(it))
ここまでをコードにして実行してみます。
コード:
// main.ts
type Apple = 'macbook pro' | 'macbook air' | 'mac mini' | 'mac studio'
type MS = 'surface pro' | 'surface go' | 'surface laptop'
type Machine = Apple | MS
const isApple = (v: Machine): boolean => { return v.startsWith('mac') }
const isMS = (v: Machine): boolean => { return v.startsWith('surface') }
const xs: Machine[] = ['surface go', 'mac mini', 'surface laptop']
const appleMachines = xs.filter((it: Machine)=> isApple(it))
console.log(appleMachines)
実行:
$ deno --check main.ts
[ "mac mini" ]
これでガードできました。
ただ・・・ここからが本題なのですが、たとえば MyMachines という自分が持っているマシンを表現する type を定義するとします:
type MyMachines = {
appleMachines: Apple[]
msMachines: MS[]
}
そして、ここに先ほど計算した appleMachines を入れる:
const myMachines: MyMachines = {
appleMachines: appleMachines,
msMachines: []
}
実行する:
$ deno --check main.ts
TS2322 [ERROR]: Type 'Machine[]' is not assignable to type 'Apple[]'.
Type 'Machine' is not assignable to type 'Apple'.
Type '"surface pro"' is not assignable to type 'Apple'.
appleMachines: appleMachines,
~~~~~~~~~~~~~
The expected type comes from property 'appleMachines' which is declared here on type 'MyMachines'
appleMachines: Apple[]
~~~~~~~~~~~~~
error: Type checking failed.
type check で エラーになります。
そもそも、この部分:
const appleMachines = xs.filter((it: Machine)=> isApple(it))
ここで appleMachines: Apple[] の型に(こちらの脳内では)なっているが、実際は appleMachines: Machine[] にしかなっていない。 つまり コンパイラからみれば appleMachines の各要素の type は 依然として Apple | MS (つまり Machine ってことですけど)のまま。
これどうしたら type を Apple に限定できるか?と思って調べたのですが(Claude にきいただけです)、 結論としてはガード関数の戻値を boolean ではなく v is Apple に変更すればOKでした。
つまり:
// const isApple = (v: Machine): boolean => { return v.startsWith('mac') }
const isApple = (v: Machine): v is Apple => { return v.startsWith('mac') }
v is Apple という型があるの?(よくわかっていない)まあこれで意図通り作動します。
MS の方も同じように変更。 そうやって、完成したコードはこちら:
// main.ts
type Apple = 'macbook pro' | 'macbook air' | 'mac mini' | 'mac studio'
type MS = 'surface pro' | 'surface go' | 'surface laptop'
type Machine = Apple | MS
const isApple = (v: Machine): v is Apple => { return v.startsWith('mac') }
const isMS = (v: Machine): v is MS => { return v.startsWith('surface') }
const xs: Machine[] = ['surface go', 'mac mini', 'surface laptop']
const appleMachines: Apple[] = xs.filter((it: Machine)=> isApple(it))
const msMachines: MS[] = xs.filter((it: Machine)=> isMS(it))
type MyMachines = {
appleMachines: Apple[]
msMachines: MS[]
}
const myMachines: MyMachines = {
appleMachines: appleMachines,
msMachines: msMachines
}
console.log(myMachines)
実行:
$ deno --check main.ts
{
appleMachines: [ "mac mini" ],
msMachines: [ "surface go", "surface laptop" ]
}
boolean ではなく v is Apple とか v is MS と記述して type を限定せよ、という話でした。
そのうち AIが棲んでいるコンパイラが登場して、AI提案を受けいれてコンパイルするオプションを指定した場合、 この手のコード(今回の例では boolean は v is Apple とか v is MS にするべき)は、 コンパイラ(の中のAI)が「こういう解釈でやっときますね」と言ってコードを修正した状態でコンパイルしてくれる機能とかありかも。
否、その手のはコンパイラではなくて Lint 系のツールがやることかもしれない。そもそも VS Code のような IDE を使っていれば、IDE 上で AI がサジェストすればいいだけだから、コンパイラがそんな機能を持つ必要もないといえばないのですが。