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