【Ruby Advent Calendar 2018】あなたのしらない Refinements の世界【3日目】

Ruby Advent Calendar 2018 3日目の記事になります。
なんとか日付が変わる前に書けました…。
何を書こうか迷ったんですが、この記事では今年 Ruby にパッチを投げまくったディープな Refinements の世界について書いてみようかと思います。
そもそも皆さん Refinements は使っていますか?どんな機能か知っていますか?
まずは Refinements についておさらいしてみましょう。

クラス拡張について

Ruby ではオープンクラスに対してメソッドを拡張する事が出来ます。

# Integer クラスに対して後からメソッドを追加する事が出来る
class Integer
  def twice
    self + self
  end
end

# 追加したメソッドは既存のオブジェクトから呼び出すことが出来るようになる
p 42.twice
# => 84


# クラスではなくてモジュールに対してメソッドを追加する事も出来る
module Enumerable
  def filter &block
    select &block
  end
end

# Enumerable が mixin されているクラスのオブジェクトで使用することが出来る
p [1, 2, 3, 4, 5].filter(&:odd?)
# => [1, 3, 5]

p ({ a: 1, b: 2, c: 3, d: 4, e: 5 }.filter { |k, v| v.even? })
# => {:b=>2, :d=>4}

このように Ruby では後からクラスに対して自由にメソッドを追加する事が出来ます。
この機能の事を Ruby では『クラス拡張』や『モンキーパッチ』などと呼びます。

このような機能があることで Ruby ではユーザが自由にメソッドの追加したり挙動を書き換えたりフックしたりして闇の深いコードを書くことが出来ます。
Rails 界隈でよく見かける #present?#blank? などと言ったメソッドも Rails が独自にメソッドを定義しています。
こんな感じでユーザは便利なメソッドを様々なケースで利用する事が出来ます。

クラス拡張の欠点

クラス拡張は Ruby の醍醐味でもあるんですが、その自由さ故に欠点もあります。
例えば、次のように #blank? メソッドを追加する事が出来ます。

class Object
  # nil? の変わりに blank? という名前を使いたいなー
  def blank?
    nil?
  end
end

p nil.blank?   # => true
p false.blank? # => false
p 0.blank?     # => false
p [].blank?    # => false

これ単体では問題ありませんが、Rails を使っているコードであれば大問題です。
先程上げたように Rails ではすでに #blank? という名前のメソッドを定義しており、このメソッドと名前が競合してしまいます。
ですので、ユーザが定義した #blank?Rails が定義した #blank? も意図して動作しない可能性があります。

このようにクラス拡張は『全てのコードに対して』反映されてしまうので上記のように『うっかり』既存のメソッドと名前がかぶってしまう可能性があります。
上記のように自分でクラス拡張したメソッドであればまだ注意する事が出来るんですが、ライブラリの中でクラス拡張が定義されている場合、なかなか気づくことが出来ません。
どうしても上記のようなメソッドを使いたいのであればモジュール関数で定義することで安全に利用できます。

module Ex
  module_function
  def blank? a
    a.nil?
  end
end

p Ex.blank? nil   # => true
p Ex.blank? false # => false
p Ex.blank? 0     # => false
p Ex.blank? []    # => false

うーん、オブジェクト指向言語である Ruby でこのようなコードは書きたくないですね…。

このようにクラス拡張はユーザが自由にメソッドを定義出来るんですが、その反面『誰でもメソッドを定義出来てしまう』という諸刃の剣でもあります。
そのためクラス拡張を行う場合は、

  • そのメソッドが引き起こす副作用
  • そのメソッドが影響する範囲

を意識して利用する必要があります。
特に gem 化するようなコードであれば『そのライブラリを使用する全てのコード』に影響してしまうため細心の注意を払う必要があります。

しかし、安心してください。
これらの問題は Refinements を使用することで解決することが出来ます。

Refinements、Refinements を使う

Refinements とは一言で言ってしまうと『特定のコンテキストでのみクラス拡張を反映させる事が出来る機能』になります。
Refinements は Ruby 2.0 で実験的に導入され、Ruby 2.1 から本格的に導入された機能になります。
ですので Ruby 2.1 以降であれば誰でも自由に利用する事が出来ます。
と、いうわけで実際にコードを例に上げて動作を見ていきましょう。

refine を使用してクラス拡張を定義する

