機械と人間の間にある言語の深淵

初学者はよく学ばなければならないプログラミング言語の数に絶句します。 そして潮の流れが早いことにも絶句します。 次に選択肢によってプログラミング言語を使い分けないといけないことを聞いて絶望します。 どんなプログラマーでも一度は必ず経験があります。

(アセンブリでプログラムをすべて書いているから経験したことないと思っているそこのあなた。 あなたは人間を辞めているのでプログラマーではありません。)

しかしその経験の後、非常におもしろい思考があります。思うことは1つです。より良いプログラミング言語を作成する余地があるのではないかと。 そんな気持ちに取り憑かれたプログラマーは、プログラミング言語の作成をいつか行いたいと思い続けます。

今回は現代のプログラミング言語のある種の氾濫とも言える潮の流れから、全体的な主流としてもっとも重要視されているであろう点をまとめて 作りたいプログラミング言語はどんなものか考えます。 (これは最近、忙しくなってきてやりたことが機械学習など他にもあるため思考をまとめるために書きます。そのため、大雑把です。いつか書き直したいです。あと独学なので大学に授業なしだとどこまで考えが偏ってしまうのか観察してみてください。)

バベルの塔が実現することはあり得ない(免責事項)

プログラミング言語は非常に不可思議な言語といえます。 人間が使うものでありながら、コンピューターという新しい知力に寄り添う言語です。 もっとも新しい言語であり、もっとも新しい形態の言語です。

ならなぜ、統一されたバレルの塔を作ることができないのでしょうか。 それは人々とコンピューターの関係が常に発展しているからであり、常に環境が変化しているからです。

統合論者のチョムスキーは「言語を人間の脳が兼ね備えたメカニズム」と称しました。 内在的な原理として認知科学の一部として捉えています。

この原理をプログラミング言語的にとらえると「言語を人間の脳の兼ね備えた機能を情報工学に落とし込む外部的なメカニズム」 ともいえます。 その外因的な情報工学は類まれな進歩を遂げているため常に外部的なメカニズムであるプログラミング言語も進化と多様化を極めていると私は思います。

そのため私が今から書くことはすべて、情報工学の進化によって立場や状況が変わる可能性があります。 (統一的なプログラミング言語を書くことは私ごときにはできません。)

またすべて独学であり、査読という査読を受けていません。 大学の専門分野も異なるため許してください。 そしてACMや過去のプログラミング言語で実証されているものもあると思いますが、悪い面を取り去って残ったものなので許してください。

最初に理想を決めます。

  • シンプルで簡単に

  • 安全に

  • 速く

  • 厳格に

  • 多言語との連携や変換を考える

  • 意図されたユースケースを必ず守る

  • 自動的なコード生成

ルールを決めた後にλ計算を一時的に捨てます。

シンプルで簡単に

シンプルさはここではシステムを記述するために必要な情報量が少ないという定義を決めます。 コルモゴロフ複雑性という深淵には今回は触れませんが、記述力が高く、できる限り情報量が少ないことを目指すのが最も良いと考えられます。 Rustのような型の中毒性のあるパズルは行いません。Pythonのような見た目は使いやすいですが、裏で信じられないほどの認識外の挙動による保守の拒絶はシンプルとは言えません。 C言語のようなシンプルなメモリ操作の方がまだマシです。 言語はツールではありません。ツールを作るためのです。できる限り、根底の原理がシンプルでなければ制作物は見た目はシンプルでも必ず複雑性を持ちます。 短い仕様と小さいランタイム、わかりやすい速いコンパイラが理想です。

安全に

安全性の定義は想定した動作が外部に影響を与えず、 与えられず、間違えないという非常に難しいものとなっています。 しかもこれは「速さ」とトレードオフになっています。

例をとして、秘密計算は私のお気に入りの理論ですが、情報を秘匿にしたまま、計算が可能な非常におもしろい特性を持っています。

しかし、計算コストが非常の高いものが多いのです。解読もコストが高いです。これは理にかなっています。暗号化のコストが計算コストとして現れてるのです。簡単にゼロコストで暗号化した状態で計算できた場合、複雑性を究極に扱えた時か理を破壊した時でしょう。

秘密計算の場合は、計算の際に常にコストを必要があります。ですがプログラミング言語はコンパイル時のみに多大な犠牲を払い代わりに実行時は安全でできる限りコストを下げた設計を目指すことが可能です。

