each、each_with_object、inject、map

Ruby の each、each_with_object、inject、map は使いどころが微妙に違う。
それぞれ適切な状況で使い分けられれば、コードはより分かりやすくなる。

どんな状況でも each で書くことはできる。だから、each だけ使いこなせればいいという考え方はある点で正しい。そのような考え方の人にとってはeach で書くのがもっとも分かりやすいコードになるだろう。

しかし慣れてみると上記のメソッドを使い分けられる方が簡潔で分かりやすいコードになる。その理由はメッセージ性の違いだ。

each ですべてを書く場合は余計なコードを書く必要があり、その分、どうしても、本質的なコードが埋もれてしまう。余分なコードがないほど、本質的なコードが際立つ。メッセージが伝わりやすくなる。意味があるコードの比率を下げることは、中級プログラマへの道を開く鍵だ。

それでは本題に入ろう。

まずは簡単に [1, 2, 3] をそれぞれ二乗して [1, 4, 9] を得るコードを書く。

なお、each_with_object の利用には Ruby 1.9 系を利用するか、Rails の一部である ActiveSupport を利用する必要かある。

# each
result = 
[1, 2, 3].each do |i|
  result << i*i
end
result # 値を返すために必要

# each_with_object
[1, 2, 3].each_with_object  do |i, result|
  result << i*i
end

# inject
[1, 2, 3].inject [] do |result, i|
  result << i*i
end

# map
[1, 2, 3].map do |i|
  i*i
end

この場合は map がもっともコードが短く簡潔になることが分かる。each の場合は返り値が self であるため、生成した result を明示的に返す必要がある。

この場合は、 each_with_object と inject の間の違いは引数の順序以外に特に分からない。

では、次の例を見てみよう。
[%w(0xff0000 red), %w(0x00ff00 green), %w(0x0000ff blue)] から {"0xff0000" => "red", "0x00ff00" => "green", "0x0000ff" => "blue"} を得たい。

# each
hash = {}
[%w(0xff0000 red), %w(0x00ff00 green),
 %w(0x0000ff blue)].each do |key, value|
  hash[key] = value
end
hash

# each_with_object
[%w(0xff0000 red), %w(0x00ff00 green),
 %w(0x0000ff blue)].each_with_object({}) do |(key, value), hash|
  hash[key] = value
end

# inject
[%w(0xff0000 red), %w(0x00ff00 green),
 %w(0x0000ff blue)].inject({}) do |hash, (key, value)|
  hash[key] = value
  hash # <- 余分だが必要な1行
end

# map
# ハッシュを返り値とできないため、かけない。

each_with_object では必ず引数のオブジェクトが返り値となるため、返り値となるオブジェクトを明示的に最後に書く必要はない。しかしながら、 inject ではブロックで最後に評価した値が次回のブロックの引数となりさらには返り値となるため、明示的に hash を最後に書く必要がある。

map は配列を返り値とする場合にしか利用できないため、ハッシュを得たい場合は利用できない。

この場合は、each_with_object を使う例がもっとも簡潔である。それは、内部の処理で副作用をもたらす処理を行っており、その副作用を与えたい対象が返したい値だからだ。inject の処理では適切な値を返すために 1行余分に必要になってしまう。

次に整数の和を求める例を示す。

# each
sum = 0
[ 1, 2, 3].each do |i|
  sum += i
end
sum

# each_with_object
# 破壊的に変更可能なオブジェクトでないと適用不可
sum = [ 1, 2, 3].each_with_object(0) do |i, j|
  j += i
end
# p sum #=> 0

# inject その1
sum = [ 1, 2, 3].inject 0 do |subtotal, i|
  subtotal + i
end
# inject その2
sum = [ 1, 2, 3].inject 0, :+
# inject その3
sum = [ 1, 2, 3].inject :+

# map は配列を返り値とする場合にしか利用不可

