Ruby Freaks Lounge

第3回Ruby1.9の新機能ひとめぐり(中編):洗練された文法と意味論

第1回では、YARVやFiberなど有名な機能と、配列処理の強化について紹介しました。

今回は、Ruby1.9で導入された新しい文法や意味論を中心に紹介します。

ブロックパラメータ(仮引数)に関する変更

ブロックパラメータのスコープがブロックローカルに

ブロック中の変数はスコープがやや曖昧でした。初出の変数の場合はブロックローカル(ブロックの終了と共に変数も消える)で、ブロックの外ですでに宣言されていた変数の場合はその変数を指す、というルールで、ブロックの外まで見ないと変数のスコープが判断できませんでした。

この問題を軽減するため、1.9ではブロックパラメータは常にブロックローカルであると改定されました。

コード1 ブロックパラメータがブロックローカルに
# ローカル変数xを定義する
x = "bear"

# ブロックローカル変数xを受け取るブロック(ローカル変数のxとは別!)
["dog", "cat", "panda"].each do |x|
  # このブロックの中でxの参照はブロックローカル変数の方を指す
  p x
  break if x == "cat"
end

# ブロックを出るとxはローカル変数の方を指すようになる
p x
図1 コード1の実行結果
# 1.8で実行した場合
$ ruby18 block-param-scope.rb
"dog"
"cat"
"cat"

# 1.9で実行した場合
$ ruby19 block-param-scope.rb
"dog"
"cat"
"bear"

これによって、ary.map{|x| ... } のような簡単なブロックを定義する際に、いちいちブロックパラメータ名の衝突を気にする必要がなくなりました。

ただしこの変更には互換性の問題があります。つまり、変数名を意図的に衝突させていたようなコードが動かなくなります。心配な人は、rubyに-wオプションを与えて実行することでwarningを出してくれますので、チェックするといいでしょう。

図2 -wつきでコード1を実行した結果
$ ruby19 -w block-param-scope.rb
block-param-scope.rb:5: warning: shadowing outer local variable - x
"dog"
"cat"
"bear"

また、ブロックパラメータの後にセミコロンと変数名の列を書くことで、ブロックパラメータ以外にブロックローカルな変数を宣言する機能もあります。

コード2 ブロックローカル変数の宣言
foo = "bear"

# ブロックローカル変数fooを宣言する
3.times do |x; foo|
  # ブロックローカル変数fooは毎回nilで初期化される
  p foo #=> nil
  foo = "dog"
  # ブロックの終了毎にブロックローカル変数fooは消える
end

# 外側のローカル変数は変更されない
p foo  #=> "bear"

ブロックとメソッドの仮引数のルールが統一

ブロックの仮引数にオプショナル引数やrest引数(*つきの引数)などが書けるようになりました。これによってブロックとメソッドの仮引数が同じルールになり、シンプルになりました。

コード3 ブロック仮引数中でオプショナル引数が使える
def foo
  yield 1, 2
  yield 1
end

foo do |x, y = :default|
  p [x, y]
end
図3 コード3の実行結果
[1, 2]
[1, :default]

この機能はAPI設計の幅を広げると思います。また、Module#define_methodによってオプショナル引数やブロックを受け取るメソッドが定義できるようになりました。

ただし、|@foo|のように、ブロックパラメータにインスタンス変数やクラス変数、アクセサメソッドなどを書くことはできなくなったことには注意が必要かもしれません。

実引数に関する改善

複数の実引数展開のサポート(multi split)

配列の各要素を引数としてメソッドを呼び出す記法は1.8にもありました。しかし、メソッド呼び出しあたり1つしか書けないという制約がありました。1.9ではこの制約が取り除かれました。

コード4 複数の実引数展開が可能に
def foo(a, b, c, d)
  p [a, b, c, d]
end

ary = [1, 2]

foo(*ary, *ary)  #=> [1, 2, 1, 2]

この機能は配列リテラルの中でも使えます。テーブルの作成などに便利でしょう。

コード5 複数の実引数展開を持つ配列リテラル
p [*"0".."2", *"7".."9"]  #=> ["0", "1", "2", "7", "8", "9"]