Refinements ではまず最初に『モジュール + refine』という形でクラス拡張を定義します。
先程例に上げた

class Object
  def blank?
    nil?
  end
end

を Refinements で定義し直すと

# クラス拡張を定義するモジュールを定義する
module ExBlank
  # refine に拡張したいクラスを渡す
  # do 〜 end 内でクラス拡張したいメソッドを定義する
  refine Object do
    def blank?
      nil?
    end
  end
end

このような形になります。
refineclass などの構文とは違い単なるメソッドなので do を定義することに注意しましょう。
ちなみに1つのモジュール内で何回も refine でメソッドを定義することが出来ます。

# Rails ライクな blank? を定義する場合
module ExBlank
  # 複数のクラスに対して拡張も出来る
  refine Object do
    def blank?
      respond_to?(:empty?) ? !!empty? : !self
    end
  end

  refine NilClass do
    def blank?
      true
    end
  end

  refine FalseClass do
    def blank?
      true
    end
  end

  refine TrueClass do
    def blank?
      false
    end
  end
end

refine したクラス拡張を反映させる

refine で定義したメソッドはそのままでは使用する事は出来ません。

module ExBlank
  refine Object do
    def blank?
      nil?
    end
  end
end

# 定義したメソッド呼び出してもエラーになる
# Error: undefined method `blank?' for nil:NilClass (NoMethodError)
p nil.blank?

逆にいえば『refine で定義しただけ』ではクラス拡張は反映されません。
ここが通常のクラス拡張と大きく異なる点です。

では、実際に反映させるためにどうするのかと言うと using を使用します。

module ExBlank
  refine Object do
    def blank?
      nil?
    end
  end
end

# refine を定義したモジュールを using に渡すことで、以降の行から反映される
using ExBlank

p nil.blank?
# => true

この using というのが肝で refine で定義したクラス拡張は using を使用して『特定のコンテキストでのみ』クラス拡張を反映させます。
と、言われてもピンと来ないと思うのでもう少し解説します。
using が使用できる場所は限られており、

  • トップレベ
  • クラス(モジュール)内

になります。
まず、トップレベルの場合は『using したファイル内全体』に対して反映されます。

module ExBlank
  refine Object do
    def blank?
      nil?
    end
  end
end

# NG: ここではまだ呼べないよー
p nil.blank?

using ExBlank

# OK: using 後から呼べるようになるよー
p nil.blank?


# クラス内からも呼び出せる
class X
  def meth a
    a.blank?
  end
end

# OK
p X.new.meth 0

このように using したファイル全体でのみクラス拡張が反映されます。
ただし、using した以降の行で反映されることに注意してください。

次にクラス内で using を使用した場合は『そのクラス内でのみ』クラス拡張が反映されます。

module ExBlank
  refine Object do
    def blank?
      nil?
    end
  end
end

class X
  # クラス内でのみ影響する
  using ExBlank

  # メソッド内で呼び出せる
  def meth a
    a.blank?
  end
  
  # OK: ここでも呼び出せる
  p [].blank?
end

# OK
p X.new.meth 0

# NG: クラス外では呼び出せない
p 0.blank?

このように Refinements は、

  • モジュール + refine でクラス拡張を定義する
  • クラス拡張したメソッドは using モジュール 反映される

と言うように役割が分担し、クラス拡張を制御します

ただし、『using 外のメソッド内ではクラス拡張したメソッドは呼び出せないこと』には注意してください。

module ExBlank
  refine Object do
    def blank?
      nil?
    end
  end
end

def meth a
  a.blank?
end

using ExBlank

# NG: メソッド内では using は反映されない
meth 0

おそらくこれが Refinements の一番のハマりポイントだと思います。
ちなみに refine したモジュールはそのファイル内で定義されているある必要はありません。
ですので、

  • 別のファイルで refine でクラス拡張を定義する
  • 必要に応じて using する

と、いうのが Refimenets の基本的な使い方になります。

まとめ 1

  • Ruby では自由にクラスに対してメソッドを追加する事が出来る
  • しかし、影響範囲が広いので何も考えずに追加してしまうと競合する可能性がある
  • Refinements を使用する事でクラス拡張の影響範囲を制限することが出来る
  • ただし、using がどの範囲まで適用されるのかは意識する必要がある

次は実際にどのような場面で Refiments を利用しているのか解説していきたいと思います。

gem で安全にクラス拡張する

