13F

備忘録

Rubyの制御構造

2007-11-26 21:11:27 | Ruby
Rubyは色々な状況でいろんな文を書くことができるので、リファレンスに載ってないような場合にどうなるのかよくわからないことがある…。たとえば制御構造。
def m1
  yield
end

def m2(&block)
  block.call
end

def m3(proc)
  proc.call
end

m1 { return :block }             # LocalJumpError: unexpected return
m1(&(Proc.new { return :proc })) # LocalJumpError: unexpected return
m1(&(lambda { return :lambda })) # LocalJumpError: unexpected return *1
m2 { return :block }             # LocalJumpError: unexpected return
m2(&(Proc.new { return :proc })) # LocalJumpError: unexpected return
m2(&(lambda { return :lambda })) # => :lambda *2
m3(Proc.new { return :proc })    # LocalJumpError: unexpected return
m3(lambda { return :lambda })    # => :lambda

m1 { break :block }              # => :block
m1(&(Proc.new { break :proc }))  # LocalJumpError: break from proc-closure *3
m1(&(lambda { break :lambda }))  # LocalJumpError: break from proc-closure
m2 { break :block }              # => :block
m2(&(Proc.new { break :proc }))  # LocalJumpError: break from proc-closure
m2(&(lambda { break :lambda }))  # => :lambda *4
m3(Proc.new { break :proc })     # LocalJumpError: break from proc-closure
m3(lambda { break :lambda })     # => :lambda

  • *1 がダメで *2 がOKなのがよくわからない……
  • *3 とか *4 は、Rubyリファレンスマニュアルの2007/10/14更新版 ではOKとされているっぽいけど、実際はダメみたい。
  • next はどのパターンでもOKみたいなので、next使うのがいいのかな?

Ruby: joinと無限ネスト配列のつづき

2007-11-17 00:01:14 | Weblog
ブロックつき join を試していて問題になった配列の無限ネスト関係だけど、元の join からして何となく良くわからない。
a = [1,2,3]; a.push a
a.inspect
=> [1, 2, 3, [...]]
b = [1,2,3]; c = [4,5,6]; b.push c; c.push b
b.inspect
=> [1, 2, 3, [4, 5, 6, [...]]]

これはわかりやすい(自分自身が再度出てきたら '[...]' になる)。ところが join だと、
a.join
=> "123123[...]"
b.join
=> "123456[...]"

なんで前者は "123[...]" じゃないのだろうか? 連結のアルゴリズムが前回自分で実装したようなものだから、という理由かもしれないけど、他人に説明しようとしたときどう言えばいいのか難しい。

というか、そもそも '[...]' が出てくるなんて機能には誰も期待していないということなのかも……。

Rubyでブロックつきjoinのつづき

2007-11-16 23:47:12 | Weblog
前にやってみたブロックをサポートする join だが、「事前にmapで変換すればいいだけじゃね?」という心の声が聞こえたのでやってみた。
[1,2,3].map {|x| x+1}.join('--')
=> "2--3--4"

なーんだ、再定義なんて悪辣なことしなくてもこれで済むんじゃん?と思ったがまだ早い。join の仕様としては「要素がまた配列であれば再帰的に (同じ sep を利用して) join した文字列を連結します。」(リファレンスマニュアルより)とのことなので、配列を含む配列も連結できなければ。
[1,2,3,[4,5,6]].map {|x| x+1}.join('--')
=> TypeError: can't convert Fixnum into Array

ですよねー。

これではもとの仕様を満たせない。map のブロックの中で Array かどうか判定するなんてことをはじめると本末転倒だけどどうしよう? ああ、でも都合よく多次元配列を一次元に直してくれる flatten なんてのがあるじゃないか。
[1,2,3,[4,5,6]].flatten.map {|x| x+1}.join('--')
=> "2--3--4--5--6--7"

できた!

……と思うのはまだ早い。リファレンスマニュアルには、さらにこんなことが書いてある。「配列要素が自身を含むような無限にネストした配列に対しては、以下のような結果になります。」
ary = [1,2,3]; ary.push ary
p ary.join      # => "123123[...]"

