トップ «前の日記(2010-03-16) 最新 次の日記(2010-03-19)» 編集

Route 477



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を読み込んだりする際にはとても便利になりました。

本日のツッコミ(全4件) [ツッコミを入れる]
rui (2010-03-18 15:39)

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)))))

yhara (2010-03-19 19:32)

どうなんでしたっけ?(おい)<br>その方が良さそうですね。ありがとうございます。<br>(もとのstart_serverは無引数なので(lambda () ..)ですね。)

nanki (2010-03-31 19:27)

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>メソッドとかぶったらどうしよう

shugo (2010-04-02 14:09)

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