『クラス拡張を伴うライブラリをつくりたい!!』と思うことは多いと思います。
実際に rubygems では『クラス拡張だけ』を行うライブラリがたくさんあります。
例えば、わたしも Binding#expand メソッドを追加する gem-binding-expand という自作ライブラリをつくって遊んだりしています。
この『クラス拡張を伴うライブラリ』ですが、当然ただ単にクラス拡張を行ってしまうと先程上げた問題のように名前が競合してしまう可能性があります。
#expand なんてめっちゃ一般的な名前なのでもしかしたらすでに誰かが使っている可能性がありますよね…。
そこで、Refinements を利用して、

  • ライブラリ側は refine したモジュール・メソッドだけ定義する
  • 利用する場合は using を使い必要に応じて有効化する

というような使い方を考えてみましょう。
先程上げた gem-binding-expand もそのような構造になっており、以下のような使い方になります。

require "binding/expand"

# using したら Binding#expand というメソッドが使用できるようになる
using Binding::Expand

def meth
  "meth"
end

hoge = 42
foo = "homu"
bar = [1, 2, 3]

p binding.expand(:hoge, :foo, :bar, :meth)
# => { hoge: hoge, foo: foo, bar: bar, meth: meth }

これによりライブラリ側は名前の競合を考える必要がなく、また利用者側も require しただけでは即座に反映されず、更に適用範囲を制御することが出来ます。
この『using しない限りは問題ない』というのが利用者の安心感に繋がっていきます。
逆に何も考えずにクラス拡張を行っているようなライブラリは何が起こるのかわからないので使いたくないですよね?
『まーこのメソッド名は競合しないだろー』と慢心せずにクラス拡張を行う場合は必ず、必ず!! Refinements を利用しましょう。
ちなみに失敗例を上げると以前 gem-cstyle_enum というライブラリを作ったんですが、Refinements 化しておらずこのままでは Railsenum と競合してしまいますね…。

require "cstyle_enum"

class Color
  # クラスメソッドとして enum が定義されるが
  # Rails も同じ名前のメソッドが生えており…
  Colors = enum {
    RED
    GREEN = 3
    BLUE
  }
end

Color::RED      # => 0
Color::GREEN    # => 3
Color::BLUE     # => 4
Color::Colors   # => {:RED=>0, :GREEN=>3, :BLUE=>4}

このように単体では問題なくても他のライブラリやフレームワークなどを組み合わせる場合に競合してしまう可能性が微粒子レベルで存在するので『このメソッド名は競合しないだろー』と思わずにクラス拡張を行う場合は必ず、必ず! Refinements を利用しましょう。

その場で雑にメソッドを生やす

今度はもう少し実用的な使い方を考えてみましょう。
次のような User クラスがあるとします。

class User < Struct.new(:name, :age)
  def self.all
    @@all ||= []
  end

  def initialize(*args)
    super
    User.all << self
  end
end

このクラスの一覧を出力するメソッドを別で定義すると以下のような感じになると思います。

# 雑に全ての User を文字列に変換する
def view_user_all
  User.all.map { |user| "#{user.last_name} : #{user.last_name} (#{user.age})" }
end

User.new("ほむら", "明美", 14)
User.new("まどか", "鹿目", 14)
User.new("まみ", "", 15)
p view_user_all
# => "明美 ほむら (14)\n鹿目 まどか (14)\n巴 まみ (15)"

まあ Rails などを使っていると Model を View が分かれていることはよくあることだと思います。 上記のコードでも問題はないんですが、Refinements を使用するともう少しすっきりとした実装になります。

# 文字列に変換するメソッドを Refinements で定義しておく
module ExView
  refine User do
    def to_s
      "#{last_name} #{first_name} (#{age})"
    end
  end

  # 拡張しづらい標準クラスに対しても安全に一時的に挙動を変えることが出来る
  refine Array do
    def to_s
      self.join("\n")
    end
  end
end
using UserView

def view_user_all
  # すっきり
  "#{User.all.map { |user| "#{user}" }}"
end

User.new("ほむら", "明美", 14)
User.new("まどか", "鹿目", 14)
User.new("まみ", "", 15)
p view_user_all
# => "明美 ほむら (14)\n鹿目 まどか (14)\n巴 まみ (15)"