後置必須引数のサポート(post arg)

メソッド定義の仮引数では、オプショナル引数やrest引数(*つきの引数)の後に必須引数を書けるようになりました。

コード6 後置必須引数の例
def foo(a, b, *ary, z)
  p [a, b, ary, z]
end

foo(1, 2, 3, 4, 5) #=> [1, 2, [3, 4], 5]

この機能は、[]=という名前のメソッドが任意個の引数を受け取るときに書きやすくなります。

コード7 []=で代入される値を後置必須引数として受け取る
# []=という名前のメソッドで使用すると便利
class Foo
  def []=(*ary, val)
    p "[#{ ary.join(",") }] <- #{ val }"
  end
end
x = Foo.new
x[1] = 9       #=> "[1] <- 9"
x[1, 2] = 9    #=> "[1, 2] <- 9"
x[1, 2, 3] = 9 #=> "[1, 2, 3] <- 9"

また、多値代入でも使用できます。

コード8 後置必須引数で配列の最後の値を取り出す
first, *, last = [1, 2, 3, 4]
p first   #=> 1
p last    #=> 4

その他細かい話

キーワード引数風表記

キーワード引数とは、引数をキーワードつきで与えることで、引数の順序を自由に変えられる仕組みです。Rubyはキーワード引数を直接サポートしませんが、メソッド呼び出しの最後の引数でキーワード引数のような記述をできるようになりました。

コード9 キーワード引数風のメソッド呼び出し
def set_pixel(h)
  # キーワード引数はシンボルがキーのハッシュとして受け取る
  @field[h[:y]][h[:x]] = h[:color]
end

# 呼び出しはキーワード引数風に
set_pixel(x:30, y:50, color:"red")
set_pixel(color:"black", x:40, y:50)

従来でもブレースのみの省略はできましたが、よりキーワード引数風の表記が可能になりました。

例えば、ファイル I/OのAPIではエンコーディングをハッシュで指定できることが多いため、以下のように書くことができます。

コード10 ファイルのエンコーディングをキーワード引数風に指定する例
File.read("sample.txt", encoding: "UTF-8")

また、この記法はハッシュリテラルの中でも使えます。

コード11 キーワード引数風の表記を用いたハッシュリテラル
p({dog: "bow-wow", cat: "mew"})  #=> {:dog => "bow-wow", :cat => "mew"}

改行可能な箇所の追加

Rubyは改行によって文を区切るため、文や式の途中で下手に改行を行うと文法エラーになります。ただし、明らかに途中であることがわかる場合には改行を入れることができます(括弧でくくられた文の中や、コンマや演算子の直後など⁠⁠。Ruby1.9でもこの方針は同じですが、改行できる箇所が2点増えました。

コード12 追加された改行可能箇所

# 条件演算子(いわゆる三項演算子)の:の直前
x = cond ? foo
         : bar

# メソッド呼び出しのピリオドの直前
 foo
.bar
.baz

ラムダ式の別記法

Rubyにはラムダ式を定義する関数lambdaがありますが、これの別記法として、定義する記法->と呼び出しを行う記法.()が追加されました。

コード13 Proc(ラムダ式)の別記法
# 1を足すクロージャを定義する(lambda {|x| x + 1 }とほぼ同義)
# '->'と'('の間にスペースを入れられないことに注意、括弧は省略可能
add_1 = ->(x) { x + 1 }

# add_1を呼び出す(add_1.call(42)の省略)
p add_1.(42) #=> 43

使うかどうかは個人の好み次第ですが、しばしば話題になるので取り上げました(->はλを右に45度傾けた形らしいです⁠⁠。

また、procがProc.newと同じ意味になったという改善もあります(Proc内でreturnした場合の挙動⁠⁠。

まとめ

以上のようにRuby1.9における文法や意味論の変更は、ルールのシンプル化が中心です。

次回の後編では、落穂ひろいとして、1.9の知っておくとお得な機能を紹介したいと思います。

おすすめ記事

記事・ニュース一覧