TypeScirptの動向を少し熱心に追っている方ならば、8月頭にAnders HejlsbergさんがTypeScriptリポジトリに次のプルリクエストを出したことは記憶に新しいでしょう。
これはTypeScript 3.7で導入される予定の機能で、関数の返り値の型宣言においてasserts x is T
(x
は引数名でT
は型)という構文を書くことが可能になるというものです。
この機能はたいへん面白いのですが、誤った使い方をするととても危険です。そこで、この記事では、asserts
という新しい型述語1を正しく使いこなせるように皆さんをガイドします。
3行でまとめると
-
asserts
による宣言はTypeScriptにより正しさがチェックされるわけではありません。 - よって、
asserts
を使う場合安全性を保証する責任はコンパイラではなく我々にあります。 - でも
asserts
はめっちゃ便利なので頑張って安全に使っていきましょう。
ということです。では早速、この機能はどういうものなのか、そしてそれがどう危険なのか、安全に使うにはどうすればいいのかを解説していきます。
asserts
型述語の解説
今回の新機能をここではasserts
型述語と呼ぶことにします(asserts
型の述語ではなくasserts
の型述語です)。このasserts
型述語は、先ほども述べた通り関数の返り値の型宣言として書くことができます。一言で言えば、これは「関数が例外を投げずに無事に終了したならば、型述語の条件が満たされる」という宣言です。
簡単な例はこんな感じです。
function assertIsNumber(x: unknown): asserts x is number {
if (typeof x !== "number") {
throw new Error("BOOM");
}
}
このassertIsNumber
は、「この関数がthrow
しなければ引数x
はnumber
型である」という宣言がされていることになります。実際、関数の中身を読むとそう書いてありますね。この関数は、引数x
の型をtypeof
でチェックし、それがnumber
でなければ例外を発生させます。ということは、この関数が正常終了するのは引数x
が数値だった場合だけです。このことにより、assertIsNumber
が正常終了したならばx
が数値であると仮定してよく、それを型の世界で表すとx
をnumber
型として扱ってもよいということになります。このことを、asserts x is number
という返り値の型で表しているのです。
使用例はこんな感じになります。
function assertIsNumber(x: unknown): asserts x is number {
if (typeof x !== "number") {
throw new Error("BOOM");
}
}
// someValueはunknown型
const someValue: unknown = "foobar";
assertIsNumber(someValue);
// ここではsomeValueはnumber型
console.log(someValue.toFixed(1));
この例では最初someValue
変数がunknown
型だったのに、assertIsNumber
関数呼び出しを通過した後はnumber
型に変化しています。これがasserts
型述語の効果です。
ちなみに、asserts
型述語は、asserts x is T
という構文の他に、単にasserts x
とだけ書く構文もあります。これは、「関数が正常終了するならばx
はtruthyな値(真偽値に変換するとtrue
になる値)である」という意味になります。
asserts
型述語の説明はこれだけです。とても簡単ですね(記事執筆時点でまだPRがマージされておらず、追加機能の議論もあるため実際にTypeScript 3.7がリリースされるときにはもう少し豪華になっているかもしれません)。
asserts
の使い道
このasserts
型述語は、その名が示す通りアサーション関数をいい感じにサポートすることがそもそもの目的です。アサーション関数というのは条件を満たさなかったら例外を発生させる関数です。
アサーション関数は、テストコードや例外ベースのバリデーション処理などで使われがちですね。例えば、謎のデータを返すgetUnknownData()
関数をテストしたい場合を考えます。今回、実はこの関数が数値を、しかも3の倍数を返すことをテストしたいとしましょう。
数値かどうかテストできるassertIsNumber
関数はあるけど、3の倍数かどうかテストできる関数は流石に無いので汎用のasserts
関数を使うというシチュエーションを考えるとだいたいテストはこんな感じに書けます。
it('getUnknownData() は3の倍数を返す', ()=> {
const data = getUnknownData();
assertIsNumber(data);
asserts(data % 3 === 0);
});
ただし、assertIsNumber
は引数が数値でなければ例外を発生させる関数で、assert
は引数がtrue
でなければ例外を発生させる関数です。テストなので例外が発生すれば当然テストが落ちますね。
では、これがTypeScriptだとどうでしょうか。とりあえずasserts
のことは一旦忘れて型を付けてみましょう。getUnknownData()
は謎のデータを返すので、返り値は当然unknown
です。
function getUnknownData(): unknown { return 123; }
function assertIsNumber(value: unknown): void { /* ... */ }
function assert(condition: boolean): void { /* ... */ }
it('getUnknownData() は3の倍数を返す', ()=> {
const data = getUnknownData();
assertIsNumber(data);
assert(data % 3 === 0);
// ^^^^ ここでエラーが発生
});
上の定義だと、ソースコード中に示した通りdata % 3
のところでエラーが発生します。これは、data
の型がunknown
なので%
という数値演算ができないからです。
実際のところassertIsNumber
をくぐり抜けたことでdata
が数値であることは保証されているのですが、従来はこれを我々は知っていてもTypeScriptコンパイラが知らなかったため、例えば((data as number) % 3
のようにas
を使ってごまかす必要がありました(そもそもdata
をany
とかにする禁断の技もありますが)。
今回の新機能であるところのasserts
を使うと、この型エラーをいい感じに排除することができます。assert
系の関数の型をいい感じに変えるといいですね。次のようにするとエラーが消えます(assertIsNumber
関数だけ変えれば今回は大丈夫です。assert
関数のほうはおまけです)。
function assertIsNumber(value: unknown): asserts value is number { /* ... */ }
function assert(condition: boolean): asserts condition { /* ... */ }
こうすると、assertIsNumber
をエラーを出さずにくぐり抜けた時点でdata
の型がnumber
となり、data % 3
の型エラーを回避できるのです。
これが基本的なasserts
型述語の使い方でした。応用編もあるのですが、それは本題でないので記事の後半で紹介します。
なお、一点誤解しないでいただきたいのは、asserts
と書いてもランタイムには一切影響を与えないということです。そもそもTypeScriptの大きな特徴は型がランタイムに影響を与えないということであり、asserts
も例外ではありません。asserts
が効果を発揮するのはあくまで型チェック時のみであり、実行時には無関係です。このことは次に説明するasserts
の危険性にもすこし関わってきます。
asserts
の危険性
ここからがやっと本題です。いま紹介した便利なasserts
型述語なのですが、実は迂闊に使うと危険なのです。危険というのはどういうことかというと、せっかくTypeScriptが保証してくれている安全性を破壊してしまうという意味です。極論、asserts
の誤った使い方をしてしまうとTypeScriptを使っている意味が消え失せます。
asserts
の危険性とは何かといういと、嘘をついても怒られないという点に集約されます。asserts
による宣言は完全に自己申告であり、asserts
で書かれた宣言をTypeScriptは一切疑わずに信じ込んでしまうのです。
ということは、最悪次のような宣言をしても問題なくコンパイルが通るということです。
function assertIsNumber(value: unknown): asserts value is number {
// 間違って型が文字列かどうかチェックしてしまった!!!!!!!!!
if (typeof value !== "string") {
throw new Error("BOOM");
}
}
このassertIsNumber
は^明らかに実装がおかしくて、返り値がasserts value is number
というのは嘘っぱちです。しかし、TypeScriptはこれに文句を言いません。もちろん、先述の通り何かランタイムのチェックが自動で挿入されるわけでもありません。ということは、上の関数を使うとバグ発生へまっしぐらということです。実行時エラーが発生してしまうということは、TypeScriptの安全性が無に帰してしまったということです。
const data = getUnknownData();
// number以外を弾いたつもり
assertIsNumber(data);
// ここではdataはnumber型(実装が変なので実際は文字列が来る可能性がある)
console.log(data.toFixed(10)); // 型チェックが通るけど実行時にエラーが発生
これがasserts
型述語の危険性です。asserts
と書いた時点でその宣言が正しいことを保証する義務はプログラマにあるのであり、TypeScriptに頼ることはできないのです。
よって、asserts
を使うときは自分が嘘をついていないか注意深く見直さなければいけないということです。コードレビュー中にコードにasserts
が登場したら黄色信号、要注意ポイントです。
このように、TypeScriptの型チェックに頼らずに自ら注意しないといけないというのは、any
型やas
などの(自分で安全性を保証する必要があるという意味で)TypeScriptの危険な機能に共通する性質です。今回は、この危険な機能ファミリーにひとつ仲間が加わったということになります。ちなみに、asserts
以外の危険な機能については筆者による以下の記事で詳しく取り扱っています。(そのうち向こうの記事にも追記します。)
この記事で皆さんに一番伝えたかったことは、asserts
はこのような危険な機能の一員であることを理解して使わなければならないということです。静的型付け言語は型をうっかり間違ってもコンパイラが指摘してくれることで安全性を担保できるのが良い点ですが、asserts
を使う場合はそうもいかず、皆さんに安全性を担保する責任が一部生じてしまうのです。
asserts
というのはある種のインターフェースであり、その宣言の真の意味は「この関数がこういう意味を持つことはプログラマが保証するから安心して使ってね」という意味なのです。
以上で一番伝えたかったことは終わりなのですが、この記事はもうすこし続きます。次の節では、asserts
の本質に迫りつつこの点をもうちょっと深堀りしていきます。
型述語の本質
今回紹介したasserts
という新機能は、ある意味では非常に革新的なのですが、ある意味では別に全然目新しくもない変更です。
というのも、TypeScriptには既にis
を使う構文がありましたよね。それは、(asserts
無しで)x is T
という構文で表されるuser-defined type guardsです。多分、このx is T
も(asserts
ではない普通の)型述語と呼ばれています。今回asserts
が加わったことで、型述語がx is T
とasserts x is T
の2種類になったわけです。
既存の型述語
このx is T
はこんな感じで使うのでした。
function isNumber(value: unknown): value is number {
return typeof value === "number";
}
const data = getUnknownData();
if (isNumber(data)) {
// ここではdataはnumber型
console.log(data.toFixed(0));
}
すでにお分かりの通り、同じis
というキーワードを使っているだけあって、この既存機能と今回の新機能はとても類似しています。特に、どちらも変数の型を絞り込めるという点で共通していますね。違うのはその方法です。
既存の型述語では、x is T
という返り値の関数は真偽値を返さなければいけません。返り値がtrue
ならばx
がT
型であるということになります(当然、これも自己申告です。x is T
もまた危険な機能のひとつです)。
それに対し、asserts
型述語では関数自体の返り値の型はvoid
です。関数の返り値ではなく、関数が正常に終了したかどうか(例外を発生させなかったかどうか)によってx is T
が満たされるかどうかが判定されるのです。従来の型述語はif
文(あるいはほかの条件分岐構文)と組み合わせる必要があったのに対し、asserts
は関数を呼ぶだけでいいというのが新しいですね。その代わり、例外という機構に頼らなければいけません。適材適所で使い分けましょう。
2つの型述語の本質的な共通点
さて、このように2種類の型述語の似ている点と異なる点を紹介しましたが、この2つにはもっと本質的な共通点があります。それはどちらも制御フロー解析に影響を与えるロジックを関数として分離するための機能であるという点です。
x is T
と制御フロー
すなわち、そもそもTypeScriptにはif
文などを適切に解釈して型を絞りこむ機能があったわけです。これはunion
型などとも関わるTypeScriptの非常に重要な機能です。例えば、上の例などはわざわざisNumber
なんて関数を作らなくてもこのように書けます。
const data = getUnknownData();
if (typeof data === "number") {
// ここではdataはnumber型
}
これは、TypeScriptがif
の条件部分に現れたtypeof data === "number"
という式をよしなに解釈してdata
をnumber
型に絞り込んでいるということができます。正確性を犠牲にして言えば、これはtypeof data === "number"
という式の型がdata is number
型に推論されるという理解もできます。
ところが、この条件部分がより複雑になってくると2つの問題が発生します。1つは長い式をif
文の中に直書きするのは辛いということ、もう1つはあまりに凝ったことをやるとTypeScriptの理解能力を超えてしまう(正しく型の絞り込みができない)ということです。
前者の問題については、単純に式を関数に押し込めて以下のようなisNumber
関数のようなものを作ってもよいのですが、それではTypeScriptはisNumber
の意味を自動的には理解してくれず、if
文の条件式に使っても何も起きません。
// data is number と明示的に書かないと if (isNumber(value)) のように書いても絞り込まれない!
function isNumber(data: unknown) {
return typeof data === "number";
}
自動的にこれをやってくれないのは、恐らく2つ目の問題のためでしょう。つまり、関数に切り出したいほど複雑なロジックの場合は処理を追うのはすぐに限界が来て、実用可能なレベルで自動推論するのは無理があります。そのため、返り値にx is T
を書くことで明示的に宣言させる仕組みになっているのです。
そして、前述の通りx is T
はTypeScriptが何の保証も行ってくれないため危険なのですが、それにも関わらず偉い点は、危険性が自動的に関数の中に閉じ込められるという点です。
レビュー時に関数の返り値の型にdata is number
などと書いてあったらそれは黄色信号なのですが、その場合どこに注意を払えばいいのかは極めて明白です。そう、まさにその関数の中ですね。
TypeScriptの危険な機能を扱う際に最もやってはいけないのは危険性の影響範囲を制御できずに無秩序に拡散させてしまうこと(any
型の関数を作って放置するとか)なのですが、このx is T
型述語は自動的に危険性が関数の中に閉じ込められる(すなわち、その関数が安全なことを人の手で確かめれば外まで見なくても大丈夫)ためこの問題が発生しないのです。それゆえに、TypeScriptにおける危険性マネジメントにおいて、このx is T
というのはとても取り回しがいい存在です。
asserts x is T
と制御フロー
さて、ちょっと回り道しましたが話をasserts
型述語に戻しましょう。今まで説明したことは全てasserts
型述語に当てはまります。例えばassertsNumber
関数というのは、次のようなロジックを関数に閉じ込めたものであるということができます。
// dataはunknown型
const data = getUnknownData();
if (typeof data !== "number") {
throw new Error("BOOM");
}
// ここではdataはnumber型
console.log(data.toFixed(0));
実は、TypeScriptはこのようにthrow
含んだ制御フローをやはりいい感じに解析してくれて、上の例くらいなら最終的にdata
がnumber
型であることを見抜いてくれます。
しかし、throw
を含むロジック(上の例だとif
文の3行ですね)を別の関数に切り出すと、TypeScriptは自動的に解析を行ってくれなくなります。それはちょうど普通のx is T
と同じ理由によるものです。
ですから、まさに同じ議論を適用することによってasserts x is T
という型述語の存在意義が説明できます。まとめ直すと、例外が発生する条件などが複雑化したときにTypeScriptが制御フローだけから自動でその意味を推論するのは無理があるため、ユーザーが明示的にasserts x is T
型述語を書くことによって、この関数がthrow
しない場合に何が保証されるのかを宣言できるようにしているのです。
もちろん、メリットデメリットもx is T
と全く同様です。デメリットはTypeScriptによる検査がなく自分の責任で安全性を保証しなければいけないことであり、メリットはそのような危険性がやはり関数内に閉じ込められていることです。
ここまでの説明により、もはや2種類の型述語の共通点は明らかですね。それは、どちらも制御フローによるロジックを関数に切り出すための機能であるということです。唯一の違いは、x is T
は条件分岐を切り出す一方でasserts x is T
は例外を切り出すという点です。
2つの違いはたったこれだけであり、ゆえにTypeScript 3.7のasserts
型述語というのはある意味では全然目新しくないのです。既にx is T
について我々が知っていたことがそのままasserts x is T
にも当てはまるのであり、既にx is T
の使い方をわきまえていたならばasserts x is T
を受け入れるのは非常に簡単でしょう。そうでない方もご安心ください。この記事をここまで読んだ貴方は大丈夫です。
型述語の使いどころと使いかた
結論から言ってしまえば、(既存のx is T
型述語と同じく)asserts x is T
というのはライブラリの型を付けたいとき(あるいはプロジェクト内でもutil的な関数に型を付けたいとき)に最も重宝するでしょう。
これらの型述語を使った時点で関数の中は危険領域なのですから、あまりに濫用するとTypeScriptに任せられない部分が増えて開発者の負担が増えてしまいます。よって、これらの機能が作り出す危険領域は最小限にすべきです。
その観点からして最も安全なのは、既成のライブラリを使う場合です。既成のライブラリにasserts
型述語などがついていれば、(ライブラリの型定義が嘘をついていないという前提のもとではありますが)我々が何も負担することなく型述語の利便性を享受できます。
ですから、ライブラリを作る場合は積極的にこれらの型を使っていきたいですね。
しかしそれだけではなく、実際のところ、プログラムで扱うデータに厳密に型を付けようとすればするほど型述語のお世話になる機会は増えていきます。(筆者はあまり好きではないのですが)Branded typeのようなテクニックを使う際にも型述語は有効でしょう。とにかく一番重要なのは、(敗北者のTypeScriptで説明したことの繰り返しですが)危険領域を小さく抑えることです。危険領域を小さく抑えることにより安全性を担保するための労力が小さくなり、バグの危険性も小さくなります。これこそが型述語のうまい使い方と言えます。
この記事を読んだ皆さんはやらないでしょうが、asserts x is T
などと書いておきながらそれが嘘であるというのは最悪のケースです。関数内の安全性を保証しなかったばかりに、関数外部まで危険性(型システムで防げないバグの可能性)の影響が及んでいることになります。これはTypeScriptを使っている意味を大きく損ないますから、避けなければいけません。本当に気をつけましょう。
まとめ
この記事ではTypeScript 3.7に入る予定の新機能、asserts x is T
型述語を紹介し、その危険性や望ましい扱い方を解説しました。
といっても、途中で説明した通りこの新機能は既存のx is T
と非常に似通ったものであり、それゆえこの記事の内容はほとんどが両者に当てはまります。
結論をもう一度繰り返すと、これらの型述語をTypeScirptは盲信します。よって型述語を使用した時点で関数の中は危険地帯である、ゆえに危険地帯の範囲を最低限にせよ、ということです。
危険性ばかりを強調しましたが、危険だから使うななどということは言いません。むしろ、筆者はこの危険性をいかに乗りこなすかがTypeScriptの醍醐味であると考えています。TypeScriptの非常に強力な型システムの恩恵を受けるには危険性は避けて通ることはできません。よく分からず危険性を使うのが初心者、危険性を避けるのが中級者、危険性を乗りこなすのが上級者です。
皆さんもぜひasserts
を使いこなして快適なTypeScriptライフを送ってください。
といってもTypeScript 3.7がリリースされるのは11月末とかそれくらいになりそうですが。ちなみに、あのoptional chainingx?.y
が入るのもTS 3.7の予定です。未来ですね。
おまけ
結論としてasserts
型述語は既存の型述語と本質的に同じであると述べましたが、その一方で少し前にどこかで「asserts
はある意味で非常に革新的である」とも述べました。せっかくなので、この記事ではその部分にも触れようと思います。
asserts
型述語の何が革新的なのかというと、(今の所返り値がvoid
であるという制限があるものの)関数を呼ぶだけで変数の型を変えられるという点です。従来TypeScriptにおいて一度宣言された関数の型を変えるのは困難であり、それこそ条件分岐などを使って絞り込むしかない状態でした。それが、関数を呼ぶだけでよいとなると夢が広がります。
さっそく例をお見せしましょう。なお、例は全て記事の冒頭で紹介したプルリクのコミット2c36249ed6bd63f4fd66813985ae2dff681349a5
で動作確認しています。
オブジェクトをデフォルト値で埋める関数
関数がオプションをオブジェクトでまとめて受け取るというのは非常によくあるシチュエーションです。そして、オプションたちは往々にして省略可能です。もし受け取ったオプションが省略されていた場合はデフォルト値を使用することになるでしょう。
asserts
型述語を用いることで、この処理をいい感じに書けます。やや長いですが例を見ましょう。
interface Options {
foo: string;
bar: number;
}
function useOptions(options: Partial<Options>) {
// これはエラー (options.fooはundefinedかもしれないので)
console.log(options.foo.length);
// オプションを埋める
fillOptions(options);
// これはエラーにならない!!!!!
console.log(options.foo.length);
}
function fillOptions(options: Partial<Options>): asserts options is Options {
if (options.foo === undefined) {
options.foo = "";
}
if (options.bar === undefined) {
options.bar = 0;
}
}
この例で登場するOptions
型はfoo
とbar
の2つのプロパティを持つオブジェクトの型です。次に定義されている関数useOptions
はこのオプションたちをオブジェクトで受け取りますが、foo
やbar
は省略可能ということを表現するために引数の型はPartial<Options>
型になっています。
となると、これをいきなりoptions.foo.length
のように使うのはまずいです。options.foo
はstring
型のはずでしたが、今回はPartial
によってoptions.foo
が存在しない、すなわちundefined
である可能性が発生しているからです。
今回鍵となるのがfillOptions
関数です。この関数を呼ぶとあら不思議、なんとその後options.foo
を使ってもエラーにならないのです。
その秘密はもちろんasserts
型述語です。今回fillOptions
の返り値はasserts options is Options
です。これはつまり、fillOptions
を呼び出して以降はoptions
は(Partial<Options>
ではなく)Options
であるという宣言になります。これにより、fillOptions
を呼び出して以降はoptions.foo
がundefined
である可能性が消えるのでoptions.foo.length
としてもエラーにならなかったのです。
もちろん、この記事で説明した通り、fillOptions
のこの宣言が正しいことを保証するのは書いた人の役目です。今回は確かにfillOptions
がこの宣言通りの動作をしている(foo
やbar
がundefined
である可能性を潰している)ことが分かりますから、これで大丈夫です。
このように、asserts
型述語によってオブジェクト等に対するミューテーション(書き換え)を型で表現することができます。これは今までにない非常に革新的な機能です。
Builderパターン再び
さらに、実はasserts
型述語によって変えられるのは引数の型だけでなく、実はthis
の型も変えることができます。これを利用してBuilderパターンをTypeScriptを書いてみましょう。「再び」というのは筆者の以下の既存記事のことを指しています。
上の記事ではBuilderオブジェクトはメソッドチェーンの形で使うことを強いられていましたが、asserts
型述語を手に入れた我々にはもはやそのような制限はありません。細かなアイデアは上の記事と同じなので、長々とした説明は飛ばして早速結果をお見せします。また、本質的ではないのでランタイム実装は省略して型部分だけ取り出しました。
type Builder<Props, Result> = ({} extends Props
? {
build: () => Result;
}
: {}) & BuilderMethods<Props, Result>
interface BuilderMethods<Props, Result> {
set<Key extends keyof Props>(key: Key, value: Props[Key]): asserts this is Builder<Pick<Props, Exclude<keyof Props, Key>>, Result>
}
// ----- 使用例 -----
// builder は foo と bar を指定する必要があるBuilder
declare const builder: Builder<{
foo: string;
bar: number;
}, string>
// まずfooを指定
builder.set("foo", "foobar");
// 次にbarを指定
builder.set("bar", 123);
// するとbuild()が呼べる
const result: string = builder.build();
前半がBuilderパターンの実装で後半が例です。例は、まずなんらかの方法でBuilder<{ foo: string; bar: number}, string>
という型のビルダーbuilder
を入手します。型が示す通り、これはfoo
とbar
を指定するとbuild
が呼べるようになります。今回は実装上の都合で、foo
やbar
という値はset
メソッドを通じて指定することにしました。一つずつset
メソッドを呼び出すことでfoo
とbar
の値を指定します。
すると、例の最後の行のようにビルダーのbuild()
メソッドが呼べるようになりました。
このようにset
メソッドで必要な値を供給してbuild
メソッドで値を生成するというのがビルダーの使い方です。安全にビルダーを使うためには、build
は必要な値を全部set
で供給するまで呼び出してはいけません。今回はこの制限が型レベルで実現しており、次のようなものは型エラーで弾くことができます。
declare const builder2: Builder<{
foo: string;
bar: number;
}, string>
// まずfooを指定
builder2.set("foo", "foobar");
// まだbarを指定していないのでこれは型エラー
// TS2339: Property 'build' does not exist on type
// 'BuilderMethods<Pick<{ foo: string; bar: number; }, "bar">, string>'.
builder2.build()
今回は別のビルダーbuilder2
を使いますが、set
でbar
を指定し忘れています。よって、builder2.build()
を呼び出そうとしても型エラーになります。
また、次の例もエラーです。
declare const builder3: Builder<{
foo: string;
bar: number;
}, string>
// まずfooを指定
builder3.set("foo", "foobar");
// もう一度fooを指定しようとすると型エラー
// error TS2345: Argument of type '"foo"' is not assignable to parameter of type '"bar"'.
builder3.set("foo", "吉野家")
このように、asserts
型述語を活用(悪用?)することで、関数やメソッドの呼出によって型を操作することができ、「メソッドの呼出の順番」などといったものに対する型チェックを(自由自在にとまでは行きませんが)行うことができます。これはやばいですね。
以上です。
-
is
というキーワードを使って書かれる物を型述語と呼んでいるようです。Type predicateの直訳ですが本当に訳語がこれでいいのかちょっと不安です。 ↩