50年以上前のいにしえのプログラミング言語Bon概要

UNIX の父であるケン・トンプソンは、C言語の元になったB言語の開発者で、B言語の前に Bon という言語を作っていたことで知られています。この Bon についての詳細は不明で、失われた言語だと思っていましたが、Bon のマニュアルがネットに落ちており、詳細が分かりました。

そういうわけで、Bon の概要解説という誰得情報をお伝えします。

https://news.ycombinator.com/item?id=26144568

ケン・トンプソンはB言語を作る際に、Bon を元にしたわけではなかったようですが、同一人物が開発した言語ですので、多くの共通点が見られます。B言語、C言語、Goとの共通点や相違点を見ればケン・トンプソンの設計思想を垣間見れるかもしれません。

Bon はインタプリタ言語であり、入力したプログラムがすぐに実行されるイミディエイトモードと、記憶されたプログラムを実行するストアドモードがありました。若い人にはわからないかもしれませんが、BASIC インタプリタのようになっています。

Multics 版 Bon では、コマンドラインから`bon <pathname>`とすることで、テキストファイルに保存されたソースコードを入力して実行することができたようです。ソースコードはあくまでテレタイプからの入力をシミュレーションするものです。つまりイミディエイトモードを立ち上げてテレタイプから入力する操作を自動化できるだけです。複数のコードをリンクしたり、別のコードを動的にロードするなどの機能はなさそうです。

なお Multics 版以外にも CTSS 版の Bon があったようです。

## 変数型

B言語は型がない設計でしたが、Bon には型がありました。変数宣言はなく、最初に代入するときに型が自動的に決まりました。そのため内部的には型があるものの、型を表すキーワードがありません。

### 数値型

数値型は整数型と浮動小数点型があり、1968 や 3.14159 といった表現の他に、10e3 ($10^3$) や 10e-2 ($10^{-2}$) といった指数表現も可能でした。整数と浮動小数点は自動的に選ばれてしまいますが、明示的に選びたい場合は、組み込み関数の fixed や float を利用できました。

例えば 1968 を浮動小数点型にしたい場合は、`a := 1968`を、`a := float(1968)`としたようです。10e3 は整数に、10e-2 は浮動小数点になります。10e+3 という表現は許されません。

### ポインタ型

ポインタはC言語と同じで、& で変数のポインタを取得し、* で中の値を参照できます。ただし実際の動きというか取り扱いは、詳細は省きますが、結構異なります。

``` c
a := &b
*a := 100
print(b)
```

### ラベル型

ステートメントの先頭に`:`で終わるラベル名を付けることができ、ラベルにジャンプしたり、ラベルをサブルーチンとして呼んだりできるようになります。

``` c
      s := 0
      i := 1
loop: s := s + a[i]*b[i]
      i := i+1
      . := loop
```

`.`は現在実行中の行を示すラベル型の組み込み変数で、この変数に代入することによって goto を実現できます。上記の例ではラベル名を代入していますが、行番号を代入することもできます。ただ行番号はプログラムを編集するとずれてしまいますので、行番号を入れることはあまりなかったと思われます。また`.`を加減算することで相対ジャンプも可能だったと思われますが、マニュアルでは明確に言及されていません。

イミディエイトモードの時は常に 0 が入っています。またプログラムの実行中に`.`に 0 を代入すると、プログラムが終了してイミディエイトモードに戻ります。

### 文字列型

文字列リテラルは`"`や`'`で囲まれます。改行コードを含むことは許されなかったようです。

``` c
print("Theta's more neatly typed 'θ'")
```

マニュアルのサンプルはこのようになっていますが、文字コードは ASCII と明記されており、θ(シータ)を出力できたのかどうかはわかりません。

なおバックスペースコードが入った bs 変数と改行コードが入った nl 変数が組み込み変数として用意されていました。次のようにして文字列に改行を混ぜることができたものと思われます。

``` c
print("Theta's more" || nl || "neatly typed 'θ'")
```

### ブーリアン型

true と false の値を取ることができました。true 変数と false 変数が事前定義されています。

### ヌル型

ヌルは`()`で表現します。関数に引数がない場合は`f()`と表記することでヌルを渡す、という一石二鳥な形になっています。

### リスト型

Bon のリスト型は配列やリストではありません。タプル(Tuple)に似ていますが、ちょっと異なる、Bon 独自の概念です。

