見出し画像

Retro-gaming and so on

Javaってメンドクサ

ちょっとした愚痴、って話。

教えて!gooに次のような質問があがってた。




またもや石取りゲームだ。以前やったよな。個人的には2度目の遭遇だ。
いや、それは別にいいんだよ。2度あることは3度ある、ってぇんで、3度目もあるだろうな、とは思ってる。
その辺は全然気にならんのだ。
問題は、これをJavaで書け、って事だ。質問者へ、ではない。単にJavaと言う存在に対して愚痴りたくなるわけだ。

いや、教えて!gooと言う媒体に対してはJavaの質問は元々鬼門だと思う。
そもそも教えて!gooやこのgoo blogってプログラミングの話題には向かねぇんだよな。
一番の問題はインデントが消えてしまう事。プログラミングの質問コーナーがあるのにインデントを消してしまう、ってのは正直言うとバカの所業だと思っている。
うん、gooっつーかNTTレゾナントはバカなんだ。多分プログラマは一人も勤めていないんだろう。
プログラマが一人もいないのにこうやってブログとかサイトとか出来るのかって?んなもん下請けのツラを札束で叩いてやらせてるに決まってんだろ(笑)。天下のNTTグループだぞ?プログラマなんざ雇わなくても下請けに丸投げすべて金で解決、だ。大企業グループだかんな。当たり前だろ、っての。
しかも字数制限がキツイ。上限3,000文字だったかしらん。プログラムなんて載せたらあっという間にオーバーなんだよ。
そして特にここにJavaの問題が絡んでくる。

言っとくけどJavaは殆ど知らん。いっつもJavaで何か書く度に検索に大量にお世話になって、何とかプログラムをでっち上げる程度だ。勉強はしたんだけど、忘れる事にも自信があるのだ(笑)。やっぱいっつも「それで」書くくらいじゃねぇとねぇ・・・どうしても・・・。
それでも教えて!gooには特にJavaが向かない、って知ってる。
何でだろ?

JavaはOOP言語の雄、とは言っても良いと思う。いや、そのOOPがいいのかどうか、って話は取り敢えず脇へ置いておいて、だ。
いずれにせよ、OOP(オブジェクト指向: Object Oriented Programming)は、本当に「プログラムを綺麗に書ける形式なのか」否かは問わないけど、少なくとも「綺麗に書こう」と思うのなら、やっぱクラスとかキチンと設計して部品化して書かなアカンって事になるだろ。言語の設計方針に従う、って事だよな。
いや、通常はそれでいいんだ。Javaのクラスに関わる問題は別のトコにある。
Javaの、企業に取っては最善だろうけど、個人使用者にとっては最悪な決定は「1クラス = 1ファイル」っつーとんでもねぇ設計を導入した事だ。
いや、だから「IT企業で使う分」にはO.K.なんだよ。確実に多人数での開発には向いてるだろう。ルールが予め言語に組み込まれてるのなら、余計な会議をやって「コーディングルールはこうしよう」って喧々囂々の議論をアツく戦わせなくって済む。
問題は個人開発だ。Javaを個人で使って何か作ってる人は「エラい」って言わざるを得ないわ。マジメな人が集ってんじゃね(笑)?よくもまぁこんなメンドクセェプログラミング言語を個人開発で使うわ(笑)。感心する。
いやだってそうだろ?例えばさ、俺が関数型言語を何か開発したとするだろ?
それで、

「1関数を1ファイルとします!」

とか言い出したらどないすんだ、って話だ(笑)。誰がそんなクソ面倒くさいプログラミング言語を使うんだよ(笑)。でもJavaはそれをやってのけたんだよな(笑)。Sun Microsystemsって言うかつての巨大IT企業が言い出さなきゃ誰もんなバカな言語を使おう、とか思わなかったんじゃないか。

