Ruby 2.7で発生する「プロを目指す人のためのRuby入門」との差異について

## はじめに

2019年12月25日に、Rubyの新しいバージョンであるRuby 2.7がリリースされました。
一方、2017年11月に出版した書籍「[プロを目指す人のためのRuby入門](https://gihyo.jp/book/2017/978-4-7741-9397-7)」（通称・チェリー本。以下、本書）は執筆当時最新だったRuby 2.4.1を対象にしています。

本書は紙の本であるため、簡単に内容をアップデートすることができません。しかし、何もしないとどんどん内容が古くなってしまい、「本の通りやってみたけど、今使っているRubyとなんか動きが違う」ということになってしまいます。

そこで新しいRubyのバージョンがリリースされて、本書の説明と異なる部分が出てきたときは、毎回ネット上でその差異を説明するようにしています。その説明を読めば、動きが違う部分があってもきっと落ち着いて対処できるはず、という算段です。

というわけで、この記事ではRuby 2.7で発生する「プロを目指す人のためのRuby入門」との差異について説明します。

なお、本文に出てくる章番号や項番号は、書籍の中で使われている番号です。

### 参考: もう読みましたか？Ruby 2.5と2.6の差異はこちら

Ruby 2.5と2.6で発生する差異については以下の記事にまとめてあります。

- [Ruby 2\.5で発生する「プロを目指す人のためのRuby入門」との差異について \- Qiita](https://qiita.com/jnchito/items/761db22aec84910fe116)
- [Ruby 2\.6で発生する「プロを目指す人のためのRuby入門」との差異について \- Qiita](https://qiita.com/jnchito/items/a2a833e3bd80a452fd61)

上記の記事と重複する内容はこの記事では説明しません。
ですので、まだ読まれていない方は先に上の記事を読んでから、この記事に戻ってくることをお勧めします。

それでは以下が本編です。

## 本文の説明と実行結果が異なるもの

以下で説明する内容は、Ruby 2.7で実行した場合に本書（つまりRuby 2.4）で説明している内容と実行結果が異なる部分です。

### キーワード引数の代わりにハッシュを渡すと警告が出るようになった（5.4.3）

Ruby 2.7ではキーワード引数の代わりにハッシュを渡すと警告が出るようになりました。
そのため、5.4.3項で説明した以下のコードは、Ruby 2.7で実行すると警告が出ます。

```ruby
def buy_burger(menu, drink:, potato:)
  # 省略
end

params = { drink: true, potato: false }

# キーワード引数の代わりにハッシュを渡しているので警告が出る
buy_burger('fish', params)
#=> warning: The last argument is used as keyword parameters; maybe ** should be added to the call
#=> warning: The called method `buy_burger' is defined here
```

この場合は引数のハッシュオブジェクトに`**`を付けると警告が出なくなります。

```ruby
# **を付けて呼び出すと警告が出ない
buy_burger('fish', **params)
```

警告が出ているコードは、今後リリースが予定されているRuby 3では動かなくなります。
一方、`**`を付けたコードはRuby 3でも動作可能です。

Ruby 2.7におけるキーワード引数の仕様変更については、以下の記事にまとめているので、詳しく知りたい方はこちらをご覧ください。

- [サンプルコードでわかる！Ruby 2\.7の主な新機能と変更点 Part 2 \- キーワード引数に関する仕様変更 \- Qiita](https://qiita.com/jnchito/items/c15ac23791e0320e0fc2)

### selfを付けてprivateメソッドが呼び出せるようになった（7.7.2）

Ruby 2.7ではprivateメソッドもselfを付けて呼び出せるようになりました。
そのため、「nameメソッドはprivateなのでselfを付けるとエラーになる」と説明していた以下のサンプルコードも、Ruby 2.7では動作可能です。

```ruby
class User
  def hello
    # Ruby 2.7ではself.nameでも動作可能
    "Hello, I am #{self.name}."
  end

  private

  def name
    'Alice'
  end
end
user = User.new

# self.nameが動作可能になったので、helloメソッドを呼んでもエラーが起きない
user.hello
#=> "Hello, I am Alice."
```

このように、privateメソッドの仕様が変わったため、7.7.2項で説明していた、

> ですが、厳密に言うとprivateメソッドは「レシーバを指定して呼び出すことができないメソッド」になります。

から後ろの説明は、Ruby 2.7ではすべて該当しなくなります。

### KernelモジュールのopenメソッドでURLを開くと警告が出る（第12章のコラム）

第12章に掲載した「requireの単位はライブラリ」というコラムのサンプルコードで、open-uriライブラリをrequireしたあとに、openメソッド（Kernelモジュールのopenメソッド）を使ってURLを開くコードがありますが、Ruby 2.7ではこのコードを実行すると警告が出ます。

```ruby
require 'open-uri'

# openメソッド（Kernelモジュールのopenメソッド）を使うと警告が出る
open 'http://example.com'
#=> warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open
```

上のコードは`open`を`URI.open`に変えると、警告が出なくなります。

```ruby
require 'open-uri'

# URI.openにすれば警告は出ない
URI.open 'http://example.com'
```

該当コラムの「たとえば、open-uriというライブラリをrequireすると」から後ろの部分は、Ruby 2.7では以下のように読み替えてください。

> たとえば、open-uriというライブラリを`require`すると、URIモジュールに`open`メソッドが追加され（厳密にはprivateメソッドがpublicメソッドになり、さらに機能拡張される）、URLを開けるようになります（新しいクラスが使えるようになるわけではありません）。

```ruby
# URIモジュールのopenメソッドは、もともとprivateメソッドなので呼び出せない
URI.open 'http://example.com'
#=> NoMethodError (private method `open' called for URI:Module)

# ただし、open-uriライブラリをrequireするとopenメソッドが使えるようになる
require 'open-uri'
URI.open 'http://example.com'
#=> #<StringIO:0x007fe8cc105d08 @base_uri=#<URI::HTTP http://...
```

open-uriライブラリの詳しい内容については、以下の公式リファレンスを参照してください。

- [library open\-uri \(Ruby 2\.6\.0 リファレンスマニュアル\)](https://docs.ruby-lang.org/ja/latest/library/open=2duri.html)

## Ruby 2.7で追加された新しい構文

このほかにもRuby 2.7では、本書執筆時点にはなかった新しい構文がいくつか追加されています。

### ブロックの仮引数を連番で指定できるようになった（4章）

Ruby 2.7ではブロックの仮引数として番号指定パラメータ（numbered parameter）が導入されました。
これにより、`|s|` のように明示的に引数名を指定する代わりに、`_1` のような連番でブロックの仮引数を受け取ることができます。

```ruby
# 番号指定パラメータを使わない場合
%w(1 20 300).map { |s| s.rjust(3, '0') }
#=> ["001", "020", "300"]

# 番号指定パラメータを使う場合
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["001", "020", "300"]
```

```ruby
# 番号指定パラメータを使わない場合
[1, 2, 3, 4].inject(0) { |memo, n| memo + n }
#=> 10

# 番号指定パラメータを使う場合
[1, 2, 3, 4].inject(0) { _1 + _2 }
#=> 10
```

ただし、むやみやたらに番号指定パラメータを使うと、コードの可読性を損なう恐れがあるため、「用量と用法を守って正しく使いましょう」というのが筆者の見解です。

番号指定パラメータに関する詳しい内容はについては、以下の記事をご覧ください。

- [サンプルコードでわかる！Ruby 2\.7の主な新機能と変更点 Part 1 \- 番号指定パラメータ（numbered parameter） \- Qiita](https://qiita.com/jnchito/items/79f0172e60f237e2c542)

### 範囲（Range）リテラルで開始値を省略できるようになった（4.5）

Ruby 2.7では次のように、開始値を省略できるようになりました。（開始値省略範囲式、beginless range)
ただし、Ruby 2.7の時点ではまだ試験的な機能です。

```ruby
numbers = [10, 20, 30, 40, 50]

# beginless rangeを使って配列の最初の3要素を取得する
numbers[..2]
#=> [10, 20, 30]

# 従来通り次のように書いても同じ
numbers[0..2]
#=> [10, 20, 30]
```

Ruby 2.6で導入されたendless rangeと組み合わせると次のようなコードも書けます。

```ruby
n = -5
ret =
  case n
  when ..-1
    'minus'
  when 1..
    'plus'
  else
    'zero'
  end
ret
#=> minus
```

### 改行を伴うメソッドチェーンにコメント行を挟み込めるようになった（10章のコラム）

Ruby 2.7では、以下のように改行を伴うメソッドチェーンにコメント行を挟み込めるようになりました（メソッドチェーンについては10章のコラム「メソッドチェーンを使ってコードを書く」で説明しています）。

```ruby
200
  # 次の値（つまり201）を得る
  .then(&:succ)
  # 文字列に変換する
  .then(&:to_s)
  # 逆順にする
  .then(&:reverse)
#=> "102"
```

ドットが後に来るパターンでも大丈夫です。

```ruby
200.
  # 次の値（つまり201）を得る
  then(&:succ).
  # 文字列に変換する
  then(&:to_s).
  # 逆順にする
  then(&:reverse)
```

ちなみに、Ruby 2.6以前では以下のように構文エラーが発生していました。

```
syntax error, unexpected '.', expecting end-of-input
  .then(&:succ)
``` 

### 全引数を別のメソッドに引き渡す`...`引数が導入された

Ruby 2.7ではあらゆる引数を受け取って、別のメソッドに引き渡す`...`引数が導入されました。

```ruby
def add(a, b)
  a + b
end

def add_with_description(...)
  # 受け取った引数をすべてそのままaddメソッドに引き渡す
  answer = add(...)
  "answer is #{answer}"
end

add_with_description(2, 3)
#=> answer is 5
```

別のメソッドを呼び出すときは丸かっこ（`()`）が必須です。丸かっこがないと開始と終了のない範囲オブジェクト（`nil...nil`）と見なされます。

```ruby
# (...)と書かなかった場合は、nil...nilをaddメソッドを渡したことになる（警告も出る）
answer = add ...
#=> warning: ... at EOL, should be parenthesized?
```

メソッドの仮引数も別メソッドに渡す引数も、いずれも`...`になっている必要があります。
片方だけが通常の引数だったり、引数の一部だけが`...`になっていたりすると、構文エラーになります。

```ruby
def add_with_description(a, b)
  # 仮引数は通常の引数で、別メソッドの呼び出しが...になっていると構文エラー
  answer = add(...)
  "answer is #{answer}"
end
```

```ruby
# 引数の一部が通常の引数で、残りが...になっていると構文エラー
def add_with_description(a, ...)
  answer = add(a, ...)
  "answer is #{answer}"
end
```

### パターンマッチ構文が試験的に導入された

Ruby 2.7ではパターンマッチ（またはパターンマッチング）構文が試験的に導入されました。

> 関数型言語で広く使われているパターンマッチという機能が実験的に導入されました。 渡されたオブジェクトの構造がパターンと一致するかどうかを調べ、一致した場合にその値を変数に代入するといったことができるようになります。
> 
> [Ruby 2\.7\.0\-rc2 リリース](https://www.ruby-lang.org/ja/news/2019/12/21/ruby-2-7-0-rc2-released/)

以下はパターンマッチ構文の使用例です。

```ruby
case {status: :error, message: 'User not found.'}
in {status: :success}
  puts "Success!"
in {status: :error, message: message}
  puts "Error: #{message}"
end
#=> Error: User not found.
```

パターンマッチについては以下の記事で詳しく説明しています。

- [サンプルコードでわかる！Ruby 2\.7の新機能・パターンマッチ（前編） \- Qiita](https://qiita.com/jnchito/items/9bb4aa1dcefa00257815)
- [サンプルコードでわかる！Ruby 2\.7の新機能・パターンマッチ（後編） \- Qiita](https://qiita.com/jnchito/items/a9c7030cb5697f992cb7)

## その他、注目の新機能など

特筆すべき差異は上で述べた内容になりますが、その他にもRuby 2.7では数多くの新機能や変更点が導入されています。
その中から「プロを目指す人のためのRuby入門」と関連が深そうなポイントを以下にピックアップしておきます。

- [全般] シンタックスハイライトや自動インデントなど、irbが大きく進化した
- [配列] select(filter)とmapを同時に行うfilter_mapメソッドが追加された（4.4に関連）
- [配列] 積集合を返すintersectionメソッド（4.7.4に関連）
- [デバッグ] 定数の定義場所を返すconst_source_locationメソッド（11.5.5に関連）

それぞれの詳細については（そしてそれ以外の新機能や変更点についても！）、下記の記事で詳しく説明しているのでこちらをご覧ください。

- [サンプルコードでわかる！Ruby 2\.7の主な新機能と変更点 Part 3 \- 新機能と変更点の総まとめ \- Qiita](https://qiita.com/jnchito/items/ff89392f46cc9d500c94)

## まとめ

というわけで、この記事ではRuby 2.7で発生する「プロを目指す人のためのRuby入門」との差異をまとめました。

Ruby 2.7ではさまざまな変更点や新しい構文が導入されましたが、（当然ながら）基本的な書き方は変わっていません。
日常的なコーディングでは本書で説明している内容でじゅうぶん実務をカバーできているはずなので、安心して学習を進めてください。

Ruby初学者の方はこの記事で紹介した内容をいきなりキャッチアップしようとするよりも、まずは本書を最後まで読み進めましょう。
その上で余裕があればRuby 2.5、2.6、2.7と、順に変更点を吸収していくのがお勧めの学習方法になります。

### PR：プロを目指す人のためのRuby入門について

「プロを目指す人のためのRuby入門」は、他の言語での開発経験があり、これからRubyを始めたい人や、Rubyプログラミングの経験はある程度あるものの、まだまだ自信がない人に向けて、Rubyの言語仕様や開発の現場で役立つ知識を詳しく、ていねいに解説したRubyの入門書です。
テスト駆動開発やデバッグ技法といった実践的なテクニックを学ぶこともできます。

まだ読まれていない方は、お近くの書店やAmazonで一度手に取っていただけると幸いです。
みなさん、どうぞよろしくお願いします😄

[プロを目指す人のためのRuby入門｜技術評論社](https://gihyo.jp/book/2017/978-4-7741-9397-7)
[![9784774193977.jpg](https://qiita-image-store.s3.amazonaws.com/0/7465/67b8854c-a86e-caa8-64d3-7fa5724a1bf0.jpeg)](https://gihyo.jp/book/2017/978-4-7741-9397-7)