«前の日記(2021-12-17) 最新 次の日記(2022-01-08)» 編集

いがいが日記


2021-12-25 [長年日記]

_ 入門書におけるRuby2.3からRuby3.0そしてRuby3.1への変化

igaigaです。これは Ruby Advent Calendar 2021 の25日目の記事です。昨日はmrknさんの 「来年リリースする bigdecimal 3.2 はどうしようかな」 でした。BigDecimal、長い間ずっと進化してきていてすごいです。

私は 「ゼロからわかるRuby超入門」 という初学者向けの入門書籍を2018年に書きました。発刊当時の最新のRubyはバージョン2.5で、この書籍ではRuby2.3から2.5までのバージョンに対応させて書きました。Ruby2.3は当時のmacOSにデフォルトで入っているバージョンで、大学などの講義で使うときにインストール不要で使えるようにこのバージョンも対象に入れました。

このRuby超入門、今年2021年の夏にひっそりとRuby3.0対応をしました。現在物理書籍として流通している第3刷以降では新しい内容になっています。改訂ではないので大きな変更はできなかったのですが、Ruby2.3や2.4だけで必要だった記述を削除したり、Ruby3.0までで変更になった点を更新しています。

この記事ではその作業を振り返って、Rubyの基礎的な範囲でのバージョンによる進化を書いていきます。

また、増刷ごとに読者のみなさまからのフィードバックを反映し、誤字の修正のほか、わかりづらい表現を変更するなどの作業をいれています。特に顧問先の フィヨルドブートキャンプ の受講生さんからたくさんのフィードバックをいただきました。この場を借りて感謝いたします。また、フィヨルドブートキャンプではRuby超入門の 講義動画 をつくりましたので、動画で学びたい方はご検討いただけましたら。

Ruby2.4でのbinding.irbの導入

Ruby2.4でbinding.irbと書くことでコード中で実行を一時停止してデバッグすることが可能となりました。入門書においてこれは革新的な変化で、Rubyをインストールしたらすぐに使えるというのが大きいです。もしもこの機能がないとpryなどのGemをいれることになりますが、その場合はGemについての説明が必要になり、これはRuby超入門では全11章中の第10章で登場するのを待たないといけなくなります。デフォルトで使える機能として入ったおかげで、2章でbinding.irbを利用したデバッグの方法を説明することができました。また、Ruby2.5では require "irb" も省略可能になったのも嬉しい変更です。

Ruby2.4でのArray#sumの導入

Ruby2.4でArray#sumが導入され [1,2,3].sum と書けるようになりました。このメソッドは説明の例題として使いやすいので例として登場させました。ちなみにRuby2.3向けには代わりに inject(:+) を使っていました。injectでは特別に inject(&:+) の&を省略できるのが懐かしいですね。

Ruby2.4でのNet:HTTP.postの導入

Ruby2.4でNet:HTTPのPOSTをかんたんに書けるようになりました。

require "net/http"
require "uri"
require "json"
uri = URI.parse("https://www.example.com")
result = Net::HTTP.post(uri, {mocha: 400}.to_json, "Content-Type" => "application/json")
p result

直感的に書けますね。このコード、Ruby2.3まではどう書いていたかというと次のようになります。

require "net/https"
require "uri"
require "json"
uri = URI.parse("https://www.example.com")
request = Net::HTTP::Post.new(uri.request_uri, "Content-Type" => "application/json")
request.body = {mocha: 400}.to_json
https_session = Net::HTTP.new(uri.host, uri.port)
https_session.use_ssl = true
response = https_session.start do |session|
  session.request(request)
end
p response

こちらは覚えるのがちょっと大変でした。Net::HTTP#postメソッドを使うと1メソッドで書けて便利になっていることが分かります。

Ruby2.4での正規表現の match? メソッド導入

Ruby2.4で正規表現マッチを行う match? メソッドが導入されました。

"カフェラテ".match?(/ラテ/)

Ruby2.3では =~ を使うように書きました。書籍にとっては真偽値を返すメソッドは末尾が?で終わる旨を説明できる例が増えるのでありがたい追加です。現実のコードもかなり読みやすくなったと感じています。

Ruby2.4でFixnumとBignumがIntegerへ統合