ロケットすらも落とす安全性の欠如はできる限り、最初に設計とコンパイル、lint、数学理論で回避します。

速く

速さは非常にシンプルです。定義は指定された条件下で実行時速度とコンパイル時間を指定の時間内に収め、できる限り早くすることです。

厳格に

厳格であるということは定義を行い、複雑性を増やさず、複雑性を制御することにあります。これはどうしても世界が複雑である以上、複雑性はどこかで複雑性でのみ対処し、どこかで生き残っています。しかし運が良ければ、もしくは制御すれば、それは明確に定義された場所に最小限の複雑性でわかりやすくいます。

複雑なシステムや、駆動系、理論は必ず複雑性を持ち合わせます。 Ashbyの法則ともいいます。しかし、必ず良いシステムはそれぞれ定義され分けられています。脳などは複雑ですが、場所によって役割が非常に分かれ秩序だっています。

プログラミング言語では秩序だてるためには、lintによる可読性の向上、曖昧さを抱擁する文法の排除、実行環境の再現性、ライブラリのエコシステム、コンパイラによる無理な最適化、ライブラリやブロジェクトなどのドキュメントでの統括的な管理と利用、コードとドキュメントが一体化できるようなドキュメントシステムの開発、管理の容易なビルドとパッケージシステム、利用しやすいCUIとGUI、利用しやすくわかりやすいパフォーマンスプロファイリングツール、ミスを把握しやすいデバックツールとコードに直接かけるビルドインのテストツール、言語全体の統計や経済指数と開発を管理するシステム、開発環境構築から始まらないわかりやすい実用的な学習教材とのビルドツールの連携、公式のLLMによるコーディングアシスタント、人間による十分なサポートとバグの補足を行えるコンパイラと連動した掲示板の作成など考えればキリがありません。

このような厳格な管理と運用がプログラミング言語には必要です。 小さなミスや知識の継承で時間を失い、脳を酷使するのはもう懲り懲りです。

多言語との連携や変換を考える

優れたプログラミング言語は何十種類もあります。 そのような言語と簡単に連携や変換を行えることは実用的な言語として非常に重要です。C言語の資源などは信じられないほど膨大であり、 それぞれの特有なCPUのアセンブリ言語へのコンパイルは円滑に行なわれなければなりません。 また既存の様々な言語の資源を活用することも必須であり、既存のソフトウェアを飼いならすことができるシステムソフトウェアとコンテナを作成できる言語が必要です

そのためにFFIが搭載されている必要があります。そうではなくても時間的、空間的な制限が十分ある細分化された権限を持つ機能が必要です。

意図されたユースケースを必ず守る

意図された使用方法は言語によって明確に異なります。 それにプログラミング言語が担う役割は非常に増えており、さまざまなことに利用できます。 そのためそれぞれ得意分野を理解し使い分ける必要があります。 その際に言語が時間と共にメリットを途中で見失い、損なってしまうことは非常に問題になります。 エコシステムの破損のみならず、デメリットでもありメリットでもある機能を失うことは言語としての 利用価値を見失う行為です。

自動的なコード生成

自動的なコード生成は以前はメタプログラミングを指していました。 現代ではそれのみならず、LLMによるアシスタントも含まれます。 LLMの使用を考慮したIssueに対する自動的な解決策の提案。 Pushの理由の説明とコードの解説を自動的に行う。 もちろんメタプログラミングを安全な保証された状態で速度改善のために行うことも重要です。 今後、コード生成を行う機会は非常に増えます。 そのような状況に合わせてLLMに対して十分な学習教材を与えられるような環境(広義のRLHF)とコード生成パイプラインを全体で作るべき状況に来ていると思います。

λ計算に縛られない

