Ruby でワンライナーでズンドコキヨシ

元ネタ

元ネタが2年以上前でめちゃくちゃ今更なんですが、やってみた。
脳内で考えたコードがワンライナーでそのまま動作したのでちょっとうれしかった

Ruby でズンドコキヨシ

puts (1..).lazy.map { %w(ズン ドコ).sample }.each_cons(5).map(&method(:p)).find(&%w(ズン ズン ズン ズン ドコ).method(:==)).join
puts "キ・ヨ・シ!"
# =>
# ["ズン", "ドコ", "ズン", "ドコ", "ドコ"]
# ["ドコ", "ズン", "ドコ", "ドコ", "ズン"]
# ["ズン", "ドコ", "ドコ", "ズン", "ズン"]
# ["ドコ", "ドコ", "ズン", "ズン", "ドコ"]
# ["ドコ", "ズン", "ズン", "ドコ", "ズン"]
# ["ズン", "ズン", "ドコ", "ズン", "ズン"]
# ["ズン", "ドコ", "ズン", "ズン", "ドコ"]
# ["ドコ", "ズン", "ズン", "ドコ", "ズン"]
# ["ズン", "ズン", "ドコ", "ズン", "ズン"]
# ["ズン", "ドコ", "ズン", "ズン", "ドコ"]
# ["ドコ", "ズン", "ズン", "ドコ", "ドコ"]
# ["ズン", "ズン", "ドコ", "ドコ", "ズン"]
# ["ズン", "ドコ", "ドコ", "ズン", "ズン"]
# ["ドコ", "ドコ", "ズン", "ズン", "ズン"]
# ["ドコ", "ズン", "ズン", "ズン", "ズン"]
# ["ズン", "ズン", "ズン", "ズン", "ドコ"]
# ズンズンズンズンドコ
# キ・ヨ・シ!

ワンライナーで書いているのでちょっと複雑かもしれませんが、やっていることは至極単純で

(1..)                           # Ruby 2.6 から入った (1..) で無限配列を生成
.lazy                           # 遅延処理するようにする
.map { %w(ズン ドコ).sample }   # ランダムな "ズン" "ドコ" な配列に変換
.each_cons(5)                   # 無限リストを5個ずつの配列に分割
.map(&method(:p))               # この時点で一旦出力
.find(&%w(ズン ズン ズン ズン ドコ).method(:==))  # ["ズン", "ズン", "ズン", "ズン", "ドコ"] の組み合わせを探す
.join                           # 結果を結合

無限リスト + #lazy を使って遅延処理しつつ、#each_cons(5) で分割して ["ズン", "ズン", "ズン", "ズン", "ドコ"] という並びがあれば終了する、っていう処理です。
やってることは割と力技。
ちなみに map(&%w(ズン ドコ).method(:sample)) と書きたかったんですが、#sample が引数を受け取ってしまうのでダメでした。

Ruby 2.6 に投げたパッチが取り込まれた話

先日 Ruby 2.6 がリリースされましたが、それにわたしが投げたパッチが4つ含まれています。
と、いうことで具体的にどんなパッチを投げてどんな機能が取り込まれたのかを簡単に書いてみようかと

Refinements で定義した #to_proc が &hoge 時に呼ばれないのを緩和する提案

表題そのままなんですが、次のように Refinements で to_proc を定義した場合、&hogeto_proc が呼ばれなかったのを呼ばれるようにしたパッチです。

using Module.new {
    # Refinements で to_proc を定義
    refine String do
        def to_proc
            proc { |it| it.send self }
        end
    end
}

p "upcase".to_proc.call "homu"
# => HOMU

# "upcase".to_proc が呼ばれるようになった
p %w(homu mami mado).map &"upcase"
# => ["HOMU", "MAMI", "MADO"]

このパッチを投げたのは Ruby 2.5 のリリース直前で、ドサクサに紛れて取り込まれないかなーという気持ちで投げました。
まあ流石に直前だと無理だったので Ruby 2.5 には入りませんでしたが、パッチ自体は特に問題なく取り込まれて Ruby 2.6 で正式に使えるようになりました。
ここから怒涛の Refinements パッチが始まります。

