BrainfuckでHello World
をするなんて記事は巷に溢れていますが、それはHello World
を表示することにのみ執着しているような気がします。
より汎用性のある方法でHello World
もそれ以外の文字列もスマートに表示する術を学びましょう。
とはいったものの、Hello World
の表示は本題に入るための足掛かりのようなものです。
個人の主観を多分に含みます。
「BrainfuckでHello Wordl
を表示する」とは何か
そもそも、あなたはなぜHello World
を表示するのでしょうか。
それは多くの言語にとって簡単で動作がわかりやすいプログラムだからでしょう。
しかしBrainfuckにとってはどうでしょうか。
Brainfuckでは.
命令を使うことで文字を1文字表示できます。
そう、たった1文字です。
あなたがどのようなHello World
を書くかは知りませんがhelloworld
と書いたとしても10文字です。
さらにBrainfuckでは文字列型なんて生ぬるいものはありません。
さらにさらに、,
命令を除いてデータを代入する手段もありません。
すなわちあなたは+
や-
を用いて対応する文字コード(ASCIIかもしれないしUTF8かもしれません)へと値をどうにか移動して.
で出力をしなければなりません。
この時点でBrainfuckにおけるHello World
は"簡単で動作がわかりやすいプログラム"という目的は達成できていません。
文字列の表示
とはいっても文字列を表示する機会は何度もあるでしょう。
だからBrainfuckに関してもHello World
は無意味ではありません。
しかしながら、それはたいていめんどくさいものです。
そのめんどくささは実際に見てもらった方が早いでしょう。
以下はhelloworld
を表示するコードです。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++.---.+++++++..+++.++++++
++.--------.+++.------.--------.
非常にわかりにくいですね。
私もそう思います。
ですが安心してください。
やっていることは比較的シンプルです。
まず、helloworld
の各文字はそれぞれ以下のような文字コードと対応しています。
//helloworld
{0x68,0x65,0x6c,0x6c,0x6f,0x77,0x6f,0x72,0x6c,0x64}
さて、初期状態が00
であることを加味した上で、それぞれの文字が出力される前状態との差を求めてみましょう。
//注意 : これは10進数です
{104,-3,7,0,3,8,-8,3,-6,-8}
これらを愚直にコードに当てはめるだけで文字列を表示できます。
それでもめんどくさいですが。
ここでBrainfuckを少しでも触ったことがある人はもっと短くかけそうだと思ったかもしれません。
その通りです、もっと短く書くことはできます。
ただ、今回はあえてそうはしていません。
その理由に関しては次の章で話します。
ループの罠
Brainfuckを触ったことがある人の何人かはこう思ったかもしれません。
「ループを使えばもっと短くかけそう」だと。
特に最初の+
を連続で書く部分などではそう思ったことでしょう。
確かにそうです。
最初を>++++++++++[-<++++++++++>]<++++.
と書き換えればソースコードを短くすることはできます。
短くかけたことによりソースコードをスマートにかけたように感じるかもしれません。
しかし、本当にそうでしょうか。
Brainfuckにおいてループによるコードの短縮にはいくつかの苦労が伴います。
- コードがわかりにくくなる
- 他のデータと干渉しないことを確認しないといけない
- 最も短いコードを見つけるのは難しい
- 短くしてもメリットが少ない
さて、これらのデメリットを抱えてまでコードを短くするメリットはあるでしょうか。
今回は「他のデータと干渉しないことを確認しないといけない」と「短くしてもメリットが少ない」について考えていきましょう。
他のデータと干渉しないことを確認しないといけない
例えばこのようなコードがあるとします。
+++++[->++++++<]>+++.
ただし現在指し示すポインタの値は00
とします。
...|00|...
^
さて、このコードは何を出力するでしょうか。
ちなみに16進数の21
(10進数の33
)に対応した文字は!
です。
答えですが、わかりません。
もし、このコードが実行される直前の状態がこうであれば?
...|00|00|...
^
出力される文字は!
となります。
もし、このコードが実行される直前の状態がこうであれば?
...|00|0F|...
^
出力される文字は0
となります。
このように現在ポインタが指す値以外の影響も受けてしまいます。
「それなら現在の指し示しているポインタの右隣(あるいは左隣、それかどこでもいいから適当な場所)が00
であることを保証したらいいじゃん」
となるかもしれません。
しかしそうすると、今後プログラムを拡張する際に00
であることを保証するために入れる内に使えない部分が発生したり、00
にする機能をつけた結果思わぬところに影響が出るかもしれません。
だから、Brainfuckにおいては極力無駄に配列の空きを使うべきではないと考えています。
短くしてもメリットが少ない
そもそも、ソースコードを短くすることにメリットはあるのでしょうか。
ソースコードが短くなるということはソースコードが短くなるということであり、それ以上の効果があるかはまた別です。
ここではコードの長さとインタプリタがBrainfuckの命令を評価する回数を考えます。
!
を表示するコード
例えば!
を出力するようなコードを考えてみましょう。
下の二つはどちらも!
を出力するコードです。
code 000
+++++++++++++++++++++++++++++++++.
code 001
+++++[->++++++<]>+++.
さて、それぞれBrainfuckの命令は何回評価されるでしょうか。
上のコードは合計34回の評価で終了します。
一方下のコードは66回の評価で終了します。
また、もし
code 002
++++++[->+++++<]>+++.
と書くと評価回数が72回にまで増えます。
この差は一体どこから来るのでしょうか。
そうです。ループ構造が原因です。
あなたがソースコードを半分にしようとする時、評価回数は何倍にも膨れている可能性があるのです。
!
を表示するコードに関して
コード | 文字数 | 評価回数 |
---|---|---|
code 000 |
34 | 34 |
code 001 |
21 | 66 |
code 002 |
21 | 72 |
Hello, World!\n
を表示するコード
Hello, World!\n
を出力するコードでも同じように見てみましょう。
code 100
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++.+++++++++++++++++++++++++++++.+++++++..+++.------------
-------------------------------------------------------.--------
----.+++++++++++++++++++++++++++++++++++++++++++++++++++++++.+++
+++++++++++++++++++++.+++.------.--------.----------------------
---------------------------------------------.------------------
-----.
code 101
+++++++++++++++[->+++++>+++++++>+++>++>++++++>+<<<<<<]>---.>----
.+++++++..+++.>-.>++.>---.<<<.+++.------.--------.>>+.>>-----.
Hello, World!\n
を表示するコードに関して
コード | 文字数 | 評価回数 |
---|---|---|
code 100 |
390 | 390 |
code 101 |
127 | 674 |
このようにループを多用することは、実行環境によっては評価回数を増やしパフォーマンスを低下させることにつながるでしょう。
また、保守性や可読性の低下にもつながります。
まあ、保守性と可読性は元の段階でも低いですが。
だから、ループを使わないでも良い場面ではループは使わない方がいいというのが私個人の考えです。