λ計算はもっとも広く使われる計算機科学のシステムです。 ML、System-F、Ocaml、Haskell、Scala という言語はλ計算に準拠したプログラミング言語といえます。 またソフトウェアのマクスウェル方程式と呼ばれるLispは純粋なλ計算の別記法だからこそ、シンプルかつ破綻のない言語となります。 (Lispの他にも抽象構文木をシンプルに書ける構文の議論をいつか行いたいです。) しかし、現代は非常に複雑なソフトウェアやセキュリティが必要になっています。 現代のCPUはスイッチが50~100億個あります。AMDEPYC第4世代プロセッサは約65億個のトランジスタを搭載しています。 そのスイッチを使いこなすには、大量のソフトウェアとアルゴリズムが必要です。 そして多言語との連携や可読性、ビルドシステム、エコシステム、並列処理、数学的な安全性の証明。 量子コンピューティング、機械学習、リバーシブルコンピューティング。 やらなければならないことはたくさんあります。 そのためλ計算を開発したアロンゾ・チャーチに申し訳ないのですが、一度隅におきます。 不動点コンビネーター(Yコンビネーター)など奥が深すぎるため最初から帳尻あわせするのが大変です。 代わりにチューリング完全という計算の心臓に欠かせない4つの要素で補填します。

  • 記憶装置

  • 算術計算(ペアノ公理)

  • 条件分岐

  • 繰り返し

まずこの要素を考察してから、λ計算に関連するものやその他の道具を考えます。

(チューリング完全であれば良いかどうかは、バグや不具合の可能性が増大することから議論の余地がありますが、 ここでは割愛します。 私はプログラミング言語はチューリング完全として存在し、ソフトウェア内部での小規模パーサーなど処理系はチューリング完全はない方がいいという考えです。)

異常に洗練されたメモリの安全性アプローチとその欠点

もっとも重要な記憶装置から考えます。 メモリの安全性はいつの時代ももっとも重要な項目の1つです。 そしてビルドに十分な計算資源を投入できるため、人間がメモリを管理する時代は終わりを迎えつつあります。 しかしかといって、実行スピードやメモリのムダ遣いできるほど人類の進化は遅くはありません。 そのため非常に多くのメモリの安全性アプローチが取られてきました。 現在でも多く用いられる古典的な手法を列挙します。

  • ガベージコレクション:人間が管理するよりも処理速度の面でも効率的なときが多いです。メモリを監視するため余分なメモリとエネルギー消費量が高いです。(1959年 ガーヴィン・ウッド、ジョン・マッカーシー)

  • 参照カウント:メモリの使用をカウントするシンプルで効率的なメモリ管理手法ですが、処理速度ととくに循環参照に関して適切に機能を使用する必要があります。(1960年代後半 初期のオブジェクト指向プログラミング)

この時代の学者は信じられないほど優れていました。シンプルに言語と計算工学を客観視し、非常に深い考察と理解していたと感じます。 この二つが組み合わせられることもあります。 また近代的と言えるかわかりませんが

  • 借用チェック:Rustにもあります。複雑なプログラム構造を管理するのは非常に困難に見舞われる可能性が高いです。自己参照データ構造や並列処理など(?)です。

が存在します。 借用チェックは参照カウントを併用することでRustで使用されていますが、その複雑性から借用チェックと戦うには人生は短すぎるかもしれません。とくに並列処理では。 Rustの借用チェックの欠点として型が推論されず、手動で複雑な型を操作し借用チェックを行わなくてはなりません。 また借用チェックが条件分岐後の状態を完全には分析できない点もあると思います。 安全性には寄与しており、速度も早くミスは防げますが可読性や学習コストが高くなっており人間に優しくありません。 メモリの安全性だけでなく、安全性全体として考えるとnullセーフな関数型のガベージコレクションを使用した言語の方が 独自のリストやベクターでまとめられる借用チェックはエッジケースや管理の難しさと多言語との互換性などで良い可能性すらあります。

ならば、古典的な方法より優れた方法はあるのでしょうか? まずメモリシステムについて考えます。

メモリシステム

まずゴールは安全性の限りなく高い実行時のコストがないメモリです。 前述のようにコストのかからない安全性はこの世に存在しないでしょう。 ですが初めから世界が安全な副作用のないメモリを許容する世界として設計されていてまだ気づいてないだけかもしれません。

このゴールに向かうにはメモリの安全性という概念を分解し、過去と現在に立ち返る必要があります。 まず概念を考えます。

まずメモリは時間的安全性と空間的安全性、相互的安全性があると定義し分解します。

  • 時間的安全性 使用後安全性(Use-After Safety)がここに該当します。メモリの時間的な軸での操作の領域に関係する安全性です。

  • 空間的安全性 境界外アクセスが含まれます。バッファオーバーフローやアンダーフローといったメモリの領域に関係する安全性です。

  • 相互的安全性 型安全性や整合性安全性、nullの参照、初期化安全性が含まれます。 主に、意図された範囲で人間がそれを意識することができずデータの内容が意図されたものと異なる際に発生します。人間の認識の齟齬による実行結果の破綻に関係する安全性です。

