pockestrap

Programmer's memo

Ruby Puzzle その3

その1はこちら

pocke.hatenablog.com

その2はこちら

pocke.hatenablog.com

解答、ヒント、解説などは充分な余白を開けた上で表示するので、安心して読んでください。

問題

mobile.twitter.com

1 || true || puts("Hello world") || 1

このRubyプログラムに1文字追加して、"Hello world\n"を1度だけ出力するプログラムにしなさい。 なお、例外などで異常終了するものは正答とはみなしません。

RubyKaigi 2019のCookpadのパズルリスペクトです。

techlife.cookpad.com

なお、hanachinさんが色々な人が出している問題をまとめてくれているので、もっと問題を解きたい方はそちらをご覧ください。

Okinawa.rb Ruby Puzzles 2019/5/15 - #rubykaigi のRubyPuzzleが面白かったので真似しました。Ruby 2.7でしか動きません。 · GitHub

ちなみに、この問題はある限られたRubyでのみ解くことができます。 この「限られたRuby」がなんなのかはヒントに書きます。

ヒント

以下にヒント(この問題を解けるRubyについて)を少しずつ書いていきます。適当にスクロールしてください。 ヒント2まであり、少しずつ核心に近づいていきます。

ヒントなしで解くのは難しいと思いますが、ヒント2はほぼ答えです。ちょうどよいヒントを出すのが難しいので…











































ヒント その1

時事ネタです。











































ヒント その2

この問題は、Ruby 2.7-devの特定のリビジョン間でのみ解くことができます。

上の記事を読むとなんとなくわかると思います。











































答え

答えです。

1 || true |>| puts("Hello world") || 1

解説

pipeline operator

この問題の鍵は、6/13に追加され、6/14には弱体化されたpipeline operator(|>)です。 これはメソッドチェインを括弧なしで書ける.だと思えば良いでしょう。

# . は括弧が必要
foo
  .bar(1)
  .baz(2)

# |> は括弧が要らない
foo
  |> bar 1
  |> baz 2

これは.と同じようなものなので、ruby/ruby@d365fd5以前は1 |>+ 2のように演算子を呼び出すこともできましたが、このコミットで禁止されてしまいました。 色々と悪用できそうなだけに残念ですね。

なお、pipeline operatorはまだ昨日入ったばかりの構文なので、今後仕様が変わる可能性は充分にあります。

解き方

この問題のメインは1 || true || puts("Hello world")で、最後の|| 1はおまけです(後ほど解説します)。 つまり、1 || trueをなんとかして偽にして2つ目の||の右辺を実行するか、2つ目の||を別のなにかに置き換えてやる必要があります。 何故ならば||は短絡評価される演算子であり、左辺が真であれば右辺は評価されないためです。

そして、今回の解法はその後者です。|||>|に置き換えていますね。これは要するに、true ||TrueClass#|の呼び出しに変えています。TrueClass#||と同じような動きをしますが、短絡評価を行いません。そのためputsが実行されるというわけです。

おまけ

|| 1は別解を防ぐために入れています。また、プログラムの先頭の1も同じ役割を持っています。

この問題の初期バージョンは次のようにシンプルな形でした。

true || true || puts("Hello world")

この問題も同様に|>を使って解くことができますが、もう1つ解ができてしまいます。それは%です。

true |%| true || puts("Hello world")

%万能ですね、やばい。 %は文字列リテラルであるため、これはtrue | "| true |" | puts("Hello world")と同じように評価されます。 先述したとおり|は短絡評価を行わないメソッドなので、puts(Hello world)が評価されるわけです。

これを防ぐために、最初のtrue1に置き換えました。 TrueClass#|||と同様の動きをしますが、Integer#|は各ビットの論理和を取るメソッドです。そして、Integer#|の右辺に文字列を取ることはできず、エラーとなります。

これで%を防ぐことはできましたが、今度は|>を使った解法までエラーになってしまいます。これはputsnilを返し、かつInteger#|nilを渡した場合もエラーになってしまうためです。 この問題の最後の|| 1は、このエラーを防ぐためのものでした。


計らずも寿命が1日のパズルを作ってしまいましたが、お楽しみいただけましたら幸いです。