Bはどんな言語なのか

ワード指向

いまどきのコンピュータはたいてい1バイト(=8ビット)を基本としていますが、B言語が作られた頃は、8ビットより大きな「ワード」を基本としたマシンが多く存在していました。以下、Wikipediaへのリンク。

たとえば最初にBが動いたというPDP-7では1ワードが18ビットで、この18ビットのワードごとにアドレスが割り付けられます。メモリ容量は標準が4Kワードで64Kワードまで拡張できたということですが、Dennis RitchieによるThe Development of the C Languageによれば、Bの作者Ken ThompsonがUNIXの開発に使っていたPDP-7は8Kワードだったようです。1ワード18ビットで8Kワードといえばビット数で144Kビット、バイトにすれば18KBです。いまどきのパソコンならこの100万倍くらいのメモリを積んでいるのも普通です。よくまあこんなわずかなメモリでB言語やUNIXの開発をしていたものだと思います※1

(初期の)Bはそういうコンピュータ向けに設計されていたので、18ビットなら18ビットの「1ワード」で表現できる整数型以外、データ型を持ちません。アドレス(ポインタ)も単なる整数も、同じワードで表現します。なので当時のBでは、a[5]5[a]と書けるのも当たり前のことだったのです。a[5]*(a + 5)のシンタックスシュガーであり、足し算は順序が逆でもよい、という事情はBもCと同じでしたから。

となると、カンのいい人は、「じゃあ文字列はどう表現していたんだ?」と思うでしょう。Cなら、str[i]stri文字目(最初の文字は0文字目と数えるとして)を取り出せます。でもBはワード指向なので、str[i]だとstri番目のワードを指すわけで、i番目の文字は取り出せません。今なら1文字のために16ビットとか32ビットとかを割り当てることもありますが、当時の貧弱なマシンでそんな贅沢なメモリの使い方が出来るわけもありません。

Bでは、文字列は、1ワードの中に何文字かをパックする形で保持されていました。たとえばHoneywellの6070という(大型)コンピュータでもBは動いていましたが、このコンピュータのワード長は36ビットだったので、1ワードに4文字を詰め込むことができました。Honeywellの6070(以後、H6070と書きます)を前提としたBのチュートリアルにはこんなサンプルコードが載っています。

main( ) {
  auto a;
  a= 'hi!';
  putchar(a);
  putchar('*n' );
}

2行目のauto a;が変数宣言であるというのは想像できるでしょうが(型は1種類しかないのでCと違ってintとか書く必要はない)、3行目で、その変数に「hi!」という3文字を詰め込んでいます。hi!を囲んでいるのがダブルクォートではなくシングルクォートであることにも注目してください。Cではシングルクォートで囲むのは(普通は)1文字ですが、Bでは1ワードに入る分の文字数を囲むことができました。

「ではstri番目の文字を取り出したければどうするんだ?」と思うでしょうが、それにはそれ用の関数がありました。stri文字目を取り出したければchar(str, i)stri文字目にcを書き込みたければlchar(str, i, c)です※2

ところで、putchar('a');と書けばちゃんとa(だけ)が出力されますが、この時の'a'はASCIIコードなら10進数で65、7ビットに収まる値です。つまり、文字はワードの中に「右詰め」で格納されます。ただしダブルクォートで囲む文字列は、ワードの並びに「左詰め」で格納されます。ここで「左詰め」はワードの上位ビットから格納されるという意味です。今のPCはバイトマシンだからといってバイトの並びとして先頭から格納すると、リトルエンディアンのCPUでは期待した動きになりません。

Bにもいろいろある

Bも時代につれて進化してきたので、いくつかのバージョンがあります。ざっと探して見つかったのはこんな感じでした。

  1. PDP-11版
    Ken Thompsonによるユーザーズリファレンスがここにあります。Dennis Ritchieのページの下です。
    Users' Reference to B
    https://www.nokia.com/bell-labs/about/dennis-m-ritchie/kbman.html
  2. MH-TSS版
    これもDennis Ritchieのページの下です。上のよりは新しいっぽい。
    USERS' REFERENCE TO B ON MH-TSS
    https://www.nokia.com/bell-labs/about/dennis-m-ritchie/bref.html
    タイトルがこうなのでここではMH-TSS版と呼びます。MH-TSSとは、H6070の上で動くタイムシェアリングシステムのひとつです。
    Brian KernighanによるBのチュートリアルも同じ環境を前提としていて、同バージョンのBであるように見えます。
    A TUTORIAL INTRODUCTION TO THE LANGUAGE B
    https://www.nokia.com/bell-labs/about/dennis-m-ritchie/btut.html
  3. GCOS8版
    検索で見つけました。これは上のふたつよりだいぶ新しいようで、高機能です。
    B Language Reference Manual
    https://www.thinkage.ca/gcos/expl/b/manu/manu.html
    これはGCOS8というOSの上で動いていたBなので、ここではGCOS8版と呼ぶことにします。ただ、上のMH-TSSもGCOSの上で動いていたようなので、この呼び分け方は適切ではないかもしれません。

