ちょっと前のツイートになるのですが、これを見かけて面白いと思って試した試みを note に供養しておきます。
BNF記法について
BNF = Bachus-Naur Form で、形式言語とかを大学で学んだことがある人であれば聞いたことがあると思います。文脈自由文法と呼ばれるものを定義されるのに使うメタ言語で、何かを生成するルールや、構造を形式的に表現することが得意です。
ざっくり要点を整理すると、次の3つをおさえておけばOKです。
| | or |
::== | 定義 |
<> | 変数 |
例えば、次のようなルールがあるとして、「あいうえお」という文字列がどのように生成されるかを考えてみますと、
<x> ::== あ | い | う
<y> ::== え | お
<A> ::== <x> | <A> <x> | <A> <y>
- <A>
- → <A> <y>
- → <A> <y> <y>
- → <A> <x> <y> <y>
- → <A> <x> <x> <y> <y>
- → <x> <x> <x> <y> <y>
- → あいうえお
となるわけです。つまりこのルールで <A> は「あいうえお」という文字列を表現することが可能になるわけです。
という小話は置いといて(気になる人は勉強してみよう)、この「構造を表現するメタ言語」で、ふわっとした概念を表現するとどうなるか?が検証内容になります。
ふわっとした概念をBNFで強引に実現する
大学生の一日の流れ
まず、大学生の一日の流れをBNFで書かせてみると
<day> ::= <wake_up> <morning_routine> <classes> <lunch> <afternoon_activities> <dinner> <evening_activities> <sleep>
<wake_up> ::= "起床"
<morning_routine> ::= <hygiene> <breakfast> <prepare_for_classes>
<hygiene> ::= "身だしなみ"
<breakfast> ::= "朝食"
<prepare_for_classes> ::= "授業の準備"
<classes> ::= <class> | <class> <classes>
<class> ::= "授業"
<lunch> ::= "昼食"
<afternoon_activities> ::= <class> | <study> | <extracurricular_activities> | <free_time> | <work>
<study> ::= "勉強"
<extracurricular_activities> ::= "課外活動"
<free_time> ::= "自由時間"
<work> ::= "アルバイト"
<dinner> ::= "夕食"
<evening_activities> ::= <study> | <extracurricular_activities> | <free_time> | <work> | <socialize>
<socialize> ::= "友達と遊ぶ"
<sleep> ::= "就寝"
ざっくり要点を整理すると、次のようになります。
- 1日 = 起きる → モーニングルーティン → 授業 → 昼食 → 午後の活動 →夕食 → 夜の活動 → 寝る
- モーニングルーティン = 身だしなみ → 朝食 → 授業準備
- 午後の活動 = 授業 or 勉強 or 課外活動 or 自由時間 or バイト
- 夜の活動 = 勉強 or 課外活動 or 自由時間 or バイト or 友だちと遊ぶ
BNFを用いて、一日の大学生の流れを構造的にまとめることができました。各自の生活は or のうち1つを選択しながらこのルールに基づいて生成することができます。つまりこれは大学生活を抽象的に構造的に表現した形と言えますね。
美味しいカレーの作り方
美味しいカレーの作り方の手順を BNF でまとめてもらい、その流れと工夫できるポイントを理解してみましょう。
<curry_recipe> ::= <ingredients> <preparation> <cooking>
<ingredients> ::= <ingredient> | <ingredient> <ingredients>
<ingredient> ::= "肉" | "野菜" | "カレールー" | "水" | "油" | "調味料"
<preparation> ::= <prepare_ingredients> | <prepare_ingredients> <preparation>
<prepare_ingredients> ::= "肉のカット" | "野菜のカット" | "調味料の準備"
<cooking> ::= <cook_step> | <cook_step> <cooking>
<cook_step> ::= "油を熱する" | "肉を炒める" | "野菜を炒める" | "水を加える" | "煮込む" | "カレールーを加える" | "調味料を加える" | "煮込む"
- 美味しいカレーの作り方は材料、下準備、調理の3工程からなる
- 材料ステップでは1つ以上の材料を集める
- 肉 or 野菜 or カレールー or 水 or 油 or 調味料
- 準備ステップでは1つ以上の準備行動を行う
- 肉のカット or 野菜のカット or 調味料の準備
- 調理ステップでは1つ以上の調理行動を行う
- 油を熱する or 肉を炒める or 野菜を炒める or 水を加える or 煮込む or カレールーを加える or 調味料を加える
ふむふむ。より詳細を生成させるとカレーの作り方として使えそうになるかもしれませんね。
もっと具体的に書いてくれと ChatGPT にお願いしてみます。
<curry_recipe> ::= <ingredients> <preparation> <cooking>
<ingredients> ::= <protein> <vegetables> <curry_roux> <liquid> <oil> <seasonings>
<protein> ::= "肉" | "魚" | "豆腐" | "テンペ"
<vegetables> ::= "たまねぎ" | "じゃがいも" | "にんじん" | "ピーマン" | "なす"
<curry_roux> ::= "カレールー"
<liquid> ::= "水" | "スープストック" | "ココナッツミルク"
<oil> ::= "サラダ油" | "オリーブ油" | "バター"
<seasonings> ::= "塩" | "こしょう" | "ガラムマサラ" | "カレーパウダー"
<preparation> ::= <prepare_ingredients> | <prepare_ingredients> <preparation>
<prepare_ingredients> ::= "肉のカット" | "野菜のカット" | "調味料の準備"
<cooking> ::= <cook_step> | <cook_step> <cooking>
<cook_step> ::= "油を熱する" | "肉を炒める" | "野菜を炒める" | "水を加える" | "煮込む" | "カレールーを加える" | "調味料を加える" | "煮込む"
おおお、より材料が具体的になりました。厳密にはこのBNFだと野菜を1つしか選べなかったりするのですが、ざっくりまとめるという用途では十分ではないでしょうか。
プロ野球選手が投球をする流れ
「プロの動作」などをBNFで理解しようとすると面白いかもしれません。ということでやってみよう!
<pitching> ::= <setup> <windup> <stride> <arm_action> <follow_through>
<setup> ::= "投手がマウンドに立つ" | "サインを受け取る" | "投球の構えを取る"
<windup> ::= "足を上げる" | "バランスを取る" | "上半身を回転させる"
<stride> ::= "前足を踏み込む" | "力を伝える"
<arm_action> ::= <grip> <arm_swing> <wrist_snap> <release>
<grip> ::= "ボールの握り方を決める"
<arm_swing> ::= "腕を振る"
<wrist_snap> ::= "手首をしならせる"
<release> ::= "ボールをリリースする"
<follow_through> ::= "腕を振り抜く" | "投球後のバランスを整える"
なるほど。ピッチングという流れをステップに分け、各ステップですべきこともある程度列挙してくれました。もう少し具体的に聞いてみましょう。ここでは <windup> の処理をより具体化してみます。
<windup> ::= <pivot> <lift_leg> <rotate_upper_body> <reach_back>
<pivot> ::= "後ろ足でマウンドを踏む"
<lift_leg> ::= "前足を上げる" | "バランスを保つ"
<rotate_upper_body> ::= "上半身を後ろに向けて回転させる"
<reach_back> ::= "投げる腕を後ろに伸ばす"
もう少し感覚的に表現してもらいましょうか。「オノマトペなどを用いてよい」と命令してみます。
<windup> ::= <pivot> <lift_leg> <rotate_upper_body> <reach_back>
<pivot> ::= "後ろ足でマウンドをゴリッと踏む"
<lift_leg> ::= "前足をスーッと上げる" | "バランスをキープ"
<rotate_upper_body> ::= "上半身をグイッと後ろに向けて回転させる"
<reach_back> ::= "投げる腕をシュッと後ろに伸ばす"
ふむふむ。オノマトペが入ることで、より具体的なイメージがしやすくなりましたね。
サッカーのファウルをBNFで理解する
サッカーのファウルのような、素人からするとよくわからない概念をメタ言語で表現するとどうなるでしょうか。
<soccer_foul> ::= <foul_initiator> <foul_type> <foul_victim> <foul_result>
<foul_initiator> ::= "プレイヤーA"
<foul_type> ::= "が" <tackle_foul> | "が" <handball_foul> | "が" <trip_foul>
<tackle_foul> ::= "スライディングタックルで" | "立ちタックルで"
<handball_foul> ::= "手でボールを触って"
<trip_foul> ::= "足で相手を倒して"
<foul_victim> ::= "プレイヤーB"
<foul_result> ::= "にファウル。" <consequence>
<consequence> ::= "フリーキックが与えられる。" | "ペナルティキックが与えられる。" | "警告や退場が与えられる。"
なるほど、ファウルの流れを分かりやすく説明してもらえました。やってて思いましたが、このように流れをBNFで表現する行為は、フローチャートを言語的に書いているようなもんですね。
「人生の目的」をBNFで理解する
ここまで流れをフローチャートにする的な用途しかなかったので、「人生の目的」のような抽象概念を構造的に理解する試みをChatGPTにやらせてみます。
<人生の目的> ::= <幸福追求> | <成長> | <貢献> | <達成> | <関係> | <自己実現>
<幸福追求> ::= "幸せを追求する" | "心の平和を求める" | "快楽を楽しむ"
<成長> ::= "スキルを向上させる" | "知識を増やす" | "自己改善をする"
<貢献> ::= "社会に貢献する" | "他者を助ける" | "環境を守る"
<達成> ::= "目標を達成する" | "成功を収める" | "競争に勝つ"
<関係> ::= "家族との関係を大切にする" | "友達と繋がる" | "愛する人と共に過ごす"
<自己実現> ::= "自己表現を行う" | "創造性を発揮する" | "自分の道を追求する"
なるほどー。確かに構造的に分解しながらよくまとまっている印象。
「人類の歴史と、ありえた世界線」をBNFで表現させ、あり得た世界線を生み出させる
まずは「人類の歴史と、あり得た世界線」を構造的に表現してもらいます。
<human_history> ::= <timeline> | <alternate_timeline>
<timeline> ::= <prehistoric> <ancient> <medieval> <modern> <future>
<alternate_timeline> ::= <what_if> <divergence_point> <alternate_history>
<prehistoric> ::= "prehistoric" | "stone_age" | "bronze_age" | "iron_age"
<ancient> ::= "ancient" | "egypt" | "mesopotamia" | "greece" | "rome" | "china" | "india"
<medieval> ::= "medieval" | "middle_ages" | "feudalism" | "renaissance" | "exploration"
<modern> ::= "modern" | "industrial_revolution" | "enlightenment" | "world_wars" | "cold_war" | "information_age"
<future> ::= "future" | "space_age" | "posthuman" | "singularity" | "utopia" | "dystopia"
<what_if> ::= "what_if" | "alternate" | "parallel" | "possible"
<divergence_point> ::= "divergence_point" | "turning_point" | "fork" | "decision"
<alternate_history> ::= "alternate_history" | "different_outcome" | "reimagined_world" | "fictional_scenario"
これに基づいて、非現実的な世界線シナリオを生成してもらいましょう。
- 潜在的世界線1:
- <what_if> = "what_if"
- <divergence_point> = "turning_point"
- <alternate_history> = "reimagined_world"
- この潜在的な世界線では、古代ローマ帝国が滅亡せずに続き、現代に至るまで存在し続けたとします。この場合、ヨーロッパと地中海地域の歴史、文化、政治が大きく異なる結果となります。また、技術や科学の発展も異なる道筋をたどるでしょう。
- 潜在的世界線2:
- <what_if> = "alternate"
- <divergence_point> = "fork"
- <alternate_history> = "different_outcome"
- この潜在的な世界線では、19世紀に蒸気機関の発展が飛躍的に進み、現代でも蒸気機関が主要なエネルギー源として利用され続けているとします。この世界では、石油産業が発展せず、エコロジーとサステナビリティが重視される社会が形成されるでしょう。
- 潜在的世界線3:
- <what_if> = "possible"
- <divergence_point> = "decision"
- <alternate_history> = "fictional_scenario"
- この潜在的な世界線では、20世紀半ばに地球外生命体が地球を訪れ、人類と友好的な交流を持ったとします。その結果、地球外の技術が地球にもたらされ、現代の科学技術や宇宙開発が大きく加速し、地球外との交流が一般的になるでしょう。
なるほど。どの変数をいじるとシナリオがどう変わるかが分かるので、クリエイティブなアイデアを検討するときに役立つかもしれません。
以上。より質を高めたいなら、一度BNFを生成させた後、不満がある点を都度指摘すれば改善がされるでしょう。ということで、メタ言語でやりとりすることで汎用性の高い出力を得る方法でした。