ここで最も対処しやすく、実行時のコストがゼロにできがすべてに関係するものは相互的安全性です。相互的安全性は可読性や人間の理解や十分に設計された文法によって改善することができます。全体に影響を与え、時と場合によってさまざまな最終形態になりますが他に比べれば解決もしやすいのでここでは触れません。(コンパイラの自動初期化だけは許しません。あれは禁止すべきです。)

それならば残りの二つはどのように実現し、コストを最小化すればいいのでしょうか。

現在アプローチを分解し、困難を分割する

時間的安全性と空間的安全性を担保するために使用されている現代の考えは主に4つの要素で構成されていると定義します。

  • ルールベースの手続的な静的メモリ管理(コンパイラが安全性の検証を行うことも含む、借用チェックなど)

  • メモリを追加で使用する動的メモリ管理(参照カウントなど)

  • 実行時に処理を行いエネルギーを使用する動的メモリ管理

  • 悪意のあるメモリの不正な利用を困難にするメモリ管理

またそれぞれの安全性は手法によって分類し定義できます。 実行時のコストの面ではルールベースの手続的なメモリ管理が最もコストが少ないです・ 時間的安全性は

  • 本番前にグラフ構造等の数学を用い安全性を検証する(無向グラフ構造など)

  • メモリアクセス時に安全性を確認する(ランタイムエラーなど)

  • メモリの使用後のミスや悪用を防止する(サンドボックス化など)

と定義し、 空間的安全性は

  • 権限ベースで安全な抽象化を行う(オブジェクト能力モデルなど)

  • 領域ベースでSafeエリアとunsafeエリア、メモリグラフを分離し、管理する

  • 型制約ベースでデータの変換、処理を管理する

と定義されます。 これらの要素と手法をコンパイラやコーディング時の複雑性と実行時のパフォーマンスでトレードオフを考え、組み合わせます。特に近年のプログラミング言語はトレードオフを技術で軽減しつつ、うまく配分されています。

安全性の限りなく高い実行時のコストがないメモリを実現するにあたってこの中で最も難しいのは時間的制約です。時間空間上に存在するため、本質的な事前の解決には決定論的なアプローチしかありません。 実行時の解決にはガベージコレクションが使用されますがこれもまた遅延解放による予測不可能なパフォーマンスのばらつきがあります。 また、型制約ベースでデータの変換、処理を管理することは非常に人間自身がデータ構造を熟知する必要があり 安全な抽象化には非常に深い知識が要求されます。 それに加えて、現代では並列処理の最適化という人間には早すぎる代物が関与してきます。 動的言語か静的言語かで方針は変わります。 困難を分割しましたが、まだゴールへの道筋は見えません。 しかしここに困難を一つずつ解決し、新たな概念を導入すればどうでしょうか。 複雑性のための家を一軒づつ建てるのです。

最新の新たな概念とその応用

まず解決できるのは空間的安全性です。 ここで非常に面白いシステムを採用しているものを紹介しようと思います。 E-langageとPonyです。 E-langageはPOLAに即しており、制約を指定するモデルによってデットロックが決して起きないことを保証します。 そしてPonyは発展的にE-langageのシステムに加え、Erlangアクターシステムを工夫し、領域ベースでメモリを分割することでガベージコレクションを完全並列型で行います。(いつか言葉で説明できるようになりたいです。) PonyのことシステムをORCAと呼び、適用可能な条件は以下のようなものです

  • Actor behaviours are atomic.

  • Message delivery is causal. Causal: messages arrive before any messages they may have caused if they have the same destination. So there needs to be some kind of causal ordering guarantee, but fewer requirements than with comparable concurrent, fast garbage collectors.

この手法は非常に優れた設計です。 権限ベースと領域ベースを巧みに利用し、並列処理を効果的に言語に組み込みます。 現代でマクロレベルで行われているモジュラーモノリスに通づるものがあります。 しかし問題点もあります。

一つ目は利用するガベージコレクションの性能予想が難しいことです。 並列に処理することで効率的にガベージコレクションを行うことが可能ですが、不確定要素がつきまといます。

二つ目は静的借用解析と権限ベースのガベージコレクションの共存難しいことです。 Ponyの応答性能を高めるにはコンパイル時の解析を組み込むことが考えられえます。 しかし、権限ベースと静的借用解析の借用チェックは類似性が高くそのまま組み合わせるのは難しいでしょう。

