8

String私の目標は、クラス内のメソッドを、追加の作業を行う他のメソッドに置き換えることです (これは研究プロジェクト用です)。Stringこれは、次のようなクラスでコードを記述することにより、多くのメソッドで機能します。

alias_method :center_OLD, :center
def center(args*)
  r = self.send(*([:center_OLD] + args))
  #do some work here 
  #return something
end

一部のメソッドでは、Proc も処理する必要がありますが、これは問題ありません。ただし、このメソッドを呼び出すと、正規表現の一致から特別なグローバル変数scanが設定されるという副作用があります。文書化されているように、これらの変数はスレッドとメソッドに対してローカルです。

残念ながら、いくつかの Rails コードは変数scanを使用する呼び出しを行います。$&その変数は私のバージョンのscanメソッド内で設定されますが、ローカルであるため、変数を使用する元の呼び出し元には戻りません。

これを回避する方法を知っている人はいますか?問題の説明が必要な場合はお知らせください。

これが役に立ったとすれば、これまでに見た変数の使用はすべて、関数に$&渡された Proc 内にあるscanため、その Proc のバインドを取得できます。ただし、ユーザーはまったく変更できないようです$&ので、それがどのように役立つかはわかりません。

現在のコード

class String
  alias_method :scan_OLD, :scan
  def scan(*args, &b)
    begin

      sargs = [:scan_OLD] + args

      if b.class == Proc
        r = self.send(*sargs, &b)
      else
        r = self.send(*sargs)
      end
      r

    rescue => error
      puts error.backtrace.join("\n")
    end
  end
end

もちろんr、 に戻る前にさらに多くのことを行いますが、これにも問題があります。簡単にするために、これに固執します。テスト ケースとして、次のことを検討してください。

"hello world".scan(/l./) { |x| puts x }

これは、私のバージョンの の有無にかかわらず正常に動作しscanます。「バニラ」Stringクラスでは、これは次と同じものを生成します

"hello world".scan(/l./) { puts $&; }

つまり、"ll" と "ld" を出力し、"hello world" を返します。変更された文字列クラスを使用すると、2 つの空白行が出力され ( $&was であるためnil)、"hello world" が返されます。それが機能するようになれば幸いです。

4

2 に答える 2

4

は、最後の MatchData$&から派生しているため、設定できません。$~ただし、$~設定することができ、それは実際にあなたが望むことをします。コツは、ブロックバインディングで設定することです。

このコードは、Pathname の古い Ruby 実装に触発されています。
(新しいコードは C で書かれており、Ruby フレームローカル変数を気にする必要はありません)

class String
  alias_method :scan_OLD, :scan
  def scan(*args, &block)
    sargs = [:scan_OLD] + args

    if block
      self.send(*sargs) do |*bargs|
        Thread.current[:string_scan_matchdata] = $~
        eval("$~ = Thread.current[:string_scan_matchdata]", block.binding)
        yield(*bargs)
      end
    else
      self.send(*sargs)
    end
  end
end

スレッド ローカル (実際にはファイバー ローカル) 変数の保存は、値を渡すためにのみ使用され、スレッドは最後のセット以外の値を読み取ることはないため、不要に思えます。おそらく、元の値を復元するためにそこにあるのでしょう (おそらくnil、変数が存在しなかったためです)。

スレッド ローカルをまったく回避する 1 つの方法は$~、ラムダとしてのセッターを作成することです (ただし、呼び出しごとにラムダを作成します)。

self.send(*sargs) do |*bargs|
  eval("lambda { |m| $~ = m }", block.binding).call($~)
  yield(*bargs)
end

これらのいずれでも、あなたの例は機能します!

于 2013-11-01T15:15:15.293 に答える
1

問題をシミュレートする簡単なコードを書きました。

"hello world".scan(/l./) { |x| puts x }
"hello world".scan(/l./) { puts $&; }

class String
   alias_method :origin_scan, :scan

   def scan *args, &b
      args.unshift :origin_scan
      @mutex ||= Mutex.new
      begin
         self.send *args do |a|
            break if !block_given?
            @mutex.synchronize do
               p $& 
               case b.arity
               when 0
                  b.call
               when 1
                  b.call a
               end
            end
         end
      rescue => error
         p error, error.backtrace.join("\n")
      end
   end
end

"hello world".scan(/l./) { |x| puts x }
"hello world".scan(/l./) { puts $& }

そして、以下を発見。変数の包含の変更は$&、関数内:call、つまり 3 番目のステップ:call $&で有効な値を含む前になりましたが、ブロック内では無効になります。:callおそらく、関数が:scanローカル状態にアクセスできないため、プロセス/スレッドコンテキストの変更中の特異点スタックと変数の復元が原因だと思います。

1 つ目は、特定の関数の再定義でグローバル変数を使用しないようにすることです。2 つ目は、Ruby のソースをより深く掘り下げることです。

于 2013-11-01T07:28:31.773 に答える