Rubyのcaseを使ってFizzBuzzを書く

Shinosaka.rb #4におじゃましてきました。

今日はFizzBuzzを書くという課題でした。

FizzBuzzは何回か書いたことがあったので、あたらしい書き方ができないか考えてみたんですが、case式を使えば今までにないFizzBuzzを書けるんじゃないかと思ったんですが、あまりうまくいきませんでした。

それがこのコードなんですが、イメージしてたのはこういうのではなくて、caseのところになんか判定するやつを書いておいて、when のところには15, 3, 5 だけ書いておけばいいようにしたかった。

そういうのをしたかったんですが、Rubyのドキュメント読みなおしてみると、whenの引数をレシーバとして === を実行し、caseの引数を右辺値として実行した結果を評価するとのことで、caseのところにlambdaを置いておくというのではうまくいかなそうなことがわかりました。caseのパラメータをレシーバにしてくれれば良かったんですが...

それで他に手は無いかなと考えてみました。

Fixnum#===をオーバーライドした解です。 case式は非常にすっきりしていい感じです。Refinementsを使っているので、Integer#===の挙動が変わるのはこのファイルだけということも保証されてます。

でもちょっと大げさな感じなので、できればlambdaを使ってなんとかしたい。

そこでこんなふうに実装してみました。

なかなかいいですね。caseにlambda渡すのは諦めて、whenにlambdaを渡すようにしました。 でも個人的に、caseの引数に何も渡してないのが不満。lambdaにiを閉じ込めてるからいいといえばいいんですが、それならifで良くない?という気がしてしまいます。

そこで、curryメソッドを使って以下のように書き換えてみました。

caseにiを渡して評価するようにしました。Proc#=== は Proc#call と同じメソッドなので、

matcher.curry[15][i]

のように実行されているわけですね。 でも今度はcurryと書いてるのがうっとおしくなってきました。curryを使うんじゃなくて、lambdaを返すlambdaを定義してみましょうか。

whenの後ろがスッキリしました。その分lambdaの定義が読みにくくなってしまいましたが...まあいいでしょう。満足しました。

新しいFizzBuzzの書き方を思いつけて良かったです。ありがとうございました。

(2014/5/1追記)

curryの使い方を勘違いしてました。curryは引数を一つづつとるlambdaを返すんだから、予めcurryしておけばスッキリ書けた。

これでOKですね。curryを使うと、上に書いた、入れ子になってるlambdaと同等のlambdaを出力してくれる。

rubygems-bundlerのしくみ

https://github.com/mpapis/rubygems-bundler

rubygems-bundlerを入れると、bundle exec がいらなくなるということを知りました。

どうやってbundle execを省略できるようにしてるのかわからなかったので、調べてみました。

結論としては、gem がインストールした実行ファイルの shebang を書き換えることで、フックしてます。

たとえば rbenv を使ってて、rakeをインストールすると、 /home/hoge/.rbenv/versions/2.1.1/bin/rake のようなパスに実行ファイルが作られます。

このファイルのshebangは、通常

#!/usr/bin/env ruby

なんですが、gem regenerate_binstubs を実行すると、これらのファイルのshebangだけを

#!/usr/bin/env ruby_executable_hooks

と書き換えてしまいます。 この ruby_executable_hooks というのが各Gemの中に lib/rubygems_executable_plugin.rb というのを探して、あればそれを実行します。

rubygems-bundlerrubygems_executable_plugin.rb は、カレントディレクトリからルートにむかって Gemfile を探し、あればそのファイルを使って Bundler.setup を呼び出します。

こうして bundle exec をつけずに rake を実行しても、 bundle exec をつけて呼び出したのと同様の効果を実現してます。

gem regenerate_binstubsコマンドを提供している executable-hooks は、Gemがインストールされるときにフックを走らせることで、以後は、実行ファイルを提供するGemがインストールされるごとに、自動で shebang を書き換えます。

「統計学が最強の学問である」を読んだ