それで教えて!gooの話に戻るけど。
そうなの。Javaで何かを綺麗に書こうとすれば、どうしても「クラス単位」で綺麗に書いていかないとなんない。これは必然なんだけど。
そうすると当然、Javaの構造上、1ファイルで済まないわけじゃん。クラスを増やす度に必然的にそれが記述されたファイルが増えるわけでさ。xクラス作ったからxファイル分あります、とか教えて!gooじゃ書きづらいだろ、って話だ。どないすんねん。
必然的に「Javaで望ましくない」Mainクラスに全部突っ込んで書くハメにあそこじゃなるんだけど。スクリプト程度ならいいけど、この問題のように「REPLが活きる」場合だとそうもいかないわけ。Mainクラスに全部突っ込むとやっぱ汚くなるんだよなぁ。困ったモンだ。Javaに慣れてない俺でもそう思うんだから、Java慣れした人の目にはどんな風に映るんだろ(苦笑)。
でも教えて!gooってサイトな以上、それもしょーがねぇんだよな。
はぁ、悩ましい。
うん、Javaって悩ましい言語なんだ。

いっつもREPL、REPLっつってるんで、もう一度整理して書こう。
REPLってのは読み込み部の関数、評価部の関数、表示部の関数を別々に作って、そいつらを組み合わせてループさせる設計技法だ。
これはインタプリタの動作原理なんだけど、同時に「全てのソフトウェアの動作原理」でもある。従って、ソフトウェアをシンプルに書こうと思ったらREPL構造のお世話に必ずなる。ならなければならない。
とまぁ、こんな事を何度も書いてるわけだけど。
でも「関数を作る」っつっただろ?じゃあ関数が無い言語はどーなんだ、と。
そんな、「関数が無い言語」なんかあるのか?と。ある。例えばJavaだ。
そう、Javaには関数がない。関数がないからこそ、必然的にクラスを作って、その中でメソッドを作らないとなんないわけ。
でもよ。REPLでしょ?Readクラス作ってEvalクラス作ってPrintクラス作って・・・もうこれでファイル3枚だ(笑)。うげぇ。
ところがここでふと思いついた。CLIのソフトに限る、ってのはあるんだけど(※1)、いつも書いてるような「ゲーム系REPL」・・・つまり、フツーの言語インタプリタじゃあまり見かけないんだけど、環境を共有してRead->Eval->Printでグルグル手渡すようなモデルだと、逆に環境クラスを作って、それのメソッドとしてRead、Eval、Printを実装したらどうだろ?って。
そうしたら、Mainクラスのファイルと合わせてファイルが2枚までに減らせるな、と。ついそう思いついたんだわ。
ぶっちゃけ、そんなにJavaは得意じゃないんで、Pythonでモックアップを作ってやってみたら上手く行った。あ、これならJavaに転用可能かも、と言う結論を得たわけだ。
つまりだな。敢えてPython記法で書くけど、例えば通常の関数を使ったREPLって言うモデルだと、

while True:
 env = print(eval(read(env), env))

とかやるわけだよな。printさえも環境(env)を返す設計にしとけば、envを更新しまくる事で動くプログラムを作る事が出来るわけだ。
一方、OOPで、Envってクラスを作って、それにreadevalprintってメソッドが引っ付いてるとする。
そうすると、上での「関数を使った」REPLはEnvクラスを利用すると次のようなモデルとして書ける、って事に気付いたわけだ。

while True:
 env.read().eval().print()

readメソッドもevalメソッドもprintメソッドもEnvのインスタンスであるenv自身を返すように設計する。Pythonだとself、Javaだとthisを返せ、っつー事なんだけど。
いずれにせよ、そうしておくとメソッドチェーンによってインスタンスenv自身を加工し続けて行く事が出来る。
そうすれば上手く行くな、って事に気付いた。ファイルも減るし(笑)。

いや、こういうやり方がJava界隈でスタンダードかどうかは知らないけどね。ただ、教えて!gooで「Javaでソフトウェアを書く」ような質問が上がった場合、REPLの精神を貫くとしたら、これがひょっとしたらJavaじゃ一番上手いやり方なのではないか、と。ファイルも1枚まで減らせる、ってトコまでは行かないけど、最低で2枚まではイケるな、と(笑)。
そういう話だ(笑・※2)。