一つ目の問題点は解決の兆しがあります。Veronaです。メモリの最大の使用量を制限することなどで不安定性を除去します。

上記のようなアプローチが最新の概念として折り入れられてきました。 二つ目の問題点はどの言語でも根本的な問題である型制約と権限と領域と並列性をうまく融合し、両立することは難しいことを示唆します。Rustでは型制約と権限をうまく融合させていますが、並列処理がナイーブなものになっています。

次に時間的安全性です。この解決は非常に難しいですが解決策があります。

それはメモリの動きを無向グラフ構造もしくは有向非巡回グラフとして設計することを コンパイラが人に強制する方法です。 これを行うことでRustのような煩わしいパズルのような戦いから解放されます。

Ada、そしてAustralはその分野で非常に類まれな先駆者です。この概念を利用したリニア型は他のプログラミング言語の多くの異なる機能を置き換えることができると考えています。私も同意します。

またVelaも非常に類稀なる先駆者です。非常に優れた言語設計が行われているように感じます。 Velaはこれに加えて並列処理も兼ね備えています。正直言ってRustの完全な上位互換です。 (Valeの理論を理解するには十分な準備がまだできていないです)

この方法の問題は数学的な解決策しかなく非常に実装が面倒でコンパイル時間も長くなる(Rustのように)可能性が 高いですが、組み込むことができれば並列処理との親和性も高く、コンパイル時に証明できるため非常にリーズナブルです。SOLIDの原理の一つである依存性反転原理にも近しい行為だと思います。

  • 人間にこのような複雑な機械を空間的安全性を担保しつつ設計できるのかという点

  • 境界チェックのようなデータの動きを十分に記述する必要がある点

  • 巨大なシステムになればなるほど全体のコンパイル時間が増大する危険性

です。 設計に関しては...まあ数学は全てを解決してくれます。 あとは計算可能性理論を考えてZFC集合論で厳格に...CTLでもいいかな...いや命題を明確にして言語特性をしっかり記述して...Coq... 楽しそうですがちょっと吐き気を催すのでここではやめておきましょう。

十分な記述能力に関してはモジュラーモノリスを言語ルールが推奨し、 GUIで簡略化されたグラフ構造を確認できるように最優先で搭載するようにすることで解決できると思います。

またコンパイルに関してはグラフ構造解析をモジュール化し動的に逐次的に局所で確認することで 一気に書いた後に長時間待たされるのを防ぎます。

(停止性問題?「ゲーデル、エッシャー、バッハ―あるいは不思議の環」のフイクションの話ですから...逃げちゃダメですよ)

もう一つあります。MVS(Mutable Value Semantics)です。 この概念はHyloにおいて用いられ、Rustの所有権の参照システムにシンプルな参照ルールを4つほど追加したものです。 これにより参照の意味論を強化しています。

この方法も非常にユニークであり、シンプルながらRustの複雑性を回避することができます。

問題点は値のコピーが従来より増えてしまう可能性があることと並列処理に対しての柔軟な対応ができるか不明な点です。 理論上は並列処理も親和性はあると論文では考えられています。私もそう思います。

そして最後に今まで様々な先進的なメモリに関する理論をとらえてきましたが、これらの全てに共通することは従来の言語との互換性(C,C++など)を破っているということです。 他言語のライブラリをUnsafe部分としてうまくカプセル化し、メモリをうまく領域で区分できればライブラリを並列で動かし、ある種の呼び出した関数がうまく動作しないことを保証するような並列特有の特性を利用したFFI以上の他言語ライブラリの組み込みと広範囲に影響与えない安全性の確保が可能になるかもしれません。 またCをRustに変換する研究がDARPAにありますが、変換して組み込むのは言語特性が常に進化するため難しいものであるのは間違いありません。 それにSIMDという恐ろしい命令を手動で簡単にかける方法も欲しいです。 JITを前提に設計されたメモリ管理も非常に面白い命題だと思います。 JITで影響されるメモリと影響されないメモリをコンパイラが区分することでJITコンパイラをより最適化できます。

現実的な問い