Refinements の部分がやや冗長に見えますが呼び出す側はすっきりしていますね。 User#full_name みたいなメソッドを追加しておくと更に汎用的になるかもしれないですね。
また Array のように『メソッドを生やすと可読性が上がるけど余計なメソッドは生やしたくない…』みたいなクラスに対しては Refinements がかなり有効になります。
このように Refinements を使用することで安全かつ最小限の副作用でメソッドを追加する事が出来ます。 これは追加したメソッドを使用する箇所が増えれば増えるほどより強力により可読性向上につながります。

メソッドを隠蔽する

さてさて、もう少しコアな Refinements の使い方を紹介してみましょう。
次のように他から参照されたくないメソッドを private にすることはよくあると思います。

class User
  def initialize name
    @__name__ = name
  end

  def to_s
    "name is #{name}"
  end

  # 直接値が参照されないように private にしておく
  private def name
    @__name__
  end
end

homu = User.new "homu"

# to_s を介してのみ参照出来る
p homu.to_s
# => "name is homu"

しかし、残念ながら Rubyprivate はガバガバなので、例えば継承した場合、継承先で意図しないで参照されてしまう(してしまう)可能性があります。

class User
  def initialize name
    @__name__ = name
  end

  def to_s
    "name is #{name}"
  end

  private def name
    @__name__
  end
end

class UserEx < User
  def meth
    # スーパークラスの private メソッドを普通に参照できてしまう
    name
  end
end

homu = UserEx.new "homu"
p homu.meth
# => "homu"

そもそも Rubyprivate はこういう意図で使用するものではないのでしょうがないんですが、それでもやっぱり参照してほしくない、参照されたくないメソッドというのは出てくると思います。
そういうときは Refinements を使用する事で完全にメソッドを隠蔽することが出来ます。

class User
  # Refinements で隠蔽したいメソッドを定義する
  # また Module.new で動的にモジュールを定義することで
  # あとから using することも防ぐ事が出来る
  using Module.new {
    refine User do
      def name
        @__name__
      end
    end
  }

  def initialize name
    @__name__ = name
  end

  def to_s
    "name is #{name}"
  end
end

class UserEx < User
  def meth
    # using してないコンテキストなので参照できない!!
    name
  end
end

homu = UserEx.new "homu"

# Error: undefined local variable or method `name' for #<UserEx:0x000055998748ee48 @__name__="homu"> (NameError)
p homu.meth

このように Refinements を利用することで『そのクラス内でのみ』参照出来るメソッドを定義することが出来ます。
また、Module.new で動的にモジュールを定義することであとから using することを防ぐことも出来ます。
実際にここまで完全に隠蔽する必要があるケースは限られると思うんですが、いざ隠蔽したいケースが出てきた時はこういうテクニックを利用する事が出来ます。
homu.instance_exec { @__name__ } で丸見えなのはご愛嬌
わたしは mixin するモジュール内で『実装』と『UI』を分けたい時などに前者を隠蔽する目的で使用する事が稀によくあります。

まとめ2

Refinements は以下のようなケースで利用できるよ!!

  • 安全にクラス拡張する
    • gem では refine でクラス拡張する!!!
    • Refinements 化しておくことで利用者の安心感を得る
  • 雑にメソッドを生やす
    • その場でメソッドを生やして可読性を Up!
    • 草みたいにどんどん生やしていこう
  • そのメソッド、完全に隠蔽出来るよ!!
    • 妖怪メソッド隠し

ここまでは Refinements のポジティブな面を見てきたのですが、次からは闇の部分になります。

Refinements の落とし穴

ここまでで Refinements が以下に便利な機能なのかわかってきたと思います。
しかし、残念ながら万能に見える Refinements もそこまで万能ではありません。
むしろ実際に使おうと思うとクソだなと思うことの方が多いです、

適用されるコンテキストが限定的すぎる

まず、大前提なのですが Refinements で定義したメソッドが呼び出せるのは『using されたコンテキスト範囲』です。
逆にいえば『using していないコンテキスト』ではそのメソッドを呼び出すことは出来ません。
どういうことかというと、次のような呼び出しはエラーになります。

def meth a
  a.twice
end

using Module.new {
  refine Object do
    def twice
      self + self
    end
  end
}

# OK: ここでは呼び出せる
10.twice

# NG: ここでは呼び出せない
meth 10

まあこれはコード見たらそれはそうって感じがしますね。

では、次のようなコードで考えてみましょう。
#grep は内部で #=== を使用して比較を行っています。
ですので、次のように #=== を定義する事で挙動を変更する事が出来ます。

