Ruby の refinements の使い途

Ruby で，特定のクラスやモジュールを拡張する方法として refinements というものがある。Ruby 2.0 で試験的に導入され，Ruby 2.1 で正式に採用された。

一言で言えば，「拡張はするんだけど，その拡張はあるスコープ内でのみ効く。スコープ外では元のまま」を実現する仕組みだ。

ここでは，単位換算のために Numeric を拡張するということをやってみる。

# 動機

cairo というグラフィックライブラリーがある。PDF や PNG や EPS なんかを統一的なインターフェースで描画・生成することができる。

Ruby にはその名もずばり `cairo` という gem があり，cairo の機能が簡単に使える。

このライブラリーで PDF を作るとき，寸法の単位は pt（ポイント）を使用しなければならない。

そのため，たとえば，A4 判（横 210 mm，縦 297 mm）の PDF ファイルを作ろうとすると，単位の換算の式を入れて，

```rb
require 'cairo'

width = 210 / 25.4 * 72 # ← ここで mm を pt に換算してる
height = 297 / 25.4 * 72

surface = Cairo::PDFSurface.new 'sample.pdf', width, height
context = Cairo::Context.new surface

# コンテキストに描画

surface.finish
```

と書くことになる。
（このスクリプトを実行すると白紙の 1 ページだけの PDF ファイルが実際に出来る）

※cairo の pt は「DTP ポイント」と呼ばれるやつで，1 pt = 1/72 inch。

でも，この単位換算が面倒くさい。あらゆる場所で mm を pt に換算しなければならない。
もちろん

```rb
def mm2pt(x)
  x / 25.4 * 72
end
```

なんて換算メソッドを作ってもいいのだが，いっそのこと `Numeric` を拡張して，

```rb
class Numeric
  def mm
    self / 25.4 * 72
  end
end
```

みたいにしておけば，

```rb
width = 210.mm
height = 297.mm
```

なんて書けて，ちょっとクールじゃね？

（ちなみに `12.5.mm` みたく数値に小数点があっても大丈夫）

回転角などの角度も `cairo` ではラジアンで指定しなきゃいけないんだが，

```rb
class Numeric
  def deg
    self * Math::PI / 180
  end
end
```

と `deg` を定義しておけば，

```rb
angle = 90.deg # 90度
```

なんて書けていいだろ。

# ちょっと待て

いくら〈Ruby は組込みクラスもいじり放題〉だと言っても，これはちょっと乱暴じゃないか？

自分だけで使う小さなスクリプトならいいけど，スクリプトが大きく複雑になってくると，`Numeric` みたいな基礎的なクラスにグローバルに手を入れるのは危なっかしい[^mathn]。

[^mathn]: mathn ライブラリー（Ruby 2.2 で非推奨）を require して `/` の挙動が変って泣いた人いるよね？　ね？

ライブラリーなんかでは，絶対こんなことやってはいけない。
いや，そもそも，だ。この拡張によって `20.mm` が 20 ミリメートルを意味するのは，換算後の数値が pt 単位であることを前提にしているが，このことに一般性は無い。
つまり cairo じゃなく他の目的でなら，インチやメートルに換算したいかもしれないではないか。

ではどうすればいいのか。

# refinements 参上

もしも，

* `20.mm` のような表記を使いたい場所でのみ `Numeric` を拡張し，
* 他の場所では `Numeric` を無傷で残す

ことができるなら，問題は一応解決するだろう。

そのために用いるのが  refinements という仕組み。

まず以下のようなモジュールを定義する。これは `Numeric` クラスを拡張するためのモジュールだ。

```rb
module NumericExt
  refine Numeric do
    def mm
      self * 72 / 25.4
    end

    def deg
      self * Math::PI / 180
    end
  end
end
```

モジュール名の `Ext` は extension（拡張）から名付けてみたが，好きに命名していい。

`refine` はメソッドだ。引数として，拡張したいモジュールを与える。もちろんクラスはモジュールの一種だから，クラスを与えることもできる。

そして，ブロックの中でメソッドを定義する。いや，メソッドだけでなく定数などを定義したっていい。

しかし，これだけでは `Numeric` には何の変化も起こらない。実際に拡張を行うには，

```rb
using NumericExt
```

と書く。すると，〈`using NumericExt` したスコープ〉の内部でのみ `Numeric` が拡張される。

スコープに注意すれば，十分に狭い範囲でのみ，このパワーアップした `Numeric` が使えることになる。

http://docs.ruby-lang.org/ja/2.2.0/class/Module.html#I_REFINE

# スコープ

では，`using` が有効となるスコープはどうなっているのか。

ローカル変数のスコープなんかよりはずっと広いようだ。

詳細は下記（英語）を見ていただくとして，ここでは少しだけ見てみよう。

https://docs.ruby-lang.org/en/master/syntax/refinements_rdoc.html#label-Scope

## `using` したところ以降

当たり前っぽいが `using` より前はスコープ外だ。

```rb
# ここでは無効
using NumericExt
# ここ以降は有効
```

これは，ローカル変数のスコープが，最初の代入以降であることと似ている。

## モジュール定義の中

```rb
# 無効
module M
  # 無効
  using NumericExt
  # 有効
end
# 無効
```

クラスもモジュールの一種なので，クラス定義内も同様だ。

注意したいのは，〈モジュール定義の中で `using` してれば，そのモジュールの中では有効〉というわけ **ではない** ことだ。以下の例を見てほしい。

```rb
module M
  using NumericExt
end

module M
  # 無効
end
```

モジュールの中ではなく，あくまで **モジュール定義の中** なんである。

## 有効なスコープ内のメソッド定義の中

```rb
using NumericExt

def foo
  # 有効
end
```

モジュールの話と絡めると，以下のこともいえる。

```rb
module M
  using NumericExt

  def m
    # 有効
  end
end

module M
  def m2
    # 無効
  end
end
```

# おわりに

組込みクラスでも何でも拡張しちゃうぜ！という Ruby 的奔放さをスコープという箱庭に閉じ込め，柔軟性と保守性を両立する面白い仕組みだと思う。

みなさんの refinements の使い途を教えてください。