この記事は Ruby Advent Calendar 2013 の 9 日目の記事です。
前日は aoitaku さんの [Ruby] かっこつけないで でした。
はじめに
2.1.0-preview2 が出たので、普段使いの Ruby を 2.1 にしてみました。 (inspired by rbenv global 2.0.0-rc1 した話 - sojourn)
インストール
$ brew install readline # if you need $ (cd ~/.rbenv/plugins/ruby-build && git pull origin master) $ CONFIGURE_OPTS="--with-readline-dir=`brew --prefix readline` --enable-bundled-libyaml" rbenv install 2.1.0-preview2 $ rbenv global 2.1.0-preview2 $ ruby -v ruby 2.1.0dev (2013-11-23 trunk 43807) [x86_64-darwin12.0]
(see also: https://github.com/sstephenson/ruby-build/blob/master/share/ruby-build/2.1.0-preview2 )
気になった変更
NEWSを読んで気になったところをピックアップしました。理解できていないところは都合よく気にならなかったことになっています。
他にも公式のリリースアナウンスに載っている笹田さんの 資料 や @konstantinhaase さんの 記事 が参考になります
変更内容の意図などを追うのに nagachika さんの PB memo が大変参考になりました。
言語レベルの変更
GC が RGenGC に置き換わりました
2.1 の大きな変更点ですが、よく理解できていないため割愛。 笹田さんの 資料 が詳しいです。
Required keyword arguments (日本語ではなんと言えば)
2.0 で入ったキーワード引数はデフォルト値が必須となっていました。2.1からはデフォルト値なしのキーワード引数が可能となりました。
# Keyword arguments def hoge(a: 1) puts a end hoge a: 5 #=> 5 hoge #=> 1 def piyo(a:) puts a end piyo a: 5 #=> 5 piyo #=> ArgumentError: missing keyword: a
Added suffixes for integer and float literals: 'r', 'i', and 'ri'.
数値リテラルに新しいsuffixが追加されました。 個人的にはいまのところ使うようなケースがないのでさらっと。
0.5r #=> (1/2) 0.5i #=> (0+0.5i) 0.5ri #=> (0+(1/2)*i)
def-expr now returns the symbol of its name instead of nil.
メソッドを定義したときに 2.0 までは nil
が返ってきていましたが、2.1 からはメソッド名がシンボルで返ってくるようになりました。
いまのところ一番便利だと思っているのは、private def
という書き方が出来るようになったところです。
大き目のクラスを定義しているときに、関係している public メソッドと private メソッドを近くに書こうとすると、メソッド定義したあとに private :meth
と書くか、private
→ 定義 → public
とする必要がありました。
この書き方は視認性が悪いし、気持ちが悪いので結局 private メソッドはクラス定義末尾にまとめてしまうのですが、2.1 からは直感的に書けるようになりました。
class A private def hoge p :private_method end end A.new.hoge #=> private method `hoge' called for #<A:0x007fc7f2a276e8> (NoMethodError)
こういうのも動きました。どうしてもメソッド定義がしたいけど使う予定はないときにどうぞ。
class A remove_method (def hoge; end) end
コアクラスの変更点
Array#to_h / Enumerable#to_h
Array
に to_h
が追加されました。キーと値の配列の配列を to_h
すると Hash に変換してくれるようになりました。
また Enumerable
にも同等のメソッドが追加されているので Enumerable#lazy
で配列を作って to_h
という書き方が可能です。
User = Struct.new(:id, :name) users = %w[John Eric].map.with_index {|name, idx| User.new(idx+1, name) } users.map {|user| [user.id, user.name] }.to_h #=> {1=>"John", 2=>"Eric"} # 年齢(age)と血液型(blood_type)と年収(income)を追加して大量のユーザを作成 users.lazy. select {|user| user.blood_type == :o }. select {|user| user.age > 20 }. select {|user| user.name ~= /\AJ/ }. map {|user| [user.id, user.income] }. to_h
Binding#local_variable_xxx
ついにローカル変数まで外部から操作できるようになってしまいました。
過去に「代入文を使わずにローカル変数aを定義できるか」という問題があって、当時はブロック引数にして上書く方法を使っていたけれど、ブロック引数がブロックローカルになってしまったので使えなくなっていました。
2.1 からは楽勝で解決ですね。
TOPLEVEL_BINDING.local_variable_set(:a, 5) p a #=> 5
ローカル変数の定義には Binding#local_variable_set
、取得には Binding#local_variable_get
が使えます。
存在していないローカル変数の値を取得しようすると NameError
になるので、Binding#local_variable_defined?
で確認してから取得します。
a = 0 counter = -> { a += 1 } p counter.() #=> 1 p counter.() #=> 2 p counter.() #=> 3 # リセット counter.binding.local_variable_set(:a, 0) p counter.() #=> 1 p counter.() #=> 2 p counter.() #=> 3 # counter.binding.local_variable_get(:b) #=> NameError p counter.binding.local_variable_defined?(:a) #=> true p counter.binding.local_variable_defined?(:b) #=> false
詳しくは ruby-trunk-changes r42445 - r42473 - PB memo の ko1:r42464 2013-08-09 18:51:00 +0900 の項目を参照してください。
※ 12月9日19時ごろ追記
@sora_h さんからの指摘で、Binding#local_variable_xxx
の存在意義は、予約語と被るキーワード引数が使いたいときのものだとのことでした。はてブのコメントで予約語と被るは良くないとの意見もありましたが、個人的にはそれで可読性が上るなら良いのではないかと思います。
たとえば class 付きのspan
タグを生成するヘルパーとかはどうか。
def span(body, class:) html_class = binding.local_variable_get(:class) %q!<span class="%s">%s</span>! % [html_class, body] end puts span 'cat', class: :cute #=> <span class="cute">cat</span>
Exception#cause
例外がチェインしたときに元の例外を辿れるようになりました。詳しくは @znz さんの記事を参照してください。
Kernel#singleton_method Module#singleton_method?
特異メソッドのMethodオブジェクトを取り出したり、特異メソッドかどうかを問い合わせできるようになりました。
Module#include and Module#prepend are now public methods.
include
と extend prepend
が private じゃなくなりました。何がうれしいのかと言うと以下のように書く必要がなくなったことです。
A.__send__(:include, Plugin::InstanceMethods) A.__send__(:prepend, Plugin::ClassMethods)
これからは直感的にこう書けます。
A.include Plugin::InstanceMethods A.prepend Plugin::ClassMethods
※ 12月9日 19時ごろ追記
@a_matsuda さんからコメントで指摘していただいて気付いたのですが、public になったのは extend
ではなく、2.0 から追加された prepend
でした。
extend は元々 public でした。
Module#prepend
存在忘れてた…。
* Module#include and Module#prepend are now public methods.
Refinements が exmerimental じゃなくなりました
2.0 で Experimental な機能としてリリースされた Refinements が正式に組込まれました。
refine
で記述した拡張を using
を呼んだコンテキストに閉じ込めることができる機能で、モンキーパッチを当てるときに本当に必要な部分だけに影響範囲を限定するような使い方ができます。
Refinements について詳しくは 前田さんの Rubyist Magazine - Refinementsとは何だったのか などを参照してください。
module Cheerful refine String do def bang "#{self}!" end end end class A using Cheerful def greet(to:) puts "Hello #{to}".bang end end A.new.greet to: 'Ruby' #=> Hello Ruby! puts 'Ruby'.bang # undefined method `bang' for "Ruby":String (NoMethodError)
ところで eval
族の中で refinements を有効にする機能は入らなかったのでしょうか。
(入らなかったようです - shugo:r38262 2012-12-08 00:49:21 +0900 via ruby-trunk-changes r38261 - r38276 - PB memo)
String#scrub and String#scrub! verify and fix invalid byte sequence.
@sonots さんの sonots:blog : Ruby 2.1.0 に追加される不正なバイト列を除去する String#scrub の紹介 を参照してください。
All symbols are now frozen.
2.0 まではシンボルにインスタンス変数を設定することができたりしていましたが、これができなくなりました。
# 2.0 :a.instance_variable_set(:@a, 5) p :a.instance_variable_get(:@a) #=> 5 p :a.frozen? #=> false # 2.1 :a.instance_variable_set(:@a, 5) #=> can't modify frozen Symbol (RuntimeError) p :a.frozen? #=> true
そういえば、2.1-preview1 では 'hoge'f
みたいに書くと 'hoge' が freeze されるシンタックスが入っていましたが preview2 では消えたようです。
代りに freeze したときに特別扱いされるようになって、同じオブジェクトIDを返すように変更されています。
また同時に Hash のキーに文字列を指定したときに、freeze される仕様になったようです。
'a'.freeze.object_id => 70276027234660 'a'.freeze.object_id => 70276027234660
非互換な変更
今回もいくつか非互換な変更が入っています。アップデートの際にご注意ください。
IO.open で external encoding が ASCII-8BIT の場合は internal encoding を無視するように変更
バイナリだから ASCII-8BIT 以外ないでしょということみたいですが、いまいち理解できていません。
eval 族のメソッドが呼び出し元のスコープ情報をコピーするようになりました。
Kernel#eval
、Kernel#instance_eval
、 Module#module_eval
で private
や public
などを呼び出しても、呼び出し元には影響がなくなったようです。
いまいち分かってない。
class Foo eval "private" def foo end end Foo.private_instance_methods.grep(/foo/) #=> []
Kernel#untrusted?, untrust, and trust が deprecated になりました
Kernel#tainted?, taint, untaint と同じだからとのこと。 $VERBOSE を true にすると警告がでるようになります。
Module#ancestors が特異クラスを返すようになりました
# 2.0 class A; p (class << self; self end).ancestors end #=> [Class, Module, Object, Kernel, BasicObject] # 2.1 class A; p (class << self; self end).ancestors end #=> [#<Class:A>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
Module#define_method と Object#define_singleton_method がシンボルを返すように変更
もともとはメソッドそのものを返していたのですが、シンボルを返すように変更になっています。
class A a = define_method(:a) { ... } a.call end
定義したメソッドを保存しておいて使っているようなコードは、以下のように変更する必要があります。
class A a = define_method(:a) { p :a } public_instance_method(:a).bind(A.new).call end
これもっと良い書き方がある気がする…。
Numeric#quo
レシーバが #to_r を実装していない場合は TypeError になるようになりました。 to_r を実装していない場合とは…。 mathn による影響がどうたらということですが理解できませんでした。
ruby-trunk-changes r41110 - r41150 - PB memo
lambda proc からの return は必ず proc からの return になるようになった
proc オブジェクトには2種類あって、-> {}
や lambda {}
で作った lambda proc と proc {}
で作ったりメソッドに渡すブロックのようないわゆる普通の proc です。
lambda proc は少し特殊で、引数チェックがあったり、lambda proc の中で return
をすると lambda proc から復帰するようになっています。
もとより lambda proc の return
は、lambda proc からの return になる仕様だったのですが、yield
で起動したときだけ、呼び出したメソッドからの return
になっていたようで、今回はどちらも lambda proc からの return
になるように変更されています。
以下のコードを 2.0 で実行したら LocalJumpError
になりました。
def a(&block) p :start yield p :end end a &lambda { return } #=> :start #=> :end
詳しくは ruby-trunk-changes r42445 - r42473 - PB memo を参照してください。
おわりに
Ruby 2.1.0 のリリースは 12月25日 です。わざわざ寒空の下でデートなんかせずに Ruby をコンパイルして暖まりましょう。
Ruby Advent Calendar 2013 10日目の明日は awakia さんです。