`(x := 3),(y := 5)`は x に 3 を代入し、y に 5 を代入する処理を、1行のステートメントで実行しています。

`i,. := i+1,loop`は、`i := i+1`と`. := loop`を1行で実行するステートメントになります。また`a,b,c`と記述すると`((a,b),c)`という形のタプルを含んだタプルになります。

リスト型には分配法則が適用されます。`(a,b)[2]`と記述すると、`a[2]`と`b[2]`の両方を意味します。`2*(a,b,c)`は`2*a,2*b,2*c`になります。

しかし真のタプルではなく、`x=(a,b,c)`とは恐らく表記できず、作ったタプルのライフサイクルはそのステートメント内で終わり、使いどころは非常に難しそうです。マニュアルには値をスワップする方法として`a,b := b,a`という例が挙げられていますが、数少ない有用な例でしょう。

## 制御構文

制御構文には while、until、if、unless、init があります。ただしこれは今日の言語で言うところの制御構文とはちょっと概念が異なります。

Bonでは1行1ステートメントで、ブロック構文(Begin～Endや`{`～`}`)がなく、制御命令はステートメントの後ろに`;`に続ける形で付与し、そのステートメント1行だけが対象になります。

for ループの記述は以下のようになります。

``` c
print(i := i+1); while i < 10; init i:=0
```

これはC言語で以下のような文と等価になります。

``` c
for (int i = 0; i < 10; i++)
  print(i);
```

## 演算子

すでに説明なしで何度も使っていますが、Bon の代入演算子は`:=`です。比較演算子は`=`(Cの`==`)と、`^=`(Cの`!=`)に加え、文字列比較用の`==`があります。

論理演算子は`|`が OR、`&`が AND で、結果は Boolean になり、C言語より自然な形になっています。Bon には逆にビット演算子がありません。Bon はシステム開発用言語ではなく、C言語はシステム開発用としてビット演算が重要だった、ということかもしれません。

`^`は論理演算子の NOT で、大小比較演算子の`>`や`>=`も`^>`や`^>=`で否定できます。そして指数演算子はなんと`!`。今の感覚ですと、`^`と逆になっています。

`||`も OR ではなく、文字列結合演算子です。

## プログラム入力の流れ

イミディエイトモードで Bon が起動し、`append()`と入力すると、ストアドプログラムの最後の行からプログラムを入力できます。`.`だけを入力すると入力モードは終了です。

``` c:start関数を追加し、nseとnoを引数にそれぞれ実行しているところ
append()
start: print('no' || A || A)
. := 0
.
A,. := 'nse',start
A,. := ' no',start
```

``` :結果
nonsense
no no no
```

入力したステートメントに文法エラーがあると、テレタイプにエラーが即時出力されます。

`delete(<label名>)`と入力するとそのラベルの関数を削除できるようです。

全てのプログラムを削除するには`delete(1); while true`と入力します。また関数をテレタイプに印刷するには、`print(<label名>)`と入力します。特定の行のステートメントを印刷したい場合は`print(label(2))`のようにします(この場合は2行目を表示)。`print(2)`とすると、単純に`2`が表示されてしまいます。これは print 関数が引数の型を数値型かラベル型か区別しているということでしょう。

ストアドプログラムの実行は 1000 ステートメントに限定されており、1000 を超えると永久ループと見なされて強制終了されます。8KB の BASIC でプログラムを作るような感覚、と言っても今の若い人にはわからないかもしれませんが、とにかくちょっとした小物プログラムくらいしか書けなかったようです。

## トリグラフ

C言語では`{`や`}`を入力できない端末のために、`??<`や`??>`で代用できるトリグラフという機能があります。

Bonでも`[`や`]`を入力できない端末のために、`$<`や`$>`で代用できる機能があります。2文字なので、ダイグラフと呼ぶべきでしょう。「CTSS と Multics の 1050 ターミナルでは`[]`だけはダイグラフが必要だが、テレタイプのモデル37では不要」という記載がマニュアルに見られます。

BCPL やB言語も同じ表記があります。当時はブロックの表記は BEGIN/END であるのが普通で、`{}`等をブロックの表記に使い始めたのは BCPL が先かB言語が先かという議論があります。Bon にダイグラフがあったというのは、この議論について何の証拠にもなりませんが、興味深い関連事実の1つと言えるでしょう。

## 関数

関数は実際にはラベルへの goto です。return 関数を呼び出すと終了します。