Proposal: Enable refinements to #public_send

先程の to_proc のパッチが取り込まれてからだいぶ時間が経って、11月取り込まれたパッチになります。
前回からだいぶ間が空いているんですが、この間にもいくつかパッチを投げており…。
matz に『日本語よりも英語で上げたほうがいいよー』とツッコまれたのでこのあたりからがんばって英語でパッチを投げるようにしました。
パッチも diff ファイルを直接添付していたんですが、CI を回したかったので github 経由で pull request を投げるようにしています。
パッチ自体は以下のように『public_send でも Refinements を有効にする』と言うような内容です。

using Module.new {
    # Refinements でメソッドを定義
    refine String do
        def twice
            self + self
        end
    end
}

# public_send 経由でもメソッドが呼ばれるようになった
p "homu".public_send(:twice)
# => "hogehoge"

まあ #send も Refinements が有効になっているから #public_send も有効になってもいいよねーという気持ちで提案しました。
提案したものの(Refinements 的な意味で)議論されて取り込まれるまでに時間がかかるかなー、と心配していたんですが割とあっさり入ったのでちょっと面食らいました。
感触としては Ruby の方針としてはこのあたりはそこまで厳密に Refinements を制限する、という感じではなさそうな感じなんですかね。

Proposal: Enable refinements to #respond_to?

これも先程の #public_send と同じで『#public_send で Refinements を有効にする』というパッチになります。

using Module.new {
    # Refinements でメソッドを定義
    refine String do
        def twice
            self + self
        end
    end
}

p "homu".respond_to?(:twice)
# => true

こちらも #public_send と同じタイミングで取り込まれました。

Ruby で定義したメソッドに&:hogeを渡しても refinements が有効にならない

これは今回投げた中では一番大変なパッチでした。
このパッチを投げた経緯はこちらを参照してもらうと詳しく書いてあります。

パッチ自体は9月ぐらいに書いてすぐに投げたんですが、その後特に進展がなく、先月頭ぐらいにバグ認定されて、今月の頭に取り込まれたんですが、テストで落ちてたみたいで一旦 revert されて…という経緯があります。
Ruby 2.6 に絶対に入れたかったのでテストで落ちていた箇所を直してもう一度パッチを投げたんですが、そしたら別の場所でテストが失敗してしまったらしくて…。
けど、原因を調べてみたら実際はこのパッチとは全然関係ない箇所で、しょうがないのでこっちで原因を調べてみたら実は Ruby のバグを踏んでいてそれを報告して…。

というのがここ1〜2週間のハイライトになります。
まさか trunk で Ruby のバグを踏んでいるとは思わなくて実はかなり悩んでいました。
まあ結果的にこの不具合が修正されたので Ruby 2.6 ではいい感じに Refinements が使えるようになると思います。

using Module.new {
    # Refinements でメソッドを定義
    refine String do
        def twice
            self + self
        end
    end
}

def meth &block
    block.call "homu"
end

# Ruby で定義したメソッドのブロック引数でも有効になった
p meth &:twice

これが今まで動かなかったんですよねー。
何が嬉しいかって ActiveRecord のリレーションで定義されている map のブロック引数とかでも Refinements が有効になったのが大きいかなーと思います。
ってか、それで使えなくて困っていたのでパッチ書いて投げました。

所感

と、言う感じで Ruby 2.6 で取り込まれたパッチをいくつか紹介しました。
改めて見ると全部 Refinements のパッチですね、これ。
Refinements はもっと使い勝手よくしたいんですよねえ。

ちなみに今回取り込まれたパッチは4つでしたが、実際にはこの倍近くのパッチを投げています。
本当は #method#method_missing で Refinements が有効になるパッチも投げていたんですが、残念ながら Ruby 2.6 には間に合いませんでした。
このあたりが Ruby 2.7 で取り込まれるとだいぶ Refinements が使いやすくなるかなーと思います。
Ruby 2.7 で入るといいですねえ。
そろそろ Ruby のパッチを書くのにも慣れてきたので、何か思いついたらまたパッチを投げていきたいですねー。
パッチを書くよりも取り込まれるまでが大変ですが…。

