前提

  • ここで話すことは公式の見解ではない.

  • "言語" についての話をするが,かなり話したいことに寄せている. 現実はそんなに単純ではない.

  • あくまで,「こういう見方もできる」という一つの側面を示すものである.

コンピュータへの命令

Webアプリケーションの実装も,ネイティブアプリケーションの実装も,ブラウザの実装も,全てはコンピュータへの命令である. そして,それらは全てバイナリで表現できる.

しかし,人間がバイナリを直接書くことは現実的ではない. これは,日本語話者に対してモールス信号で会話を強いるようなものである. 意味を伝達する手段としては成立するが,実用的ではない.

プログラミング言語という抽象

プログラミング言語は大きく2つに分類される.

汎用プログラミング言語 (general-purpose programming language)は,特定の領域に限定されない汎用的な計算を記述するための言語である. Rust, TypeScript, JavaScript, Python, C, Haskellなどがこれに該当する.

一方,ドメイン固有言語 (DSL: domain-specific language)は,特定の問題領域に特化した言語である. HTML, CSS, SQL, JSONなどがこれに当たる. そして,Vueもここに位置づけることができる.

抽象の階層

バイナリとISA

ISA (Instruction Set Architecture)はCPUの命令セットを定義するものである. x86, ARM, RISC-Vなどがこれに該当する.

ff 43 00 d1
ff 0f 00 b9
48 05 80 52
...

レジスタへの書き込み,算術演算,条件分岐,ジャンプ. これらの原始的な命令の組み合わせで,あらゆる計算が表現される. if文もwhileループも関数呼び出しも,最終的にはこれらに帰着する.

アセンブリ

バイナリを人間が読める形式に変換したものがアセンブリである.

my_program:
  sub   sp, sp, #16
  str   wzr, [sp, #12]
  mov   w8, #42
  str   w8, [sp, #8]
  ldr   w8, [sp, #8]
  mov   w10, #2
...

subは減算,movは値の転送,jmpはジャンプ. ニーモニックによって,バイナリ列に意味が与えられた.

しかし,これで十分だろうか.

変数に意味のある名前をつけたい. 条件分岐を構造化したい. 繰り返しを簡潔に記述したい. 関数という単位で処理をまとめたい. アセンブリは計算機にとっては十分でも,人間の認知にとっては依然として負荷が高い.

高級言語への進化

int main() {
  int a = 42;
  if (a % 2 == 0) {
    a = 5;
  }
  a = a + 999;
  return a;
}

C言語(Fortran)の登場により,人間の思考に近い形式でプログラムを記述できるようになった. 変数,条件分岐,ループ,関数といった抽象が導入され,開発者の認知負荷は大幅に軽減された.

しかし,これで終わりではなかった.

メモリ管理の自動化,型システムの強化,オブジェクト指向,関数型プログラミング,並行処理の抽象化... 新たな要求が生まれるたびに,新たな言語が生まれてきた.

言語とは,ある種の「翻訳機」である. 人間の意図をコンピュータが理解できる形式に変換する. そして,その翻訳の過程で,何を簡潔に書けるようにし,何を隠蔽するかが,言語の設計思想を決定づける.

言語設計のモチベーション

言語が生まれる背景には,常に開発者体験(DX)の向上がある.

  • 構文の意味づけ: if, while, モジュール, 名前空間など,思考の単位に対応する構文を提供する

  • 対象領域の意味づけ: OOPならJavaやC++,システムプログラミングならRustやGo,UI記述ならHTML/CSS

  • コンパイラによる最適化: 開発者は意図を記述し,最適化はコンパイラに委ねる

言語とは,特定の問題領域において「何を書きやすくするか」という設計判断の集積である.

Vue.jsは言語である

ここで本題に入る.

Vue.jsとは何か. フレームワーク,ライブラリ,様々な呼び方があるが,ここでは一つの見方を提示したい.

Vue.jsは,Web UIを記述するためのドメイン固有言語である.

UIを宣言的に記述するために必要なものを考えてみる.

  • Web標準であるHTML, CSS, JavaScriptとの親和性

  • データバインディングによる動的なViewの記述

  • コンポーネントを単位とした設計

  • スコープ付きスタイル

  • スロットによるコンテンツの受け渡し

<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
function increment() {
  count.value++;
}
</script>

<template>
  <button type="button" @click="increment">count is: {{ count }}</button>
</template>

<style scoped>
button {
  color: red;
}
</style>

このSFC (Single File Component)という形式は,HTML/CSS/JavaScriptの構文を借りながら,UI記述に特化した意味論を与えている.

実用性と言語設計

完全に新しい言語を設計することも可能である. しかし,それは現実的だろうか.

新しい言語を作るということは,エコシステムをゼロから構築することを意味する. エディタサポート,型チェッカー,リンター,フォーマッター,ビルドツール... これらを全て新規に開発する必要がある.

Vue.jsはインクリメンタルなアプローチを取った.

既存の資産を活用する. JavaScript/TypeScriptのエコシステム, による恩恵をある程度そのまま享受する.

メンタルモデルを維持する. script部分はJavaScriptのセマンティクスをそのまま使う. HTMLとCSSの書き心地を損なわない.

これは,新しい言語を「方言」として設計するようなものである. 母語の文法を大きく逸脱せず,しかし特定の表現については独自の語彙と構文を導入する.

コンパイラによる最適化

言語がコンパイラを持つことで,最適化の恩恵を受けることができる.

開発者は意図を記述し,コンパイラが効率的なコードを生成する. この分業により,開発者は本質的な問題に集中でき,低レベルの最適化から解放される.

Vue.jsのコンパイラには以下の最適化が実装されている.

  • Static Hoisting: 静的なノードをレンダリング関数の外に巻き上げ,再生成を回避する

  • Patch Flags: 動的な部分にフラグを付与し,差分検出を高速化する

  • Tree Flattening: ネストした静的ノードをフラット化し,走査コストを削減する

  • Vapor Mode: 仮想DOMを介さない,より直接的なレンダリングモード

これらの詳細は 公式ドキュメントに記載されている.

SFC Playgroundで出力コードを眺めてみると,コンパイラがどのような変換を行っているかを観察できる.

課題

言語を設計し維持することには,相応のコストが伴う.

  • 言語仕様の設計: 一貫性と表現力のバランス

  • コンパイラの実装: 正確性と性能の両立

  • 周辺ツールの整備: 言語サーバー,他のエコシステムとの統合

Vue.jsはすでに多くの部分が成熟しているが,探せばまだやるべきことは見つかる. 貢献の機会は常に存在する.

結び

バイナリからアセンブリへ,アセンブリから高級言語へ. プログラミングの歴史は,抽象化の積み重ねの歴史である.

Vue.jsもその系譜に位置づけることができる. Web UIという問題領域に対して,HTML/CSS/JavaScriptを基盤としながら,宣言的UI記述のための意味論を与えた言語.

Vue.jsとは何か.

言語である.