ここまでで分かったことはゴールに必要なものは

  • 制限なしのガベージコレクションは理想的には使用しない(ガベージコレクションを一部に使うことは許容できる)

  • 並列処理に現時点で最も向いているのは領域ベースと権限ベース

  • 型制約はそれぞれのメモリ管理と密接に関わっており、途中からの変更は難しい

  • グラフを用いたメモリの管理は非常に有効的

  • MVSもまた非常に有効的

  • 従来の言語との互換性をFFI以外に行う方法

  • SIMDを効率的に直感的に扱う(があると最高)

  • JITを前提に設計を行う(ともに最高)

今までのことをまとめるために解決すべきことを問いにして書くのが最もわかりやすいと個人的には思っています。 この最終的な結論の中で私が導き出した明確な問いを書きます。

  • 並列処理を行う際に流動的なメモリ管理をガベージコレクションを使用せず行えるか(並列処理のメモリ管理の体験の向上)

  • 言語レベルの領域ベース、権利ベースをリニア型、MVSと組み合わせることは複雑にならないか(型制約を増やさない)

  • メモリ管理を犠牲にせず、リニア型、MVSの制約をどこまで減らすことができるか(型制約を減らす)

  • コンパイル時間をどこまで減らせるか(JITのようなアプローチが解決するかも?)

  • 他言語との連携をメモリの管理を通じてどこまで歩み寄れるか(並列処理の安全性の向上)

  • 新しいアプローチはないのか

また組み込みなどのメモリを操作する必要がない場合は

  • GCをうまく手動で制限をつけ、コントロールすることでMMMと同等の性能を出すことはできないか

ちょっとしたTip

  • 安全でないライブラリや別の言語のライブラリはGCを使用する。
  • 分散システムではGCとメモリ管理を導入(Unison)

永続的データベースの話もあります。 メモリ管理では言語自体が永続的データベースを持つことは誰もの夢です。 プログラムを閉じて、また起動するときにプログラムに書かれているデータが保存されています。 しかし、永続的データベースがコードに組み込まれるとコードを実行するごとに値が変化する可能性がある箇所が出てきます。 そのため大抵、組み込んだ言語は複雑性が増すことで管理がむずかしなります。 しかし、非常に面白い視点があります。Lispのようにコードをデータとして扱います! Unisonはコード自体をデータの一部としてデータベースで扱います。 それにより、コードの履歴、差分とデータが処理され追跡を容易にします。 正直、自分はSQLが嫌いなのでこの概念は本当に導入したいです。

最終的な結論

私はこれらの煩雑な考えと明確な難しい問いの中からメモリ管理に関して以下の自身の言語の指針を決めます。

  • グラフを用いることでデフォルトで定義された動作のみが可能なメモリ(未定義はUnsafeで明示的にコンパイラに伝え、分ける。)

  • ガベージコレクションは行わない(特定の状況で意図的に導入することはできるようにする)

  • 領域ベースや権利ベースのようなセキュリティモデルに直結するような言語設計を組み込む

  • 並列処理などの実行されない可能性のあるメモリ管理を持つ関数や同時アクセスなどの危険性がある関数をアクターのような関数というメモリを管理するシステムを導入する(関数は明示的に指定する?タイムアウトや関数をスキップする)

  • メモリでの型制約はできるかぎり削減して簡素な言語仕様にする

  • グラフを用いたメモリ管理とそれをGUIでブラウザや画像、ターミナルで表示できるコンパイラを作成する。

  • JITを前提に設計されたメモリ管理を行いコンパイル時間を短縮する。

  • 他言語のメモリ管理での連携を強化されたFFIを通して可能にする。

  • 永続的データベースを言語設計にバージョン管理のような形で組み込みメモリを監視する

できるかどうかは多分難しいですが、今までの言語の反省と良いところを引っ張ってきたら難しいものになりました。 GPUへの対応のための並列処理、JITを組み込むことでデメリットであったコンパイル時間と動的言語の特性を発生、関数というメモリを管理することを利用した他言語との強固な連携、 データの巨大化を想定した永続的データベースが非常に野心的な設計だと思います。 もちろん根底にあるRustより簡略で並列処理が簡素にできる型制約も忘れていません。

このほかに新しい概念を思いついたら追記か新しい記事を書きたいです。

算術計算

プログラミング言語には、算術、比較、ビット単位、ブールなど、多くのカテゴリのバイナリ演算子があります。それらの計算順序や計算された結果の範囲が十分に人間の中で定義されていない場合、それは人間にとって未定義の動作となります 想定された計算と異なる計算が行われることは避けなければなりません。 Astralは非常に良くできています。 演算子の優先順位を完全になくし、すべての2項演算(binary operation)は括弧で明示的にグループ化しなければならないというルールは非常に明確なものです。