今日は Ruby 2.6 がリリースされる日!!

今日は年に一度の Ruby がリリースされる日ですね!!!
クリスマス?知らないですねえ…。

と、言うことで Ruby 2.6 が無事にリリースされました。やったー!

そんなわけで気になった機能をいくつか上げてみようかと思います。
ちなみに Ruby 2.6 にパッチを10個ぐらい投げた結果 4つ取り込まれました。わーい。

Kernel#thenKernel#yield_selfエイリアスとして追加

Kernel#yield_selfエイリアスとして Kernel#then が新しく追加されました。
yield_selfthen と短く書けるやったー、と思う一方 then って名前はどうなんですかね…という思いもあり…。
まあ文字数が半分以下になるのは喜ばしいこと。

Enumerable#filter Enumerable#filter! が追加

Enumerable#selectエイリアスとして Enumerable#filter が追加されました。
これで同機能の Enumerable の絞り込みメソッドが

  • Enumerable#select
  • Enumerable#find_all
  • Enumerable#filter

の3つに増えました。
知らない人から見るとますます混乱しそうな気もしますが…。
Ruby をやり始めた頃は『なんで filter がないんだよ』と思っていた思い出。

Proc#<<Proc#>> が追加

Proc にはいわゆる関数合成を行う演算子が新しく追加されました。

f = proc{|x| x + 2}
g = proc{|x| x * 3}
p (f << g).call(3) # -> 11; identical to f(g(3))
p (f >> g).call(3) # -> 15; identical to g(f(3))

f << gf(g()) になり、f >> gg(f()) になります。
また、Method にも同等の演算子が追加されています。

f = proc{|x| x + 2}
g = proc{|x| x * 3}

p (1.method(:+) << 2.method(:*)).call(3)  # => 7  (1 + (2 * 3))
p (1.method(:+) >> 2.method(:*)).call(3)  # => 8  ((1 + 3) * 2)

# Proc と組み合わせたり
p (1.method(:+) << g).call(3)   # => 10  ((3 * 3) + 1)
p (f >> 2.method(:+)).call(3)   # => 7   ((3 + 2) + 2)

Proc#<<Proc#>> ともに『#call が定義されている』オブジェクトを渡すことが出来ます。

class G
    def call x
        x * 3
    end
end

f = proc{|x| x + 2}
g = G.new
p (f << g).call(3)  # => 11  f(g(3))

所感

と、いう感じで少しですが Ruby 2.6 の機能を取り上げてみました。
他には終端なし Range が追加されたり #to_h にブロック引数が追加されたりなどなど。
気になる方はリリースノートを見てみるといいともいます。
さて、Ruby 2.6 がリリースされたということは Ruby 2.7 の開発が始まったことになります。
Ruby 2.6 ではパッチを10個投げた結果 4つ取り込まれました。
Ruby 2.7 でもまだまだ追加したい機能がたくさんあるので、Ruby 2.7 ではもう少しがんばってパッチを投げていきたいと思います。
あと直せる範囲であればバグ修正とかも手伝えるといいですねえ…。
そんな感じで来年も Ruby 2.7 を楽しみにしています!!

Ruby における関数オブジェクトとブロック引数とは…?

Ruby 2.6 で追加される Proc#>>Symbol も渡したいよねー内部で #to_proc も呼び出してほしいよねーと考えた時の覚書。

現在

  • Proc#>> には #call が定義されているオブジェクトを渡せる

なにをしたい

  • Proc#>>#to_proc が定義されているオブジェクトも渡したい
  • method(:hoge) >> foo.to_procmethod(:hoge) >> foo と書きたい
  • Proc#>>Symbol も渡したい
  • #to_proc を Refinements で定義されている場合にも渡したい
class Array
  # to_proc を Refinements で定義したい…
  def to_proc
    proc { |*args|
      self.map { |it| it.to_proc.call(*args) }
    }
  end
end

User = Struct.new(:id, :name, :age)

