せかいや

いまいるここを、おもしろく http://sekai-in-the-box.appspot.com/

【Ruby】パーフェクトRuby 学習感想文 ~余談:Moduleのinclude、extend

 
何でModuleモジュールのプライベートメソッドが任意のクラスで見えるんだー
って書いてたら
師匠からメールが来た

module  をmix-inするには
include
extend
の2種類あります。
この違いを理解すれば、わかるかと。

そうなのか。
なら理解してみよう!


 

状況のまとめ

p Module.private_instance_methods

■実行結果

[:included, :extended, :prepended, :method_added, :method_removed, :method_undefined, :initialize_copy, :attr, :attr_reader, :attr_writer, :attr_accessor, :initialize, :remove_const, :append_features, :extend_object, :include, :prepend_features, :prepend, :refine, :remove_method, :undef_method, :alias_method, :public, :protected, :private, :module_function, :define_method, :initialize_dup, :initialize_clone, :sprintf, :format, :Integer, :Float, :String, :Array, :Hash, :warn, :raise, :fail, :global_variables, :__method__, :__callee__, :__dir__, :eval, :local_variables, :iterator?, :block_given?, :catch, :throw, :loop, :respond_to_missing?, :trace_var, :untrace_var, :at_exit, :syscall, :open, :printf, :print, :putc, :puts, :gets, :readline, :select, :readlines, :`, :p, :test, :srand, :rand, :trap, :exec, :fork, :exit!, :system, :spawn, :sleep, :exit, :abort, :load, :require, :require_relative, :proc, :lambda, :binding, :caller, :caller_locations, :Rational, :Complex, :set_trace_func, :gem, :gem_original_require, :singleton_method_added, :singleton_method_removed, :singleton_method_undefined, :method_missing]

 ⇒ Moduleのプライベートメソッド一覧にattr_accessor がいる。

 

class Hoge
end
p Hoge.method(:attr_accessor)
p Hoge.new.method(:attr_accessor)

■実行結果

#<Method: Class(Module)#attr_accessor>
`method': undefined method `attr_accessor' for class `Hoge' (NameError)

 ⇒ Hogeオブジェクトと、Hoge.newオブジェクトで見えてるメソッドが違う
なんでHoge.methodだと、他のクラスのプライベートメソッドまで取れるんだろう。

いまこういう状況です。


 

include? exclude?

本にはこう書いてある(P131,133)

インスタンスメソッドとしてモジュールを取り込むにはincludeを用います。
オブジェクトにモジュールのメソッドを取り込むにはextendを用います。
クラスもまたオブジェクトの一つですので、モジュールをextendすることが出来ます。

 
include インスタンスメソッドとして取り込む
extend クラスメソッドとして取り込む

module Momo
  def hello
    p "oha"
  end
end

class Aaa
  include Momo
end

class Bbb
  extend Momo
end

Aaa.new.hello
Bbb.hello

■実行結果

"oha"
"oha"

ふむ。
プライベートだったら?

module Momo
  def hello
    p "oha"
  end
  private :hello
end

class Aaa
  include Momo
end

class Bbb
  extend Momo
end

Aaa.new.hello
Bbb.hello

■実行結果

private method `hello' called for #<Aaa:0x2cf7c18> (NoMethodError)
private method `hello' called for Bbb:Class (NoMethodError)

どっちもエラー。
そうやんね。プライベートメソッドはレシーバつきで呼び出せないから。

あ!
クラスの地の文のselfはClassクラスのオブジェクトだった!
 ⇒http://sekai.hateblo.jp/entry/2013/08/22/063502
だから、
extendしたクラスの地の文では、Momoモジュールのメソッドをレシーバなしで呼べる
のでは!?
やってみよう

module Momo
  def hello
    p "oha"
  end
  private :hello
end

class Aaa
  include Momo
end

class Bbb
  extend Momo
  hello
end

p Bbb.method(:hello)

■実行結果

"oha"
#<Method: Class(Momo)#hello>

 
やっぱり!!
Modleをextendしたクラスは、クラス内の地の文では、Moduleのプライベートメソッドが実行できるんだ!

Rubyにおけるprivateの概念って、自分のクラスで閉じているという意味ではなくて、
レシーバがある・ない、で考えたほうがしっくり来る。
レシーバを省略した形でも呼べる場所であるかぎり、どこでも呼べる。


 

もう一回attr_accessorを自作してみる

前の記事では
attr_accessorを自作したものの、レシーバをつけないと呼び出せなかったけど
プライベートメソッドにすればいけるのか。


 

挫折の状況(ふりかえり)

前はこんなコードを書いていた↓。 Sekai.attr_sekaiじゃないと実行できなかった。

module Sekai
  def self.attr_sekai(attr)
    eval <<-EOD
      @#{attr}
      def #{attr}=(val)
        @#{attr} = val
      end
      def #{attr}
        @#{attr}.upcase
      end
    EOD
  end
end

class Hoge
  include Sekai
  Sekai.attr_sekai :name
end

hoge = Hoge.new
hoge.name= "tanaka"
p hoge.name

■実行結果

"TANAKA"

こうすれば↓

module Sekai
  def attr_sekai(attr)
    eval <<-EOD
      @#{attr}
      def #{attr}=(val)
        @#{attr} = val
      end
      def #{attr}
        @#{attr}.upcase
      end
    EOD
  end
  private :attr_sekai
end

class Hoge
  extend Sekai
  attr_sekai :name
end

hoge = Hoge.new
hoge.name= "tanaka"
p hoge.name

■実行結果

undefined method `name=' for #<Hoge:0x2b874d0> (NoMethodError)

レシーバなしで attr_sekai がよびだせた。
でもextend Sekai にしてしまうとeval で作成するメソッドも
クラスメソッドとして認識されちゃう。だからエラー。
こんなコードしか実行できない↓

module Sekai
  def attr_sekai(attr)
  
    eval <<-EOD
      @#{attr}
      def #{attr}=(val)
        @#{attr} = val
      end
      def #{attr}
        @#{attr}.upcase
      end
    EOD
  end
  
  #private :attr_sekai
end

class Hoge
  extend Sekai
  attr_sekai :name
end

Hoge.name ="maeda"
p Hoge.name

■実行結果

"MAEDA"

モジュールにクラスメソッド/インスタンスメソッド、
どちらも持ちたい場合はどうすれば?

まて次号(あるのか?)