📖

TypeScriptが書けるなら、日本語もわかる?Typed Japaneseがそれを可能にする(かも)

に公開

最近、Yifeng Wang 氏と Satoshi Terasaki 氏が Typed Japanese というプロジェクトを公開しました。このプロジェクトは、TypeScript の型システムを活用して、日本語学習を支援することを目的としています。

本プロジェクトでは、「TypeScript が書けるなら、日本語も理解できる」をスローガンに掲げています。これは、自然言語の文法規則を TypeScript の型システムに対応させるというアイデアに基づいています。

また、日本語の文法規則を DSL(ドメイン固有言語)として、TypeScript の型レベル(type-level)で記述することが可能です。これにより、TypeScript のコード上で日本語の文を構築し、それが正しい文法に従っているかどうかを、TypeScript の型チェックを通じて検証することができます

本記事では、Typed Japanese の中核的な仕組みについて、「用言の活用」と「文の構成」という二つの側面から解説していきます

用言の活用形

まずは、以下のコードを通じて、Typed Japanese がどのように DSL を使って動詞の活用形を表現しているかを見てみましょう。

// https://github.com/typedgrammar/typed-japanese/blob/main/blog.md#implementing-japanese-verb-conjugation-with-type-programming

// Define simplified Japanese verb types
type SimpleJapaneseVerb = {
  type: "godan" | "ichidan";
  stem: string;
  ending: string;
};

// Define conjugation forms
type JapaneseVerbForm = "辞書形" | "て形" | "た形";

まずは、簡易的な日本語の動詞の構造を定義しています。動詞は以下の 3 つの要素から構成されます:

  • type: 五段動詞か一段動詞かの種類
  • stem: 語幹
  • ending: 語尾(送り仮名)

そして、ここでは 3 種類の活用形(「辞書形」「て形」「た形」)が定義されています。

ちなみに、「て形」や「た形」という呼び方は、おそらく接続助詞「て」や助動詞「た」に合わせて、(音便)連用形を分けて説明している外国人向けの教材の影響だと思われます。このような分け方なら、「たら」が接続される時に「たら形」なんて呼び方も出てきそうで、ちょっと気になっちゃいますよね。「買わせたくなかったら」なんて……(笑)

さて、ここから先のコードがなかなか面白くなって、ぜひ注目してみてください。

// Simplified Japanese verb conjugation
type ConjugateSimpleJapaneseVerb<
  V extends SimpleJapaneseVerb,
  Form extends JapaneseVerbForm
> = Form extends "辞書形"
  ? `${V["stem"]}${V["ending"]}`
  : Form extends "て形"
  ? V["type"] extends "godan"
    ? V["ending"] extends "う"
      ? `${V["stem"]}って`
      : V["ending"] extends "く"
      ? `${V["stem"]}いて`
      : `${V["stem"]}${V["ending"]}`
    : V["type"] extends "ichidan"
    ? `${V["stem"]}`
    : `${V["stem"]}${V["ending"]}`
  : Form extends "た形"
  ? V["type"] extends "godan"
    ? V["ending"] extends "う"
      ? `${V["stem"]}った`
      : V["ending"] extends "く"
      ? `${V["stem"]}いた`
      : `${V["stem"]}${V["ending"]}`
    : V["type"] extends "ichidan"
    ? `${V["stem"]}`
    : `${V["stem"]}${V["ending"]}`
  : `${V["stem"]}${V["ending"]}`;

// Examples
type KauVerb = { type: "godan"; stem: "買"; ending: "う" };
type KauTeForm = ConjugateSimpleJapaneseVerb<KauVerb, "て形">; // "買って"
type KauTaForm = ConjugateSimpleJapaneseVerb<KauVerb, "た形">; // "買った"

ConjugateSimpleJapaneseVerbgeneric type alias(ジェネリック型エイリ) と言います。ここで、型パラメータ V は一つの動詞を表し、その型は SimpleJapaneseVerb に制約されています。もう一つのパラメータ Form は活用形(「辞書形」「て形」「た形」)を指定します。

その定義本体では、三項演算子(a ? b : c)のような構文を使った conditional types(条件型) が用いられています。これは、与えられた Form の種類に応じて異なる template literal types(テンプレートリテラル型) を返す、TypeScript の型システムにおける分岐処理の仕組みです。

Form extends "辞書形"
  ? ...
  : Form extends "て形"
  ? ...
  : Form extends "た形"
  ? ...
  : ...

