前提
Rubyのgsub!でガンガン文章を置換していく、というか削除していくことってあると思います。
でも、その削除した部分、本当に削除したかった部分?って思うこともあるはず。正規表現に自信がないほど確認したいのが人情です。gsub!のブロック記法を使っていちいち出力していけばいいですが、それも面倒だよね……。
というわけで、メタプログラミングを使って、filename
を読み込ませてgsub!で置換した部分だけ抜き出し、filename_deleted
という名前で出力するようにしてみました。
このサンプルコードには銀河鉄道の夜の第1章、午后の授業を読み込ませてあげてください。使い方は、ruby black_magic.rb filename
です。
サンプルコード
# gsub!のフック用のオープンクラス
# ここでは、gsub!が呼ばれる度に置換した部分を
# 別ファイルに書き込むための下処理を行なっている
class String
alias_method :org_gsub!, :gsub!
def gsub!(arg1, arg2)
org_gsub!(arg1){|str|
$i << arg1.to_s << "\n" << str << "\n"
arg2
}
end
end
# 各種変数の宣言
f = ""
h = ""
$i = ""
file_path = ARGV[0]
IO.foreach(file_path) do |line|
line.gsub!(/天の川/, "Milky Way")
line.gsub!(/星/, "Star")
h << line
end
IO.write(file_path + "_processed", h)
IO.write(file_path + "_deleted", $i)
解説
これはRubyの「オープンクラス」という機能を使っています。
この機能、この名前では公式の日本語リファレンスには載っていません。名前は出ていなくても機能自体の紹介はあるのですが、以下の通りです。
クラスが既に定義されているとき、さらに同じクラス名でクラス定義を書くとク ラスの定義の追加になります。 ただし、元のクラスと異なるスーパークラスを指定すると TypeError が発生します。
https://docs.ruby-lang.org/ja/2.5.0/doc/spec=2fdef.html
これだけ聞いてもなんだかピンときません。
英語の方のリファレンスにはもう少し詳しい解説があります。
Due to Ruby's open classes you can redefine or add functionality to existing classes. This is called a “monkey patch”. Unfortunately the scope of such changes is global. All users of the monkey-patched class see the same changes. This can cause unintended side-effects or breakage of programs.
https://docs.ruby-lang.org/en/2.5.0/syntax/refinements_rdoc.html
軽く訳すと以下のような感じです。
Rubyのオープンクラスにより、既存のクラスを再定義したり関数を追加したりできます。これはモンキーパッチと呼ばれています。残念なことに、これらの変更のスコープはグローバルです。モンキーパッチで変更されたクラスのすべてのユーザーが同じ変更の影響を受けます。これは意図していない副作用やプログラムの破壊を起こすことがあります。
というわけで、サンプルコードで書いたのはモンキーパッチです。黒魔術です。この英語のリファレンスではそれでもこのモンキーパッチを少しでも安全に使うための工夫を解説してますが、私のコードにはそれはありません。
書き捨てコードなのでいいかな、みたいな判断ですが、ちゃんとしたプロダクトでオープンクラスを使う必要に迫られた時は上記英語リファレンスに載っているモジュールを使う方法で少しでも影響範囲を狭めてください。
関数の再定義
サンプルコードではStringクラスのgsub!メソッドを再定義しています。順に説明します。
まず、普通にクラス定義をする方法と同じ文法で、クラス名を書くところにString
を書きます。これでStringクラスの定義を追加しますという意味になります。
次に、gsub!
のエイリアスとしてorg_gsub!
というメソッドを定義します。このorg_gsub!
というメソッドが本来のgsub!
メソッドと同じ挙動をします。
今度は、gsub!
メソッドを再定義します。def gsub!(arg1, arg2)
の部分です。
org_gsub!
が本来のgsub!
メソッドですので、これを使ってgsub!
との差分を書いていきます。
arg1
には正規表現が入っています。それで取り出せた文字列がstr
に入ります。そして、グローバル変数$i
に正規表現とそれによって取り出せた文字列を詰めています。こうすることで、どの文字列がどの正規表現に引っかかったのかがわかりやすくなります。最後に、置換したい文字列のarg2
を評価させて、gsub!の再定義は終わりです。
そして$iをファイルに書き込んで、やりたいことは完了です。
注意点
ここで再定義したgsub!は、ブロック形式では使えません。そのように再定義してはいないからです。
まとめ
というわけで、黒魔術入門記事でした。偉そうに言えた身分ではありませんが、黒魔術は基本非推奨です。メリットとデメリットを冷静に比べてメリットを取るべきだと判断できた時にだけ使うようにしましょう。