class String
  def === other
    self == other.to_s
  end
end

p "homu" === :homu
# => true

# #=== を拡張する事で #grep でも String === Symbol 出来るようになる
p [:homu, :mami, :mado].grep "homu"
# => [:homu]

しかし、ここまで読んでいた方であればこんなコードはすぐに Refinements 化したくなりますよね。
と、言うわけで素直に Refinements 化してみますが…。

# Refinements でこのコンテキストでのみ動作するようにする
using Module.new {
  refine String do
    def === other
      self == other.to_s
    end
  end
}

p "homu" === :homu
# => true

# 先程であれば [:homu] が返ってきたが [] が返ってくる…
p [:homu, :mami, :mado].grep "homu"
# => []

このように何故か #=== が適用されなくなってしまいました。 ナンデナンデ!
なぜ、このようになってしまうのかというと(当然ではあるんですが)『#grep のメソッド内では using が反映されないから』です。

そうです、お察しの通り Refinements はダックタイピングにめちゃくちゃ弱いんです… Ruby なのに…どうして…どうして…。
using したコンテキストでメソッドを呼び出す場合には問題がないんですが『メソッド内で呼ばれる事を期待するメソッド』に Refinements は全く役に立ちません。
どうしたもんですかね、これ。

Ruby の標準メソッドでも対応していないよ!!

Refinements の受難はまだ続きます。
Ruby ではしばしばメソッド名で処理を行ったりする事があります。

# メソッドを動的に呼び出したり
p -42.send(:abs)         # => 42
p -42.public_send(:abs)  # => 42

# メソッドが呼び出せるか確認したり
p 42.respond_to? :abs    # true
p 42.respond_to? :hoge   # false

# メソッドをオブジェクト化したり
pow = 2.method(:pow)
p pow.call 3
# => 8

# メソッド名一覧を取得したり
p 2.methods
# [:-@, :**, :<=>, :upto, :<<, :<=, :>=, :==, :chr, :===, :>>, :[], :%, :&, :inspect, :+, :ord, :-, :/, :*, :size, :succ, :<, :>, :to_int, :coerce, :divmod, :to_s, :to_i, :fdiv, :modulo, :remainder, :abs, :magnitude, :integer?, :numerator, :denominator, :to_r, :floor, :ceil, :round, :truncate, :gcd, :to_f, :^, :odd?, :even?, :allbits?, :anybits?, :nobits?, :downto, :times, :pred, :pow, :bit_length, :digits, :rationalize, :lcm, :gcdlcm, :next, :div, :|, :~, :+@, :eql?, :singleton_method_added, :i, :real?, :zero?, :nonzero?, :finite?, :infinite?, :step, :positive?, :negative?, :arg, :rectangular, :rect, :real, :imag, :abs2, :imaginary, :angle, :phase, :conjugate, :to_c, :polar, :conj, :clone, :dup, :quo, :between?, :clamp, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_get, :public_methods, :instance_variables, :method, :public_method, :singleton_method, :define_singleton_method, :public_send, :extend, :to_enum, :enum_for, :pp, :=~, :!~, :respond_to?, :freeze, :object_id, :send, :display, :nil?, :hash, :class, :singleton_class, :itself, :yield_self, :taint, :untaint, :tainted?, :untrusted?, :untrust, :frozen?, :trust, :singleton_methods, :methods, :private_methods, :protected_methods, :!, :equal?, :instance_eval, :instance_exec, :!=, :__id__, :__send__]

このあたりは普通に Ruby を使っていてもしばしば使うことがあると思います。
では、このようなメソッドに対して Refinements で定義したメソッドを渡してみると…。

using Module.new {
  refine Object do
    def twice
      self + self
    end
  end
}

# OK
p 42.send(:twice)
# => 84

# NG: undefined method `twice' for 42:Integer (NoMethodError)
42.public_send(:twice)

# NG: false が返ってくる…。  
p 42.respond_to? :twice    # false

# NG: undefined method `twice' for class `Integer' (NameError)
2.method(:twice)

# NG: 含まれていない…
p 2.methods.include? :twice

なんと #send 以外全部 Refinements が反映されていません!!!
ほんまか???Ruby 大丈夫?????バグってない?????
42.respond_to? :twicefalse を返すのに 42.twice は呼び出し可能な不思議!!
#send は Refinements が反映されているけど #public_send は動かない!!
ないよ、メソッドないよぉ!!!!!