ここにこの発想で書いたJavaによる石取りゲームのファイルを置いておく。

Javaの場合は、クラス名とファイル名が一致してないとならない。
Nimクラスが定義されてるファイル名は必ずNim.javaと言うファイル名じゃなきゃならないし、Envクラスが定義されてるファイル名は必ずEnv.javaと言うファイル名じゃないとならない。
この2つのファイルに対して端末で、

javac Nim.java Env.java

とコンパイルすれば、実行ファイルNimが生成される(※3)。
あとは端末で

java Nim

と打てばJava版石取りゲームがプレイ出来る、って寸法だ。


Java特有の問題を除けば、実装の中身自体は殆ど以前書いたモノと同じだ。
ただし、今回のJavaのヴァージョンは、AIって程じゃないけど、コンピュータ側の手順には必勝法が仕込んである。
問題の要請で先手になるか後手になるか、は乱数で決めるが(ターンは真偽値で表現してて、人間側がtrue、コンピュータ側がfalseで、evalメソッドを通る毎に、!が適用されてターンが反転してる)、コンピュータが先手になったら必勝法が適用されて人間側は勝つ事が出来ないようになっている。
また、仮に人間側が先手でも、一旦ミスしたらすかさずコンピュータが必勝法を駆使するようになっている。
と言うわけで、「まず人間が勝てないプログラム」になっている(笑)。
とは言っても、マジでAIって程じゃない。いや、AIって呼んでもいいけど、どっちかっつーと古典的な「ルールベースプログラミング」に近いだろう。

この「コンピュータがどう打つか」の手順はこうだ。
色々やったんだけどアルゴリズム的にはコンピュータが先手かどうかはこのプログラム上ではあまり関係ない。何故なら「人間がミスした途端」コンピュータが正しく勝ち筋の通り打つから、だ。
コンピュータの手番(つまりturn変数がfalse)の時、現在残ってる石の数(num変数)を4で割った余りを調べる。後手必勝のパターンだと4n + 1個、それ以外の場合だと4n + k(kは0, 2, 3のどれか)って事になるんだけど、いずれにせよ、まずそう場合分けをしておいて、

  • 余りが0の場合 -> コンピュータが3を引く(xに3を代入する)
  • 余りが2の場合 -> コンピュータが1を引く(xに1を代入する)
  • 余りが3の場合 -> コンピュータが2を引く(xに2を代入する)
余りが1の場合は?ってのはまた別に場合分けする。

  • 石の残りが1だった時はこれはもうコンピュータが負けの状態なんでおとなしくその1を取る(xに1を代入する)。
  • そうじゃなかったら前回取った値(プログラム上はそうだが、人間の手番)を調べて、自分が今回取る値と合わせると4になるように取っていく。

これがこのゲームの中でのルールベースのAIで、これだけでコンピュータが先手になったら人間は勝てないし、人間が先手でもミスすれば一気にコンピュータが巻き返すプログラムになる。

※1: と言うのも経験上、GUIのソフトを書こうとして、このモデルのまま持っていくのは難しいだろうな、と言う予感があるからだ。

※2: 「Javaでは上手く行く」。
まぁ、Pythonなんかでも上手く行くが、要は、このモデルだと「破壊的変更」前提にはなる。メソッドは自身が含まれるオブジェクトのインスタンス変数を破壊的に変更するからだ。
結果、フツーの(関数もある)OOP言語だと好みになるが、こういうメソッドチェーン方式でのREPLにこだわらなくでもいい、って話になる。
Javaの場合だと関数もねぇし、最初に書いたように、「教えて!goo」に解をあげるには、しばしば「ファイル数が多くなる」傾向があるから、こういうスタイルはある種苦肉の策ではあるんだ。

※3: 当然Javaコンパイラが無いとならない。
公式にはオラクルのサイトからJDKをダウンロード/インストールする。
非公式にはJava互換のオープンソース実装、OpenJDKと言うのがある。
現状だと、Windowsではオラクルの正規実装、Linuxなんかではオラクルは嫌われて、互換のOpenJDKの方が人気がある模様だ。


  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「プログラミング」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事