統計学が最強の学問である」を読んだ。

統計学が最強の学問である

統計学が最強の学問である

最近統計ブームになってきて、データ解析してますとかビッグデータ扱ってますという話をよく聞くけど、ただデータを集めて数を測定して何か分かった気になった、というようなやり方は全然金の無駄で、ちゃんと目的を設定した上でデータ集めや解析をしないといけないというようなことが書いてあった。

たとえばビジネスなら、目標は利益をあげることだから、以下の問いを立てて分析しないといけない。

  • 何かの要因が変化すれば利益が向上するのか?
  • そうした変化を起こすような行動は実際に可能なのか?
  • 変化を起こす行動が可能だとして、そのコストは利益を上回るのか?

確かにそのとおりだと思った。

必要のない正確さについて

他には、「意思決定に必要のない正確さは、無駄なコストだ」ということも書いてあった。普段、何も考えずに正確さを求めてる気がするので反省したい。

A/Bテストについて

A/Bテストのことも書いてあった。A/Bテストは統計の世界ではランダム化比較実験と呼ばれてきたものらしい。実験対象にランダムにそれぞれの方法を試すことで、比較したい両グループの諸条件がほぼ揃う。たとえば畑を40区画に分けて、肥料A、Bをそれぞれランダムな区画に散布すると、肥料Aをまいた土地が日当たりの悪い区画ばかりあたる確率は100万分の1になる。日当たりの良い区画が同数になる確率は13%、+-1まで許容すると36%、+-2まで許容すると57%になる。これは日当たり以外の水はけや土壌などの他の条件についても同じことが言える。ランダム化比較実験は複雑な要素を持ってる実験対象が相手の時に威力を発揮する。なので人間相手に実験したいときには非常に有効。

また、得られた結果から導かれた結論が、どの程度確かなのかを検証する計算も確立されていて、誤差によってその結果が導かれた可能性が5%以下なら、誤差とは考えにくいとみなすのが一般的らしい。

A/Bテストは最近記事などでよく見かけるけど、それがコンバージョン率7%から9%に上がったとして、そこから単純に優劣が決まるのか、それとも誤差のことを考えなくていいのかがよくわかってなかった。ランダム化の強力さと、誤差のことも考えて比較できるということがわかって、非常に腑に落ちた。機会があればA/Bテストを導入していきたい。

その他

統計の分析手法は一般化線形モデルというカテゴリでくくると共通化できるものが多くて、どういうケースでどの手法を使うかという表が載ってて便利。きっとデータ分析が必要になったら見直すと思う。

統計について何もわかってない状態から読んでみたけど、統計学の概観と、統計学が怪しげな学問じゃなくて、数学の裏づけがある学問だということがわかってよかった。もっと統計について勉強したい。

SexyPresenterというRails Presentater Gem作った

以前ブログに書いた、Refinementsを使ったRailsのプレゼンテーション層処理をGemにまとめた。

kmdsbng/sexy_presenter · GitHub

Gemfileに

gem "sexy_presenter"

と書いておくと使える。

このGemが提供する機能は1つだけだ。viewテンプレートの先頭にYAML形式で presenter モジュールを指定する。これだけ。

---
presenter: HeaderPresenter
---
Welcome to our web site.
Now we have <%= @customer_count %> customers. Join us!

presenterと指定したモジュールでは2つのことが書ける。

  • 既存クラスのrefineを書ける。refineで定義したメソッドは、viewテンプレートの中だけで有効化される。
  • viewテンプレート描画直前の処理を書くための before_render フックを書ける。

refineが使えるのは、単に using でモジュールをインクルードしてるから。 before_renderフックは、render :partial でviewを読み込んだ時に、データを準備したいことがよくあるのでつけた機能。結構便利。

便利な render :partial 機能に特化した CellsというGemがあるが、SexyPresenterで置き換えられると思う。

SexyPresenterはRefinementsを使ってるので、Ruby 2.1 以降でのみ使える。 また、モンキーパッチしてる関係で、現状Rails 4のみ対応してる。