一番下のGCOS8版は、浮動小数点数が使えたり等かなり機能強化されています。マニュアルもなかなか長くて、私は読み通せていません。

上のふたつ、PDP-11版とMH-TSS版だと、以下のような違いがあります。

  1. 自動変数の配列の宣言方法が違う
    PDP-11版だと、要素数10の配列は以下のように宣言するようです。
    auto a 10;
    
    この10は初期化子ではなく配列の要素数です。
    MH-TSS版、GCOS8版は、最大の添字が10の配列を、以下のように宣言します。
    auto a[10];
    
    Cに慣れた身としては、PDP-11版の見た目はどうも気持ちが悪いです。MH-TSS版とかの「配列の要素数は指定した数+1」というのも気持ち悪いですが。
  2. PDP-11版は、switchの後ろの式をカッコで囲まない
    式なのでカッコで囲んでも動くでしょうが、構文規則上はカッコがありません。ifwhileの後ろのカッコが必須であることを考えると気持ちが悪いです。
  3. PDP-11版は、switchはあるのにbreakdefaultもない
    そりゃまあbreakがなくてもgotoがあればよいでしょうが、気持ちが悪いです。
  4. たとえばputchar()のような関数を呼ぶにも、extrnの必要がある
    Cの感覚だと、式の中に識別子(変数名とか)が出てきたら、まずローカル変数を探して、次にグローバル変数を探しに行く、と思うでしょう。これがBだと、関数内で宣言された名前しか探しません。なので、putchar()のようなライブラリ関数を呼ぶにもextrn(externではない)宣言する必要があります。Users' Reference to Bのサンプルコードを見ると実際そう書いてあります。
    MH-TSS版でも「関数内で宣言された名前しか探さない」のは同じですが、以下の謎ルールがあるようです(6.1 External Declarations)。

    If the first use of a name is immediately followed by a left parenthesis '(', the name is typed external by default; thus the library functions need not normally be declared.

    訳)名前の最初の使用直後に左括弧 『(』 が続く場合、その名前はデフォルトで外部型となる。したがってライブラリ関数は通常宣言不要である。

    これはこれで気持ち悪いですが、PDP-11版のは面倒くさいですね……

考えた結果、ここで作るB、Mae-Bでは、MH-TSS版をベースとすることにしました。どうせ実用に使おうというわけじゃなし、Bを知るなら一番低機能なPDP-11版で十分かもしれませんが、やっぱりなんか、いろいろ気持ちが悪いので。

Cとの違い

まあCの前の言語なので、CにあってBにない機能はたくさんあります。そもそも型が1種類しかないので、構造体も共用体もありません。実用プログラムを書くにはこの辺が一番の障害かと思います。

その他の目につく違いはこんな感じ。

  1. 文字列のエスケープシーケンスが「\n」とかでなくて「*n」
    上の例にちょっと出ていますが、改行は\nではなくて*nです。エスケープシーケンス全体を示すとこんな感じ(H6070用Bのリファレンスから)。
    *0	null
    *e	end-of-file
    *(	{
    *)	}
    *t	tab
    **	*
    *'	'
    *"	"
    *n	new line
    
    ――「{」とか「}」とかのエスケープシーケンスがわざわざ用意されているということは、当時キーボードからこれが入力できないコンピュータがそれなりにあったんでしょうか。そうだとするとそもそもBのソースなんて入力できなそうですが(Cにおけるトライグラフのようなものは、私が探した範囲では見当たりませんでした)。
  2. 文字列は*eで終端する
    改行が\nではなく*nなら、文字列の終端は\0ではなく*0なのか、と思ってしまいますが、そうでもなくて*eです。ASCIIコード表におけるEOT、コードなら4です。
    getchar()とかがファイル終端に達した時に返すのも、今のCならEOFで実態は-1ですが、Bでは*eでした。
  3. for文がない(while文はあるけど)
    これはまあ、while文だけあれば困らないといえば困らない。
  4. 複合代入演算子が+=ではなくて=+
    これはBとCの違いというわけでもなく、Cでも最初の頃はaに3を足したければa =+ 3と書くようになっていました。しかしこれでは単にaに-3を代入したいとき、うっかりスペースを省いてa=-3;と書くと、aから3を減算する、という意味になってしまい、あまりにも紛らわしいのでCの途中から+=-=という書き方に変わったわけです。
    ところで、Bのチュートリアルを見てみると、以下のように書いてある。
    But the spaces around the operator are critical! For instance,
      x = -10
    
    sets x to -10, while
      x =- 10
    
    subtracts 10 from x. When no space is present,
      x=-10
    
    also decreases x by 10. This is quite contrary to the experience of most programmers.

    訳)ただし演算子周辺のスペースが極めて重要です!例えば、
      x = -10
    
    xを-10に設定しますが、

      x =- 10
    
    はxから10を引きます。スペースがない場合、
      x=-10
    
    もxを10減らします。これはほとんどのプログラマーの経験とは正反対です。
    ――これが紛らわしいことがこのころからわかってたのなら、もっと早く変えておけよ、と思ってしまいますが…… 「The Development of The C Language」によれば

    this mistake, repaired in 1976, was induced by a seductively easy way of handling the first form in B's lexical analyzer.

    訳)この誤りは1976年に修正されたが、Bの字句解析器において前者を扱う誘惑的に簡単な方法が原因で生じた。

    とのこと。
  5. 複合代入演算子の数がえらく多い
    その複合代入演算子ですが、Cでは +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=くらいですが、Bでは、すべての2項演算子について複合代入演算子がありました。つまり=*, =/, =%, =+, =-, =<<, =>>, =<, =<=, =>, =>=, ===, =!=, =&, =^, =|の16種類です。===なんかは今のJavaScriptにもありますが、意味は全く違います。
    それにしても===なんて使うときがあるのか、というと多分ないわけで、だからCでは削除されたのでしょうけど。
  6. gotoの後ろに続くのはrvalue
    Cならgotoの後ろに続くのはラベルですが、Bの構文規則を見るとgoto文は「goto rvalue ;」となっています。これはつまりgotoの後ろに任意の式が書けるということです。
    GCOS8向けのBの仕様を見ると、こんなおっかないことが書いてある。
    Never try to pass a label as an argument to a function and then use that label to transfer to another function. The program will end up in the destination function, but with the previous function's stack pointer. This is bound to result in disaster eventually.

    訳)関数の引数としてラベルを渡した後、そのラベルを使って別の関数へ遷移しようとしないでください。プログラムは目的の関数に到達しますが、前の関数のスタックポインタを引き継いだ状態になります。これは最終的に必ず致命的な結果を招きます。
    関数の引数としてラベルが渡せるらしい。そしてちゃんとジャンプもするらしい。でも、その方法で別の関数に飛び込んだとしてもスタックがめちゃくちゃになる。そりゃそうだ。
  7. 配列は0オリジンだが、要素数は宣言した数+1
    上にも少し書きましたが、MH-TSS版以後のBでは、
    auto a[10];
    
    と書けばa[0]からa[10]の11個の要素が使えます。CおよびCから派生したいまどきの言語に慣れた人からすれば「え?」と思うでしょうが、Cの初心者はたいていa[10]に代入してしまうので、こっちの方が「自然」なのかもしれません。昔の8ビットパソコン時代のBASICはこうだった気がするし、VBAは今でもこうであるようだし。
    MH-TSS版のBには、Cならmalloc()/free()に相当するgetvec()/rlsevec()というヒープメモリ管理用の関数があります。このgetvec()も、引数で指定したサイズ+1の領域を確保します。また、(Cのfree()と違って)rlsevec()には開放する領域のサイズを渡さなければいけないのですが、このサイズはgetvec()で指定した値すなわち実際の領域のサイズ-1です。
  8. 配列はあるが、2次元配列はない
    C言語でも、2次元配列と呼ばれているものは「配列の配列」ですが、Bの場合そもそも型が(ワードの型しか)ないので、配列(型)の配列は作れません。ただ、だいぶ後の方のBになると、初期化をうまくやれば配列の先頭要素へのポインタの配列でIliffe vector(アイレフベクタ)※3は作れたようです。GCOS8版マニュアルの「4.1 External Variables」を参照してください。
  9. 関数の本体を{ }で囲む必要がない
    Cの構文規則では、関数定義は以下の形になっています。
    function-definition:
            declaration-specifiersopt declarator declaration-listopt compound-statement
    
    Cの宣言の構文は変態なので前半部分はわけがわからないことになっていますが、最後に「compound-statement」がついていることがわかるでしょう。compound-statementというのは、{ }で囲まれた複合文のことです。つまりCでは、関数の処理本体は{ }で囲まなければいけません。
    「当たり前では?」と思うかもしれませんが、Bの構文規則はこうなっています(definitionにはグローバル変数の定義も含まれますが、関数定義の側だけ抜粋)。
    definition ::=
            name ( {name {, name}0}01 ) statement
    
    末尾はstatementです。よって、(while文とかがそうであるように)関数本体の処理が1文であれば{ }で囲む必要はありません。
    「構文規則がこうなっていても、実際はそんな書き方はできなかったのでは? パースのあと、意味解析の時点でエラーにしてたのでは?」とも思いましたが、Bのチュートリアルで実際の使用例が出ていた。
       char(s,n) return((s[n/4]>>(27-9*(n%4)))&0777);
    
    Notice that char is written without {}, because it can be expressed as a simple statement.
    訳)charは単一の文として記述できるため、{}なしで記述されていることに注意。
  10. 変数宣言をブロック冒頭に書く必要がない
    これは、C99からCでもできるようになりましたが、Bではできたようです(少なくとも構文規則ではそうなっているように見える)。Cでは意図してブロック冒頭に限定したんですかね。
  11. 関数の呼び出され側で、関数の数がわかる
    Cだと、printf()に代表される可変長引数の関数というものがありますが、そのような関数は、引数を先頭から見ていって引数の数が判断できるようになっていなければなりません。たとえばprintf()なら、第1引数のフォーマット文字列の%dとかの数で、それに続く可変個の引数の数がわかります。
    Bでは、nargs()という関数で、自身に渡された引数の数がわかります。たとえばconcat()関数は、以下のような形式で、b1bnを連結した文字列をaに格納する、という仕様になっています。こういう仕様の関数は、Cでは実現できません。
    concat( a, b1, b2, . . .. b10)
    
    Mae-Bでは、「隠れた最初の引数で引数の数を渡す」ようにしました。
  12. 変数名にピリオドとバックスペースが使える
    Cなら、変数名に使えるのは英数字とアンダースコアですが、Bではそれの他に、ピリオドとバックスペースが使えました。MH-TSSのリファレンスには以下のように書いてあります。

    The characters A through Z, a through z, _, ., and backspace are alphabetic characters and may be used in names. The characters O through 9 are digits and may be used in constants or names; however, a name may not begin with a digit.

    訳)AからZ、aからz、_、.、およびバックスペースはアルファベット文字であり、名前で使用できます。Oから9は数字であり、定数や名前で使用できます。ただし、名前は数字で始めてはいけません。

    そして、実際、MH-TSSでは、現在の入出力ユニットを示すグローバル変数としてrd.unitwr.unitが用意されています。ピリオドが入っているので構造体かな? と思ってしまいますが、Bにそんなものはないので、ただのグローバル変数の名前の一部です。
    ピリオドはそれでいいとして、さすがにバックスペースは、これを名前に含めて何かいいことあるの? と思ってしまいますが…… ちなみにPDP-11版では、ピリオドは許していませんが、バックスペースは許しています。
    Mae-Bでは、ピリオドは許しましたが、バックスペースは許しませんでした。さすがに意味がわからないので。
  13. &&, ||演算子がない
    これもBとCの違いというよりは、Bと「今のC」の違いかと思います。昔はCでも、&&, ||がありませんでした。代わりに&, |演算子を使っていたおかげで、後から導入された&&, ||演算子については優先順位がおかしくなっていたりします。
    だからCの前身であるBに&&||がないのは当たり前ではあるのですが、以降は「」に続きます。

    ラベルに代入できる?

    MH-TSSのリファレンスには以下の記述があります。

    Declarations in B specify storage class of variables, and also, in some circumstances, specify initialization. There are three storage classes in B. Automatic storage is allocated at each function invocation, and becomes undefined upon return from the function. External storage is allocated before execution of the program, and is available to any and all functions. Internal storage is also allocated before execution, but is available to only one function; labels are the only current use of internal storage.

    訳)B言語における宣言は変数の記憶域クラスを指定し、状況によっては初期化も指定する。B言語には三つの記憶域クラスが存在する。自動記憶域は各関数呼び出し時に割り当てられ、関数からの戻り時に未定義となる。外部記憶域はプログラム実行前に割り当てられ、あらゆる関数から利用可能である。内部記憶域も実行前に割り当てられるが、単一の関数からのみ利用可能である。現在のところ、内部記憶域はラベルのみに使用されている。

    「自動記憶域は関数呼び出し時に割り当てられる」ふむふむCと同じだな、「外部記憶域はプログラム実行前に割り当てられ、あらゆる関数から利用可能である」なるほどグローバル変数だな、「内部記憶域も実行前に割り当てられるが、単一の関数からのみ利用可能である」これはCでいうところのstatic指定したローカル変数相当かな、と思っていると、「現在のところ、内部記憶域はラベルのみに使用されている」と続いて「あれっ?」となります。内部記憶域(Internal storage)というからには何か記憶するわけで、つまりstatic指定したローカル変数同様、ラベルに代入ができるのかなあ。それが役に立つケースは思いつきませんが。「現在のところ、内部記憶域はラベルのみに使用されている」なので、いずれstatic指定したローカル変数みたいな使い方も想定していたとか?

    内部記憶域の説明(6.3 Internal Declaration)には以下のように書いてある。

    The first reference to a variable not declared as external or automatic constitutes an internal declaration. The major use of internal declarations is with labels; at the end of each program, internal names not defined as labels will cause an error message. A label is defined by writing

        name :※4
    

    preceding any statement.


    訳)外部宣言または自動宣言として宣言されていない変数への最初の参照は、内部宣言を構成する。内部宣言の主な用途はラベルである。各プログラムの末尾において、ラベルとして定義されていない内部名はエラーメッセージを引き起こす。ラベルは、

        name :
    

    を任意の文の前に記述することで定義される。

    普通の変数は、自動変数のauto宣言であれグローバル変数のextrn宣言であれ、使うところより前に書く必要があります。ただ、ラベルはgotoで下の方に向かって飛ぶこともあるわけで、使うところより前に「name :」が書けるとは限りません。だから関数の最後まで来てからエラーになる。それはわかりますが、やっぱりこれも変数の説明に見える。

    GCOS8版のマニュアルを見てみると以下のように書いてあって、

    Labels are also stored in the body of a function's executable code, and are not directly accessible to the user.

    訳)ラベルも関数の実行コード本体に格納され、ユーザーが直接アクセスすることはできません。

    こちらは明確に代入不能と書いてある。でもGCOS8版はMH-TSS版までのBとはだいぶ違うので、GCOS8版での修正点かもしれないということで、Mae-Bでは「代入可能」の方に倒しました。だってそっちの方が面白そうだし。

    ivalって?

    Bではグローバル変数を初期化しつつ定義する場合、以下のように書きます。

    a 10;
    

    こう書けば、グローバル変数aが定義されて、10で初期化されます(=は要りません)。

    それはいいのですが、構文規則を見ると、この「10」のところは非終端子ivalになっていて、その定義は定数または名前です。

    an ival (initial value) is a constant or a name; the external name is defined and initialized with the value of the constant, or the lvalue of the name, respectively.

    訳) ival(初期値)は定数または名前である。外部名は、それぞれ定数の値、または名前の左辺値で定義・初期化される。

    名前を書くとその左辺値で初期化されるようです。

    Mae-Bではそのように実装したつもりです。よって、

    a 10;
    b a;
    

    と書くとbaアドレスで初期化されます。aの右辺値で初期化する方がまだ使いでがあるのでは、とも思えますが、この実装だと以下のような書き方で多次元配列のようなもの(アイレフベクタ)が作れます。

    v1[] 1, 2, 3, 4;
    v2[] 5, 6, 7, 8;
    v3[] 9, 10, 11, 12;
    v4[] 13, 14, 15, 16;
    matrix[] v1, v2, v3, v4;
    

    関数宣言

    Cだと、関数を呼び出す前に、関数定義するかプロトタイプ宣言しなければいけません。C89より前のCだとプロトタイプ宣言はありませんが、カッコ内が空の関数宣言はします。しかし、Bには、それ用の構文はなく、関数を定義や宣言なしで呼び出すのは完全に合法です。たとえばBのチュートリアルの冒頭に出ているサンプルでも、main()が先頭に書いてあります。

       main( ) {
       -- statements --
       }
    
       newfunc(arg1, arg2) {
       -- statements --
       }
    
       fun3(arg) {
       -- more statements --
       }
    

    Bでも関数呼び出しの( )は演算子であり、関数へのポインタを経由した関数呼び出しが可能です。よってfunc();という関数呼び出しを書いたとき、funcがそのまま関数なのか、それとも関数へのポインタが格納された変数なのかがわからないと困ります(呼び出し先を取得するための機械語なりバイトコートなりが異なってくるため)。今なら、メモリはたくさんあるのでソースを最後まで読み込んで判断できますが(Mae-Bではそうしていますが)、当時のBではどう判断していたんでしょう?

    ――でも、それを言い出すとグローバル変数の配列も同じなんだよな。もしかして私は根本的にBを勘違いしてますでしょうか。

    分割コンパイル

    Dennis RitchieによるThe Development of the C Languageには、BCPL, B, Cについて、以下のように書いてあります。

    Each of the languages (except for earliest versions of B) recognizes separate compilation, and provides a means for including text from named files.

    訳)各言語(初期バージョンのBを除く)は分割コンパイルを認識し、名前付きファイルからテキストをインクルードする手段を提供する。

    この「初期バージョンのB」(ealiest versions of B)というのがどこまでを含むのかわかりませんが、PDP-11版、MH-TSS版ともに分割コンパイルの機能はないように見えます。GCOS8版だと「%ファイル名」の形でCの#include相当のことができるようで、これのことを言っているのかもしれませんが、Dennis RitchieにとってBといえばDennis RitchieのWebページ以下にあるもののことなんじゃないのかなという先入観があるので、どうも違和感が。

    &, |演算子について

    (今の)Cなら、&, |はビット演算子で、&&, ||が論理演算子です。if文とかでAND条件を書きたければ今なら&&を使いますが、かつては&で代用していて、まあこれでほぼ困らないのですが、たとえば「1 & 2」は0でfalseになってしまう、そういう不便さから後になって&&演算子が導入されて、でもいまさら&演算子とかの優先順位を下げるわけにもいかないから「value & mask == HOGE_BIT」といった式で直感に反する動きになった、と私は理解していたのですが。

    The Development of the C Languageには、以下の記述があります。

    Rapid changes continued after the language had been named, for example the introduction of the && and || operators. In BCPL and B, the evaluation of expressions depends on context: within if and other conditional statements that compare an expression's value with zero, these languages place a special interpretation on the and (&) and or (|) operators. In ordinary contexts, they operate bitwise, but in the B statement

    if (e1 & e2) ...
    
    the compiler must evaluate e1 and if it is non-zero, evaluate e2, and if it too is non-zero, elaborate the statement dependent on the if. The requirement descends recursively on & and | operators within e1 and e2. The short-circuit semantics of the Boolean operators in such `truth-value' context seemed desirable, but the overloading of the operators was difficult to explain and use. At the suggestion of Alan Snyder, I introduced the && and ||

    operators to make the mechanism more explicit.


    訳)言語名が命名された後も急速な変化は続き、例えば&&演算子や||演算子の導入などがあった。BCPLやB言語では、式の評価は文脈に依存する。if文やその他の条件文内で式の値を0と比較する場合、これらの言語ではAND(&)演算子とOR(|)演算子に特別な解釈が適用される。通常の文脈ではビット単位で動作するが、B言語の文

        if (e1 & e2) ...
    
    では、コンパイラはe1を評価し、それが0でない場合にe2を評価し、e2も0でない場合にifの中の文を処理対象にしなければならない。この要求はe1e2内の&および|演算子に対して再帰的に適用される。このような「真偽値」文脈におけるブール演算子の短絡評価は望ましい特性だったが、演算子の多重定義は説明や使用が困難だった。アラン・スナイダーの提案を受け、この仕組みをより明示的にするため&&||演算子を導入した。

    この文章を読むと、&|演算子は、条件式を評価している文脈では短絡演算子になるという特別な解釈がされる、かのようですが、boolean型もなければ非ゼロを気楽にtrueとして使うC(やB)のような言語で、そんなことは不可能だと思うのです。Mae-Bでは、&|は単なるビット演算子として実装しています。都合に応じて短絡演算子になったりはしません。

    公開日: 2026/01/18



    間違い等ありましたら、掲示板にご連絡願います。

    次のページ(実装) | ひとつ上のページへ戻る | トップページへ戻る