結論から言うと、今となっては型の記述は後置のほうがやりやすいです。これは、型推論の事情から後置表現じゃないとうまくいきません。
例えば、C/C++ の場合、型を前に書きます。
int x, y;
int sum ;
逆に Pascal を始めとして、次のように後置になるものが最近の傾向です。
var x, y: Integer;
var sum: Integer;
一見すると、どちらでも変わらないような気がするのですが、ここに C++ のイテレータを書くと
varctor<int> vec = {1,2,3};
std::vector<int>::iterator it = vec.begin();
いや、これはもう無理です。まあ、実際こう書いていたわけですが、ちょっとインテリセンスとか何かの IDE の助けがないと難しいです。イテレータぐらいだから大丈夫だけど、C# のややこしい LINQ のポインタだとか諸々を加えると無理です。なので、型推論させて、C++ のほうは auto とか、C# の場合は var で受けるのが常です。
varctor<int> vec = {1,2,3};
auto it = vec.begin();
と型推論をさせます。ハッキリ言って、C/C++ でも後置型に変えてしまって、
auto x = 10 : int ;
auto y = 20 : int ;
だろうとは思います。実際のところ auto が使えるので、下記のようにキャストを使って無理矢理推論させることが可能です。あまり意味がありませんが。
auto x = (int)10 ;
auto y = (int)20 ;
C言語以前は、前置型であったのか?
C 言語以前が、前置型だったのか、とは思っていたのですが、これが違います。Ada が後置型で、なんと COBOL が後置型です。
– Fortran 1957 年 前置型
– COBOL 1959 年 後置型
– Pascal 1970 年代 後置型
– C 言語 1972 年 前置型
– Ada 1980 年代 後置型
– Java 1995 年 前置型
ここで、調べたときに「ああ、これ、前置型とか後置型とかはプログラム言語を作った人の趣味だわ…」と気づいたわけで、何か意図があって指定したわけではなさそうです。
ただ、Pascal が教育言語として出て来たときには、「C 言語のように前置きで型指定をするのではなく、教育的に理解しやすいように Pascal は型の指定が後置にしてある」という話があったので、何等かの理由があって、C 言語以前は前置きが多かったのではないか?と思っていたわけです。
これ、実際にコンパイラを作ろうとすると型の指定が前のほうにあったほうが楽で(これも人に依るとは思うのですが)、後にあるときはちょっと面倒なんですよね。あと、変数の前に var とか let とかあるのが無意味では?と思ったものです。まあ、Basic なんて Dim で定義したりするので、型はないのですが「変数であること」宣言みたいなものが var/let/Dim には存在します。
構文解析のときに前置型と後置型でメモリ使用量などに差異はでるのか?
ここからが本題で、例えばコンパイラを作ろうとしたときに何等かのメモリの制約などに違いがでるのか?という視点で見ていきます。今となっては、メモリが潤沢にあるのでどちらでも構わないのですが、歴史上「32kB 程度で動くことを想定」したときに差異がでていたのかもしれません。
ただし、これも結論を先に言えば、COBOL が後置型なので、仮説としても弱いです。
agnet.md を作成する
この手の実験をするのに、AI エージェントはとても役に立ちます。かつては、上記のことを証明しようと思ったら疑似コンパイラをちまちまとプログラミングして比較しないといけないところなのですが、バイブコーディングでかなりイイ線までいってくれます。
疑似的にコンパイラを作ればいいので、字句解析と構文解析だけ作ります。想定としては変数の型指定のところだけなので、変数定義と関数定義だけを例にとっています。もう少し話を広げれば構造体とかも含めてもいいかもしれません。あと、C 言語風のポインタ指定とかも入れるとよいでしょう。
プログラム言語は、C言語、Pascal、Fortran、COBOL、Ada、Java、Kotlin、Lisp を対象にしています。最初は、C言語、Pascal だけ作って後から少しずつ増やしていきました。
# コンパイル時の使用メモリ量をチェックする
プログラム言語には、型指定を前置と後置するものがある。
例えば、C言語やJavaは前置型指定であり、変数宣言時に型を指定する。
```c
int x = 10;
```
一方、Pascal や Kotlin などは後置型指定となる。
```pascal
var x: Integer;
x := 10;
```
プログラム言語の歴史上、前置型指定が主流であったが、後置型指定も徐々に普及してきている。
前置型指定は、コンパイルするときに型の指定が前置のほうがメモリ使用量が少なかったのではないか?と仮説を立てる。
これを検証する。
C 言語以前には、Ada が後置型であるので、Pascal 以前にも後置型があってもおかしくはない
```Ada
X : Integer := 10;
```
## 登場時期
- Fortran 1957 年 前置型
- COBOL 1959 年 後置型
- Pascal 1970 年代 後置型
- C 言語 1972 年 前置型
- Ada 1980 年代 後置型
- Java 1995 年 前置型
## 仮説
a. 前置型指定のほうがコンパイル時のメモリ使用量が少ない。このため、前置型が主流であり、メモリが増えるときに後置型が発生した
b. 前置型指定は慣習であり、メモリ使用量には影響しない。C 言語前後で前置型が作られたのは偶然である
a/b を検証する。
## 検証方法
1. 前置型指定言語と後置型指定言語で、同じアルゴリズムを実装する
2. コンパイル時のメモリ使用量を測定する
比較対象は、
- 構文木のメモリ使用量
- シンボルテーブルのメモリ使用量
利用メモリは、32KB 以下とする。
## 実装
- 字句解析の実装
- 構文解析の実装
- シンボルテーブルのメモリ容量を比較
- 構文解析時の構文木のメモリ容量を比較
## 文法
以下の文法を解析できるコードを作成する。
### C 言語(前置型指定)
```C
type_specifier: "int" | "float" | "char" ;
declaration: type_specifier identifier ";" ;
```
```C
int x, y;
int sum ;
x = 10 ;
y = 20 ;
sum = x + y ;
int func(int a, int b) {
return a + b ;
}
```
### Pascal(後置型指定)
```pascal
type_specifier: "Integer" | "Real" | "Char" ;
declaration: "var" identifier ":" type_specifier ";" ;
```
```pascal
var x, y: Integer;
var sum: Integer;
x := 10 ;
y := 20 ;
sum := x + y ;
function func(a: Integer; b: Integer): Integer;
begin
return a + b ;
end;
```
### Fortran(前置型指定)
```fortran
type_specifier: "INTEGER" | "REAL" | "CHARACTER" ;
declaration: type_specifier identifier_list ";" ;
```
```fortran
INTEGER x, y
INTEGER sum
x = 10
y = 20
sum = x + y
INTEGER FUNCTION func(a, b)
INTEGER a, b
func = a + b
END FUNCTION func
```
### COBOL(後置型指定)
```cobol
type_specifier: "PIC 9" | "PIC X" ;
declaration: "01" identifier "PIC" type_specifier "." ;
```
```cobol
IDENTIFICATION DIVISION.
PROGRAM-ID. SAMPLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 X PIC 9(4) VALUE 0.
01 Y PIC 9(4) VALUE 0.
01 SUM PIC 9(4) VALUE 0.
01 A PIC 9(4) VALUE 0.
01 B PIC 9(4) VALUE 0.
01 RESULT PIC 9(4) VALUE 0.
PROCEDURE DIVISION.
MAIN-PARA.
MOVE 10 TO X.
MOVE 20 TO Y.
ADD X TO Y GIVING SUM.
MOVE 5 TO A.
MOVE 7 TO B.
PERFORM FUNC-PARA.
DISPLAY "SUM = " SUM.
DISPLAY "FUNC(A,B) = " RESULT.
STOP RUN.
FUNC-PARA.
ADD A TO B GIVING RESULT.
EXIT.
```
### Ada(後置型指定)
```ada
type_specifier: "Integer" | "Float" | "Character" ;
declaration: identifier ":" type_specifier ";" ;
```
```ada
X : Integer;
Y : Integer;
SUM : Integer;
X := 10;
Y := 20;
SUM := X + Y;
function Func(A : Integer; B : Integer) return Integer is
begin
return A + B;
end Func;
```
### Java (前置型指定)
```java
type_specifier: "int" | "float" | "char" ;
declaration: type_specifier identifier ";" ;
```
```java
int x, y;
int sum;
x = 10;
y = 20;
sum = x + y;
int func(int a, int b) {
return a + b;
}
```
### Kotlin(後置型指定)
```kotlin
type_specifier: "Int" | "Float" | "Char" ;
declaration: "var" identifier ":" type_specifier ;
```
```kotlin
var x: Int
var y: Int
var sum: Int
x = 10
y = 20
sum = x + y
fun func(a: Int, b: Int): Int {
return a + b
}
```
### Lisp(前置型指定)
```lisp
type_specifier: 'int | 'float | 'char ;
declaration: (declaim (type type_specifier identifier)) ;
```
```lisp
(declaim (type 'int x))
(declaim (type 'int y))
(declaim (type 'int sum))
(setf x 10)
(setf y 20)
(setf sum (+ x y))
(defun func (a b)
(+ a b))
```
Plan モードで作成
vscode + copilot に Plan モードがやって来たので、先の agent.md(つまりは要件定義)から具体的な実装計画を立てて貰います。
実装時に足りなそうなところは、「Further Considerations」に追加されるので、この部分を補足していきます。
## Plan: 前置型/後置型コンパイラメモリ使用量比較ツールの実装
C言語(前置型指定)とPascal(後置型指定)の構文解析器を実装し、コンパイル時のシンボルテーブルとASTのメモリ使用量を比較して、仮説を検証します。現在のワークスペースには実装が存在しないため、すべてゼロから構築する必要があります。
### Steps
1. **トークン定義の実装** - `Token.cs` を作成し、両言語で必要なトークン種別(`int`, `var`, `:=`, `=` など)と `Token` クラスを定義する
2. **字句解析器の実装** - `Lexer.cs` を作成し、C言語用とPascal用の2つのLexerクラスを実装する(共通基底クラス + 派生クラス構成)
3. **ASTノード定義** - `Ast.cs` を作成し、`ProgramNode`, `DeclarationNode`, `FunctionNode`, `AssignmentNode`, `BinaryExpressionNode` などのノードクラスを定義する
4. **シンボルテーブルの実装** - `SymbolTable.cs` を作成し、変数名・型のマッピングとスコープ管理を実装する
5. **パーサーの実装** - `CParser.cs`(前置型)と `PascalParser.cs`(後置型)を作成し、それぞれの文法に基づく構文解析器を実装する
6. **メモリ測定機能の実装** - `MemoryMeasurement.cs` を作成し、GCとオブジェクトサイズを使ったメモリ使用量計測ロジックを実装し、`Program.cs` で比較実行と結果出力を行う
### Further Considerations
1. **メモリ測定手法の選択** - `GC.GetTotalMemory()` を使うか、各オブジェクトのサイズを手動計算するか? → 正確性を重視するなら手動計算を推奨
構文木のノード数、シンボル数のカウントを行い、メモリを計算する。
C# のメモリではなく、計測のために、構文木のノードとシンボルの容量を allocate するように実装する。
できるだけ小さなメモリ(32KB 以下)で動作するように注意する。
2. **Lexer の共通化レベル** - 完全に別クラスにするか、共通基底クラスを使うか? → 比較の公平性のため、できるだけ共通化を推奨
他の文法も試したいので、できるだけ共通化して。
完全に共通化できなくても、構文木の作成とシンボルテーブルの部分は共通化する。
3. **テストデータの規模** - agent.md のサンプルコードだけで十分か、より大きなテストケースを用意するか? → 32KB制約内で複数パターンのテストを推奨
テスト用の文法は、いくつかのパターンを用意して、32KB 以下を想定して実験する。
### メモリ計算の定義
- ASTノード: 基底部分 8byte + 子ノード参照 4byte × 子数
- シンボルエントリ: 識別子名(固定長 16byte)+ 型情報 2byte + スコープ情報 2byte = 20byte
- 測定対象: パース完了時点での構文木 + シンボルテーブルの合計サイズ、およびパース中の最大使用量
### allocate シミュレーション
- `MemoryAllocator` クラスを作成し、32KB のバッファを管理(ノードのカウント数、シンボル数から計算する)
- ノード/シンボル生成時に `Allocate(size)` を呼び出し、上限を越えたら警告を出す。実行は続行してよい。
比較するところはコンパイルするときのメモリ消費量なので、構文木のノードとシンボルテーブルの量を疑似的に計算します。コンパイラによっては、構文解析の部分が言語ごとに異なると思うので、それは別途計算しないといけないかもしれません。今回は、それはパスして、単にノードの数だけを概算しています。
プログラムを作る

