Rubyのエラーメッセージを日本語に差し替えてみた

はじめに

Rubyの初学者の方から「エラーメッセージの読み方がよくわからない」という話を聞き、「Rubyのエラーメッセージを日本語化できないか」試してみた記事です。

作ったもの

Konnyakuというgemを作りました。

github.com

rubygems.org

gem install konnyakuでインストールできます。

このgemではRubyのエラーメッセージを日本語に変換して出力することができます。 例えば、以下のようなコードがあったとします

puts Hoge # => 未定義の定数 Hogeがputsに渡されている

このコードを実行すると以下のようなエラーが表示されます

uninitialized constant Hoge (NameError)

実行結果: [Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

定義されていない定数Hogeを参照しているのでエラーになっているわけですね 慣れていれば問題はないのですが、初学者の方には英語表記であるため読みにくさを感じるようです

そこで、Konnyaku出番です! 先ほどのコードにrequire "konnyaku"を追加すると以下のようにエラーメッセージが日本語で表示されます

require "konnyaku"

p Hoge
#=> 例外:NameError が発生しました
#=> ソースコード: bug.rb の 3 行目にエラーの原因があります
#=> 定義されていない定数 Hoge があります

Konnyakuを使うことで、こんな感じでエラーメッセージを日本語として表示してくれます

やったこと

TracePointを使って、例外が発生したタイミングでエラーメッセージを日本語に強制的に書き換えることをしています。

class Konnyaku
  class << Konnyaku
    def run
        tp = TracePoint.new(:raise) do |tp|
            lineno = tp.lineno
            path   = tp.path
            msg = tp.raised_exception.message
            puts "例外:#{tp.raised_exception.class} が発生しました"
            puts "ソースコード: #{path}#{lineno.to_s} 行目にエラーの原因があります"
            
            msg = translate(msg) 
            puts msg
        end
        tp.enable
    end
  end
end

エラーメッセージなどの変換はKonnyaku.translateで以下のように無理やり置換しています(このへんもう少しきれいにしたい……)

class Konnyaku
  class << Konnyaku
    def translate(msg)
        case msg
            when /uninitialized constant/
                return msg.gsub(/#{$&}/, "定義されていない定数") + " があります"
            when /undefined local variable or method/
                return msg.gsub(/#{$&}/, "定義されていない変数またはメソッド").gsub(/ for /, "").sub(/`|'/, "").gsub(/for/, "") + " にあります"
            when /undefined method/
                return msg.gsub(/#{$&}/, "定義されていないメソッド").gsub(/ for /, "").gsub(/`|'/, "") + " にあります"
            when /uninitialized class variable/
                return msg.gsub(/#{$&}/, "初期化されていないクラス変数").gsub(/ for.+$/, "").gsub(/ in /, "") + " クラスにあります"
        end 
    end
  end
end

今後

とりあえず、対応していないエラーメッセージとかまだまだあるので追加していこうかと思います。 あと、テスト書けてないのでその辺も進めていきたい……

Refinementsで遊んでみた

はじめに

Refinementsで遊んでみた記事です。具体的な内容としてはトップレベルでRefinementsが使えないかなと試してみたとき備忘録です。

Refinementsとは

Rubyでは、後から自由にクラスにメソッドを追加することができる。

class Integer
  def hoge
    puts :hoge
  end
end

42.hoge
# => :hoge

これはこれで便利なんだけど、グローバルに変更されるし、継承とかでも意図しない動きをする可能性があるのであまりいいとは言えない。

そこでRefinementsの出番。

moduel Refine
   refine Integer do
      def hoge
         p :hoge
      end
   end
end

# 42.hoge
# => Error!

using Refine

42.hoge
# => :hoge

Refinementsではusingした移行で再定義したものを使える。 またusingした箇所のファイルスコープに限定されるので影響範囲が小さくて済む。

やりたかったこと

以下のようにトップレベルでrefineを使って特定のクラスにメソッドを生やしたかった(毎回、usingとか書くのが面倒だったので……)

refine Integer do 
   def hoge
      puts :hoge 
   end
end

42.hoge

ただ、このコードはrefineというメソッドが定義されていないのでエラーになる。

やってみた

で、出来そうか試してみたところ以下のコードで動作した。

module Kernel
    module_function
    def refine klass, &block
        Module.new do
           refine klass do
              yield
           end
        end
    end
end

refine Integer do 
   public
   def hoge
      p :hoge 
   end
end

42.hoge
# => :hoge

やっていること

Refinementsをよく使われる人は以下のようなコードで無名モジュールを使っているんじゃないかと思う。

using Module.new {
    refine Integer do
        def hoge
            p :hoge
        end
    end
}

42.hoge
# => :hoge

このコードを元にKernelモジュールにrefineメソッドを作成。

module Kernel
    module_function
    def refine klass, &block
        Module.new do
           refine klass do
              yield
           end
        end
    end
end

refineメソッドは第一引数に再定義したいクラスなどを受け取り、メソッドの再定義などはブロック引数で渡します。

これにより以下のようにhogeメソッドを追加できます。

refine Integer do 
   public
   def hoge
      puts :hoge 
   end
end

42.hoge
# => :hoge

ただし……

ただ、このコードはusingしていないのに再定義した内容が使用できているんですよね……。

module Kernel
    module_function
    def refine klass, &block
        Module.new do
           refine klass do
              yield
           end
        end
    end
end

# ここで usingを使っていないが作成したhogeメソッドが使えている……
refine Integer do 
   public
   def hoge
      p :hoge 
   end
end

42.hoge
# => :hoge

これはRefinementsの仕様通りの動きなのかがはっきりとしていないのでなぜ動いているのやら……。

それと今回のコードではpublicを追加しないとメソッドが呼び出されなかった。

module Kernel
    module_function
    def refine klass, &block
        Module.new do
           refine klass do
              yield
           end
        end
    end
end

refine Integer do 
   def hoge
      p :hoge 
   end
end

42.hoge
# => private method `hoge' called for 42:Integer (NoMethodError)

エラーからprivateメソッドとして定義されているらしいことがわかったのでpublicを付けてみたら動いたという……。 この辺も仕様通りの動きなんだろうか……?

おわりに

とりあえず、やりたいことはできた。 これが仕様通りの動きなのかは判断がつかないので、Refinementsの実装とか読んでみようかと思う。

参考

qiita.com

secret-garden.hatenablog.com

secret-garden.hatenablog.com

【Ruby 3.0 Advent Calendar 2020】Dataクラスが廃止された【23日目】

はじめに

Ruby 3.0 Advent Calendar 2020 23日目の記事になります。

昨日は、【Ruby 3.0 Advent Calendar 2020】Ruby 3.0 で WEBrick と SDBM が標準ライブラリから削除される【22日目】です。

secret-garden.hatenablog.com

今日は、Ruby3.0でC拡張などで使われていたDataクラスが削除されたことを紹介します。

Dataクラスって?

DataクラスはObjectクラスからnewallocateundefしたクラスで、C拡張を書く際にnewなどが定義されていると困るケースがあったので使用されていました。 そのためRubyを普通に書く上では特に意識することはありません。

docs.ruby-lang.org

廃止されたことによる影響

基本的にRubyのコードレベルでは大きな影響はありません。 あるとすれば、C拡張を使っているgemなどを作っている人は対応が必要になると思います。

とはいえ、こちらのコミットのようにrb_cObjectを使うように変更すればよさそうなので大きな問題はないかと思います

github.com

あと、大きな問題になりそうなケースとしては古くからメンテナンスされていないC拡張のgemなどは3.0から動かなくなると思います。 なので、かなり古いC拡張のgemを使っている場合は移行先を考えるか自作することを検討するといいかもしれませんね。

【Ruby 3.0 Advent Calendar 2020】C APIのヘッダーが分割された話【16日目】

はじめに

Ruby 3.0 Advent Calendar 2020 16日目の記事になります。

昨日は、【Ruby 3.0 Advent Calendar 2020】ArrayやStringのメソッドの返り値が変更された話【15日目】です。

今日は、Ruby3.0でC APIのヘッダーが分割れたことを紹介します。

C APIとは?

RubyでC(またはC++拡張)を書く際に使用するに使用するAPIです。 このC APIを使ってCレベルでHashやArrayを作成することができるようになります。

// Cレベルで新しい配列 ary を作成
VALUE ary = rb_ary_new()

また、独自クラスの作成などもできたりします。

// Arrayクラスを継承してSubArrayを作成しています。
VALUE rb_cSubArray = rb_define_class("SubArray", rb_cArray)

で、今回の変更は?

簡単に言うと、Ruby側のC APIをメンテナンスしやすくする意図で導入されました。

github.com

またCのマクロを廃止してインライン関数へと変更し、簡単な型チェック(Cレベルでの)が可能になっているようです。

C APIを使うユーザー向けの変更なので、Rubyをそのまま使うユーザー層からすると特に今までのRubyと違う部分はありません。 ただ、いくらかRubyのビルドに時間がかかるかもしれません。

なので、クリスマスにリリースされるRuby3.0のビルド時間は今までより時間がかかるかもしれないので紅茶でも入れながら待つといいかもしれません。

【Ruby 3.0 Advent Calendar 2020】ArrayやStringのメソッドの返り値が変更された話【15日目】

はじめに

Ruby 3.0 Advent Calendar 2020 15日目の記事になります。

昨日は、「Ruby3.0で導入される型定義!TypeprofでBlockやProcを解析してみる」です。

qiita.com

今日は、ArrayStringの返り値が変更された話を紹介します。

これまでの返り値

これまでのRubyではArrayStringを継承したクラスで新しくオブジェクトを作成し、メソッドを実行した際の戻り値が一定のものではありませんでした。

class SubArray < Array
end

sub = SubArray.new([1, 2, 3])
p sub
p sub.class.name
# => SubArray
p sub.uniq.class.name
# => SubArray
p sub.rotate.class.name
# => Array

その為、戻り値のオブジェクトがどのクラスかを確認しなければならなくなり、非常に能率の良くない状況がありました。

実際にArrayの戻り値のクラスが一定ではないことに関してチケットも作成されていました。

bugs.ruby-lang.org

Ruby 3.0ではどうなるのか?

Ruby 3.0ではArrayStringを継承したクラスを使って作成されたに対して継承元(ArrayとかString)のクラスのメソッドを実行すると以下のように継承元のクラスのオブジェクトが返ります。

class SubArray < Array
end

sub = SubArray.new([1, 2, 3])
p sub
p sub.class.name
# => SubArray
p sub.uniq.class.name
# => Array
p sub.rotate.class.name
# => Array

継承を使って作成したSubArrayから一律でArrayが返ってくるので分かりやすくなってますね。 こういうケースだと、SubArrayが返ってくれた方が良かったりすんじゃないかと思ったりもしますが、どちらかにそろっているのは分かり易くてよさそうです

参考

github.com

bugs.ruby-lang.org

【Ruby 3.0 Advent Calendar 2020】Win32APIが廃止された【11日目】

はじめに

Ruby 3.0 Advent Calendar 2020 11日目の記事になります。

昨日は、【Ruby 3.0 Advent Calendar 2020】Arguments forwardingがちょっと便利になった【10日目】です。

gamelinks007.hatenablog.com

今日は、Win323APIが廃止されたことを紹介します。

Win32APIとは?

WIndowsの32bit用のAPIのことで、WindowsAPIを使うことで簡単にメッセージボックスなどを作成することができます。 WindowsではそれらのAPIを経由してWindowsアプリケーションを作ることが出来たりします。

廃止されたWin32API

今回廃止されたのはWin32APIをRubyから呼び出す部分のようですね。 Rubyでは、1.9.1以降からdeprecatedとなっていたようで、今回のRuby3.0のリリースに合わせて廃止されたようです。

github.com

ちなみにチケットはこちら

bugs.ruby-lang.org

ちなみに、代替としてfiddleというライブラリを使うようです。 fiddleからダイナミックリンクライブラリを読み込んで、読み込んだライブラリ経由でAPIを実行する感じっぽい?

docs.ruby-lang.org

ちなみに、他のRuby内部のコードでWin32APIを使っている箇所はfiddleに置き換えられたりしてます。

github.com

Windowsを使うRubyユーザーでWin32APIを使っているという人たちは対応が必要そうですが、母数はあまり多くないかもしれませんね。

参考

ruby-trunk-changes.hatenablog.com

bugs.ruby-lang.org

github.com

【Ruby 3.0 Advent Calendar 2020】Arguments forwardingがちょっと便利になった【10日目】

はじめに

Ruby 3.0 Advent Calendar 2020 10日目の記事になります。

昨日は、【Ruby 3.0 Advent Calendar 2020】Ruby 3.0.0-preview2 がリリースされた【9日目】です。

secret-garden.hatenablog.com

今日はRuby 3.0で入ったArguments forwardingの変更について解説します。

Arguments forwardingって?

Arguments forwardingとはRuby 2.7で入った機能で、メソッドに渡された引数をそのままフォワード(転送)するというものです。

def hoge(arg)
    puts "arg is #{arg}"
end

def foo(...)
    hoge(...)
end

foo(1)

上記のようにメソッドfooに渡された引数をそのままメソッドhogeに渡すことができます。 ちなみに、今回の例では引数を一つだけ渡していますが、複数の引数を以下のように渡すこともできます。

def hoge(arg1, arg2)
    puts "arg1 is #{arg1}"
    puts "arg2 is #{arg2}"
end

def foo(...)
    hoge(...)
end

foo(1, 2)
# => arg1 is 1
# => arg2 is 2 

Ruby 3.0で入った変更

以下のようにフォワード(転送)された引数の最初の引数を受け取ることができます。

def hoge(arg1, arg2)
    puts "arg1 is #{arg1}"
    puts "arg2 is #{arg2}"
end

def foo(first, ...)
    puts "first is #{first}"
    hoge(...)
end

foo(1, 2, 3)
# => first is 1
# => arg1 is 2
# => arg2 is 3

引数をそのまま渡したいが、引数の最初だけ特定のメソッドに渡したいケースなどで重宝しそうです。

ちなみに、この機能を使うと以下のようなコードが簡単に書けます。

def arg_forward(arg, ...)
    puts arg
    arg_forward(...)
rescue => e
    puts :finish
end

arg_forward(1, 2, 3)
# => 1
# => 2
# => 3
# => finish

再帰させて引数を一つづつ出力することが簡単に書けます。

再帰周りとかのコードは書きやすそうなので重宝しそうですね。

参考

github.com

bugs.ruby-lang.org

koic.hatenablog.com