Ruby 1.9 の新機能もうひとめぐり (後編)

ref: http://gihyo.jp/dev/serial/01/ruby/0005 (Ruby Freak Lounge 第 5 回: Ruby 1.9 の新機能ひとめぐり (後編))

の補足など。

鬼車による正規表現の強化: 名前を使った参照

/(?...)/ =~ string によって勝手にローカル変数を定義されるのがいや!という人もいるようです。気持ちはわかります。いやな理由は、たぶん

  • 名前つきでキャプチャ・参照したいだけで、変数まで定義してほしくない。既存の変数名と衝突しそうで怖い。
  • =~ というのはただのメソッド呼び出しなのに、なんで変数を定義することがあるんだ。def_foo(1) で変数 foo が定義されたらキモいだろうに。
  • /(?...)/ =~ string では変数定義して、re = /(?...)/; re =~ string や string =~ /(?...)/ や string[/(?...)/] や /(?#{ "..." })/ =~ string では変数定義しないのがややこしい。

というあたりかと思います。返答としては

  • まあそういう仕様なんで我慢してください。慣れれば便利ですよ。「○○しそうで怖い」でなく「○○して実際にはまった」という経験談なら考慮されるかも。
  • 既存の変数との衝突に関しては、コマンドラインオプション -w をつければ衝突を教えてくれます。
  • =~ はメソッドですが、// =~ string の形で書いたときは実はメソッド呼び出しではないです。Regexp#=~ を再定義しても、// =~ string の挙動は変えられません。そういう意味で、「ただのメソッド」ではないです。(ruby-dev:21050)
  • 変数が定義される条件がちょっとややこしいのは確か。条件式中の .. みたいな裏技機能の雰囲気はありますよね。

という感じ。


最近この機能を意識的に使うようにしてます。コードの可読性やメンテナンス性は上がってるんじゃないかなあ。たぶん。ただ、正規表現が長くなる (≒ 1 行が長くなる) のは書く楽しさを少し犠牲にする。あと、

if /(?<foo>abc...)/ =~ string
  foo
elsif /(?<foo>def...)/ =~ string
  foo
end

case string
when /(?<foo>abc...)/
  foo
when /(?<foo>def...)/
  foo
end

に置き換えられないのが不満だったことが 1 回ある。

鬼車による正規表現の強化: 表現能力の向上

田中哲スペシャルは確かに強力なんですが、役に立つ状況がどのくらいあるかのは正直よくわかりません。括弧の対応にしても、「括弧の対応がとれてるかチェックしたいけれど、パースして構文木にする必要はない」という状況はどのくらいあるかなあ。
ところで今の Ruby の (というか鬼車の) 正規表現の表現能力っていったいどのくらいなんでしょうね。田中哲スペシャルによって文脈自由文法は包含したのかな?

順序付き Hash

2 月の ramaze がこのせいで無限ループしてはまってた。

プロセスの起動

ぼくはわりと最近まで知らなかった機能なんですが、これはほんとーに便利ですよ。従来の system や `` は標準エラー出力すら指定できなくて、ぶっちゃけ使い捨てスクリプトくらいにしか使えなかったですから。open3 だの open4 だの、もう要らない。

Kernel#p が引数を返すように

printf デバッグ (笑) な話ですが、笑えるのはまともなデバッガが用意されてからですかね。まあデバッガあってもぼくは printf デバッグしそうだけど。
そもそも printf デバッグの問題点を考えると

  • 出力先や出力フォーマットが自由に変更できないので不便
  • 出力によってパフォーマンスが下がる
  • 埋め込んだ printf を確実に除去するのが難しい

くらいかと思います。それぞれに対するぼくの返答は

  • Ruby では p を再定義すれば、出力先やフォーマットは変えられる (実際ちょくちょくやります)
  • p は完成品に残すようなメソッドじゃないので問題ない (デバッグ時の p のコストをも気にするようなプログラムなら Ruby を使うのを考え直した方がいいかも)
  • 除去が難しいのはその通り (ruby-dev:38153 が採用されるといいなあ)

というところです。

改行の入らない base64エンコーディング

RFC 4648 では、余計な記号を含んだエンコード文字列をデコードしようとしたら拒絶しなければなりません (MUST) 。なので、改行 (余計な記号) を含んだ文字列を m0 で unpack すると例外になります。ちょっと注意。

p "YWFh\n".unpack("m")   #=> ["aaa"]
p "YWFh\n".unpack("m0")  #=> ArgumentError: invalid base64

記事に書けなかったこと

-w を付けて実行すると、対応する構文のインデントをチェックしてくれます。何が嬉しいかというと、以下のようなバグを書いてしまったとき

class Foo
  def foo
    3.times do |x|
      p x
    end
  edn  # typo のせいで edn というメソッド呼び出しになっている

  def bar
    4.times do |x|
      p x
    end
  end
end

-w なしで実行すると

$ ruby19 -r broken-indent.rb -e ''
ruby19:0:in `require': /home/mame/work/ruby19/ruby/broken-indent.rb:13: syntax error, unexpected $end, expecting keyword_end (SyntaxError)

というように、「最後の行に end がないぞ!」とだけ言われて、どこが悪いのかわかりませんでした。メソッドが増えると結構困る。そこで -w をつけて実行すると

$ ruby19 -w -r broken-indent.rb -e ''
/home/mame/work/ruby19/ruby/broken-indent.rb:13: warning: mismatched indentations at 'end' with 'def' at 2
ruby19:0:in `require': /home/mame/work/ruby19/ruby/broken-indent.rb:13: syntax error, unexpected $end, expecting keyword_end (SyntaxError)

というように、2 行目の def に対応する end のあたりが怪しいことがわかります。
まあ、do/end の対応を教えてくれるエディタを使ってる人には lint 程度の意味しかないですけどね。ぼくも vim 使ってるのでそれほど恩恵はない。
で、なぜ紹介できなかったかというと、require 以外で動かないから。

$ ruby19 -w broken-indent.rb
broken-indent.rb:13: syntax error, unexpected $end, expecting keyword_end

いわゆるひとつのバグなんですよね。まだ直ってない。同じ原因で、coverage.so も動いてない。悲しい。

最後に

Ruby 1.9 についてぼくの知る限りの新機能を書き並べました。この記事が皆さんの役に立つかどうかはわかりませんが、調べてるうちにぼく自身が初めて知った機能が結構あって、少なくとも自分のためにはなりました。よかったよかった。
ちなみに今回書いたのはあくまで「1.9 の新機能」であって、「1.9 で廃止された機能」についてはほとんど触れていません。移行ノウハウは、ほぼ 1.9 しか使ってないぼくにはうまく書けないと思うので、誰か書くといいです。