vscode + copilot + Claude Opus 4.5 で作っています。最近 Codex が入ったらしいので、そっちを使ったほうが早いかもしれません。
生成コードは C# にしてあります。コード直すことはないのですが、私の場合は C# が直しやすいのはこれで。生成コードは Python にしても構いません。
ファイル名を見るとわかりますが、典型的なコンパイラのファイル名で返してくれます。プログラム言語ごとに Parser が違うだけで、そのほかは共通になっています。Token.cs が各言語でごちゃまぜになっていますが、研究ツールとしてはこれで十分です。
結果
PS H:\ai\check-mem-parser> dotnet run
======================================================================
前置型 vs 後置型 コンパイル時メモリ使用量比較
8言語比較: C, Fortran, Java, Lisp (前置型) vs Pascal, COBOL, Ada, Kotlin (後置型)
======================================================================
=== C言語(前置型) パース結果 ===
AST メモリ: 260 bytes
シンボルテーブル メモリ: 80 bytes
合計メモリ: 340 bytes (0.33 KB)
ノード数: 19
シンボル数: 6
パース中最大メモリ: 632 bytes
エラー数: 0
警告数: 0
=== Fortran(前置型) パース結果 ===
AST メモリ: 284 bytes
シンボルテーブル メモリ: 80 bytes
合計メモリ: 364 bytes (0.36 KB)
ノード数: 20
シンボル数: 6
パース中最大メモリ: 732 bytes
エラー数: 0
警告数: 0
=== Java(前置型) パース結果 ===
AST メモリ: 260 bytes
シンボルテーブル メモリ: 80 bytes
合計メモリ: 340 bytes (0.33 KB)
ノード数: 19
シンボル数: 6
パース中最大メモリ: 632 bytes
エラー数: 0
警告数: 0
=== Lisp(前置型) パース結果 ===
AST メモリ: 280 bytes
シンボルテーブル メモリ: 80 bytes
合計メモリ: 360 bytes (0.35 KB)
ノード数: 20
シンボル数: 6
パース中最大メモリ: 532 bytes
エラー数: 0
警告数: 0
=== Pascal(後置型) パース結果 ===
AST メモリ: 260 bytes
シンボルテーブル メモリ: 60 bytes
合計メモリ: 320 bytes (0.31 KB)
ノード数: 19
シンボル数: 6
パース中最大メモリ: 632 bytes
エラー数: 0
警告数: 0
=== COBOL(後置型) パース結果 ===
AST メモリ: 496 bytes
シンボルテーブル メモリ: 140 bytes
合計メモリ: 636 bytes (0.62 KB)
ノード数: 30
シンボル数: 8
パース中最大メモリ: 724 bytes
エラー数: 0
警告数: 0
=== Ada(後置型) パース結果 ===
AST メモリ: 280 bytes
シンボルテーブル メモリ: 60 bytes
合計メモリ: 340 bytes (0.33 KB)
ノード数: 20
シンボル数: 6
パース中最大メモリ: 648 bytes
エラー数: 0
警告数: 0
=== Kotlin(後置型) パース結果 ===
AST メモリ: 280 bytes
シンボルテーブル メモリ: 60 bytes
合計メモリ: 340 bytes (0.33 KB)
ノード数: 20
シンボル数: 6
パース中最大メモリ: 648 bytes
エラー数: 0
警告数: 0
========================================================================================================================
8言語比較結果
========================================================================================================================
項目 C言語 Fortran Java Lisp Pascal COBOL Ada Kotlin
------------------------------------------------------------------------------------------------------------------------
AST (bytes) 260 284 260 280 260 496 280 280
Symbol (bytes) 80 80 80 80 60 140 60 60
合計 (bytes) 340 364 340 360 320 636 340 340
最大 (bytes) 632 732 632 532 632 724 648 648
ノード数 19 20 19 20 19 30 20 20
シンボル数 6 6 6 6 6 8 6 6
======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================
前置型(C, Fortran, Java, Lisp)平均パース中最大メモリ: 632.0 bytes
後置型(Pascal, COBOL, Ada, Kotlin)平均パース中最大メモリ: 663.0 bytes
結果: 後置型は前置型より平均 31.0 bytes 多くメモリを使用
→ 仮説 a を支持: 前置型の方がメモリ効率が良い可能性
======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================
前置型(C, Fortran, Java, Lisp)平均パース中最大メモリ: 632.0 bytes
後置型(Pascal, COBOL, Ada, Kotlin)平均パース中最大メモリ: 663.0 bytes
======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================
前置型(C, Fortran, Java, Lisp)平均パース中最大メモリ: 632.0 bytes
======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================
======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================
======================================================================
======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================
前置型(C, Fortran, Java, Lisp)平均パース中最大メモリ: 632.0 bytes
後置型(Pascal, COBOL, Ada, Kotlin)平均パース中最大メモリ: 663.0 bytes
結果: 後置型は前置型より平均 31.0 bytes 多くメモリを使用
→ 仮説 a を支持: 前置型の方がメモリ効率が良い可能性
注: 後置型では識別子を一時的にバッファリングする必要がありますが、
このサンプルコードの規模では顕著な差は見られません。
より大規模なコード(多数の変数宣言)で差が出る可能性があります。
PS H:\ai\check-mem-parser>
結果は、前置型と後置型で平均を出して比較しているので、これはあまり意味がありません(AI エージェントが適当に提案したものなので)。個々のノード量を見るとわかりますが、さほど差がありません。COBOL だけ AST が多いのは、これだけ DISPLAY を呼び出して画面表示の関数を呼び出しているからですね。基本的にこれぐらいだと差が出ないはずだし、おそらく構文木のノードにしてもほとんど変わらない筈です。もうちょっと突飛な文法を付けると変化するとは思うのですが、今回は変数定義の前置/後置の違いを検証したかったので、実際にコンパイラを作ってもメモリ容量はさほど変わらない、という結果になります。
ここは発展として、
- C言語のポインタ「*」の表現を各言語にいれたらどうなるのか?
- 構造体やクラスの場合は違いがでるのか?
- ひょっとしたら Lisp とか極端にノードが減ったりしないか?
- Lua とかスクリプト言語はどうなるのか?
参考コード
https://github.com/moonmile/check-mem-parser
考察 2025/12/06
理解しやすいプログラム言語を考えるうえで、何を基準にするかというと、工学的にはなんらかの計測可能な測定基準が必要になると思うのです。もちろん、進化論的に偶発的に発生する問題(右利き、左利きとか)でもありますが、これを「変数の前置/後置」にして再考したのが上記の結果というわけです。
わたしの経験上、C言語、Fortran 時代が長いので後置型を長く使っているのと「COBOL が前置だった」という勘違いが大きいので、なんらかのメモリの制約があって前置ではなかったか、という仮説を持っていたわけですが…先に書いた通り Ada、COBOL という古い言語に関しても後置となっているので、それは否定できますね。
じゃあ、もう少し視野を広くして「人間にとって理解しやすいプログラム言語の文法とは何か?」を考えるときに、短期記憶の負荷が少ないプログラム言語が理解しやすいのではないか?という仮定に切り替えてみます。つまり、構文解析をするときに探索木が少なくて済むとか、一時的なシンボルのメモリが少なくてすむとか、という明確な測定基準を設定することができます。先の実験プログラムで言えば、ノード数とかシンボル数とかを比較します。そこで、比較したいのは一時的なメモリの最大値です。これは、人間で言えば短期記憶になります。
C 言語の書き方でよく言われるように、一時的な変数は近くに書くとか、スコープを制限して書くとかいう方法があります。関数を短めにするのもそれです。それを数値化するのが短期記憶容量であって一時的なシンボルや構文木ノードの数になります。
また、Rust や関数型プログラムにあるように、束縛変数(変更できない変数)も短期記憶の削減に寄与します。これは、変更されない安全性の確保にもつながりますが、同時に理解しやすいプログラム言語の測定やプログラムコードの書き方に応用できるかも、ってわけです。プログラムコードの複雑さについては密結合に関してや条件文のからみあいを計測する手法(サイクロマティック複雑度 https://emb-sw-eng.com/cyclomatic_complexity/) が既にあるのですが、もうちょっと違う視点でできるかなと。あと、プログラム言語の文法の設計あるいは言語拡張にも応用できるかもしれません。









