と、いう感じで標準メソッドでも Refinements が考慮されていないメソッドはかなりあります。
むしろ対応しているメソッドを上げたほうが早い

method_missingconst_missingmethod_added が呼ばれない…

もうやめたげてぇ!となりますがまだまだ続きます。
method_missingconst_missingmethod_added といえば Ruby の黒魔術には欠かせませんね。
これらのメソッドはあまりにも闇が深すぎるのでなるべく副作用を抑えるべく Refinements で利用したいのですが…。

# Hash に対してキーの名前でメソッド参照したいが…
using Module.new {
    refine Hash do
        def method_missing(name)
            self[name.to_sym] || self[name.to_s]
        end
    end
}

hash = { name: "homu", "age" => 14 }
# NG: undefined method `name' for {:name=>"homu", "age"=>14}:Hash (NoMethodError)
pp hash.name

残念ながら利用することは出来ません…。
こういう時こそ Refinements を利用したいのに恥ずかしくないの???

それでも希望はある

ここまで見て『なんだよ Refinements 残念だなあ』と思った方も多いと思います。
しかし、安心してください。
最初はガチガチだった Refinements もだんだん緩和されてきています。

例えば、先程上げた #send も実は Refinements が実装された当初は動作しませんでしたが Ruby 2.4 で緩和されました。
また、&:hoge のような Symbol#to_proc 呼び出しも同様に Ruby 2.4 から呼び出せるようになり、文字列の式展開(#{こういうの})で呼び出される #to_sRuby 2.5 から Refinements が適用されるようになっています。

using Module.new {
  refine Integer do
    def twice
      self + self
    end

    def to_s
      "call to to_s"
    end
  end
}

# 以下は問題なく動作する
p 42.send(:twice)
# => 84

p (1..10).map(&:twice)
# => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

p "#{42}"
# => "call to to_s"

なんだよ、Refinements やるじゃん。

更に Ruby 2.6 では…

更にもうすぐリリースされる Ruby 2.6 では以下のメソッドで Refinements が有効になっています。

また、以下のパッチも投げられているのでもしかしたら Ruby 2.6 に入るかも…?(入らないかも…

今後の Refinements には期待できそうですね!!!
あるよ、メソッドあるよぉ!!!!!
ちなみに上のパッチは全部わたしが書いて投げましたてへぺろ

まとめ

と、言う感じで Refinements に関して長々と書いてみました。
Refinements って言うと使う人が限られると思っている人も多いと思いますが、全然そんなことはなくて実際に使ってみるとものすごく使い勝手がよく(そしてとても悪く)て色んな所で Refinements を使いたいと思うようになります。
また、Refinements がいくらクソと言ってもRuby という言語の性質上、クラス拡張の範囲を制限する必要はどうしても必要になって来ると思います。
と、いうか各々が好き勝手クラス拡張しまくってるとそれこそ破綻していき Ruby オワタになってしまう…。

よくわからないクラス拡張に刺されないように gem を作る人は必ず Refinements を使いましょう。
そうでない人も Refinements を使って恐れることなくクラス拡張を使っていきましょう。
クラス拡張は Ruby をいう言語を書く上での醍醐味なので使わないともったいないなあ、というのがわたしの意見です。
Ruby でクラス拡張しないなんて C++C言語書いているようなものですよ!!C++20 早く!!!!
さて、Refinements 関連で書き足りないことがたくさんあるんですが、そろそろ時間なのでこの辺にしておきます。
もっと闇の深い Refinements はまた次の機会にでも…。
Refinements 便利なのでみんな使おう!!!
そしてどんどんよくしていこう!!!
来年もどんどんパッチ投げていくぞ!!!!

こういうのは Refinements に向かない

最後に覚え書き程度に書いておきます。

  • 毎ファイルで using するのがめんどくさい
    • Rails みたいなフレームワークを使うとファイル数が無限に増えてくるので汎用的なメソッドの場合、ファイル毎に using するのがかなり手間
    • もうちょっと広い範囲で using を制御したい
  • デバッグとかで使うなら素直にモンキーパッチでいいと思う
    • コンテキストが限られているという前提。 .pryrc で定義するとか
    • 実際デバッグ用のライブラリを Refinements 化してみたけどデバッグする度に using するのがクッソだるい

では、よい Refinements ライフを。