読者です 読者をやめる 読者になる 読者になる

ちなみに

火曜日の空は僕を押しつぶした。

rbenv global 2.1.0-preview2 した話

この記事は 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

Arrayto_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.

includeextend 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 以外ないでしょということみたいですが、いまいち理解できていません。

Bug #8342: IO.readlines ignores Encoding.default_internal if Encoding.default_external is ASCII-8BIT - ruby-trunk - Ruby Issue Tracking System

eval 族のメソッドが呼び出し元のスコープ情報をコピーするようになりました。

Kernel#evalKernel#instance_evalModule#module_evalprivatepublic などを呼び出しても、呼び出し元には影響がなくなったようです。
いまいち分かってない。

  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 さんです。