無限ネストの場合、途中で '[...]' なんていう良くわからない固定値が出力されなければならないのだ。flatten はこんなことをしてくれるだろうか?
a = [1,2,3]; a.push a
a.flatten.map {|x| x+1}.join('--')
=> ArgumentError: tried to flatten re

ああー、やっぱり無限ネストは想定の範囲外のようで。

結局、あんまりいい方法はなさそうだ。無駄な時間を過ごしてしまった。

Rubyを勉強中 2

2007-11-12 21:57:29 | Weblog
なんとなく以下のようなものを書いてハマるの巻。
class Foo
  def method_missing(name, *args)
    # なぜかマッチしない
    if name =~ /Aadd_(.*)/ && args.length == 1
      auto_push($1, args[0])
    end
  end

  def auto_push(name, arg)
    v_name = "@#{name}"
    array = instance_variable_get(v_name)
    unless array
      array = []
      instance_variable_set(v_name, array)
    end
    array.push(arg)
  end
end

x = Foo.new
x.add_bar('bar!')
p x

4行目の単純な正規表現がマッチしないため auto_push が呼ばれず困惑。"puts name" とか挿入してみてもきちんと "add_bar" と出てくるため、なんでだよーと思ってしまった。

よくドキュメントを見ると method_missing の第一引数は Symbol で、そのままでは正規表現にマッチしないのだった……。
:add_bar =~ /bar/  # マッチしない
100 =~ /0/         # こういうのも同じ

はじめから相手にする気がないのにTypeErrorとかにしないのは何故だろう? できるだけ例外は吐かないようにするポリシーなのかな。

教訓としては、
  • ドキュメントをきちんと読め。
  • 「printfデバッグ」をやるときは puts ではなく p を使う?(p なら :add_bar のようにシンボルとして表示された)


追記:正規表現のマッチングは、Rubyでは以下のように書くのが正しくなったらしい。
/bar/ =~ :add_bar
/0/ =~ 100

これだと期待通り(?)TypeError になる。

Rubyを勉強中

2007-11-12 21:36:40 | Weblog
12/1の試験を受けることになったので真面目に勉強することにした。

いろいろいじっていて、Array#join が
[1,2,3].join('--') {|x| x+1}
=> "2--3--4"

のように変換のためのブロックをとってほしいなあ、と思ったので拡張してみた。
class Array
  alias :join_org :join

  def join(sep=$,, &block)
    if block
      join_with_block(sep || '', block, [])
    else
      join_org(sep)
    end
  end

protected
  def join_with_block(sep, block, stack)
    result = ''
    first = true

    each do |elem|
      result += sep unless first

      if elem.kind_of? Array
        if stack.include? elem
          result += '[...]'
        else
          stack.push(self)
          result += elem.join_with_block(sep, block, stack)
          stack.pop
        end
      else
        result += block.call(elem).to_s
      end

      first = false
    end

    result
  end
end


感想としては、
  • メソッドの再定義をして、その中で元のメソッドを使いたい場合はaliasするしかないのだろうか? 継承時のsuperのように元のメソッドを呼べるとうれしいんだけど……(不必要な「名前」が増えるのがきもちわるい)
  • そもそも、上のようなことが必要なのか(既存のクラスにもっといい方法があったりして)。
  • オープンクラスってすばらしい。

まだまだ東方Projectプレイ中

2007-11-12 21:16:22 | Weblog
まだ断続的にプレイ中。

風神録は魔理沙Cで全作品中初のHardクリア。これは感動した。風神録はボムがたくさん使えるためにボスの弾幕はほとんど飛ばすことができるので、道中をパターン化できればわたしのような素人でもクリアが可能にできているようだ。

あとははじめてプレイしたときからの目標だった魔理沙(恋符)での妖々夢Normalを達成した。風神録Hardで鍛えたおかげで以前と比べて弾幕が多少「見える」ようになったみたい。

次の目標は魔理沙で妖々夢のHardをクリアすること。こっちはまだ2回くらいコンティニューが必要なので、時間がかかりそう。