2010-03-18
■ [ruby] Hashのデフォルト値にHashを設定しようとしてはまった話
h = {}
ary.each do |x, y, z|
h[x] ||= {}
h[x][y] = z
end
をもっと短くしようとして、
h = Hash.new{ {} }
ary.each do |x, y, z|
h[x][y] = z
end
としたら上手く動かない。あれっ?
デフォルト値が{}じゃなくて数値の場合、例えば
h = Hash.new{ 0 }
ary.each do |x|
h[x] += 1
end
みたいのはちゃんと動くわけです。上のとどう違う?
h[x][y] = z
はどういう動作になるか考えてみよう。
- hにxというキーがある場合: h[x]というハッシュに、(y, z)のペアを保存。
- hにxというキーがない場合: ブロックが呼ばれて、その値が返る。返り値であるハッシュに、(y, z)のペアを保存。
というところで気づきましたが、後者が原因ですね。毎回新しいハッシュが作られて捨てられてしまう!
というわけで
h = Hash.new{|h, k| h[k] = {} }
ary.each do |x, y, z|
h[x][y] = z
end
が正しいコードでした。
まあ素直に冒頭のように書けばいい気もしますが。
■ [ruby] メソッドが最初に呼ばれたときだけ処理を実行する
例えば、重い計算をキャッシュしたい時とか。
begin-endを使うとこう書ける。
def start_server
@started ||= begin
# 重い計算
true
end
end
(以下、余談)
ただなー、偶然@startedという名前が被ってしまう可能性が残ってしまうのが気になる。
こういう場合、Schemeとかだと以下のようにして関数ローカルな状態変数を作れる。
(let ((started #f))
(define (start_server)
(unless started
; ...
(set! started #t))))
Rubyで同じことができないかと思ったが駄目だった。
proc{
started = nil
def start_server
started ||= begin #=> このstartedはfooのローカル変数になってしまう
# ...
true
end
end
}.call
■ [ruby] Ruby 1.9.2のリリースプランが発表 (7月末リリース予定)
- 3月末日 : 仕様の凍結 (この日までに合意が得られなかった仕様・修正は1.9.2に入らない)
- 4月末日 : コードの凍結 (この日までに実装できなかった仕様・修正は1.9.2に入らない)
- 5月末日 : 1.9.2-preview2
- 6月末日 : 1.9.2-rc
- 7月末日 : 1.9.2-p0 (正式リリース)
ということで、8月末のRubyKaigi2010に合わせた感じになったようです。
Ruby 1.9.2の新機能
Ruby 1.9.2は、既に公開されている1.9.1に対し、完成度を高めたバージョンです。特にTimeクラスの2038年問題が解決されたのが嬉しいですね(最近はまったので)。
この他大きめの新機能の一覧が、先日公開されたRubyist Magazine(るびま)で列挙されています。
Ruby 1.9.2の非互換
重要な非互換としては、$LOAD_PATHに"."が入らなくなったことが挙げられます。
例えば main.rb と util.rb が同じディレクトリにあった場合、従来は main.rbから以下のように直接util.rbを読み込むことができました。
require 'util.rb' ...
ですが、1.9.2ではセキュリティ上の理由から、これができなくなりました (Unix で、PATHに"."が含まれないのと同じでしょうか)。
対応としては、とりあえず「./」を明示的に付ければOKです。
require './util.rb'
が、(元のスクリプトもそうですが) これだと別のディレクトリからmain.rbを起動した際にうまく読み込めません。 Ruby 1.9.2では、現在のファイルからの相対パスでrequireするファイルを指定するメソッド、require_relativeが追加されました。
require_relative 'util.rb'
このようにすると、main.rbと同じディレクトリにあるutil.rbを必ず読み込むことができます。
従来は同じことをするのに
require File.expand_path(File.join(File.dirname(__FILE__), "util.rb"))
と書く必要があったので、特にユニットテスト内からtest_helper.rbを読み込んだりする際にはとても便利になりました。

letの中に書いてもtop-level define扱いなんでしたっけ? こういうふうに書いたほうがよさそう。 <br> <br>(define start_server <br> (let ((started #f)) <br> (lambda (start_server) <br> (unless started <br> ... <br> (set! started #t)))))
どうなんでしたっけ?(おい) <br>その方が良さそうですね。ありがとうございます。 <br>(もとのstart_serverは無引数なので(lambda () ..)ですね。)
class Once <br> def run <br> class << self <br> v = rand <br> define_method(:run){v} <br> v <br> end <br> end <br>end <br> <br>a = Once.new <br>b = Once.new <br>p [a, a, b, a, b, a].map(&:run) <br> <br>メソッドとかぶったらどうしよう
define_method使えば一応できるけど、conforming programじゃないね。 <br> <br>proc{ <br> started = nil <br> <br> Object.send(:define_method, :start_server) do <br> started ||= begin <br> # ... <br> true <br> end <br> end <br>}.call <br>