つまり、型レベルで文字列を結合し、新しいテンプレートリテラル型を生成する仕組みです。例えば、次のように書くと:

type KauTeForm = ConjugateSimpleJapaneseVerb<KauVerb, "て形">`

得られる型は "買って"(値ではなく)となります。すなわち、型レベルで動詞の活用形そのものを表現できるのです。

"買って"であるため、KauTeForm 型の変数 kau には、"買って" という文字列を代入しなければなりません。

// const kau: KauTeForm = "買って" // ✅ OK
const kau: KauTeForm = "買うて" // ❌ Type '"買うて"' is not assignable to type '"買って"'.

日本語の初心者がうっかり "買うて" と書いてしまった場合、"買うて"は型 KauTeForm に一致しないため、型エラーが発生するのです。

なお、KauTeForm はあくまで型レベルでの操作であるため、実行時に "買って" という値が自動的に生成されるわけではありません。実際にその値を使用する場合は、別途処理が必要です。

単なるコピーすると、

type IkuVerb = { type: "godan"; stem: "行"; ending: "く" };
type IkuTeForm = ConjugateSimpleJapaneseVerb<IkuVerb, "て形">;
// const iku: IkuTeForm = "行いて" // ✅ OK
const iku: IkuTeForm = "行って"    // ❌ Type '"行って"' is not assignable to type '"行いて"'.

おそらく extends ? ... : ... をしっかり書かないと、「行く」の「て形」が「行いて」となってしまい、むしろ日本語初学者を誤解させてしまうかもしれません。

簡単に言うと、このような処理を経て、各動詞の各活用形はstring 型の値ではなく、型システム内でユニークなテンプレートリテラル型として生成されます。

「一語一型」から「一文一型」へと

次に、TypeScript の型システムを基にして、どのように日本語の文を定義するかを見ていきましょう。

例として「いいよ、来いよ」を取り上げ、以下のような単文の型 PhraseWithParticle と複文(重文?)の型 ConnectedPhrases を定義してから:

// https://github.com/typedgrammar/typed-japanese/blob/main/blog.md#compound-sentence-type-gymnastics-good-times-come-on 

// Phrase with particle
type PhraseWithParticle<
  Phrase extends string,
  P extends Particle
> = `${Phrase}${P}`;

// Phrases connected by Japanese comma
type ConnectedPhrases<P1 extends string, P2 extends string> = `${P1}${P2}`;

形容詞の「いい」と動詞の「来る」に対応する型をも定義しました:

// Define the i-adjective "いい" (good), note that it has irregular conjugation
type いい = IAdjective & { stem: "い"; ending: "い"; irregular: true };
type いいよ = PhraseWithParticle<ConjugateAdjective<いい, "基本形">, "よ">;

// Define the irregular verb "来る" (to come)
type 来る = IrregularVerb & { dictionary: "来る" };
type 来いよ = PhraseWithParticle<ConjugateVerb<来る, "命令形">, "よ">;

こうすることで、型システムを使って、複数の独立した単文から成る複文を表現することができます。

// Connect the two short sentences -> "いいよ、来いよ"
type いいよ来いよ = ConnectedPhrases<いいよ, 来いよ>;

ところで、「いいよ」の「よ」は名詞の「世」ではないのでしょうか? 2番目の「よ」が終助詞(Particle)だと思いますが……


Typed Japanese は完全に TypeScript の型システムを基に構築されており、ジェネリック型エイリアス + 条件型 + テンプレートリテラル型を活用して日本語の文法を記述し、型チェッカーを文法チェッカーとして利用するようになります。

「行いて」のような誤りは存在しますが、Typed Japanese は型システムの強力な表現力を示すだけでなく、AI による言語学習支援に新たな視点を提供しています。それは、言語構造を JSON ではなく TypeScript のコードで表現するという全く新しい中間表現フォーマットであり、LLM(大規模言語モデル)が日本語の文法解析結果を生成する際に、より厳密な文法検証を実現することが期待されています。

茶道を大成させた千利休の美学思想において、「一期一会」という言葉があります。これと同様に、TypeScript の型プログラミングの世界では、ジェネリック型エイリアスに条件型で推論されたテンプレートリテラル型の値が、それぞれ唯一無二の存在であるように、「一語一型」から「一文一型」へと進化しています。

🔚

Discussion