この記事はChatGPTとの壁打ちで生まれた文章を編集して作成したものです。
はじめに
ある日、私はノートに同じ形式の文章を何個も書いていました。
どの文章も、
どうしたら我々は〜
という書き出しでした。
しかし、何度も同じ言葉を書くのは面倒なので、2つ目以降は、
〃
という同じく記号を使って省略していました。
ところが、しばらく書き続けているうちに、私は無意識に再び、
どうしたら我々は〜
と全文を書いていました。
咄嗟に「あ、形式を揃えなければ」と思い、消しゴムで消して〃に書き直しました。
些細な出来事でしたが、この瞬間にふと、
「伝統と革新というものは、案外こういう構造をしているのではないか」
と思いました。
そこでこの記事では、この小さな出来事から始めて、
- 伝統と革新の循環
- 革新の制度化
- 「選択される伝統」
- レガシー技術
- Javaの後方互換性
- KotlinやScalaとの違い
まで考えてみたいと思います。
伝統と革新についての一考
革新は、最初はたいてい「便利だから」生まれる
今回の〃は、別に思想的な理由で導入したものではないです。
単純に、同じ文章を何度も書くのが面倒だったからです。
つまり、
どうしたら我々は〜
どうしたら我々は〜
どうしたら我々は〜
と書く代わりに、
どうしたら我々は〜
〃
〃
としました。
これは非常に小さな「革新」だと言えるでしょう。
旧来の方法に不便がある。
そこで、それを簡略化する新しい方法を導入する。
構造だけ取り出せば、
旧来の方法
↓
不便さが意識される
↓
より便利な方法が導入される
となります。
技術の歴史でも制度の歴史でも、よく見られる形だと思われます。
ところが、「便利だから使う」が「そうするものだから使う」に変わる
面白いのは、その後でした。
私は途中で無意識に、
どうしたら我々は〜
と書いてしまいました。
本来なら、それでも意味は通じる。
むしろ全文を書いた方が分かりやすいとも言えるでしょう。
それでも私は、
ここまで〃で揃えているから
という理由で消しゴムを使いました。
ここでは、〃を使用する理由が変わっています。
最初は「書く手間を省くため」でしたが、途中から「見た目や形式を統一するため」になっています。
つまり、
利便性のための革新
↓
定着
↓
形式
↓
規範
↓
「こうするもの」
という変化が起きています。
これは興味深い。
革新は成功すると、それ自体が伝統になる。
革新の最大の敵は「過去の伝統」とは限らない
伝統と革新という言葉を使うと、
伝統 vs 革新
という構図を思い浮かべがちです。
古いものが伝統で、新しいものが革新。
しかし実際には、もっと複雑なのではないでしょうか。
今日「伝統」と呼ばれているものも、それが生まれた当初は革新的だった可能性がある。
そして現在の革新も、成功して定着すれば、未来には伝統になる。
だとすれば、革新の最大の敵は古い伝統ではなく、過去に成功した革新なのではないでしょうか。
成功した革新は制度になり、その周囲に慣習やインフラやルールが生まれ、やがて「なぜそうするのか」ではなく「そうするものだから」という理由で維持されるようになる。
この時点で、かつての革新はすでに伝統として振る舞い始めています。
「昔に戻る」ことが革新的になる
さらに時間が経つと、別の反転が起こります。
かつて捨てられた方法が、むしろ新鮮に見えてくるのです。
たとえば、
- レコード
- フィルム写真
- 万年筆
- 機械式時計
- 紙の本
- 手書き
- ドット絵
などが分かりやすいでしょう。
昔は、単にそれしかなかった。
フィルムカメラしかなかった時代にフィルム写真を撮ることは、特別な選択ではないでしょう。
しかしデジタルカメラやスマートフォンが一般化した後でフィルムを選ぶことには、
あえてフィルムを使う
という意味が生まれる。
つまり同じ行為でも、社会的位置づけが変わっています。
ここで、
必要としての伝統
から、
選択としての伝統
への転換が起きているのです。
復活した伝統は、昔と同じものではない
ここで重要なのは、復活した伝統は厳密には昔と同じものではない、という点です。
たとえば現代にフィルム写真を楽しむ人たちは、デジタル技術を知った上であえてフィルムを選んでいる人たちです。
つまり、
A
↓
B
↓
A
と単純に元へ戻っているわけではなく、
A
↓
B
↓
A'
だと言えます。
A'はAに似ています。
しかし、Bを経験した後のAです。
だから循環というより、螺旋に近い。
同じ場所へ戻っているように見えて、意味のレベルでは一段違うところへ戻っているのです。
「伝統」と「革新」は物そのものの属性ではない
ここまで考えると、
これは伝統だ
これは革新だ
と固定的に分類すること自体が怪しくなってきます。
伝統や革新は、物そのものに備わった属性ではないのではないでしょうか。
たとえばCLI。
GUIが普及する以前、CLIは単なる普通の操作方法でした。
その後GUIが普及すると、古い操作方法と見なされるようになりました。
ところが現在では、開発者がCLIを積極的に選択することも多い。
同じCLIでも、
昔:
他に方法がないから使う
現在:
GUIもあるが、CLIの方が効率的だから使う
では意味が違います。
したがって、
ある方法が伝統なのか革新なのか
という問いより、
ある時点において、何との関係で伝統的/革新的なのか
と問った方が本質に近いのかもしれません。
ここからJavaの話
「レガシー(言語)」Java
この話をプログラミング言語に当てはめると、Javaは非常に面白い例になるでしょう。
Javaはしばしば、「レガシー(言語)」と評されます。
確かJavaは歴史の長い言語であり、古い設計も多く残っているでしょう。
しかしJavaの歴史を単純に、
新しい言語
↓
古くなる
↓
レガシー
と捉えるのは不十分だと思います。
むしろJavaは、
革新
↓
成功
↓
制度化
↓
停滞感
↓
外部からの刺激
↓
自己改革
↓
新しい制度化
を繰り返してきた言語として見る方が面白いです。
Javaは「古いからレガシー」なのか
Javaには巨大な資産があります。
- JVM
- クラス中心のオブジェクト指向
- 静的型付け
- 標準ライブラリ
- 各種フレームワーク
- 膨大な既存コード
- 既存バイナリ
- 企業システム
- 開発ツール
- 世界中のJava開発者
これらはJavaを成功させた理由でもあります。
しかし同時に「これまでのJavaを壊せない」という強烈な制約にもなり、JavaはJavaらしさを壊して新言語になるのではなく、Javaらしさを残したまま、昔ならJavaらしくなかったものをJava化して吸収する形の進化を遂げていきました。
たとえばJava 8ではラムダ式が導入され、それまでのJavaにはかなり異質だった関数型プログラミング的な記述が本格的に入ってきました。OpenJDK自身もLambda Projectを、クロージャなどをJavaへ導入するものとして位置づけています。
その後もRecordsによって「単にデータを保持するためだけのクラス」を簡潔に表現できるようになり、instanceof や switch のPattern Matchingも段階的に導入されてきました。
Javaの革新は「革命」より「改憲」に近い
Kotlinはそもそも、Javaとの相互運用性を強く意識しつつ、Javaで不便だったものを言語設計から改めようとしていました。公式ドキュメント自身、null安全性、raw type、関数型、checked exceptionなど、Javaとの設計上の違いを明示しています。
ここには非常に重要な非対称性があります。
Kotlinは、
Javaの資産は利用したい。しかしJavaの言語仕様そのものを背負う必要はない。
という立場を取れます。
たとえばJavaで、
String name = null;
が歴史的に合法だったからといって、Kotlinまで同じ型システムにする必要はありません。
そこで、
var name: String
var nullableName: String?
と再設計できます。
しかし、もしJavaがある日突然、
Stringはnull禁止
と変更したら、膨大な既存コードが影響を受けるでしょう。
つまりKotlinは、Javaの歴史を外側から参照できる一方、JavaはJavaの歴史を内側から背負わなければならない。
ここが決定的です。
また、ScalaもJVM上でJavaの資産を利用できますが、Java言語そのものではありません。
そのためScala 2からScala 3への移行では、実際にソースレベルの非互換性が存在し、公式のMigration Guideもそのための互換性・移行戦略を説明しています。
つまりScalaには、
「これは旧来の設計だから変更しよう。ただし移行手段を用意しよう」
という選択肢があります。
対してJavaの思想はもっと保守的です。
OpenJDKの開発者ガイドでは変更に対して、binary compatibility、source compatibility、behavioral compatibilityを高い水準で維持することが求められ、「一見無害な変更」の互換性への影響さえ意外に大きくなり得ると明記されています。
この差を図式化すると、
Kotlin
Javaの問題
↓
別の言語として再設計
↓
Javaとはinteropで接続
Scala
既存Scala
↓
必要なら非互換変更
↓
migrationで橋を架ける
Java
既存Java
↓
できるだけ壊さない
↓
その制約内で新設計を追加
となります。
比喩的に言えば、KotlinやScalaが新しい国家の憲法を書く作業だとすれば、Java自身を進化させるということは、
既に膨大な判例・制度・権利関係が存在する国家の憲法を改正する
ことに近い。
「この制度、最初からこう設計しなければよかったのでは?」と言うのは簡単です。
しかし、もう何十年もこの制度を前提に社会が形成されている。
そのため設計者は、
理想的か?
だけではなく、
既存コードは動くか?
既存ライブラリはどうなるか?
既存バイナリは?
reflectionは?
フレームワークは?
IDEは?
コンパイラは?
JVMは?
まで考えなければならないでしょう。
後方互換性は革新の敵なのか
一面では、確実に敵であると言えるでしょう。
もし互換性を気にしなくてよいなら、
- 古いAPIを削除する
- 言語仕様を整理する
- nullを根本的に再設計する
- プリミティブ型と参照型を統一する
- checked exceptionを再検討する
- 古い構文を消す
といった大胆な変更もできます。
したがって、
後方互換性は設計可能な範囲を狭める。
この意味では、間違いなく革新を阻害します。
しかし、制約があるからこそ、Java独特の革新が生まれたとも考えられます。
たとえば、Java 21で導入されたSequenced Collectionsは興味深い例です。
それは既存のCollections Frameworkを捨て、
Collection 2
List 2
Set 2
Map 2
を作ったわけではありません。
既存の型階層へ、
SequencedCollection
SequencedSet
SequencedMap
という新しい抽象を後付けで組み込む設計を採りました。しかも追加メソッドにはdefault implementationを利用しています。
これこそ、
後方互換性が設計を邪魔した
とも言えるし、
後方互換性という制約が、この設計を生んだ
とも言える。
一般化すると、
削除できない
↓
共存させる方法を考える
置き換えられない
↓
既存設計を拡張する
意味を変えられない
↓
新しい意味を追加する
昔のコードを壊せない
↓
新旧が共存する設計を考える
必要が生じます。
つまり、
制約は革新可能性を減らすと同時に、別種の革新を要求する。
私はこれがJavaのかなり本質的な特徴だと思います。
Javaの進化は「昔の方法の再発明」でもある
Virtual Threadsは、この構造を考える上で象徴的な例だと思います。
サーバー側Javaでは、昔は、
リクエスト
↓
スレッド
↓
処理
↓
I/O待ち
↓
続き
という、かなり素直なthread-per-request型の書き方が自然だったようです。
しかし、量の同時接続を扱うにはOSスレッドのコストが問題になり、
- 非同期I/O
- Future
- CompletableFuture
- Reactive Programming
などが発展しました。
これは、
従来型の書き方ではスケールしないから、新しい方式へ進む
という革新でした。
ところがVirtual Threadsによって、
var result = database.query();
var response = service.call(result);
return response;
のような、上から下へ素直に書くスタイルが再び魅力を持つようになりました。
これは昔へ単純に戻ったわけではありません。
昔のthread-per-request
↓
非同期・Reactive
↓
Virtual Threadsによるthread-per-task
つまり、
A
↓
B
↓
A'
昔の分かりやすい抽象を、新しい実装技術で取り戻した。
これはまさに「伝統の再発明」と呼べるのではないでしょうか。
for文も「選択される伝統」になる
もっと小さな例なら、for文とStreamがあります。
昔は、
for (var item : items) {
// 処理
}
と書くのが普通でした。
ところがJava 8以降、Stream APIが普及し、
items.stream()
.filter(...)
.map(...)
.toList();
とも書けるようになりました。
これは非常に強力です。
しかし、「モダンJavaならStreamにする」という空気が生まれ始めると、逆に「これ、普通のfor文の方が読みやすいのでは?」という人も現れました。
ここでも、
for
↓
Stream
↓
Streamの規範化
↓
forの再評価
という動きがあります。
ただし最後のfor文は、昔のfor文とは意味合いが違うと言えるでしょう。
昔は、
他に選択肢がないからforを書く。
今なら、
Streamも分かった上で、この処理ならforの方が明快だから選ぶ。
これが、
必要としての伝統から、選択としての伝統への転換
です。
Javaは「古い言語」ではなく「歴史を内部に抱え込んだ言語」
そのため、私はJavaについて「レガシーか、モダンか」という二択をすると、かなり本質を取り逃がすと思います。
むしろJavaは、
1990年代以来の設計思想
→ 企業システムでの制度化
→ ボイラープレートへの批判
→ 関数型言語などからの刺激
→ lambda / Stream
→ record / sealed class / pattern matching
→ Virtual Threads
→ さらにValhallaなどによる根本的なオブジェクトモデルの再検討
というように、自分の過去を捨てないまま、その過去を何度も再解釈している言語と見ることができます。たとえばProject Valhallaでは、従来すべてのオブジェクトが持っていたidentityという前提そのものを拡張し、value class/value objectを導入する方向で現在も開発が進んでいます。
だから、最初のノートの話に重ねるならJavaはずっと、
「どうしたら我々は〜」
〃
〃
〃
「どうしたら我々は〜」
と書いているような言語なのかもしれません。
そして興味深いのは、最後の「どうしたら我々は〜」が単なる先祖返りではないことです。
一度〃を経験したからこそ、「全部書くことの意味」を以前とは違った形で理解している。
Javaも同じで、昔の単純さ → 複雑化 → 抽象化 → その抽象化の複雑化 → 新しい技術による単純さの回復を繰り返している。
そう考えると、Javaの「レガシー性」は必ずしも進化の反対ではありません。むしろ「伝統という重い制約そのものを材料にして革新する」という、Java特有の進化様式なのではないかと思います。
したがって、Java、Kotlin、Scalaの三者は「進化速度」で単純比較できません。
よく三者は、
Scala → 実験的・先進的
Kotlin → モダン
Java → 保守的・遅い
という一本の物差しで評価されます。
しかし実際には、
| 言語 | 主な進化問題 |
|---|---|
| Scala | より強力な言語抽象をどう設計するか |
| Kotlin | Java/JVM資産と接続しながら、より快適な言語をどう作るか |
| Java | Javaそのものの巨大な歴史を維持しながら、どう現代化するか |
という違いがあります。
Scala 3自身にも当然互換性問題はあり、公式に移行ガイドが必要になっています。
KotlinもJavaとの相互運用性を極めて重視しています。
つまり三者とも制約がないわけではありません。
背負っている過去が違うのです。
後方互換性は「壁」ではなく「重力」
私はここまで考えて、後方互換性を次のように捉えると分かりやすいと思うようになりました。
後方互換性は、
革新を止める壁
ではありません。
むしろ、
革新の軌道を曲げる重力
だと言えるでしょう。
制約がなければ、
現在 ⇒ 理想
と一直線に設計できます。
しかしJavaには、
既存コード
既存API
既存ライブラリ
既存ユーザー
既存バイナリ
という巨大な過去があり、軌道が曲がる。
そのため、理想へと一直線には進めません。
しかし、止まっているわけでもない。
この「曲がり方」そのものに、Java特有の設計思想が現れると考えられます。
伝統とは過去ではなく、現在を方向づける力なのかもしれない
最初のノートの話に戻りましょう。
私は、
どうしたら我々は〜
を書くのが面倒だから〃を使いました。
〃は便利でしたが、やがて〃は形式になりました。
そして、その形式を守るため、自然に書いてしまった文章を消しゴムで消しました。
この非常に小さな出来事の中に、
革新
↓
定着
↓
規範化
↓
伝統化
↓
旧来の方法の再発見
という構造が見えます。
そしてJavaもまた、似たことを繰り返している。
古い仕組みが問題になる。
新しい仕組みが導入される。
その革新が定着する。
さらに新しい問題が生まれる。
すると昔の考え方が、新しい技術を伴って戻ってくる。
だから伝統と革新は、単純な敵同士ではないのでしょう。
伝統は革新を制約する。
しかしその制約が、次の革新の形を決める。
そして革新は成功すると、次の伝統になる。
おわりに
「伝統をすべて捨てれば、自由になれる」という考え方は、一面では正しいと思います。
しかし、完全な自由だけが革新を生むとも限りません。
むしろ、
変えてはいけないものがあるとき、人はどう変えるかを真剣に考える。
Javaの後方互換性は、その典型かもしれないです。
そして伝統というものも、本質的には、
古いから残っているもの
ではなく、
現在の選択肢を制限しながら、同時に次の選択肢を形作っているもの
なのでしょう。
そう考えると、「伝統か、革新か」という問いそのものを少し変えた方がよさそうです。
問うべきなのは、
どの伝統を残し、その制約の中からどのような革新を生み出すのか。
なのではないでしょうか。
ノートに書いた小さな「〃」から始まった思索でしたが、最終的にはJavaの後方互換性までつながりました。
そして考えてみれば、それ自体も、
昔ながらの手書きのノートから、現代のプログラミング言語について考える
という、ちょっとした「伝統と革新」の組み合わせなのかもしれませんね。