users = [
  User.new(1, "Homu", 14),
  User.new(2, "Mami", 15),
  User.new(3, "Mado", 14),
]

users.map &method(:pp) >> [:name, :age]

メモ

#call について

  • Ruby では #call が生えているオブジェクトは関数オブジェクトとして扱われる
    • ProcMethod など
  • meth(&block) のようにブロック引数で受け取って block.call のように呼び出す

#to_proc について

  • #to_proc は『関数オブジェクト』を返すメソッド
  • #to_proc はブロック引数のシンタックスシュガーとして使われる
    • &hoge の時に hoge.to_proc が呼ばれる
  • #to_proc が定義されているだけでは『関数オブジェクト』と呼べない
  • ブロック引数で渡したいと気に #to_proc を定義する

Proc#>> について

  • Proc#>> は関数オブジェクトを受け取る
    • 内部で受け取ったオブジェクトの #call を呼び出している
  • なので内部で #to_proc を呼び出すような挙動は一貫性がない
    • #to_proc が定義されているだけのオブジェクトは『関数オブジェクト』ではない
  • Proc#>>#to_proc が定義されているオブジェクトを受け取りたい場合は Proc#>>(a) ではなくて Proc#>>(&block) で受け取るべき
    • 結果的に #to_proc が定義されているオブジェクトを渡すことが出来る
  • しかし method(:hoge).<< do ... end みたいに『ブロック構文』を使うような使い方は想定していない(と思うので) Proc#>>(&block) を定義するのはおかしい
    • #to_proc を呼び出したいだけのために &block 引数を定義するのはおかしい
  • Proc#(a, &block) みたいに定義することで #call#to_proc の両方を受け取ることが出来る
    • ただし、 a&block の両方を渡した場合にどうするのか、という問題は残る

まとめ

任意の関数で、

  • 関数オブジェクトを受け取りたい
  • ブロック引数で受け取りたい
    • ブロック引数で受け取るという前提で meth(&block) 渡しが出来る

を切り分ける必要がある。 今回の Proc#>> は『ブロック引数』で受け取るのではなくて『関数オブジェクト』を受け取るので #to_proc を渡すのは難しそう Proc#>> のように『関数オブジェクト』を期待するメソッドに対しては #call を定義したオブジェクトを渡すべき