整数の和を得たい場合は、inject が一番簡潔になる。この場合は each_with_object は意図どおり動作しない。

最後に、文字列の連結処理を考える。

# each
path = ""
%w(usr local ruby ruby1.9.1 bin).each do |dir|
  path << "/" + dir
end
path

# each_with_object
%w(usr local ruby ruby1.9.1 bin).each_with_object "" do |dir, path|
  path << "/" + dir   # path += "/" + dir は不可
end

# inject
%w(usr local ruby ruby1.9.1 bin).inject "" do |path, dir|
  path + "/" + dir
end

# map
# 今回の主な趣旨とは異なるが Array#join を使う解を示す。
%w(usr local ruby ruby1.9.1 bin).map do |dir|
  "/" + dir
end.join("")

この場合も inject が簡潔であるように感じられるが、それは主にeach_with_object がスペルが長いからだ。

その部分を差し引いて考えれば、同等だろう。each_with_object はブロック引数のオブジェクトを破壊的に変更し続ける場合に便利なメソッドなので、この場合 path += ではなく path << を利用する必要がある。inject はブロックの最後の評価結果を利用するため、path << や path += などとする必要はなく、単に + として良い。

map と join を組み合わせる例が、案外簡潔で分かりやすい点も見落とすべきでない。この例もありうる。

総じて each を使うと、返したいオブジェクトの初期化と、値を返すために2行長くなる。この2行は本質的な処理を行っている箇所ではないため、each_with_object や inject、map を利用すれば2行短くできる。
逆にいうと、すでに得られているオブジェクトに対する処理で、返り値などを利用する必要がとくにない場合は each を利用する方が分かりやすいコードになる

補足であるが、tap (Ruby 1.8.7 以降)というメソッドや、returning (ActiveSupport が提供)というメソッドがある。これらを利用すると、返り値の1行などをなくせる場合がある。

# tap
{}.tap do |hash|
  [%w(0xff0000 red), %w(0x00ff00 green),
   %w(0x0000ff blue)].each do |key, value|
    hash[key] = value
  end
end

# returning
returning ({}) do |hash|
  [%w(0xff0000 red), %w(0x00ff00 green),
   %w(0x0000ff blue)].each do |key, value|
    hash[key] = value
  end
end


蛇足だが、さらに上記は Hash[] メソッドを使うことできわめて簡潔に書ける。

a = [%w(0xff0000 red), %w(0x00ff00 green), %w(0x0000ff blue)]
Hash[*a.flatten]

(仮称)FastMars : FAST Mobile Access for RSs 作成中

私は地下鉄を利用して通勤しているのだが、悩みがあった。それは、駅と駅の間では携帯電話の通信が途切れ、RSS などの閲覧ができなくなることだ。
さらにいうと、読みたい長文の記事はたいていの場合、5ページほどに分割されており、読み進めるのに、何度もページ遷移が必要となる。このページ遷移をするために駅に到着するのを待つ必要があり、駅に到着するのを待つ時間がとても無駄でいらいらしたものであった。

これらの悩みを解消するために、新しい WEBアプリケーションの作成にとりかかった。以前、契約したレンタルサーバFastLadder をインストールしたが、それにさらにカスタマイズして、携帯電話からアクセスしやすくした。

ブログの記事に完全に落としこむ時間が十分でないため、簡単に行ったことを列挙する。

1. もともとの FastLadderRails 2.3系に対応していなかったため、2.3 に対応する改造が加えられたものをインストール。

git clone git://github.com/hsbt/fastladder.git

2.Jpmobile プラグインをインストール。

git clone git://github.com/darashi/jpmobile.git vendor/plugins/jpmobile

3.Jpmobile がそのままでは Rake がなぜか動かなかったので修正。

 --- a/test/rails/overrides/spec/rspec.rake
 +++ b/test/rails/overrides/spec/rspec.rake
 @@ -8,7 +8,7 @@ begin
 rescue MissingSourceFile
    module Spec
      module Rake
 -      class SpecTask
 +      class SpecTask < ::Rake::TaskLib
          def initialize(name)
            task name do