また並列処理を行うことを想定してベクトル演算子のサポートもプログラミング言語には必須となる時代が来ていると思います。ですが、APLのような特殊な記号を使うことで見分けがつかないことは避けなければなりません。

また記号計算も同時にプログラミング言語は標準でサポートする必要があると思います。 Wolframのような代数的操作や方程式の解放、微分積分、数式への代入、特別な関数の操作、数値計算への切り替え、 複雑な数式の簡略化などです。そのために代数的データ型をサポートする必要もあります。

関数の変換の型制約も重要です。これにより台数的処理を行いやすくなります。

またそのほかにはLATEXで記述された数式の変換と、数式のLATEX形式への変換を公式のドキュメントがサポートすることは非常に学習に有用だと考えられます。様々な分野の人が数学に触れることができるようにするためです。

条件分岐

条件分岐にはシンプルな比較表現とパターンマッチングのみが最も複雑な条件を解決する手段だと考えています。

また型を比較することが可能な論理的型づけにもとづいた比較も比較表現に含まれます。 型制約に柔軟性を保つために必要です。

パターンマッチングで先駆者と考えられるのが Egisonという言語のパターンマッチングのシステムです。 非常に 面白いパターンマッチングとなっています。

繰り返し

繰り返しの機構は私は非常に不満に思っている点が多くあります。 繰り返しの機構がどこで終わってるのかカッコでわからない点です。 エディターのサポートなしでは書くのが面倒ですが、カッコの終わりには どのループの終端か明示すべきです。

不要なメモリ確保を避けるために事前に確保することでループの速度を高めることができます。 従来の言語ではそれを別のプロトコルで書くことで高速化を図ってきました。

しかし、様々な書き方があると混乱するため ループではイテレータに見せかけることができるfor文一つで十分だと思います。 その際に実際にはコンパイラはメモリを事前に確保する形をとるべきだと思います。

関数定義

関数は非常に十分な信頼性があります。 関数の駆動が失敗することはほとんどありません。 しかし並列処理や分散システム、安全性の低いライブラリでの未定義の動作は十分関数の破壊が行われます。

そのため関数が破壊される危険性や関数を呼び出したこと自体がなくなる危険性を許容することが重要だと考えています。そのために必要な機能はアクターのような機能であり、

  • 処理が一定の区画で十分な時間がたっても帰ってこない際に割り込む

  • 関数の処理を飛ばし、それをエラーとして捉えそのまま継続して処理を行える

  • 関数の領域内のメモリが外部に影響を与えないように監視する。

このような機構が言語自体に必要だと考えています。 もちろんこのような機構なしでも並列処理ができるようにします。 そしてこの機構があったとしても逐次処理を行えるようにします。

権限ベースの関数設計も必要です。 十分な権限がそこにあるのか、そして十分以上の権限がそこに存在してしまうのか。それをコンパイラで静的に解析できるようにする仕組みが必要です。 直感的だとなお良いです。

能力の受け渡しの本質はメモリ操作なのでここでは触れません メモリ管理の際に書きましたし、くどいかもしれません。

再起処理

末尾再帰を強調し、コンパイラが指示するようにします。 またパターンマッチングと組み合わせられるようにします。

コンパイラ

コンパイラは以下の要素で構成されます。

  • コンパイラ本体(3層構造: 字句解析-構文解析-型チェック-中間表現生 : 中間表現の最適化とJITとの連携 : LLVM)

  • ビルドツール

  • パッケージマネージャー

  • モジュラー構造の複数のlintからなるlint

  • データベースとプロファイリングツールとデバックツール

  • 多言語対応したエラーメッセージと初心者向け内蔵チュートリアル

  • ドキュメント生成の自動化

  • コードに書くことができるテストツールの内蔵

  • LLM(new!! 問題が発生した際の理解や改善のアシスト 関数の説明の自動化 学習アシスタント)

  • ブラウザに表示できるコードの構造のGUI、もしくは画像

  • コミュニティとの連携

機能

野心的な機能を書きます。

  • Appで単純な構文は実行できるようにする。

  • IRからコードを復元できるようにする

  • ドキュメントをコードから非常に細かく作成できる

  • 少し動的な言語