まとめ2

  • Proc#>>Symbol を渡したいのであれば
    • Symbol#call を定義すべき
  • Proc#>>#to_proc が定義されているオブジェクトを渡したいのであれば
    • Proc#>>(&block) のようにブロック引数で受け取るべき
  • #to_proc を呼び出すシンタックスシュガーがほしい…
    • 例えば ~hogehoge.to_procシンタックスシュガーであれば(~ は仮
    • method(:foo) >> :hoge.to_procmethod(:foo) >> ~:hoge とかけたり when :even?.to_procwhen ~:even? とかけたりする

そもそも…

次のような構文はシンタックスエラーになる…

class X
    def << &block
    end
end

# syntax error, unexpected &
X.new << &:hoge

備考

  • 関数オブジェクト:#call が定義されているオブジェクト
  • ブロッカブルオブジェクト:#to_proc が定義されているオブジェクト
  • 両対応する場合、どうするのがよいか

コード例

Proc#>> に渡したいのであれば以下のように #to_proc を定義するのではなくて

class Array
  def to_proc
    proc { |*args|
      self.map { |it| it.to_proc.call(*args) }
    }
  end
end

User = Struct.new(:id, :name, :age)

users = [
  User.new(1, "Homu", 14),
  User.new(2, "Mami", 15),
  User.new(3, "Mado", 14),
]

users.map &method(:pp) >> [:name, :age]

以下のように #call を定義するべき

require "pp"

class Array
  def call *args
    self.map { |it| it.to_proc.call(*args) }
  end
end

User = Struct.new(:id, :name, :age)

users = [
  User.new(1, "Homu", 14),
  User.new(2, "Mami", 15),
  User.new(3, "Mado", 14),
]

users.map &(method(:pp) << [:name, :age])

【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 ライフを。

Ruby の Hash をソートする時の注意

Hash をソートしたい場合、Hash#sort でソートすることが出来るんですが、このメソッドは [key, value] の配列に変換してからソートが行われます。
なので Hash#sort の戻り値も配列になります。

hash = { b: "homu", c: "mami", a: "mado" }

# #sort の戻り値は [key, value] の配列になる
pp hash.sort
# => [[:a, "mado"], [:b, "homu"], [:c, "mami"]]

この時に結果を Hash 化したい場合は、#to_h を呼ぶことで Hash になります。

hash = { b: "homu", c: "mami", a: "mado" }

# #to_h で [key, value] の配列を Hash 化
pp hash.sort.to_h
# => {:a=>"mado", :b=>"homu", :c=>"mami"}

ちなみに value でソートしたい場合は、配列と同様に #sort_by が利用できます。

hash = { c: "mami", b: "homu", a: "mado", d: "an" }

# #sort_by で value を基準としたソート
pp hash.sort_by { |key, value| value }.to_h
# => {:d=>"an", :b=>"homu", :a=>"mado", :c=>"mami"}

# こうもかけるはず
pp hash.sort_by(&:last).to_h
# => {:d=>"an", :b=>"homu", :a=>"mado", :c=>"mami"}

知ってると便利

Ruby で yield_self のめっちゃ便利な使い道

Ruby 2.5 で追加された #yield_self という地味に便利なメソッドがあります。
これは『レシーバを引数としてブロックを呼び出す』というメソッドになります。
既存のメソッドとして #tap と似ていますが、#tap とは異なり『ブロックの戻り値を #yield_self の戻り値として』返します。

# ブロックの引数で yield_self のレシーバを受け取る
# #tap とは違いブロックの戻り値が yield_self の戻り値となる
p [1, 2, 3].yield_self { |it| it + [4, 5, 6] }
# => [1, 2, 3, 4, 5, 6]

どういう時に便利なの

例えば、次のように『hash でキーの値が存在すれば任意の処理を行う』というようなコードはよく書くと思います。

def meth opt
    # 存在するキーの値だけ result に突っ込んでいく
    result = []
    result << opt[:hoge] if opt[:hoge]
    result << opt[:foo]  if opt[:foo]
    result << opt[:bar]  if opt[:bar]
    result
end

pp meth(hoge: 1, foo: 2)
# => [1, 2]

しかし、上記の場合では opt[:hoge] というのを2回書く必要がありちょっと手間ですね。
こういう場合、#yield_self を使うことでスッキリと書くことが出来ます。

def meth opt
    result = []
    opt[:hoge]&.yield_self { |it| result << it }
    opt[:foo]&.yield_self  { |it| result << it }
    opt[:bar]&.yield_self  { |it| result << it }
    result
end

pp meth(hoge: 1, foo: 2)
# => [1, 2]

こんな感じで &. 演算子と合わせることで『レシーバが nil の場合はブロックを評価しない』というような処理を行うことが出来ます。
更に、次のように書くことも出来ます。

def meth opt
    result = []
    opt[:hoge]&.yield_self(&result.method(:<<))
    opt[:foo]&.yield_self(&result.method(:<<))
    opt[:bar]&.yield_self(&result.method(:<<))
    result
end

pp meth(hoge: 1, foo: 2)
# => [1, 2]

#yield_self はメソッドチェーンしている時に便利、と言われていましたがこういう使い方の方が有効な気がしてきましたね!!

更に更に Ruby 2.6 では

#yield_self はめちゃくちゃ便利という事がわかったと思うんですが、メソッド名が長いという根本的な問題がありました。
しかし、この問題は Ruby 2.6 で解決しており、Ruby 2.6 では #yield_self の別名として #then が追加されました。
そう、半分以上も短くなったんです!! それを踏まえた上で先程のコードを #then で置き換えてみましょう。

def meth opt
    result = []
    opt[:hoge]&.then { |it| result << it }
    opt[:foo]&.then  { |it| result << it }
    opt[:bar]&.then  { |it| result << it }
    result
end

pp meth(hoge: 1, foo: 2)
# => [1, 2]

ね? &.then の組み合わせはありだと思いませんか?