GWでまとまった時間をとれているので、『パーフェクト Ruby 』を久々に読み直しています。Rubyを深く勉強したいと思った時に、それに答えてくれるすばらしい本だとあらためて思いました!
この記事は「Part2言語仕様、Part3メタプログラミング 」を読んでいる中で忘れたくない部分を備忘録メモしただけの記事です。
(05-10 17:30) メタプログラミング再勉強
Part2言語仕様 🍄 帯域脱出catch・throw構文 catch :triple_loop do loop do puts 'one' loop do puts 'two' loop do puts 'three' throw :triple_loop end end end end #=> one two three と出力して終了
🗽 Procオブジェクトをブロックとして渡す 最近ちゃんとしたプログラミングでProcやlambda
をはじめて使いました。処理の再利用性や可読性をうまく高めるコトができるのでオススメです!
people = %w(John Doe Geoge) block = Proc.new { |name| puts "Hello #{name}!" } people.each &block #=> Hello John! #=> Hello Doe! #=> Hello Geoge!
🐡 Enumerable : reverse_each, each_slice # 末尾から逆順に繰り返す (1..3).reverse_each do |val| puts val end #=> "3" "2" "1" # 要素をn個ずつ区切って繰り返す (1..5).each_slice(2) do |a, b| p [a, b] end #=> [1, 2] [3, 4] [5, nil] # ブロックの戻り値が真となる要素をすべて返す p (1..5).to_a.select { |i| i.even? } #=> [2, 4] # selectの逆の動作をする p (1..5).to_a.reject { |i| i.even? } #=> [1, 3, 5] # 畳み込み演算 inject p (1..5).to_a.inject(:+) #=> 15
Part3メタプログラミング 🐰 class定義式とClass.new Rubyでクラスを定義するためには、class式
を使うのが一般的ですが、Class.new
でもクラスを定義できます。この2つの定義方法の違いは次のようなソースで現れます。
external_scope = 1 class ExpDefineClass puts external_scope # NoNameErrorが発生 end NewDefineClass = Class.new do puts external_scope #=> 1 end
Class.new
でクラスを定義すると外側のスコープを参照できるので、動的にクラスを定義する場合に有用です。
😎 クラス変数とクラスインスタンス変数 Rubyにはクラス変数(@@xxx
)とクラスオブジェクトがもつインスタンス変数(@xxx
)があります。
class Klass @class_instance_val = :class_instance_val @@class_val = :class_val def self.class_instance_val @class_instance_val end def instance_method puts @class_instance_val #=> nil puts @@class_val #=> :class_val end end Klass.class_instance_val #=> :class_instance_val klass = Klass.new klass.instance_method #=> nil #=> :class_val
クラス変数と、クラス・インスタンス変数の違いは次のとおり。
# クラス変数
* クラスに関わる全てで共通して使いたい情報を保持する
* 継承しても保持され続ける
# クラス・インスタンス変数
* そのクラスで閉じた情報を保持する
* 継承したクラスからは参照・変更ができない
🤔 メソッドの定義 モジュール#define_method
を使用すれば、メソッド定義式def xx; end
を省略して定義できます。
class Klass define_method :instance_method, -> { :instance_method } end object = Klass.new object.instance_method #=> :instance_method
これを応用すると、呼び出されるメソッドの優先順位を変更できます。
* 通常は継承した際に最後にインクルードしたメソッドが呼ばれる
* Module#define_methodを使うことで、特定のメソッドのみ継承ツリーを無視して利用できる
例としては次のとおり。
module FirstIncludeModule def same_name_method :first_same_method end end module SecondIncludeModule def same_name_method :second_same_method end end class Klass include FirstIncludeModule include SecondIncludeModule end object = Klass.new puts object.same_name_method # => :second_same_method class Klass include FirstIncludeModule include SecondIncludeModule define_method :same_name_method, FirstIncludeModule.instance_method(:same_name_method) end object = Klass.new puts object.same_name_method #=> :first_same_method
🚌 特異クラスと特異メソッド 特異クラスとは特異メソッドが定義されているクラス。
class OriginalClass end obj = OriginalClass.new def obj.new_singleton_method :new_singleton_method end puts obj.class.method_defined? :new_singleton_method #=> false puts obj.singleton_class.method_defined? :new_singleton_method #=> true
特異メソッドの作成タイミングは次のとおり。
* 特異メソッドを定義するタイミング
* 特異クラスの定義式を評価したタイミング
nil, Fixnum, Symbol
のインスタンスやtrue, false, nil
オブジェクトは特異クラスが定義できないようになっています。(TypeErrorが発生する)
また、特異クラスはインスタンスを生成しようとしたり、サブクラスを作ろうとするとエラーが発生する。
特異クラスのオブジェクトにモジュールの機能を組み込む場合は、Object#extend
を使う。
module ExtendedModule def extend_method :extend_method end end obj = Object.new obj.extend ExtendedModule puts obj.extend_method #=> :extend_method
👽 Rubyのメソッド探索順序 Rubyには特異メソッドをもつ特異クラスや、Mixinなどがありますが、それらを含むメソッド探索順序のまとめは次のとおりです。
(1) Rubyインタプリタはレシーバの特異クラスからメソッドを探す
(2) レシーバの直接のクラス(特異クラスの親クラス)からメソッドを探す
(3) 親クラスを参照してメソッドを探す。継承ツリーの一番上まで繰り返す
(4) 引数を『メソッド名、引数』にして method_missing を呼び出す
検索順序のサンプルソースはこちら。
module HelloModule def hello :hello_module end end class GrandParentClass def hello :grand_parent_hello end end class ParentClass < GrandParentClass include HelloModule end class ChildClass < ParentClass end child = ChildClass.new p child.hello #=> :hello_module
ちなみに『mod.ancestors
』メソッドを使うと、継承関係を確認できます。
🏀 モジュール#prepend Ruby 2。ゼロから追加されたメソッドで、モジュール#include
のようにクラスやモジュールに別のモジュールのメソッドを追加します。モジュール#include
との違いは、『自分で定義したメソッドよりも組み込んだメソッドが優先される 』点です。
module PrependModule def embeded_method :prepended_module end end class PrependedClass prepend PrependModule def embeded_method :prepended_class end end obj = PrependedClass.new obj.embeded_method #=> :prepended_module
これによりRailsのbefore_action
などのように、メソッドが呼び出される前に行う特定の処理を簡単に記述できます。
🎂 Refinements まず、Rubyのクラスは「オープンクラス」と呼ばれており、自由に書き換えることができます(別名ではモンキーパッチと呼ばれます)。例としてはこちら。
class String def good_night puts "#{self} good night" end end ’John’.good_night #=> 'John good night'
そして、Ruby 2。ゼロからはRefinementsによって同様のことができるようになりました。ただし、Ruby 2.0では実験的な機能として、ファイル単位に限定されていました。Ruby 2。ゼロからはモジュール内でのみ特定のクラスのメソッドを書き換えることができるようになりました。
具体的な使い方の例としてはこちら。
module RefineModule refine String do def good_night puts "#{self} good night" end end end using RefineModule 'John'.good_night #=> 'John good night'
モジュール#refine
とusing
メソッドで使うことができます。
「#Ruby 2.1どこが新しい?」問題解説記事
🐝 メソッドの定義場所を探す Method#source_location
を使うと、メソッドの定義場所を確認できます。
require 'csv' csv = CSV.new('') m = csv.method(:convert) m.source_location #=> ["/Users/komji/.rbenv/versions/2.1.0/lib/ruby/2.1.0/csv.rb", 1686]
🎉 オープンクラス Rubyでは定義済のクラスに対して、再度メソッドを定義し直したり、メソッドを追加できます。たとえば、ゼロからselfまでの配列を返す、Numeric#steps
を作ります。
class Numeric def steps return [] if self <= 0< span> 0.upto(self).to_a end end p 8.steps #=> [0, 1, 2, 3, 4, 5, 6, 7, 8] =>
オープンクラスは気軽に使える反面、影響が大きく、意図していない動きをする可能性があります。そのため、使い方にはくれぐれも注意すべきですし、Refinements
などで代替も要検討です!
🐮 BasicObject#method_missing BasicObject#method_missing
を使うと、定義されていないメソッドに対しても処理を行うことが可能です。
class DelegateArray def initialize @array = [] end def method_missing(name, *args) # 特定のメソッドのみ上書きするようにする if name == :<< return @array.__send__ name, *args end super end end da = DelegateArray.new da << 1 p da
存在しないメソッドを#method_missing
無いで存在しないメソッドを呼び出すと、SystemStackError
が発生します。
Active Recordではmethod_missing
をつかっているため、method_missingをオーバーライドすると予期せぬ動作をしすぎると思います。それを考えて、method_missingをオーバーライドする場合は、必要なメソッドのハンドリングのみ行い、super
を呼び出すほうが行儀のよいプログラムといえます。
🍣 evalについて eval入門: Rubyのyieldは羊の皮を被ったevalだ!
eval
のパートは単純な使い方は分かるんですが例題を読み進めるとかなり詰まってしまいました。ということで助けてもらったのが、『Rubyのyieldは羊の皮を被ったevalだ! 』です。@merborne さん、いつもありがとうございます!
evalを使って動的にメソッドを定義する eval
(evaluate/評価の略)の整理。たとえば次のように動的にメソッドを定義する際にeval
を使うと便利。
class Sample val_list = %w(first second third) val_list.each do |val| eval <<-evl< span> attr_reader :#{val}, :before_#{val} def #{val}=(val) @before_#{val} = @#{val} @#{val} = val end EVL end end obj = Sample.new obj.first = 10 puts obj.first #=> 10 obj.second = 20 puts obj.second #=> 20 obj.third = 30 puts obj.third #=> 30 obj.third = 40 puts obj.before_third #=> 30 puts obj.third #=> 40 -evl<>
Bindingオブジェクト Rubyではあるコンテキストで定義された変数やメソッドをまとめたBindingオブジェクト
が存在します。これを使うと次のようなことができます。
class EvalTarget def initialize @initialize_val = 'init val' end def instance_binding local_val = 'local val' binding end private def private_method 'private method' end end target = EvalTarget.new binding_obj = target.instance_binding puts (eval '@initialize_val', binding_obj) #=> 'init val' puts (eval 'local_val', binding_obj) #=> 'local val' puts (eval 'private_method', binding_obj) #=> 'private method'
🗻 Procについて Procオブジェクトとは『処理をオブジェクトとして抽象化したもの 』だそうです。Procオブジェクトを使うとブロックをオブジェクトとして扱えます。
Procをcase文の条件式に当てはめる Procの実用例のひとつに、Procをcase文の条件式に当てはめるというものがありました。
def what_class(obj) case obj when proc { |x| x.kind_of? String } String when proc { |x| x.kind_of? Numeric } Numeric else Class end end what_class "string" #=> String what_class 1 #=> Numeric what_class [] #=> Class
メソッドの引数にブロックを受け取る # callで呼び出す場合 def convert_proc(&block) block end proc_obj = convert_proc { puts 10 } proc_obj.call #=> 10 # yieldを使う場合 def yield_proc if block_given? yield else 'no block' end end proc_obj2 = Proc.new { 'Proc block' } p yield_proc &proc_obj2 #=> "Proc block"
Procとlambdaの違い Procとlambdaの違いについて。まずはreturn, break, next
について。
# return
Proc: Procの外のメソッドに影響する
lambda: lambda内でのみ影響する
# break
Proc: エラーが発生
lambda: returnと同じく処理が終了
# next
Proc, lambda: 処理を中断するのに使う
次に、引数の扱いについて。
p Proc.new { |x, y| x }.call(1) #=> 1 p lambda { |x, y| x }.call(1) #=> `block in ': wrong number of arguments (1 for 2) (ArgumentError)
Procを使ったクロージャ クロージャとは、引数以外にも関数定義のコンテキストに含まれる変数名のどの情報をもつ関数オブジェクトのことです。
def create_proc str = 'from create_proc' Proc.new { puts str } end proc_obj = create_proc str = 'top level' proc_obj.call #=> from crate_proc
😼 Active Support::Concern 拡張モジュールを作成する際に有効活用できるのがActive Support::Concern
です。
require 'active_support/concern' module IncludeModule extend ActiveSupport::Concern module ClassMethods def hello_class_method puts 'hello class method' end end end class IncludeClass include IncludeModule end IncludeClass.hello_class_method #=> 'hello class method'
🐠 変更来歴 (02/05 22:00) 社畜から開放されてメタプロ勉強 (02/08 18:30) 奇跡の有給取得によりメタプロとか勉強@Coedo茅場町 (05-06 08:30) 言語仕様を追加 (05-10 17:30) メタプログラミング再勉強
🖥 VULTRおすすめ
「VULTR 」はVPSサーバのサービスです。日本にリージョンがあり、最安は512MBで2.5ドル/月($0.004/時間)で借りることができます。4GBメモリでも月20ドルです。
最近はVULTR のヘビーユーザーになので、「ここ 」から会員登録してもらえるとサービス開発が捗ります!