4.MobileControllerを作成
携帯電話から表示するコントローラをゴニョゴニョ作成。wedata にある AutoPagerize の items.json をうまく使って、ページ遷移が必要な場合も1画面で一気に表示できるように工夫。あと、複数の記事をチェックボックスでチェックすることで、一気に表示できるようにする機能も追加。

5.twitter閲覧機能も追加
oauth を使って、twitter を閲覧する機能も追加。

もうちょっと、ソースコードをきれいにしてから、github で公開する予定。全体として、作りたい機能とは無関係なところで、ハマって時間を費やすことが多い。もうちょっと、調査をすばやくしないとな。

できる限りログインするための操作などもスキップできるようにして、とにかく私がすばやく RSS などを見る機能に注力したつもり。

自分でも使ってみたところ、かなり便利。なんで、こんな便利なアプリをもっと早く作らなかったのかが自分で不思議だ。

RVMをインストール

いろいろな Ruby のバージョンを同時並行で利用するためにRVM http://rvm.beginrescueend.com/ というツールがある。大変便利であったので、インストール方法のメモを保存。

# JRuby も一緒に利用できるようにしたかったので、Sun の
# Java をインストール。

$ echo "deb http://archive.ubuntu.com/ubuntu jaunty multiverse" >> /etc/apt/sources.list
$ aptitude update
$ aptitude install sun-java6-jdk
$ vim /etc/apt/sources.list #multiverse の行を削除
$ aptitude update


# RVM をインストール

$ gem source -a http://gemcutter.org/
$ gem install rvm
# パスを通していなかったので、絶対パスで指定
$ /var/lib/gems/1.8/bin/rvm-install

# 各種 Rubyのインストール
$ rvm install 1.8.6,1.8.7,ree,1.9.1,jruby