これは利用する側ではほとんど影響はなく、エラーメッセージやclassメソッドの結果が異なるくらいでした。Ruby core内部では大きな変更だったのではないでしょうか。(どうなのだろう?) 現実のコードではバージョンアップ当初はFixnumやBignumクラスがないよ、というエラーがよく出ていたのが懐かしいです。

追記(2022/01/09: FixNum, BigNumをFixnum, Bignumへ修正しました。コメントありがとうございました。

Ruby2.5でブロック内でbeginなしのrescueが書けるようになった

地味ですが、インデントが減るので読みやすくなりますね。メソッドでも同様に書けます。

bill = 100
numbers = [0, 1, 2]

numbers.each do |number|
  warikan = bill / number
  puts "1人あたり#{warikan}円です"
rescue ZeroDivisionError
  puts "おっと、0人では割り勘できません"
end

Ruby2.6からbundlerがデフォルトでインストールされる

これも神変更。今までは「BundlerでGem群をインストールするには3つの手順を踏みます。Bundlerのインストール、Gemfileの作成、bundle installコマンドの実行です。」となっていた手順が2つで済むようになりました。本当にありがたい。仕事でも新しくつかうRubyのバージョンをインストールしたあとにbunlde installしてbundlerがありませんというエラーを出すことがよくあったので、それがなくなってかなり楽になった。

Ruby2.7でprivateメソッド呼び出しのルールが変更

「private なメソッドはレシーバ付きでは呼びだせない」というルールから、「self.methodの形式でも呼び出し可能」に変更されました。今までprivateメソッドが呼べていた範囲内ではself.と書けるはずなので、privateメソッドを書ける場所が増えたわけではないです。

これは入門書ではかなり説明が難しくなってちょっと困っています。Ruby超入門では最低限の文章変更にとどめて、「privateなメソッドはレシーバを指定した呼び出し方ができない」といった説明にしています。self.methodの形式はselfだけが許可されるのでレシーバを指定しているわけではない、という解釈です。意図としては間違っていないと思うのですが、もっとうまい説明方法があったら教えてください。

Ruby2.7でパターンマッチが導入

case inで書けるパターンマッチがRuby2.7でexperimentalで追加され、Ruby3.0で正式版になりました。Ruby超入門では変更が大きくなってしまうのでまだ書けていませんが、書くならばcase whenのときにcase inもあるよとだけ伝えて、正規表現の節または次の節でパターンマッチという便利な道具もあると書き足したいと思っています。改訂のチャンスがあれば書きたいので、改訂できるくらい売れるようにみなさん書籍の宣伝よろしくお願いします。

Ruby2.7でobject_idの仕様変更

object_idがメモリ番地ベースだったのに代わり、なんらかの通し番号に変更されました。コンパクションGCが導入されてメモリ番地が変更されるケースへの対応だと考えています。利用者側からすると「表示される数値の桁が少なくなったな」と気づくくらいでしょうか。Ruby超入門ではオブジェクトが同一であることの説明でオブジェクトIDをつかっているので、オブジェクトの識別番号としての意図が残ってくれたのは本当によかったです。

Ruby2.7でEnumerable#tallyが追加

章末の練習問題に「文字列"caffelatte"の中で使われているアルファベットと、その回数を数えてください。」という問題があります。これはハッシュを生成してキーに文字、値に回数をカウントしていってもらうのを意図した問題でした。Rubyの本だと問題を作っても1メソッド2メソッドで解けてしまうことも多いのですが、プログラミングの練習としてはもう少し長く3手、5手かかる問題が欲しいのでかなり考えて作った問題で、お気に入りの問題でもありました。

Ruby2.7から追加されたEnumerable#tallyを使うと、"caffelatte".chars.tally の2メソッドで同様の結果が得られます。Rubyが便利になっていくと練習問題作成は難しくなっていく例です。いたしかたないです。

Ruby3.0でWebrickが標準添付ライブラリから除外

10章ではSinatra gemをつかってWebアプリの基礎について説明しています。SinatraをつかうとSinatraが仕様変更したりして将来動かなくなることもあるのでは?と執筆時に検討はしていたのですが、Webrickが外れるところまでは予想していませんでした。脆弱性対応は本当に大変なので妥当な判断なので、予想できてもよかったなとも思いますが、執筆時は標準添付ライブラリのGem化もまだ始まってなかった?と思うので、その状況では確かに難しいなとも思います。

Ruby3.0ではgem install webrickでインストールしてください。これは書籍のWeb正誤表にも載せていたのですが、エラーで動かなくなることもあり、Ruby3.0でRuby超入門の範囲で一番聞かれることが多かった質問でした。3刷では変更対応済みです。

Ruby3.1へ

Ruby3.1では新しいデバッガであるdebug.gemが標準添付され、今から本を書くならば、デバッグについての記述も書籍の前半に説明を少なく書けるのはありがたいですし、書籍の学習を進める上でのデバッグ方法としても活用できて便利になります。Ruby2.7で入った新しいirbも色がついて見やすく使いやすくなりました。

また、Ruby3.0で導入された型定義ファイルRBSとその周辺ツールも整備が進み、エディタに型情報をサジェスト表示したり、呼び出せるメソッド一覧を選ぶこともできるようになりました。これらも学習の助けになりそうです。Rubyで書かれるコードでの変数名などの命名にこれらのツールが影響を与えるのかどうかも興味深いです。

このように見てみると、入門書の範囲でもRubyは便利な変更がいろいろと入り、それでいて互換性が壊れるような変更はとても少ないことがわかります。Rubyの進化を提供してくれているコミッタのみなさんに本当に感謝します。

書籍を書くときに感じたのは、Rubyで入門書を書くと説明が少なくて済むということです。他の言語の入門書を参考に読んでいると「これはRubyの場合は説明の必要がない部分だな」という部分がいろいろとありました。言語仕様としてシンプルで覚えやすいルールが行き届いていたり、記述量が少なくて済むこと、組込ライブラリのクラス群によく使う便利なメソッドが用意されていること、などがその要因かもしれません。

一方でエディタでのメソッド入力補完が弱いことは弱点だったかと思いますが、それが改善されていっていることはとても嬉しいです。また、この本を書くときに感じたbinding.irbがすぐに使えるありがたさと同じことが、debug.gemでも感じられるかもしれません。これらの改善は学習カリキュラムにも良い影響を与えていると感じています。

Ruby3.1のリリースは今日予定されています。みなさん引き続き良いRubyライフを!

本日のツッコミ(全3件) [ツッコミを入れる]
_ (2021-12-28 20:01)

細かいことですが,FixNum,BigNum は正しくは Fixnum,Bignum ですね。<br><br>private メソッドの self.method 形式の呼び出しの件は(初心者向けの説明として)悩ましいですよね。<br>「レシーバを指定しているわけではない」というのはかえって理解しづらいような気がします。<br>「private メソッドは self 以外のレシーバーを指定して呼び出すことはできない」<br>とか<br>「private メソッドはレシーバーを指定した呼び出しはできない。ただし,例外として self を指定した呼び出しはできる」<br>といった説明になるのかなあ,と思いますがいかがでしょうか。

_ otn (2022-01-02 22:33)

「privateなメソッドは任意のレシーバを指定した呼び出し方ができない」<br>「privateなメソッドはレシーバを任意に指定して呼び出せない」<br>「privateなメソッドのレシーバはself固定です」<br>「privateなメソッドのレシーバは常にselfです」

_ igaiga (2022-01-09 09:47)

コメントありがとうございます!Fixnum, Bignumを修正しました。<br><br>privateなメソッドの説明もありがとうございます。「privateなメソッドはレーバーを書けない」を最大主張できる文にしたいと私は考えていて、そうすると「private メソッドはレシーバーを指定した呼び出しはできない。ただし,例外として self を指定した呼び出しはできる」がよさそうなのですが、でもそれを言ったときにselfについての理解がまだないと「selfって何?」とprivateメソッドの本筋じゃないところで分からなくなって、本筋も理解してもらえないのも困るなと。<br><br>selfの説明がすごく難しいので、selfを説明する前にprivateを説明したいのですが、どういう組み立てにすればいいのか、また次に書く機会まで考えてみようと思います。


«前の日記(2021-12-17) 最新 次の日記(2022-01-08)» 編集