現代の言語が大きすぎる問題がありますが Appでも実行できるようにすると非常に便利で初学者の学習を促進できると思います。

IRからコードを作成できる。可逆性を持って欲しいです。 ある程度の言語の機能をロックすることで可逆性を持つことができるのではないかと考えています。

ドキュメントを複雑に書く仕組みを取り入れたいです。例えばリンクや関数の説明のルールの強制、またはルールの解説の自動生成。

少し動的にすることで書きながらエラーを出したり、コードの警告を出せるようにするといいと思います。

言語の個別の専門的な目的

今後のために多くのことを視野に入れた設計が求められます。 それか、一つのことに異常に特化した言語か。 正直なところ一つに特化した言語は便利ですが、非常に多くの言語を勉強しなければならなくなり、それぞれの言語の連携も熟練の技となる場合があります。 できたら汎用的な他言語の中心となるような連携がうまくできる言語がいいです。

かといってどんな言語も最も良い長所や、特性から最も推奨される利用方法があり、その意図されたユースケースは守らなければなりません。

ということで私が今後必要だと思うユースケースは以下の通りです。

  • 並列処理への対応

  • Robotのための組み込み開発(セキュリティの高い)

  • JITへの言語レベルでの対応

  • 言語レベルでのセキュリティ理念の対応

  • 現代のプログラミング言語の学習コストの削減

  • サーバーなどのための分散システムへの対応

Ocamlの型制約とRustのメモリの安全性を両立し、言語仕様を削減しつつ、パフォーマンスを両立する並列処理と組み込みに対応した言語を作成したいです。

エコシステム

エコシステムに関しては非常に難しいですが、 私はハードウェアとの連携を公式が管理すべきだと思っています。

ハードウェアごとの認証を公式が行うことでライブラリの統一を行います。

それ以外は公式のサイトでパッケージを見ることができるようにします。 またGithubと連携し、簡単にデモをコンパイラを通して一行でコンパイラがクローンまで行います。

また、初学者むけの楽しい言語学習をエコシステムを通して支援すべきだと考えています。開発環境の作成の敷居を下げることはもちろんですが、親しみやすい見た目が必要だと思います。

またコンパイラの開発のみGitを利用しない手法で敷居をできる限り下げる必要があると思っています。初学者むけの言語学習の延長線上に置くことで簡単にコンパイラをいじることができ、投稿できることへの体験を強制することが重要だと考えています。 様々なルートが実績のように開放されていくゲームのようなシステムがベストです。

最終的に、このシステムを統合し、データを収集することで言語全体の統計や経済指数と言語開発を管理するエコシステムに昇華することが最終目標になると思います。

そして、APIやその他の変更があった場合にコンパイラから通知が届くようにする必要があると思います。

また、新しいプログラミング言語の開発を行える環境を整えるようにすべきだと思います。主にそのような観点で公式が整備すべきものは、OS開発環境、ハードウェア開発環境、CPU開発環境、クロスプラットフォーム開発環境、そして新しいプログラミング言語開発です。(もっとあるかもしれません)

そしてその全てがLLMを強化し、LLMによって効率化されつつ、次世代の言語を作成する基盤を作ると思います。

ロードマップ

ロードマップを考えると

  1. 言語仕様の作成

  2. コンパイラ本体の作成(Ocaml)

  3. 学習システムの整備(重要)

  4. ビルドツールの作成

  5. パッケージマネージャーの作成

  6. LLMの作成

の順番などになると思います。

終わりに

計算機の性能が低かった昔は技巧的なコードを書く手間が必要でしたが今は必要ではありません。 現代の機械学習は非常に進んだことでプログラミング言語が書けるとが少なくなり、 文章を書ける人すら少なくなるかもしれません。

しかし機械学習のもとでプログラミング言語は素晴らしい進化と未来があると思います。

新しい機械とのコミュニケーションには新しい言語の進化が必要不可欠であり、 言語は私たちにある内在的な原理であり、機械学習にとっても本質の一つだからです。 次は、上記を踏まえてTLA+で言語仕様を考え、Menhirによって実装し、ブートストラップまでいつか行いたいです。

愛すべき言語たち

  • CYCLONE

  • Pony

  • E

  • Vale

  • Austral

  • Verona

  • Wolfram

  • Egison

  • Koka

  • Swift

  • C

  • Rust

  • JavaScript

  • Go

  • Nim