フロントエンドのテックリードをやってます。みゅーとん(@_mew_ton)です。
前回は作りたいライブラリの設計を全部ChatGPT にやってもらいました。
今回は、その実装を楽にすすめていこうと思います。
また、前回と異なり、今回は手動でブログを書いています。ほとんどが失敗の記録です。
TL; DR
3行でまとめます
- ChatGPT にコードを書かせる際、 TypeScript の型パズルはまだ難しい
- Google Copilot に1からコードを書かせるのも難しい
- 型パズルはまだ人力である必要がある
AIに型パズルはまだ早い・・?
そもそもの要件は、「型ガード関数7種を提供する」「型ガード関数を作るための関数を提供する」の2つでした。
さっそく ChatGPT に 型ガード関数を作るための関数を提供する
の実装をお願いすると、以下のようになりました。
以下に、指定されたメソッドを作成しました。 type TypeGuard<T> = (value: unknown) => value is T; type AssertFn<T> = (value: unknown, errMsgFn?: () => string) => asserts value is T; type EnsureFn<T> = (value: unknown) => T; type FallbackFn<T> = (value: unknown, defaultValue: T) => T; function createGuard<T>(typeGuard: TypeGuard<T>): TypeGuard<T> { return typeGuard; } function createAssert<T>(typeGuard: TypeGuard<T>): AssertFn<T> { return (value: unknown, errMsgFn?: () => string): asserts value is T => { if (!typeGuard(value)) { throw new Error(errMsgFn ? errMsgFn() : 'Type assertion failed'); } }; } function createEnsure<T>(assertFn: AssertFn<T>): EnsureFn<T> { return (value: unknown): T => { assertFn(value); return value as T; }; } function createFallback<T>(typeGuard: TypeGuard<T>): FallbackFn<T> { return (value: unknown, defaultValue: T): T => { return typeGuard(value) ? (value as T) : defaultValue; }; } function not<T>(typeGuard: TypeGuard<T>): TypeGuard<Exclude<unknown, T>> { return (value: unknown): value is Exclude<unknown, T> => { return !typeGuard(value); }; } function createInvertedAssert<T>(typeGuard: TypeGuard<T>): AssertFn<Exclude<unknown, T>> { return createAssert(not(typeGuard)); } function createInvertedEnsure<T>(assertFn: AssertFn<T>): EnsureFn<Exclude<unknown, T>> { return createEnsure(createInvertedAssert(assertFn)); } function createInvertedFallback<T>(typeGuard: TypeGuard<T>): FallbackFn<Exclude<unknown, T>> { return createFallback(not(typeGuard)); } これらの関数を使用して、 `isString` のようなタイプガード関数を生成し、それらを元に `assertString`、 `ensureString`、 `fallbackString` などの関数を作成できます。 また、それらの反転したバージョンも作成できます。 注意: `createInvertedEnsure` と `createInvertedFallback` は、 `createInvertedAssert` 関数を使用していますが、これは `createAssert` の逆バージョンです。 逆バージョンを作成するために、 `not` 関数を使用しています。
違う!
一見すごく、よくできています。
多少ロジックは9割がた書けていました。これは非常に良く出来てると思います。
しかし、型推論については残念ながら半分近く間違っていました。
Google Copilot 作戦
ちょうど同じ頃、弊社では GitHub の Enterprise プランへの契約と Google Copilot の導入が始まったので、同様に試してみました。
Google Copilot では途中までの入力に対して、続きを保管してくれます。
試しに、
type TypeGuard<T> =
まで入力すると・・
type TypeGuard<T> = (x: unknown) => x is T;
と補完されました。よさそうです。
しかし、続けて TypeAssert
InvertedTypeAssert
入力してみるとこのように補完されました
type TypeAssert<T> = (x: unknown) => asserts x is T; type InvertedTypeAssert<T> = (x: unknown) => asserts x is Exclude<unknown, T>;
残念ながらこれも間違いです。
何が違うのか?
ChatGPT も Google Copilot も、タイプガードの否定を正しく作れませんでした。
例えば以下のようなユニオン型の定数があったとして・・
const hoge = 'string' as string | number
isString を条件にすれば、if文の内側で hoge は string
であると解釈されますし
if (isString(hoge)) { // ココで、 hoge は string }
否定すれば、 number
であると解釈されます
if (!isString(hoge)) { // ココで、 hoge は number }
今回設計した assert, ensure, fallback のようなメソッドでは、isXxxx のような !
による否定ができないので、assertNotString
のようなメソッドを提供するように設計しています。
以下のコードのように、assertNotString を使えば、 hoge は string ではない型だと解釈されてほしかったのです。
assertNotString(hoge) // hoge がここで number になってほしい!!
しかし、 ChatGPT, GoogleCopilot 両者とも、hoge は Exclude<unknown, string>
という型 (つまり、 unknown
と同等の型) だと推論されるコードになっています
つまるところ、InvertedTypeAssert
は以下の型が正解でした
type InvertedTypeAssert<T> = <U>(x: T | U) => asserts x is U;
結論
まとめると以下のとおりでした。
- ChatGPT にコードを書かせる際、 TypeScript の型パズルはまだ難しい
- Google Copilot に型パズルを1からを書かせるのも難しい
- 型パズルはまだ人力である必要がある
結局の所、自力でやらざるを得ませんでした。
型推論がちゃんと効く型ガード7種関数を作るための関数と、その型は以下のファイルです。どちらも 100% 人力での実装となりました。
- https://github.com/hacomono-lib/type-assurer/blob/main/src/factory.ts
- https://github.com/hacomono-lib/type-assurer/blob/main/src/type.ts
また、これをもとに isString に対応する関数7種 (assertString
assertNotString
ensureString
ensureNotString
fallbackString
fallbackNotString
) を1回実装したあと、新たに isNumber に対応するなど、他の関数を作る際は GoogleCopilot はかなり真価を発揮しました。
過去の傾向からの推測には非常に強いのを改めて感じました。
まとめ
100% 楽するのはできませんでした。
とはいえ、実装の最初の一歩を人力で対応して以降はほとんどAIによる力で実装が進んだので、ある程度楽できたと思います。
まだまだ実装を 完全に AI に頼り切ることはできないようです。名前の通り Copilot (副操縦士) ですから、任せきりにするのはよくないですね。
引き続き、AIに頼りつつ、今度はOps 周りに手を入れてみようとおもいます。シリーズはまだ続きます。