SexyPresenterができてしまったので、これまで RailsのPresentation Layerライブラリとして有名だった Draper などのライブラリは、申し訳ないがすべてオワコンになってしまった。

この種のライブラリのやっていることはほぼ同じだ。モデルなどのクラスをextendしたりwrapしたりして、その場でだけ使える拡張を施す。しかしextend方式にしてもwrap方式にしても副作用があり、メンバのクラスを拡張するのが難しかったりして、痛し痒しな使い心地だった。

例えば Draper は対象モデルをPresenterでwrapするライブラリだ。Presenterでメソッドを追加し、モデルの要素にアクセスしたいときは委譲してくれる。ただ、アソシエーションまでwrapするので、生モデルと挙動が変わってしまうことがある。

ActiveDecoratorはDecoratorモジュールでモデルをextendする。extendはwrapに比べると副作用が少ないのが利点だが、モデルのメンバまではextendしてくれない。

もう一点重要な問題が、どちらの方式でも対象オブジェクトを指定するのが面倒ということだ。ActiveDecoratorはcontrollerからviewに渡される変数を勝手にextendしてくれるが、そのメンバまではextendしてくれないので、機能を使おうとするとわざわざ変数にセットしないといけなかったりして、使ってうれしいのかわからなくなってくる。しかも、これらの問題を解決するために涙ぐましい努力をしている。

だが、Rubyに Refinements が実装された現在、あるコンテキスト限定のクラスの拡張は、Refinementsを使うのが最もスマートな解法になってしまった。

Refinementsを使えば、ひとつのモジュールに、コンテキストに関連するクラスの拡張を全て記述できる。しかもusingを定義したファイルでは自動的に拡張されるため、漏れもない。作りが簡単になり、拡張のために今までやってたような苦労も必要ない。

だから、Presentation Layerを実現するのにextendしたりwrapするライブラリはもはや時代遅れになってしまった。

ではviewテンプレートを使わないケースなら使えるかというとそんなこともない。その場合は単にRefinementsを使えばいいだけなので、やはりextendやwrapは選択肢にならない。*1

まとめると、

  • Ruby2.1時代はRefinementsを使ってコンテキスト限定の拡張をするのがオシャレ。
  • SexyPresenterは今一番オシャレなイマドキRails Presentation Layer Library

ということです。

ぜひあなたのRailsアプリケーションにSexyPresenterを使ってみてください!

*1:局所的に機能を追加するというケース以外なら、もちろんextendもwrapも使えるケースはある。

徳川綱吉(と自称するハデな金色の着物のおっさん)と節分豆をぶつけあう夢を見た。

ルールがあって、それぞれ交代で攻守に分かれて、攻撃側はテーブルの向かいから

相手にむかって1個だけ好きなタイミングで豆をぶつける。

どこに当てると高得点ということはなくて、とにかく相手の体のどこかにぶつければいい。テーブルがあるので、自然上半身のどこかを狙うことになる。

攻撃するときは、相手に自分の立ち位置と投げるタイミングを悟らせないため、透明になってから投げる。守り側が透明になると、攻撃側がどこに投げればいいか分からないので、透明にはならない。

僕は何度か守りの順番で間違って透明になってしまい、素人っぷりをさらけ出して失笑を買った。敵のおっさんのほかに2人ほどギャラリーがいた。

 

僕は元野球部の技術を生かして、突如空中から現れる豆を何度も華麗にキャッチして、手練(らしい)の敵のおっさんをうならせた。

夢の中で、「節分豆は球ではないため、あまり強く投げすぎるとコントロールがつかない。なのでコントロール重視で投げるほうがよい」という知見を得ていた。

 

最後の方になると攻守が交代するたびに目の前が激しく明滅するエフェクトが入ってた。

 

目が覚めて、だいぶ疲れてると思った。

 

Vim Ruby EvalというVimプラグインを作りました

