名古屋で数学するプログラマ(仮)

@antimon2 が趣味兼一部本職の数学で何かするときのブログ。

続・文字と数値リテラルを使わない Hello World 考 (in Ruby)

先日の続き。

解答集サイトを見ていて、他にもこういう解き方があるな、と色々発見があったので、引き続き Ruby で、先日のとは全く違う方向性の別解をまたいくつか。

関数指向の解答

所謂「関数型言語」での解答例は、もちろん関数(もしくはラムダ式?)を定義してそれを組み合わせることで目的の数値を算出するパターンです。
この方法、別に関数型言語でなくても実現可能ですよね。
ということで。Ruby に翻訳してみました。

関数は Rubyラムダ式で表現。わー見事にカッコだらけですね(^-^;
Ruby のラムダはカッコが省略できない(fn[], fn.(), という書き方の場合)ので、どうしてもこうなってしまいます。
なんか Ruby っぽくないなー、と思ってしまいますが。

別に関数をラムダ式で書かなくても良いんですよね。
メソッドの書き方でも今回は充分だし、そうすればある程度カッコを省略できますね。
てことで再び書き換えてみました。

うん、だいぶすっきりしましたね(^-^)

メソッドチェイン

先ほどの関数指向の例は、最初に 1 を作って、それを「×2」もしくは「×2+1」していく、というもの。
oney を「1」、x を「0」と読み替えると、2進数を逆順に書き記したような感じになっているわけですね。
これは、関数の戻り値をまた次の関数に渡す、という繰り返しの記述の流れと、1 と 0 の列から 2 進数値を作り出す流れが、ちょうど逆順になっているからですね。

ところでオブジェクト指向言語の考え方だと、前の結果を次の計算に引き渡すのに、別の方法があります。
それが、メソッドチェイン。
メソッドの戻り値のオブジェクトに対して、そのメソッドをまた呼ぶ、それを繰り返すことで、どんどん次の値を生み出していく方法。

それを利用して、先ほどの関数指向の解答をメソッドチェインを利用して書き換えてみました。

今度は分かりやすいように、メソッド名を "one", "zero" にしました。
そして注目すべきは、メソッドチェインだと処理の流れがそのまま左から右になるので、1 と 0 の列から 2 進数を作り出す流れと一致する、ということ。見た目と結果がマッチして、より直感的(^-^)

method_missing 、const_missing を使ってみる

さて最後に、他の言語にはない(かもしれない)Ruby 特有の書き方で(つまり本来の出題意図である「数値を生成してそこから文字を生成」で文字列を形成していくタイプではない)解答例を思いついたのでそれを。

Ruby のメタプログラミングを勉強すると、すぐに出てくるのが、BasicObject#method_missing メソッド。
これは、「呼び出そうとしたメソッドが定義されていなかったときに呼び出されるメソッド」であり、その引数は「そのメソッド名(Symbol)と、そのメソッド呼出時に与えられた引数」なのですが、標準の動作は単純に「NoMethodError を raise する」だけになっています。

また同様、Module#const_missing というメソッドもあり、これは「参照しようとした定数が定義されていなかったときに呼び出されるメソッド」、引数は「その定数名(Symbol)」、標準の動作は「NameError を raise する」だけです。

これらをオーバーライドすると、クラスによって必要なメソッドや定数を必要になったときに動的に定義して実行する、というようなことができるのですが。
これを利用して、"Hello World" という文字列を動的に生成できそうだ、というわけで。

まずこんなの書いてみました。

軽く解説。

  • "Hello", "World" は、定数とみなされるので const.missing によりその定数名を表す文字列として処理される。
  • "sp" は、定義されていないメソッドとみなされ、method_missing により半角空白 " " に変換される。

各メソッド定義の中で何やってるかの詳細は、各自読み解いてください。

なお、本来 method_missing メソッドをオーバーライドするときは、「メソッド名をパターン認識して特定のパターンの時だけ処理し、そうでないときは super を呼ぶ(=標準動作なら NoMethodError を raise する)」とか「respond_to? メソッドや respond_to_missing? メソッド等も適切にオーバーライドする」とかするのがお作法的には良いのですが、ここではこのスクリプトが動けば良いという判断で必要最低限の記述だけをしています。
決して中身を理解しないでこのコードを流用しないでください。

さて。
読み解いていただけると分かるのは、"sp" は実は別に "sp" じゃなくても、小文字から始まる変数としてもメソッドしても定義されていない識別子ならなんでも半角空白 " " に変換されます。
じゃ、"sp" もわざわざ記述しなくて済むような書き方も出来そうですね。

ということでこんな風に書き換えてみました。

こちらも簡単に解説。

  • "Hello" は、メソッド呼出と認識され、method_missing で処理され、「メソッド名文字列と引数を並べた配列」を半角空白 " " で join される。
  • "World" は、定数とみなされるので const.missing によりその定数名を表す文字列として処理される。
  • 結果として "Hello World" という文字列が出力される。

前準備は先ほどよりはちょっと長くなりますが、結果を出力する書き方は直感的ですっきりしましたね(^-^)

なお、先ほどと同様 method_missing メソッドはここではこのスクリプトが動けば良いという判断で必要最低限の記述だけをしています。決して中身を理解しないでこのコードを流用しないでください*1

あと、先ほどの例では "Hello" は定数として扱われ、今度の "Hello" はメソッド呼出として扱われます。
なぜでしょう?
Ruby の定数とは何か」とか「Ruby のメソッドとは何か」とか「Ruby で大文字で始まるメソッドってどういう扱いなのか」とかっていうのを勉強すれば理解できると思いますが、書籍なら メタプログラミング Ruby とか パーフェクト Ruby とかが参考になると思います。

*1:大事なことなので、2 回言いました。