$ rvm list
   ruby-1.8.6-p383  ( ruby -v # => ruby 1.8.6 (2009-08-04 patchlevel 383) [i686-linux]) )
=> ruby-1.8.7-p174  ( ruby -v # => ruby 1.8.7 (2009-06-12 patchlevel 174) [i686-linux]) )
   ruby-1.9.1-p243  ( ruby -v # => ruby 1.9.1p243 (2009-07-16 revision 24175) [i686-linux]) )
   jruby-1.4.0  ( ruby -v # => jruby 1.4.0 (ruby 1.8.7 patchlevel 174) (2009-11-02 69fbfa3) (Java HotSpot(TM) Client VM 1.6.0_13) [i386-java]) )
   system ( ruby -v # => ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux] )

$ rvm use 1.8.7
 Now using ruby 1.8.7 p174 
$ ruby --version
ruby 1.8.7 (2009-06-12 patchlevel 174) [i686-linux]

$ rvm use 1.8.6
 Now using ruby 1.8.6 p383 

私のレンタルサーバでは java の -Xmx500m となっている設定を -Xmx64m に変更しないと、JRuby は動作しなかった。このあたりがレンタルサーバの限界でメモリに非常に大きな制約があると感じた。メモリを大きくする必要がある作業は、手元のパソコンですることにしよう。

FastLadder をセットアップ

ふと思い立ってというか、FastLadderRuby on Rails 上で動いていること
を知ってよっこいしょとインストールしてみた。

備忘録として、その内容をメモする。

やったこと。

1. レンタルサーバの契約。
本当はローカルパソコン上で動かそうと思っていたのだが、Ruby 1.8.6 推奨かつ crawler を常に動かし続ける必要があるようなので、このためにレンタルサーバを新規に契約することにした。

https://www.burst.net/ の一番安いプランで、月間6ドル。自宅サーバを構築した場合の電気代よりも安いかな、と思って契約。

2.アカウントの追加
作業用のアカウントを追加。

 useradd -m cuzic

3.レンタルサーバの設定の見直し。

このレンタルサーバは初期状態では ssh で root でのログインを許していた。さすがにそれはまずかろう、ということで変更。

vim /etc/ssh/sshd_config
ssh -l cuzic localhost
# cuzic でログインできるか確認
/etc/init.d/ssh reload

4.いろいろインストール。
整理すると次の内容のインストールを行うことになった。

apt-get install aptitude
aptitude install rails

aptitude install libssl-dev
aptitude install zlibc

wget ftp://xyz.lcs.mit.edu/pub/ruby/ruby-1.8.6-p383.tar.bz2
tar jxvf ruby-1.8.6-p383.tar.bz2
./configure --prefix=/usr/local
make
make install
wget http://rubyforge.org/frs/download.php/60718/rubygems-1.3.5.tgz
tar zxvf rubygems-1.3.5.tgz
cd rubygems-1.3.5
ruby setup.rb

gem install rake
gem install rails --include_dependencies -v 2.0.2
aptitude install subversion
aptitude install libfreeimage3 libfreeimage-dev
gem install rfeedfinder feed-normalizer opml mongrel sqlite3-ruby sqlite3

gem install gettext -v 1.10.0

# 以下ユーザ権限
svn checkout http://fastladder.googlecode.com/svn/trunk/ fastladder
RAILS_ENV=production rake db:migrate

# なぜか、Feed の追加時にエラーが発生するので修正。
cuzic:~/fastladder/fastladder$ svn diff lib/
Index: lib/fastladder/feedfinder.rb
 ===================================================================
 --- lib/fastladder/feedfinder.rb        (revision 32)
 +++ lib/fastladder/feedfinder.rb        (working copy)
 @@ -1,6 +1,6 @@
  require "rfeedfinder"
 
 -def Rfeedfinder.open_doc(link)
 +def Rfeedfinder.open_doc(link, options)
    html_body = Fastladder::simple_fetch(link.to_s)
    return nil unless html_body
    Hpricot(html_body, :xml => true)

# crawler を起動
RAILS_ENV=production ./script/crawler > /dev/null &
# fastladder を起動
./script/server -d -e production

これで、FastLadder を利用できるようになった。

作業をしていて月額 600円でこれほど軽快に作業できるならかなりアリだなぁ、と思った。
私は現在、Unix 環境は VMwareCygwin に頼っていたので、リモートとはいえ、いままでよりも圧倒的に動作が素早い。
rubyコンパイルがこれだけ軽快なだけでもレンタルサーバにした価値がある。
自由に遊べる Unix 環境は必要なので、続けていこう。

P.S.
FastLadder は実際には ruby 1.8.7 でも動作する模様。
自分でコンパイルするのは不要であった。

RRRの使い方

今回は、前回に続いて、RRR の使い方を紹介します。

開発者でない多くの VARDIA東芝のハードディスクレコーダ)使いの方は、git のインストールをしていないし、git をインストールしたいとも思っていないでしょうから、コンパイル後のファイルを http://cuzic.net/rrr.zip に置きました。

使い方の手順は次のとおりです。

1. http://cuzic.net/rrr.zip からファイルをダウンロードします。
2. rrr.zip を展開し、 rrr.exe と rrr-example.yaml を同じディレクトリに置きます。
3. rrr-example.yaml を rrr.yaml に名前を変更します。
4. rrr.yaml を メモ帳等で編集します。
5. 次のアトリビュートが特に重要です。

submit_type: の行を mail を指定すると、録画予約のためにメール送信します。
host: には SMTP サーバのホスト名を指定します。
port: には SMTP サーバと接続するポート番号(25, 465, 587 等)を指定し
ます。
recipients: には、VARDIA 側で設定した録画予約用のメールアドレスを指定します。
username: には SMTP-Auth で使用する POP ID を指定します。
password: には SMTP-Auth で使用する POP パスワードを指定します。
enable_tls: に true を指定すると、SSL を使用します。
stations: 以下には iepg ファイルに記述された station の行のテレビ局名
を指定します。関西以外の地域在住の場合は、この部分を自分の地域に適切な
設定を行う必要があります。
設定については、
http://ja.wikipedia.org/wiki/%E3%83%86%E3%83%AC%E3%83%93%E5%91%A8%E6%B3%A2%E6%95%B0%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB
などを参考にしてください。

6.rrr.exe を 拡張子 tvpi に関連付けます。
パワーユーザの権限があればコマンドプロンプトで、

  assoc .tvpi=tvpi_auto_file
  ftype tvpi_auto_file="(rrr.exe があるディレクトリ)\rrr.exe" "%1"

と実行することで関連付けができます。

7.テレビ王国等のテレビ番組表サイトから録画予約を実行します。

以上です。

これで、パソコン経由で快適な録画予約を行えます。

RRRのメール対応

以前このブログで紹介した RRR (Remote Recording Reservation)という東芝VARDIAシリーズのハードディスクレコーダのための遠隔録画支援ツールの続きです。

ラーメン屋行った帰りに、znz さんと会話していたときに、「どうして、Google Apps Engineを利用する必要があるのか」を質問されて、ハッと気づいたのです。

私は SMTPサーバと POP サーバを同じにする必要があるかのように考えていたのですが、実際にはいくつもの SMTP サーバを利用するように実装することだってできるわけです。

というわけで、私が持っていた GmailWindows Livehotmail)と GMX の3つのメールアカウントを使って、メール送信するように RRR を改変してみました。

しかしながら、やってみたところ、この方法はすぐにうまくいかないことが分かりました。

理由は、私は一週間に一度、一週間分の録画予約をするわけなんですが、その3つのメールアカウントすべてを使っても30件弱のメールしか送れず一週間分にはほど遠い件数しか録画予約できないのでした。

VARDIA の使用上、一通のメールで複数の録画予約を行うこともできないようで、やはり Google Apps Engine を使う方法しかないか、ということでしばらく落ちついていました。

しかし、実際問題として Google Apps Engine を使う方法よりもメールを直接送付できるようにする方が、利便性は高いです。

大量に録画予約したあとに時間が重複している場合の調整を行っています。今のやり方のままですと、GAE でのメール送信ペースを抑制している部分もあり、録画予約したあと一晩程度時間をおいてから、重複時の予約の調整を行う必要があります。

間のクッションとなる GAE を省略して大量のメール送付が可能になれば、もう少しストレスなく録画予約ができそうです。

というわけで、やはり正攻法しかない、と思いたち、プロバイダの書類をごそごそ探し出すことにしました。しかし、非常に残念なことに私が利用しているプロバイダでは、メールアドレスは1個目からオプションで有料であることが分かりました。

がっかりです。

万策尽きたと思いましたが、よくよく調べなおすと私が使っているプロバイダでは、OP25B は私の契約に適用されなさそうです。とすると、SMTPし放題のメールアドレスを手に入れればいいだけになるので、話はだいぶ簡単になります。

私が利用している RailsPlayground.com のホスティング契約を再確認したところ、メールホスティングも含まれているようです。今まで権利だけ持っていて一切使っていませんでしたが、メールアドレスを
追加したところ、問題なく利用できるようです。

ばっちりです。

というわけで、うまくいったので、GAE を介さず、直接メール送信することによって、録画可能としたバージョンの RRR をgithub においておきました。

 git clone git://github.com/cuzic/rrr.git

で、手に入ります。

今回から、ExerbWindows 実行可能形式にしたものも一緒においております。

直接のメール送信であれば、Google Apps Engine のセットアップは不要となり、ずっと初期設定が簡単になりました。行う必要があるのは、 rrr.yml という設定ファイルの編集だけです。見よう見まねで、メールの設定とテレビ局の設定を変更すれば、RRR を利用できます。

次回は、RRR の設定ファイルについてなんか書くかも。

RRR: Remote Recording Reservation

前回紹介した 東芝のハードディスクレコーダ(VARDIA)への iEPG による録画システム(RRR)を github に公開したので、簡単に紹介します。

前回説明しましたとおり、
 1. RubyiEPG をパース。Google App Engine にメール送信指示。
 2. GAE から RD 専用メールアドレスにメール送信
 3. RD からメールで録画予約
という手順を行います。

試してみたい方は、
git clone git://github.com/cuzic/rrr.git
で、ダウンロードしてみてください。

簡単な使い方は以下のとおりです。

1. 準備
 Ruby をインストールする
 Ruby をお持ちでない方はインストールしてください。
  作者は ActiveScriptRuby で検証しています。
 http://arton.hp.infoseek.co.jp/indexj.html

 RD で利用するためのメールアドレスを用意する。
 作者は GMX http://gmx.com/ で用意しました。

 RD の管理者画面で、用意したメールアドレスの設定を行う。
 東芝のホームページも参考にしてください。
http://www3.toshiba.co.jp/hdd-dvd/support/faq/ans/net_mail005b.htm
 http://www.rd-style.com/network/explain2/0405.htm

2.Google App Engine を利用可能にする。
Google App Engine のアカウント登録、SDK のダウンロード、
Python のインストール等が必要です。
GAE アカウントの取得 http://appengine.google.com/
SDK のダウンロード http://code.google.com/intl/ja/appengine/downloads.html

3.GAE の「create an Application」ボタンを使って、RRR 用のアプケーションを作成しましょう。

4.3で作成したアプリケーション名や、RD で設定したパスワード画質、自動削除等の設定を適切に
./ruby/rrr-example.yaml
を編集して設定し
./ruby/rrr.yaml
として保存します。
(注意:以下カレントディレクトリは git clone したときの rrr ディレクトリ)

6.Ruby スクリプトExerb http://exerb.sourceforge.jp/コンパイルします。
rake rrr

7.作成した Ruby スクリプトiEPG ファイルに関連付けします。
FireFox の場合 「ツール」⇒「オプション」⇒「プログラム」でTVPI ファイルに、「他のプログラムを選択」を利用して、6で作成した exe ファイルを指定します。

8.3で作成したアプリーション名を
./gae/app-example.yaml
に入力し、
./gae/app.yaml
として保存します。

9. RD 側に設定したメールアドレスと
Google App Engine の管理者アドレスを
./gae/config-example.yaml
に入力し
./gae/config.yaml
として保存します。

10.Rakefile の定数 GAE_DIR を環境に適した値に設定し、gae ファイルをアップロードします。
rake update

11.すべて適切に行えていた場合、ブラウザからテレビ番組表の画面にアクセスして iEPG ボタンをクリックすると、rrr.exe が起動して GAE の方にメッセージが飛び、そのうち RD(東芝のハードディスクレコーダ)側で設定されます。

(うまくいかない場合)
1.大阪以外在住の方は、./ruby/rrr.yaml の局設定の編集が必要です。いろいろ見ながら、適切に設定してください。

2. http://RDのIP/setup/log.cgi で、RDのログを確認できます。
メールを取得していっているか確認してください。

3. Google App Engine の Data Viewer で、メール送信待ち、メール送信済みのデータを確認できます。

4.RD はメールを取得すると、サーバにメールを残しません。メールがサーバから削除されているか確認しましょう。

5.RD 側で、./app/config.yaml に設定したメールアドレスからの予約が許可されているか、確認してください。

6.RD 側で設定したパスワードと、./ruby/rrr.yaml のパスワードが一致しているか確認してください。

次回は、このスクリプトを作っていく途中の失敗エピソードなどを簡単に紹介するかもです。