これはゆめみ その 2 Advent Calendar 2019 25 日目の記事です。
前書き
最近巷で話題になっている(かもしれない)漢文プログラミング言語:wenyan-lang を、ご存知ですか?古代中国語の「文言文(漢文)」で記述する独特なプログラミング言語で、しかもなんとチューリング完全らしいです1!名前の由来については、中国語で漢文のことを「文言文」と言い、「文言」は拼音で wenyan
、そして最後は言語の意味を持つ「文」をそのまま英語の lang
にして、最後くっつけて wenyan-lang
になったと思われるこの言語ですが、コンパイルすると JavaScript や Python のコードに変換されるから、最初は何かの言語をキーワードだけ置き換えて作られた言語かと思われるかもしれませんが、実はちゃんとした自身の文法があり、ただのキーワード置き換え言語ではないです。
簡単な文法解説は既に他にも記事を書かれた方がいらっしゃるのでぜひそちらをご覧ください!今日はクリスマスなので、せっかくだからこの wenyan-lang でズンドコキヨシをもじったメリークリスマスのプログラムを試作して、そのコードを解説してみたいと思います。
元ソースコード
とりあえずまず全コードを載せておきますね:
吾有一術。名之曰「考拉茲」。欲行是術。必先得一數。曰「甲」。乃行是術曰。
有數零。名之曰「總」。
為是九千九百九十九遍。
若「甲」等於一者。乃得「總」。
若非。
除「甲」以二。所餘幾何。名之曰「丙」。
若「丙」等於零者。除「甲」以二。名之曰「半」。昔之「甲」者。今「半」也。
若非。
乘「甲」以三。名之曰「丁」。加一於「丁」。名之曰「戊」。昔之「甲」者。今「戊」是也。
加「總」以一。名之曰「己」。
昔之「總」者。今「己」是也。
云云。
是謂「考拉茲」之術也。
吾有一列。名之曰「祝」。充「祝」以「「聖」」。以「「誕」」。以「「快」」。以「「樂」」。
吾有一列。名之曰「回」。
有數一。名之曰「戊」。恆為是。
施「考拉茲」於「戊」。名之曰「寂」。
除「寂」以四。所餘幾何。名之曰「餘」。
充「回」以「餘」。
若「戊」大於四者。
減「戊」以四。名之曰「甲」。夫「回」之「甲」。加其以一。名之曰「子」。
減「戊」以三。名之曰「乙」。夫「回」之「乙」。加其以一。名之曰「丑」。
減「戊」以二。名之曰「丙」。夫「回」之「丙」。加其以一。名之曰「寅」。
減「戊」以一。名之曰「丁」。夫「回」之「丁」。加其以一。名之曰「卯」。
吾有一列。名之曰「文」。
夫「祝」之「子」。充「文」以其。
夫「祝」之「丑」。充「文」以其。
夫「祝」之「寅」。充「文」以其。
夫「祝」之「卯」。充「文」以其。
吾有一言。曰「文」。書之。
有數零。名之曰「了」。
若「子」等於一者。加一以「了」。昔之「了」者。今其是矣云云。
若「丑」等於二者。加一以「了」。昔之「了」者。今其是矣云云。
若「寅」等於三者。加一以「了」。昔之「了」者。今其是矣云云。
若「卯」等於四者。加一以「了」。昔之「了」者。今其是矣云云。
若「了」等於四者。
吾有一言。曰「「神聖誕辰,樂兮此日。」」。書之。
乃止。
也。
也。
加一以「戊」。昔之「戊」者。今其是矣云云。
こちらのソースコードをオンラインエディタにコピペすれば、実行して下記のような結果が見られます:
聖,誕,樂,快
誕,樂,快,誕
樂,快,誕,聖
// 中略...
樂,誕,聖,誕
誕,聖,誕,快
聖,誕,快,樂
神聖誕辰,樂兮此日。
解説
最初はズンドコキヨシレベルなら簡単だろと思ってましたが、なんといきなり壁にぶつかってしまったのです:
執筆時の現時点ではなんと、wenyan-lang がまだ乱数生成関数を実装していないのです😱
でもラッキーなことに、作者さんはオンラインエディタに様々なサンプルプログラムを用意しています。なのでとりあえずその中からなんか通常の数列からいろんな数字を生成してくれる関数が書かれてあればいけると思いました。というわけで早速コラッツの問題を解決する collatz というサンプルが見つかりました。これを実行すると、渡された数字がコラッツ処理を掛けられてその過程を出してくれます。例えば for (let i = 1; i += 1; i <= 10)
の数列を渡すと、下記のような出力を出してくれます:
1,1
2,1,1
3,10,5,16,8,4,2,1,1
4,2,1,1
5,16,8,4,2,1,1
6,3,10,5,16,8,4,2,1,1
7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1,1
8,4,2,1,1
9,28,14,7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1,1
10,5,16,8,4,2,1,1
というわけで、最初にこれ見て「おお、これを適当な数字で割った余を取ればちょっとランダム数字っぽくない?」と思って、早速出力を別の命令に代入してみたのですが、いきなり本日二番目の壁にぶつ蹴ったわけです:
数字の入力は漢数字以外受け付けないのです😱
文法解説記事やいろんなサンプルコードを読んでみればわかりますが、数字は全て一
とか十
とか十一萬四千五百一十四
とかで記入されているのですが、上記のプログラムは出力がアラビア数字です2。そしてアラビア数字の入力はそもそもプログラムに無視されてしまいます…残念。というわけで別のプログラムを探してみたら、同じような処理だけどちゃんと漢数字を出力してくれるサンプル collatz2 がありました!同じく for (let i = 1; i += 1; i <= 10)
の数列を渡すと、下記のような出力を出してくれます:
零
一
七
二
五
八
一十六
三
一十九
六
ふむふむ、今回はその過程ではなく、何回処理すれば終わるかという出力ですね。というわけでおかげさまでこれでランダム数字っぽいプログラムができました。これが最初の
吾有一術。名之曰「考拉茲」。欲行是術。必先得一數。曰「甲」。乃行是術曰。
有數零。名之曰「總」。
為是九千九百九十九遍。
若「甲」等於一者。乃得「總」。
若非。
除「甲」以二。所餘幾何。名之曰「丙」。
若「丙」等於零者。除「甲」以二。名之曰「半」。昔之「甲」者。今「半」也。
若非。
乘「甲」以三。名之曰「丁」。加一於「丁」。名之曰「戊」。昔之「甲」者。今「戊」是也。
加「總」以一。名之曰「己」。
昔之「總」者。今「己」是也。
云云。
是謂「考拉茲」之術也。
です。というわけでこのサンプルを見ながら解説していきますね。
まず吾有一術
は明らかに関数宣言です。そして名之曰「考拉茲」
でこの関数を考拉茲
で命名します。さらに欲行是術。必先得一數。曰「甲」
で、この関数には一つの数字引数があり、その引数名を甲
に命名します。これで関数宣言が終わります。
次に関数の中身は乃行是術曰
と是謂「考拉茲」之術也
で囲まれて、その間の記述が関数処理です。
処理を見てみると、まず有數零。名之曰「總」
はつまり「値が 0
の数字があり、その名は總
である」という変数宣言です。wenyan-lang は変数宣言にはいくつかの方法があり、これがその中で一番短い方法です。他の方法はまた別のコードで追って解説します。
そして今度は為是九千九百九十九遍
というループ処理宣言です。これはつまり 999
回ループしますよの宣言で、ループの処理は下の云云
で終わります。
その中のループ処理はさらに若
による分岐処理が発生します。そして乃得
で関数の戻り値を返します。なので最初の若「甲」等於一者。乃得「總」
は引数の甲
が 1
であれば、總
を返すということです。
もし甲
が 1
じゃなかったの処理はさらに若非
と也
で囲まれます。これはちょっとインデントがないとわかりにくいところですが。というわけで、まず除「甲」以二。所餘幾何。名之曰「丙」
は甲
を 2
で割って、その余を丙
という変数を作って代入します。この丙
が 0
なら、甲
を 2
で割って商を半
という変数を作って代入し、そして最後甲
の値を半
の値で置き換えます。逆にもし丙
が 0
じゃなかったら、甲
を 3
で掛けて、その積を丁
という変数を作って代入し、さらに丁
に 1
を足して戊
という変数を作って代入します。最後は甲
の値を戊
の値で置き換えます。なおこの甲
が 1
じゃなかった時の処理を行ったら、總
に 1
を足して己
という変数を作って代入し、總
の値をこの己
の値で置き換えます。
以上がこの考拉茲
という関数の処理です。
必要なランダム数っぽい関数を導入したら、今度は初期配列を作ります。ズンドコキヨシのズンドコ
ですね。これは
吾有一列。名之曰「祝」。充「祝」以「「聖」」。以「「誕」」。以「「快」」。以「「樂」」。
で作られています。まず吾有一列。名之曰「祝」
は祝
という名前の配列を宣言します、という意味です。先ほど wenyan-lang には様々な変数宣言方法があると言いましたが、これが一番オーソドックスな方法ですかね。ちなみに列
や數
などの型を示すキーワードがありますが、コンパイル対象の JavaScript も Python も Ruby もそもそも型がなかったりしますので、このキーワードは今後の型の拡張のためにあるのか、それともとりあえず漢文っぽい文法のためにあるのかがわかりません。そして最後充「祝」以「「聖」」。以「「誕」」。以「「快」」。以「「樂」」
はつまりこの祝
という配列に、"聖"``"誕"``"快"``"樂"
の漢字を順番に入れます。せっかくの漢文プログラミング言語なので、日本語ではなく中国語の漢文で入れてみました。意味はメリークリスマスそのままです。
初期配列作ったら、今度はランダムで生成された文字を保存する配列を作ります。これはもう解説する必要はないですね、そのままです:
吾有一列。名之曰「回」。
これで回
という空配列が作られました。
次にいよいよ本番のループ処理です。まずはループ回数を示す変数を作成:
有數一。名之曰「戊」。
これは先ほどの有數零。名之曰「總」
と同じように、戊
という変数に 1
を代入します。簡単ですね。
恆為是。
これで while(true)
が作られます。はいそうです無限ループです。
施「考拉茲」於「戊」。名之曰「寂」。
除「寂」以四。所餘幾何。名之曰「餘」。
充「回」以「餘」。
これでループ回数を示す戊
を考拉茲
の関数の引数として代入し、その結果を寂
という変数を作って代入します。さらに寂
を 4
で割った余をランダム数として餘
という変数を作って代入します。最後は先ほど作ったランダム数を保存する配列回
に入れます。
若「戊」大於四者。
目標の文字列は聖誕快樂
の 4 文字なので、このループの最初の 3 回目はこれ以上何もしません。4 回目からいよいよ文字の取り出し処理を始めます
減「戊」以四。名之曰「甲」。夫「回」之「甲」。加其以一。名之曰「子」。
減「戊」以三。名之曰「乙」。夫「回」之「乙」。加其以一。名之曰「丑」。
減「戊」以二。名之曰「丙」。夫「回」之「丙」。加其以一。名之曰「寅」。
減「戊」以一。名之曰「丁」。夫「回」之「丁」。加其以一。名之曰「卯」。
まずは今の回数戊
から 4 を減らした数字を甲
とし、ランダム数を保存している配列回
の甲
番目の数字を取り出して、さらにそれに 1
を足して子
という変数を作って代入します。
さらに同じような処理で順番に丑``寅``卯
を作ります。ちなみにここでなぜ 1
を足すかというと、wenyan-lang の配列アクセスは通常の多くのプログラミング言語と違って、0
ではなく一
番からのアクセスだからです。4
で割った余は 0~3
の範囲なので、それを 1
足して 1~4
の範囲を作ります。
ちなみに本当は配列の要素数自体をそもそも 4
にキープできたら一番楽ですが、残念ながら調べた限り wenyan-lang に配列に要素を入れるメソッドがあっても、減らすメソッドが見当たりませんでしたので、このような配列の最後の 4 つの要素を取り出す方法にしました。
ここができたら出力を書きます:
吾有一列。名之曰「文」。
夫「祝」之「子」。充「文」以其。
夫「祝」之「丑」。充「文」以其。
夫「祝」之「寅」。充「文」以其。
夫「祝」之「卯」。充「文」以其。
吾有一言。曰「文」。書之。
ここはまず文
という出力用の配列を作ります。そして初期配列の祝
から、先ほど取得した子``丑``寅``卯
番目の文字を取得して文
に入れます。最後は文
を出力します。これが出力例に出てきてる聖,誕,樂,快
とかの部分です。
さてこれではランダムの文字列の出力ができたので、最後は希望している文字列と一致するかどうかの判定で、一致したら別の文字列を出力してプログラムを終わらせる処理ですね。ズンドコキヨシならズ``ン``ド``コ
の出力がでてるかどうかを判定し、できたらキ・ヨ・シ
の出力ですが、せっかくのクリスマスなのでさらにそれっぽい漢文の"神聖誕辰,樂兮此日。"
にしてみました。文字通り「神聖たる(イエスキリストの)誕生の今日を祝福せよ」の意味です(文字通りとは)。
有數零。名之曰「了」。
若「子」等於一者。加一以「了」。昔之「了」者。今其是矣云云。
若「丑」等於二者。加一以「了」。昔之「了」者。今其是矣云云。
若「寅」等於三者。加一以「了」。昔之「了」者。今其是矣云云。
若「卯」等於四者。加一以「了」。昔之「了」者。今其是矣云云。
若「了」等於四者。
吾有一言。曰「「神聖誕辰,樂兮此日。」」。書之。
乃止。
也。
残念ながら、本当は若「子」等於一且「丑」等於二...
の文法があればいいのですが今のところそれがないっぽいのです。なので仕方なくここは達成条件数での判断にしました。一つの条件が達成されるたびに了
という変数に 1
を足して、合計 4 つの条件があるので了
が 4
と等しければ全部の条件が達成できたという判定です。
そして了
の判定条件はつまり一つ目の乱数子
が 1
、二つ目の乱数丑
が 2
、三つ目の乱数寅
が 3
、四つ目の乱数卯
が 4
であるという単純な判定です。文字列の判定も wenyan-lang では提供されていないっぽいので数値の判定にしました。
最後了
が 4
になったら、さらに"神聖誕辰,樂兮此日。"
の文字列を出力し、乃止
で恆為是
による無限ループを抜け出します。
ちなみに云云
も也
も同じく、若
の条件判定文の終了キーワードです。どれを使うかは漢文の文法に合わせて選べます。ツヨい。
そしてこの無限ループの最後に、ループ回数戊
の処理を入れればいいですね:
加一以「戊」。昔之「戊」者。今其是矣云云。
これで戊
に 1
を足して、得られた値をまた戊
に戻します。つまりループが回るたびに戊
が大きくなる、というわけです。これで、最後に生成したランダム数じが順番に 1
2
3
4
になったら、聖,誕,快,樂
が出力されたのち神聖誕辰,樂兮此日。
が出力されて、プログラムが終了します。
後書き
いかがでしたでしょうか、wenyan-lang のメリークリスマスプログラム。残念ながらこのプログラムはランダム数っぽい数字を生成していますが本物のランダム数ではないし、しかも何回実行しても全く同じ結果が出てきてしまいます。本当はもうちょっとブラッシュアップして、せめて現在の時間が取得できたらそれを seed として毎回違う結果が得られるように改造したいのですが、流石に業務時間を使いすぎるのは良くありませんのでこれでよしとします。もしこの解説で少しでも wenyan-lang に興味が湧いて来れたら幸いです。なお筆者は宗教上の理由で Swift 以外の言語はこれ以上触りたくないです。