matzryo / mynotes

自分の学習メモ帳
0 stars 0 forks source link

refinmentsでprotectedメソッドを期待通り動かせない #3

Open matzryo opened 6 years ago

matzryo commented 6 years ago

module MyQuickSort1
  refine Array do
    def my_quicksort
      # 非破壊的メソッドにしたい
      clone.my_inner_quicksort
    end

    protected

    def my_inner_quicksort
      return self if length <= 1

      small, large = my_partition

      small.my_quicksort + large.my_quicksort
    end

    private

    def my_partition
      left = 0
      right = length - 1

      while left < right
        left, right = my_search(left, right)
        break if left >= right
        my_swap(left, right)
      end

      [self[0...left], self[left..-1]]
    end

    def pivot
      self[(length / 2)]
    end

    def my_search(left, right)
      left += 1 while self[left] < pivot
      right -= 1 while self[right] > pivot
      [left, right]
    end

    def my_swap(one, another)
      tmp = self[one]
      self[one] = self[another]
      self[another] = tmp
    end
  end
end

using MyQuickSort1

[].my_quicksort 
=> Failure/Error: clone.my_inner_quicksort
=> NoMethodError:
=>  protected method `my_inner_quicksort' called for []:Array
matzryo commented 6 years ago

Arrayをモンキーパッチしたときは、うまく動いていた。

matzryo commented 6 years ago

refineは何をしているのか?protectedメソッドはどういう場合は呼べるのか?

matzryo commented 6 years ago

protected =>

『そのメソッドを定義したクラス自身と、そのサブクラスのインスタンスメソッドからレシーバ付きで呼び出せます』(プロを目指す人のためのRuby p251)

using以降は、Arrayにメソッドが付与されているはず。だったら my_inner_quicksort も呼べるはずでは?

matzryo commented 6 years ago

そもそも、publicで追加したつもりのmy_quicksortメソッドも、#respond?to? には引っかからないが、見つかってはいる。この挙動も自分の想定外。

   104: using MyQuickSort1
   105:
   106: byebug
   107:
=> 108: [].my_quicksort
(byebug) [].respond_to? :my_quicksort
false
(byebug) [].my_quicksort
*** NoMethodError Exception: protected method `my_inner_quicksort' called for []:Array

nil
matzryo commented 6 years ago

Refinmentのドキュメント

によると、respond_to? などの間接的なメソッド探索ではrefinementsを考慮しないとのこと。上の挙動はそれで説明できる。

matzryo commented 6 years ago

Module#refineで無名モジュールが作成されるらしい。refineブロック内の#selfがその無名モジュールらしい。

   39: end
   40:
   41: module MyQuickSort1
   42:   refine Array do
   43:     byebug
=> 44:     def my_quicksort
   45:       # 非破壊的メソッドにしたい
   46:       clone.my_inner_quicksort
   47:     end
   48:
(byebug) self
#<refinement:Array@MyQuickSort1>
matzryo commented 6 years ago

ancestors, included_modulesにも出てこない。

   100:       smaller.my_quicksort2.append + [pivot] + larger.my_quicksort2
   101:     end
   102:   end
   103: end
   104:
   105: using MyQuickSort1
   106:
   107: byebug
   108:
=> 109: [].my_quicksort
(byebug) [].class.ancestors
[Array, Enumerable, Object, Kernel, BasicObject]
(byebug) [].class.included_modules
[Enumerable, Kernel]
matzryo commented 6 years ago

defined refinement table: A table which holds refinements defined in a module. Keys are classes to be refined, and values are refinements.

これを確認したい

matzryo commented 6 years ago

https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/RefinementsSpec#Method-lookup-with-refinements

fefinementで定義するメソッドは、refineされるクラスのサブクラスよりあと、refineされるクラスより前に探索される。prependのようなものかな。

matzryo commented 6 years ago

https://docs.ruby-lang.org/ja/latest/doc/spec=2fdef.html#limit

protected に設定されたメソッドは、そのメソッドを持つオブジェクトが selfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せ ます。

そのメソッドを持つオブジェクト、というのは、無名モジュールか? それが[].my_quicksortだと、self => []になる。

   41: module MyQuickSort1
   42:   refine Array do
   43:     def my_quicksort
   44:       byebug
   45:       # 非破壊的メソッドにしたい
=> 46:       clone.my_inner_quicksort
   47:     end
   48:
   49:     protected
   50:
(byebug) self
[]
matzryo commented 6 years ago

今回は、

refinement:Array@MyQuickSort1 -> Arrayの順にメソッド探索される。

my_quicksort1メソッドは、#refinement:Array@MyQuickSort1 に属していると推測する。

Ruby 2.5.0 リファレンスマニュアルによって考える限り、 self[]は、そのメソッド(my_quicksort1)を持つと思うので呼べる気がするのだが。(間接的なメソッド探索では考慮されないのが関係しているのか?)

プロを目指す人のためのRuby入門に沿って考える限り、 Arrayは、#refinement:Array@MyQuickSort1自身でなければ、#refinement:Array@MyQuickSort1のサブクラスでもない。だからArrayから呼べないのか?

matzryo commented 6 years ago

prependにしたら、関節的なメソッド探索にひっかかるようになった。そして、protectedが効くようになった!

    97:   prepend MyQuickSort1
    98:   prepend MyQuickSort2
    99:   prepend MyInclude
   100:   prepend MyFetch
   101:   prepend MyConcat
   102: end
   103:
   104: byebug
   105:
=> 106: []
(byebug) [].class.ancestors
[MyConcat, MyFetch, MyInclude, MyQuickSort2, MyQuickSort1, Array, Enumerable, Object, Kernel, BasicObject]
(byebug) [].respond_to? :my_quicksort
true
(byebug) [].class.included_modules
[MyConcat, MyFetch, MyInclude, MyQuickSort2, MyQuickSort1, Enumerable, Kernel]
(byebug) c
...........

Finished in 0.01483 seconds (files took 1 minute 24.21 seconds to load)
11 examples, 0 failures
matzryo commented 6 years ago

refinementsでできるのは無名モジュールで、関節的なメソッド探索に考慮されない。prependは、名前のあるモジュールで、間接的な探索で考慮される。これが違いか?

matzryo commented 6 years ago

参考

https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/RefinementsSpec https://techracho.bpsinc.jp/hachi8833/2017_03_23/37464