マニュアルには明記されていませんが、return せずに関数内から外にジャンプしてしまうことも可能だったのではないかと思われます。

### ローカル変数

ローカル変数の定義には assoc 関数を使います。assoc は associate の略です。

``` c
assoc(s,t,n) := x,1
```

この例では`s,t,n`の3つの変数を定義しています。右辺値は2つだけしかありませんが、これは実は assoc の`s,t,n`が`((s,t),n)`と解釈され、`(s,t):=x`と`n:=1`になり、s と t にはいずれも x が入ります。

``` c
assoc(a,b) := param(0)
```

関数で引数を受け取るには param 関数を使います。この例ではリスト型として2つの値を受けています。param 関数の引数は引数の順序を示しているようで、上記の例は`assoc(a) := param(0)`と`assoc(b) := param(1)`を実行するのと同じことであるようです。

entry 関数は、assoc と param を一緒に実行してくれるだけのシンタックスシュガー的関数です。

``` c
rect: entry(r, θ)
      return(r*cos(θ), r*sin(θ))
```

第1引数を r に、第2引数を θ に割り当てています。

entry や assoc で確保した変数は、関数が終了すると解放されます。そう聞くと普通そうですが、マニュアルではその説明の後に、「解放されると元の変数が復帰する」と書かれてあります。これを文字通りに読めば、内部的にはグローバル変数しかなく、ローカル変数として使えるようにバックアップしている、と受け取れます。

それか、ローカル変数が分からない人のためにかみ砕いて説明しただけで、今の言語と同じ仕組みであるのかもしれません。

### 参照渡し

関数は参照を渡して、参照を返すこともできるようです。

``` c:max関数
max: assoc(x,y) := &param(0)
     return(*x); if *x >= *y
     return(*y)
```

``` c
max(a,b) := max(a,b) + 1
```

上記の例では、a と b のいずれか大きい方に 1 を加算しています。a が大きいのであれば、左辺の結果は a への参照になり、右辺の結果は a+1 になるという…。

ということは、右辺の max の結果も参照であるはずで、ポインタ型ではないから、ポインタ+1ではなく中身+1になる、ということでしょうかね。直感的ではありません。

``` c
sin: entry(x)
     assoc(s,t,n) := x,1
     s:=s+(t:=-1*x!2/*n+1)*(n:=n+2)); until s+t=s
     return([s])
```

テイラー展開で sin の値を返す関数の例。return は`s`ではなく、`[s]`を返しています。「`s`を返してしまうと、ローカル変数への参照を返してしまうことになるので、`[s]`として値を返している」と解説にあります。

この説明から推測するに、関数呼び出しのパラメーターや、戻り値に使う変数はグローバル変数を使うのが元々の設計で、assoc によるローカル変数の定義は苦肉の後付けだったものと思われます。

### 再帰

``` c
max: entry(x,y)
     x := max(x); if dimen([x])>1
     y := max(y); if diemn([y])>1
     return([x]); if x>=y
     return([y])
```

二分探索でツリーから最大値を見つける関数。再起もちゃんと実装できることが示されています。

## 感想

Bon は歴史的な理由で洗練されていない部分はあるものの、関数、ローカル変数、型などがある、想像よりもずいぶんしっかりした言語であることが分かりました。B言語には型がなく、Bon はさらに原始的な言語だろうと想像していましたが、いい意味で想像を裏切られました。

元々プログラミング言語の設計に詳しいケン・トンプソンが、Bon という当時としては割といい線をいいっていた言語を作っておきながら、低機能なB言語を改めて作ったのは、当時のコンピュータがまだ低性能で、システム開発用にはさらに低レベルな言語が求められたからでしょう。

B言語を洗練させてC言語に進化させたのはデニス・リッチーですが、もしケン・トンプソンが UNIX のカーネル開発で忙しくなく、B言語がそのまま進化していれば、もう少し違った言語になっていたかもしれません。

ローカル変数の取り扱いについて試行錯誤の跡がうかがえます。当時すでに ALGOL などでローカル変数は実現されていたはずですが、安心して使える量のスタック領域の確保が当時のミニコンではまだ難しかったのかもしれません。

Bon の特徴で一番特筆すべきはリスト型でしょう。ペアの組み合わせしかない、まだこなれていない参照型をリストに入れるとメンドクサイなどの理由で使いこなすのが難しかったためか、B言語やC言語では消えてしまいました。最近になって、どのC系列言語でもタプルとして復権しているところが面白いですね。