Vim Ruby EvalというVimプラグインを作りました。

 

https://github.com/kmdsbng/vim-ruby-eval/

 

どういうプラグインかというと、以下の画像のようなことができるようになります。

 

f:id:kmdsbng:20140222222544g:plain

 

Ruby Tapasでこういうことをやってたのがかっこ良くて、どうやったらできるのか調べてみたところ、Sublime TextだとできるらしいけどVimではできないみたいだったので、作ってみた。

 

すごくかっこよかったので作ったんだけど、正直ライブコーディングですごい!と言われる以外にどういう使いどころがあるのか自分でも疑問だったんですが、実際に使ってみると、結構使える。

 

たとえばライブラリの挙動を確認したりするときには、pryを使ったり、短いサンプルコードを書いてQuickRun で動かしてみる、ということをやると思うんですが、QuickRunの代わりにVIM Ruby Evalを使うと、結果がコードのコメントとして残るので、あとから参照できるのがなかなか便利。あとは出力の整形を考えなくてもコードの途中に結果が出てくれるのはかなり気持ちいい。意外と実作業でも便利に使えることがわかってびっくりしました。

 

ただ、実装はあんまり良くなくて、ASTを作ってプログラムを解析するとかいうことはしてなくて、単に行レベルでマーカーコメントを見つけたら、その行の結果を出力するようにプログラムを変形してます。なので文字列の中でマーカーコメントと同じ文字列が含まれてたり、引数の途中でマーカーコメント入れたりするとうまく動きません。あまりそういうエッジケースでちゃんと動くことを求めたわけではないのでまあいいかなと思ってます。あとVimRuby Interfaceに依存してますので、入ってない場合は configureで --ruby-interp を有効にして Vimをビルドしなおしてください。

 

ライブコーディングや他人に見せるときに使うと、デキる奴感をアピールできると思います。便利に使ってください。

 

 

 

 

 

Refinementsを使ったRails Presenter

Ruby2.1のRefinementsを使って、RailsのPresenter層ライブラリを作れるんじゃないかと週末にがんばってみたところ、いい感じに実装できた。

 

現状はサンプルのRailsアプリケーションで動くようになったところ。

 

https://github.com/kmdsbng/refinements_rails_presenter_test

 

動きを一言で説明すると、特定のViewのファイルでのみ、refineで定義したメソッドが生える。例えば、あるViewでのみ、modelにメソッドを生やす、ということができる。

これはRuby2.1のRefinementsの機能を使って実現してるだけなんだけど、Viewのなかでusingを呼び出すだけでは動いてくれなかったので、動くようにActionpackに手を加えてる。

 

まず、refine定義するモジュールのサンプル。

 

https://github.com/kmdsbng/refinements_rails_presenter_test/blob/master/app/models/concerns/post_show_presenter.rb

refinement module sample

 

普通にrefineを使って、Postモデルにメソッドを定義してる。これはAutoLoadされるディレクトリならどこに置いてもいい。

 

https://github.com/kmdsbng/refinements_rails_presenter_test/blob/master/app/views/posts/show.html.slim

refinemehts rails presenter sample

 

これがViewの定義。 slimで書いてる。

先頭の using と書いてる行で、refineを定義しているmoduleを指定してる。

これで、このViewではPost#body_lengthが使えるようになる。

 

usingがViewの中で使えるなら、特別な処理の必要はなかったんだけど、残念ながら using は現状トップレベルからしか呼び出せないという制限がある。

だから、Viewテンプレートをevalしてるところで、usingを加えてevalするようにモンキーパッチを当てたところ、うまくいった。

 

Draperなどの既存のRails Presenterライブラリは、 modelをラップしたりextendを使ったりして model の機能拡張を行ってる。このやり方だとmodelインスタンスすべてに処理しないといけないのでどうしても手間がかかってしまったが、Refinementsが使えるならもっとスマートに同様の機能が実現できる。かなり有望なアプローチだと思う。