RPGツクールVXAceのスクリプト言語であるRGSS3(Ruby1.9.2p0 + ツクールライブラリ)に関する個人メモ。あくまでも個人メモなので管理人以外には編集権限はありません。

×


Rubyの本質を理解する

表記の意味/言語ルール

  • 基本的にはRuby公式表記に準ずる。
    • クラス/モジュール名#メソッド名 はインスタンスメソッドを表す。Methodクラス情報から確認可能。
    • クラス/モジュール名.メソッド名 は特異メソッドを表す。Methodクラス情報から確認可能。
    • クラス/モジュール名.#メソッド名 はモジュール関数を表す。Methodクラス情報では特異メソッドと同じ表示になる。
    • クラス/モジュール名::定数名 は定数またはサブクラス名を表す。Rubyではクラス名も定数である。
    • メソッド名(引数) -> 戻り値の型 はメソッドの定義と戻り値を表す。戻り値が [String] のように角カッコで囲まれている場合はStringの配列を返す意味。
    • メソッド名{|value| ...} はブロックを渡せるメソッドを表す。valueはブロックパラメータ。ブロック引数とは言わないので注意。
      • ブロック引数は、メソッド定義で使用される &block の形の引数。主にブロックを渡された際にブロック自体をオブジェクトとして受け取る場合(Procオブジェクトを受け取る場合)に利用される。
      • ブロックパラメータは、|value| の形でブロックに渡されるパラメータ。メソッド内で yield(ブロックパラメータ) の形で定義されていて、ブロック本文が yield と置き換わる形でメソッドが実行される。ブロックパラメータの名前は一例であり自由に変更可能(value,index を v,i などとする事が多い)。
    • コード #=> 結果 は左側のコードの出力結果を示すコメントを表す。当wikiでは => を省略している事も多いので注意。
  • 言語ルール
    • 定数は半角英大文字で始める必要がある。Rubyではクラス名(モジュール名)は定数である。
    • 定数呼び出しには ::(ダブルコロン演算子)を使う必要がある。
      • .(ピリオド)で呼び出すとメソッド呼び出しとして扱われる。逆にメソッドは :: でも呼び出せる。
    • 変数名、メソッド名の先頭文字には半角数字は使えない。
      • 一般的には半角英小文字か _(アンダーバー)で始まり、半角英数字と _ で構成される。マルチバイト文字も使用可能。
  • 暗黙の了解(ローカルルール)
    • クラス名定数はキャメルケース(単語の頭を大文字、他を小文字)で書く。(例:BasicObject)
    • クラス名ではない定数は全て英大文字で書く。(例:Math::PI)
    • クラスメソッド呼び出しには ::(ダブルコロン)を使う。とはいえ公式リファレンスのサンプルコードでもあまり徹底されていないが。
    • インスタンスメソッド呼び出しには .(ピリオド)を使う。::は使わない。
    • 変数名やメソッド名はスネークケース(小文字の半角英数字を使い単語を _ で繋ぐ)で書く。(例:each_with_index)
    • 真偽値を返すメソッド名は末尾に ? を付ける。(例:Object#nil?)
    • 破壊的メソッド(自身の内容を変更するメソッド)の末尾には ! を付ける。(例:Array#map!)
      • 組み込みでは付いてないものも割りとある。(例:Array#delete_if)

真・構文解析の優先順位リスト

  • Ruby公式リファレンスのリストには抜けがある(演算子しかない)ので以下に真のパース優先順位リストを示す。
  • 説明にある名称は独自定義のものも含む(実態とあわない名称などは改名)。
  • 表の上にあるほど優先順位が高く、下にあるほど低い。また同じ列にあるもの同士は優先順位がなく、記述順(左から右)に評価される。
    • 4点リーダ(....)でつないだものはセットで扱うものを示す。単体では構文エラーになるので注意。
    • デリミタ(delimiter)や演算子が行末にある場合、\ を置かなくとも改行を挟める。これはデリミタや演算子が明確に右辺を必要とするため、右辺が見つかるまで行をまたいでパースが継続するためである。
    • 対になっている定義デリミタの場合は開始デリミタなら改行を挟んでパースを継続できる。閉じデリミタでの改行は普通に文の終わりを示す。ブロックパラメータデリミタは例外的に閉じデリミタのあとに改行を挟んでも問題ない。
    • 「メソッドを呼び出す」と明記された演算子は実体がメソッドであり、構文解析の特例で演算子としてパースされる。ゆえに再定義可能。
    • ハッシュ式に使われる :(コロン)や =>(ハッシュ代入式)が引数解析時に構文エラーを吐くため、引数として使う場合にはグループ化が必要となる。※後述のサンプル参照
      • これはmethodの引数評価により、ハッシュ式がブロック定義文として解釈されるため。ブロック定義は引数定義と同列に解釈されるためハッシュ式よりも優先される。
    • 後置修飾子は引数に直接含めることはできない。引数に含めたい場合は引数内で更にグループ化し、(()) (二重括弧)で囲む必要がある。
      • Ruby2.4以降では二重括弧は不要になったが、RGSS3では必要。
    • method() と method () はメソッド直後にスペースがあるかどうかで解析方法が大きく変わる。
      • スペースが無い場合は引数定義として解釈される。
      • スペースがある場合には第一引数のグループ化として解釈される。
(....)                  # グループ化定義
method                  # メソッド評価 右辺(最初の一つ)を引数として優先評価する 改行は評価終了
(....)                  # 引数定義  開始 ( の前にスペース不可、閉じ ) は引数評価終了
{....} |....|           # ブロック定義文、ブロックパラメータ定義(ブロック定義内限定)
class/module/def....end # クラス/モジュール/メソッド定義節 内側にrescue〜ensure節を定義可能
begin/case....end       # 制御構造定義節(if や while、for も含む) 他の定義節も内包可能
                          # begin....endは内側にrescue〜ensure節を定義可能
[....]                  # 配列定義式   example: [1,2,3]
{....}                  # ハッシュ定義式  example: {a: 1, g: 2} ※引数とするなら()が必要
[....]                  # インデックス参照 []メソッドを呼び出す example: ary[0]
:: .                    # 参照デリミタ メソッドデリミタ 参照デリミタは定数やメソッドを参照する
                          # メソッドデリミタはレシーバ(左辺)のメソッド(右辺)を実行する
+(単項) ! ~             # 単項+は+@ 単項否定は! ビット否定は~ それぞれのメソッドを呼び出す
                          # ビット否定はRegexpの場合、単項マッチ演算子(self =~ $_)として動作する
**                      # べき乗演算子 **メソッドを呼び出す
-(単項)                 # 単項-は-@メソッドを呼び出す
* / %                   # 乗除演算子 それぞれのメソッドを呼び出す
+ -                     # 加減演算子 それぞれのメソッドを呼び出す
<< >>                   # シフト演算子 それぞれのメソッドを呼び出す
&                       # ビット演算子AND(論理積) &メソッドを呼び出す
| ^                     # ビット演算子OR(論理和)XOR(排他的論理和) それぞれのメソッドを呼び出す
> >= < <=               # 関係演算子 それぞれのメソッドを呼び出す
<=> ==  === != =~ !~    # 等価演算子、正規表現演算子 それぞれのメソッドを呼び出す
&&                      # 論理演算子AND
||                      # 論理演算子OR
..  ...                 # 範囲演算子
? :                     # 三項演算子
rescue                  # rescue修飾子 左辺の後置修飾子を抱え込む ※引数とするなら(())が必要
= (+= ||= ... ) []=     # 代入演算子(=と演算子の合成構文) インデックス参照代入は[]=メソッドを呼び出す
defined?                # 定義判定 複数の引数は取れない
,                       # 引数デリミタ 引数の区切り 引数評価を継続する
do....end               # ブロック定義節 戻り値を求めないブロックの定義
not                     # 式否定演算子 右辺の式全体を否定する
and or                  # フロー制御演算子 フローと戻り値を制御する
if unless while until   # 後置修飾子 左辺の式をまるごと抱え込む ※引数とするなら(())が必要
\ ;                     # パースデリミタ 行末\はパースを次行も継続する ;はパースを一旦完了する
サンプル
p Object::
Array                     #=> Array     #parsed p (Object::Array)  '::' is delimiter
p -begin 5 end.succ       #=> -6        #parsed p -((begin 5 end).succ)
p -case when true; 7 end.succ  #=> -8   #parsed p -((case when true; 7 end).succ)
p -[1,2,3] [1].succ       #=> -3        #parsed p -((([1,2,3]) [1]).succ)
p defined? a, defined? a  #=> nil, nil  #parsed p ((defined? a), (defined? a))
p [1,2,3]::empty?         #=> false     #parsed p (([1,2,3])::empty?)
p p 1                     #=> 1\n1      #parsed p(p 1)
p p(1).object_id          #=> 1\n3      #parsed p((p(1)).object_id)
p [1,2,3] = b = [5,6,7]   #=> [5,6,7]   #parsed p [1,2,3].[]=(b = [5,6,7])

p {1=>5}::empty?          #=> Syntax Error tASSOC(=>)      #perhaps p(){1  '=>' error in the block
p {a: 5} || {b: 6}        #=> Syntax Error unexpected ':'  #perhaps p(){a  ':'  error in the block
p ({:a=>5}::empty?)       #=> false                        #parsed p (({:a=>5})::empty?)
p ({a: 5} || {b: 6})      #=> false                        #parsed p (({a: 5}) || ({b: 6}))
p false ? {a: 5} : {b: 6} #=> {:b=>6}                      #parsed p (false ? ({a: 5}) : ({b: 6}))

p 1,1    #=> 1\n1          #parsed p(1,1)  2 arguments in the argument definition(二つの引数として解釈)
p(1,1)   #=> 1\n1          #parsed p(1,1)  2 arguments in the argument definition(二つの引数として解釈)
p (1)    #=> 1             #parsed p (1)     first grouped argument(第一引数がグループ化されていると解釈)
p (1,1)  #=> Syntax Error  #parsed p((1,1))  first grouped argument(第一引数がグループ化されていると解釈)

r = raise rescue $!                                                         #parsed r = (raise rescue $!)
p a rescue p $!           #=> "#<NameError undefined local variables 'a'>"  #parsed (p a) rescue (p $!)
p a if false rescue p $!  #=>                                               #parsed (p a (if false)) #rescue p $!
p a if true rescue p $!   #=> "#<NameError undefined local variables 'a'>"  #parsed (p a (if true)) rescue (p $!)
p (a rescue $!)           #=> Syntax Error unexpected modifier _rescue      #parsed first grouped argument(第一引数グループとして解釈)
p((a rescue $!))          #=> "#<NameError undefined local variables 'a'>"  #parsed Grouping within the first grouped argument(第一引数内でのグループ化として解釈)

p :a; p :b if false   #=> :a      #parsed (p :a); (p :b (if false))
p :a\
; p :b if false       #=> :a      #parsed (p :a); (p :b (if false))
p :a; \
p :b if false         #=> :a      #parsed (p :a); (p :b (if false))
(p :a; p :b) if true  #=> :a\n:b  #parsed ((p :a; p :b) (if true))
制御構造の優先順位(旧記事)
a = raise rescue $!            #parsed a = (raise rescue $!)
p raise rescue $!              #parsed (p raise) rescue $!
p a = raise rescue $!          #parsed p (a = (raise rescue $!))
p a = 1 if raise rescue $!     #parsed p (a = ((1 if raise) rescue $!))
p 1 if raise rescue $!         #parsed ((p 1) if raise) rescue $!
p 1 and 3                      #parsed (p 1) and 3
p not false  #=> Syntax Error  #parsed (p not) false

def t(n=1); n*2; end
p t 2                        #parsed p (t(2))
p t a = 2                    #parsed p (t (a = 2))
p a = t 2  #=> Syntax Error  #parsed p (a = t) 2

# 実際のコーディングにおいてこのような理解しがたいコードを書くこと自体がそもそも間違っている
# ここではあくまでも実験結果の展示だけにとどめ、重要なTipsだけを提示する
p a rescue $!      # この例が最も使用頻度が多く、そしてプログラマの思い通りに動作しない例である
                   # p (a rescue $!) を期待したいが (p a) rescue $! と解釈され、エラー時に何も表示されない
p((a rescue $!))   # 二重括弧でくくれば通るが、正直括弧は入力しづらいコーディング文字の筆頭である
p a rescue p $!    # rescueの後にも p を書けば期待通りに動作するが p と p がかぶって孤独なグルメ気分になる
p b = a rescue $!  # 代入式を挟めばrescueも巻き込むが無意味なローカル変数を汚染するのは冴えたやり方ではない
# 恐らくrescueの後にも p を付けるのがベストプラクティス・・・とまでは言えないが少なくともグッドプラクティスではある

主要用語集

オブジェクト、クラス、イスタンス、コンテキスト、スコープ、モジュール
  • オブジェクトとは、クラスのインスタンスの事である(後述)。
    • Rubyではデータとして表せるものは全てがオブジェクトである。つまりそれらは全てクラスのインスタンスであると言い換えることもできる。
    • このことはtrue、false、nilがそれぞれTrueClass、FalseClass、NilClassの唯一のインスタンスである事からもわかる。class情報も一意のインスタンスを持つ。
  • クラスとは、オブジェクトの振る舞い(主にメソッド)を定義する設計図のようなもの。class定義式で定義する。
    • クラスは継承してサブクラス(子クラス)を作ることができる。サブクラスから見た親クラスをスーパークラスという。
    • 例えばRGSS3組み込みクラスで言うと、RPG::UsableItemのスーパークラスはRPG::BaseItem、サブクラスはRPG::SkillとRPG::Itemとなる。
    • 継承関係は上記のように共通の情報(インスタンス変数)や振る舞い(メソッド)をスーパークラスとしてまとめ、そこから違いのある分類ごとにサブクラスを作り、そこから更に派生するサブクラスを・・・という風にメソッドとインスタンス変数を再利用する形で木構造を形成する。コードが実行される際にはこの木構造を遡る形で参照経路が作られる。そのためサブクラスからはスーパークラスのメソッドを自分自身のメソッドのように扱えるし、スーパークラスで定義されたインスタンス変数は自分自身のインスタンス変数として情報を持つ事になる。
  • インスタンスとは、クラスの定義に従って生成された実体。インスタンスごとに個別の情報(インスタンス変数)を持つ。
    • クラス情報(設計図)からインスタンス(実体)を生成する事をインスタンス化と呼ぶ。
  • クラスオブジェクトインスタンスオブジェクトとは、オブジェクトの種類である。例えば a = Object.new を例に取ると、a がインスタンスオブジェクトであり、a.class(Object) がクラスオブジェクトとなる。通常、単にオブジェクトと言えばインスタンスオブジェクトの事を指す。クラスオブジェクトはクラスのインスタンスオブジェクトと言い換える事もできる。
    • BindingオブジェクトやProcオブジェクトのようにクラス名を関するオブジェクトは、そのクラスをクラスオブジェクトとして持つのインスタンスオブジェクトである事を表す。
  • コンテキストとは、実行時の環境(定数や変数、メソッド、それらのスコープ等)を指す。ざっくり言えばselfが何を指すかを表す。
    • 例えば a = Array.new と前提を置き、「 a のコンテキストで実行される」と表現されている場合、a というインスタンスに所属しているのと同等の実行環境で実行されると言う意味。a のクラスであるArrayのメソッドやArrayがincludeしているEnumerableモジュールのメソッド、また a の特異クラスのメソッドや変数にもアクセスできることを表す。
    • 例えば「 a の特異クラス定義のコンテキストで実行される」と表現されている場合、a というインスタンスの特異クラス定義内で実行するのと同じ実行環境で実行されると言う意味。
    • コンテキストはクロージャ(後述)に保存される性質を持つ。またBindingオブジェクトとしてインスタンス化する事もできる。
  • スコープとは、定数やメソッド、変数をどこから参照可能かを表す有効範囲(可視性)の事。
    • インスタンス変数やメソッドのスコープはコンテキストに併せて変化するが、定数やローカル変数のスコープはレキシカルスコープ(後述)となる。
    • 例えばメソッド実行コンテキストで定義されたローカル変数aのスコープは、そのメソッド定義が終了するまでとなる。
      • begin〜endのような制御構造内はスコープが変化しない。each{|v| ...} のようなブロック内はスコープが変化する(ただしブロックは外のスコープを内包する)。この場合ブロック内で定義したbはブロックの外からは参照できない。
  • モジュールとは、様々な役割を持つ特殊な仕組みである。主に使用目的によって下記のように分類される。
    • Mix-inモジュールは、他のクラスに組み込む(Mix-inする)事で機能を追加する使い方である。Mix-inに使うメソッドによって挙動が異なる。
      • includeでMix-inされたモジュールはスーパークラスのように振舞う。Javaのデフォルト付きインターフェイスと同じようなものである。
      • extendでMix-inされたモジュールは特異クラスとして振る舞う。つまりモジュールのメソッドはクラスメソッドとして振舞う。
      • extendはオブジェクトに対しても使用できる。オブジェクトにMix-inされたモジュールはオブジェクトの特異クラスとなり、まるでオブジェクトのクラスをスーパークラスとしたカレントクラスのように振る舞う。これによりモジュールのメソッドはクラスのインスタンスメソッドよりも優先して探索される。このような振る舞いを持つオブジェクトの特異メソッドには一般的な呼び名は無いようだ。当Wikiではインスタンスメソッドに優先して実行される点を考慮して”オーバーコールメソッド”と呼称するものとする。
      • Ruby2.0以降であればincludeとextendを併せたような強力なprependでMix-inできる。クラスにprependされたモジュールはそのクラスをスーパークラスとしたカレントクラスのように振る舞う。つまり元のクラスに影響を与えない強力なモンキーパッチをクラスレベルで実現できる。残念ながらRuby1.9.2ベースであるRGSS3では使用できない。
      • 代表的なMix-inモジュールはKernelモジュールである。KernelはObjectクラスにMix-inされているため、あらゆる場所で関数のように使うことができる。
    • 関数型モジュールは、外部参照される定数やメソッドをまとめて一括管理する使い方である。Javaのstaticライブラリのようなものである。
      • module_function等を使う事でMix-inモジュールとして扱う事もできる。Javaでstaticライブラリをimport staticするイメージが近い。
      • 代表的な関数型モジュールはMathモジュールなどである。MathはMix-inとしても扱える。RGSS3ではInputモジュールなども関数型モジュールと言える。
    • 名前空間モジュールは、クラスなどをまとめて名前空間を提供する使い方である。Javaのパッケージと同じようなもので名前衝突回避に使われる。
      • モジュールの名前空間で定義されたクラスは内部クラスとなる。結果モジュール名::クラス名の形で参照可能になり、モジュールの名前空間に所属できる。
      • RGSS3の代表的な名前空間モジュールはRPGモジュールである。詳しくはVXAceヘルプのRPGVXAceデータ構造を参照の事。
      • 名前空間はクラスで実装することもできる。名前衝突回避だけでなく相互機能も持たせたい場合はクラスでの実装が向く。このようなクラスを当Wikiでは便宜上名前空間クラスと呼称するものとする。
内部クラス、名前空間、レキシカルスコープ、クロージャ
  • 内部クラス(InnerClass)とは、クラス内にネスト(入れ子に)して定義されたクラスである。クラス名::内部クラス名の形で参照される。
    • 内部クラスに対し、内部クラスが所属している(内部クラスの外側にある)クラスを、外部クラス(OuterClass)と呼ぶ。
    • 内部クラスと外部クラスは継承関係のようなメソッドの参照経路は存在しない。あくまでも内部クラスは外部クラスの名前空間に所属しているだけであり、コンテキストは独立している。
      • 例えるなら雑居ビルと入居テナントの関係が近いか。テナント(内部クラス)はビル(外部クラス)に所属しているがビルの運営(コンテキスト)に関わっているわけではないのでビル自体の動向(メソッド)や内部事情(インスタンス変数)を無条件には参照できないイメージ。あくまでも住所(名前空間)がビル内○○になるだけの関係と言える。
      • ただし定数については異なる。定数は親よりも外部の定数を優先して参照する。つまり定数の参照経路は自身から徐々に外側へ向かい、その後親を遡る経路を取る。これはクラス名が定数である事と関係している。つまり内部から外部のクラス名には名前空間を指定せずにアクセスできる。この参照はレキシカルスコープで行われる。先ほどの雑居ビルの例で言えば、定数は名前空間(住所やビル名に付随する名前)に属する。例えばビル内で2階の張り紙(定数)と言えば、ビル::2階::張り紙を参照できるので内容を読める。しかし外のファミレスで2階の張り紙と言った場合、ファミレス::2階::張り紙(異なるレキシカルスコープの定数)をさんしょうしてしまう。また2階のないファミレスでは2階が存在しないため伝わらない(エラーになる)。これがレキシカルスコープの例である。
# ビルと張り紙のレキシカルスコープの例
module Building
  module SecondFloor
    NOTICE = "Building 2F"
  end
  class Tenamt
    p SecondFloor::NOTICE  #=> "Building 2F"
  end
end

class Building::Tenamt
  p SecondFloor::NOTICE rescue p $!  #=> NameError(::SecondFloor::NOTICE を探す)
end

module TwoStoryRestaurant
  module SecondFloor
    NOTICE = "Restaurant 2F"
  end
  class ::Building::Tenamt
    p SecondFloor::NOTICE  #=> "Restaurant 2F"
  end
end

module OneStoryRestaurant
  class ::Building::Tenamt
    p SecondFloor::NOTICE rescue p $!  #=> NameError(SecondFloor が無い)
  end
end
  • 名前空間(NameSpace)とは、文字通り名前が所属する空間であり、プログラミングにおいては名前の衝突を避けるためのグループ化の仕様(概念)を指す。
    • 名前空間では外側をOuter、内側をInnerと呼ぶ。Outer側から見ると名前空間を構築し、Inner側から見ると名前空間に所属する形となる。
    • ここでいう名前とはRubyではモジュール名、クラス名を含む定数名を指す。Rubyの名前空間ではレキシカルスコープ優先で定数を参照する。メソッド経路は参照されない。
    • 多くのプログラミング言語において、同じ名前空間に同じ名前の定数を複数定義することはできない。名前空間が異なれば同じ名前でも定義できる(より正確には::演算子を含めたフルネームが異なれば別名とみなされる)。これが名前空間を扱う理由である。Javaで言えばパッケージに相当する。
    • 余談だが、Rubyの場合トップレベルに定義された定数(クラス名)はObjectクラスの名前空間に属する。Rubyの組み込みクラスは全てトップレベルに定義されている。そのためArrayはObject::Arrayとも書ける。またトップレベルを表すObjectは省略可能なので、::Arrayとも書ける。
      • 通常だと使い道は無いが、独自の名前空間に組み込みクラスと同名のクラス(例えばArray)を定義した場合、その名前空間内から明示的に組み込みクラスを呼ぶ場合に必要になる。
  • レキシカルスコープとは、コードに書かれた状態を基準とするスコープの事である。多くの言語は基本的にレキシカルスコープを採用している。
    • レキシカル(静的)と呼ばれてはいるが、ほとんどの場合これらはレキシカルな定義と動的な制限を併せ持つハイブリッドな構文として処理されている。しかしRubyの定数は唯一と言っていい動的な制限を含まない真のレキシカル宣言と、クラス継承経路というシンプルな経路を併せて解決される。
    • Rubyの場合、帰属するオブジェクトによって更に細かくクラススコープやオブジェクトスコープに分かれるが、あえてレキシカルスコープと呼ばれる場合はローカル変数のように最も狭いレキシカルスコープを指す場合や、名前空間の物理的なネスト状況によって振る舞いが変わる定数探索を指す場合に、特に用いられる。
# 名前空間のネストの有無によって振る舞いが変わるレキシカルスコープの例
class Outer
  X = Outer
  class Inner1  # Inner1クラスはOuterのレキシカルスコープに入っている
  end
end

class Outer::Inner2  # Inner2クラスはOuterのレキシカルスコープに入っていない
end

p Outer::Inner1::X  #=> Outer     Outerの定数を参照できる =レキシカルスコープに入っている
p Outer::Inner2::X  #=> NameError Outerの定数を参照できない=レキシカルスコープに入っていない
  • クロージャ(closure)とは、手続きとコンテキストをカプセル化したものを指す。宣言時のコンテキストとレキシカルスコープ(ローカル変数など)を持ち歩いて実行時にアクセスできる。関数閉包とも呼ばれる。
    • 公式リファレンスには手続きをオブジェクト化したものとあるが、厳密にはオブジェクト化されていないクロージャも存在するため、ここではカプセル化と表現する。
      • 例えば「&引数を取らないメソッド」に渡されたブロックは「オブジェクト化されていないクロージャ」である。
    • Rubyのクロージャはブロック構文で定義される。ブロックをオブジェクト化したものがProcオブジェクトであり、Procオブジェクトはprocとlambdaに分けられる。これらをまとめてクロージャと呼ぶ。
    • Rubyでは特にオブジェクト化されたクロージャを手続きオブジェクト(クロージャオブジェクト)と呼ぶ。Procオブジェクトは手続きオブジェクトである。
特異クラス、特異メソッド、英字表記
  • 特異クラス(SingletonClass、メタクラス)とは、全てのオブジェクト(インスタンス)に対して一意に存在できる隠された無名クラスである。Object#singleton_class で取得できる。
    • Rubyではclass情報自体も一意のインスタンスを持つのでクラスやモジュールの情報も特異クラスを持つ。更に特異クラスもインスタンスとクラス情報を持つ普通のクラスである。
    • クラスやモジュールの情報は定義の数だけメモリ上に存在するが、特異クラスは理論上オブジェクトの数だけメモリ上に存在できる。一つのオブジェクトはselfとsingleton_class、2つのインスタンスを抱える事ができ、更にclassへの参照を持つ。また特異クラスのインスタンスに特異クラスを定義することも(意味は無いが)できるため、理論上は一つのオブジェクトに無限のインスタンスを生成できる事になる。
    • Fixnum(Ruby2.4以降ではIntegerの一部)、Symbolのインスタンス、true、false、nilには特異クラスは定義できない。これらのインスタンスはメモリにオブジェクト構造体へのポインタを持たないため特異クラスを管理できない。詳細はRubyソースコード解説などを参照。
  • 特異メソッドとは、特異クラスに定義されたメソッド。ゆえにオブジェクト(インスタンス)ごとに一意のメソッドを定義できる。
    • 特にクラスに定義された特異メソッドはクラスメソッドとも呼ばれる。Javaでいうところのstaticメソッドに近い。
    • クラスメソッドで構成されたモジュールを関数型モジュールと呼ぶ。
    • 特異メソッドはインスタンスメソッドよりも優先して探索される性質を持つ。
    • 特にオブジェクトに定義された特異メソッドを当Wikiではオーバーコールメソッドと呼ぶ。これは元のクラスに影響を与えない強力なモンキーパッチツールである。
  • 英字で表記された場合は言葉の意味ではなく、実際に定義された情報を表す。
    • Objectとは、Objectクラスを表す。Object.newで生成したものがObjectオブジェクト(Objectクラスのインスタンス)である。
    • Classとは、Classクラスを表す。class定義文で定義したものがClassオブジェクト(Classクラスのインスタンス)である。
      • Classもクラスなので実行時にSingletonオブジェクトを持つ。これがClassの特異クラスである。Classに定義された特異メソッドを特にクラスメソッドと呼ぶ。
    • Moduleとは、Moduleクラスをあわらす。module定義文で定義したものがモジュール(Moduleオブジェクト、つまりModuleクラスのインスタンス)である。
      • 紛らわしいが、ModuleはClassのスーパークラスであるがModuleはクラスである。Moduleクラス自体はclass定義式で定義されていてmodule定義されているわけではない。*1
      • ようするにmodule定義式で定義されたモジュールのclassがModuleクラスとなる。
    • ModuleとClassの違いは、ClassがModuleを継承してモジュール専用機能を廃止し、インスタンス化やクラス継承などのクラス専用機能を追加したクラスであるという点である。
参照: Ruby 1.9.3 リファレンスマニュアル > オブジェクト
参照: Ruby 1.9.3 リファレンスマニュアル > クラス/メソッドの定義
参照: Ruby 最新版 リファレンスマニュアル > Ruby用語集
参照: #コンテキスト/クロージャの詳細

スコープ早見表(変数の種類)
  • Rubyは基本的にレキシカルスコープ(静的スコープ)を採用しており、動的スコープ(ダイナミックスコープ)は採用していない。
    • レキシカルスコープにはいくつかの種類があり、帰属するオブジェクトによって振る舞いが変化する。
  • Rubyのprivate、protectedはスコープ(可視性)ではなく、呼び出し制限(call limit)であるためここには載せていない。
項目スコープ帰属備考
グローバル変数グローバルスコープ
またはスコープなし
プログラム全体どこからでもアクセス可能
擬似変数(self)コンテキストで変化コンテキスト現在のコンテキストの主体をあらわす疑似変数
コンテキスト内のどこからでもアクセス可能
クラス変数クラススコープクラス/モジュールクラスごとの変数 サブクラスと値を共有する
クラスインスタンス変数クラススコープクラス/モジュールクラスごとの変数 サブクラスごとに値を持つ
インスタンス変数オブジェクトスコープインスタンスインスタンスごとに値を持つ
ローカル変数レキシカル(厳密)コンテキスト使い捨ての変数 メソッドの引数やブロックパラメータにも使われる
ローカル変数は最も厳密な(狭い)レキシカルスコープを持つ
定数名前空間 + クラスクラス/モジュール定数はレキシカル名前空間とクラス継承経路を併せたスコープを持つ(*1)
クラス名名前空間 + クラスクラス/モジュールRubyのクラス名は定数
メソッドメソッドスコープクラス/モジュール特異メソッドとクラス継承経路を併せた参照経路を持つ
クラスメソッドメソッドスコープクラス/モジュールクラスの特異メソッドとして定義される
ブロックブロックスコープselfコンテキストブロックは外側のローカル変数を保持するクロージャとして働く
(*1)Rubyの定数は、F-BASICからアセンブラ、N88BASICコンパイラ、QuickBASIC、Delphi、ExcelVBA、Perl、PHP、JavaScript、Java、Rubyと様々な言語を渡り歩いてきた筆者にとっても初めて目にする、完全なレキシカルスコープの境界を認識できる唯一の例である。クラス名が定数であり、クラス名定数の中身がClassオブジェクトであるという点も含めて、定数こそが最もRubyの象徴たる存在なのではないだろうか。完全なクロージャであるブロック構文と合わせて二大巨頭と言っても過言では無いはずだ。

オブジェクト、クラス、特異クラスの関係

  • まず最初に覚えておきたいのは、Rubyに静的メソッドは存在しないという事。特にJava経験者はここがネック。
    • クラスメソッドと呼ばれるものが静的メソッドに近い使い方ができるが、静的メソッドではない。
    • クラスメソッドを実現するために特異クラス(特異メソッド)の仕組みを利用している。
  • 特異クラスはオブジェクト(インスタンス)に紐付いている。1インスタンスにつき1特異クラス。
    • クラス情報もインスタンスを持つ。クラス情報インスタンスに紐付いた特異クラスのメソッドが、クラスメソッドとして動作する。
  • 特異クラスは元のオブジェクトの探索経路(継承経路)を内包する。
クラスと特異クラスの関係表
  • a = MyClass.new とする
取得する情報クラス特異クラス備考
aオブジェクトで参照可能な
オブジェクトの種類
self(インスタンスオブジェクト)
class(クラスオブジェクト)
singleton_class(クラスオブジェクト)特異クラスにはインスタンスオブジェクトは生成されない
つまり1つのselfは2つのクラスオブジェクトを持つ
aのオブジェクトの取得a
#=> #<MyClass:0x4456780>
a.singleton_class
#=> #<Class:#<MyClass:0x4465780>>
特異クラスオブジェクトは要求されるまで生成されない
MyClassのオブジェクトの取得a.class
MyClass
#=> MyClass
a.class.singleton_class
MyClass.singleton_class
#=> #<Class:MyClass>
aのクラス継承関係a.class.ancestors
MyClass.ancestors
#=> [MyClass, Object...]
a.singleton_class.ancestors
#=> [MyClass, Object...]
特異クラスは元のクラスの継承関係を自身の経路に内包する
参照経路は後述
MyClassのクラス継承関係a.class.class.ancestors
MyClass.class.ancestors
#=> [Class, Module, Object...]
MyClass.singleton_class.ancestors
#=> [Class, Module, Object...]
インスタンスメソッドの取得a.instance_methodsa.singleton_class.instance_methods特異メソッドは特異クラスに直接定義されたメソッド
特異メソッドの取得a.singleton_methods割愛
クラス定義class MyClassclass << MyClass特異クラスはクラスオブジェクトに対して定義される
オブジェクトへのクラス定義不可class << a特異クラスはオブジェクトに対して定義される
メソッド定義def method_namedef MyClass.method_name特異メソッドはクラスオブジェクトに対して定義される
オブジェクトへのメソッド定義不可def a.method_name特異メソッドはオブジェクトに対して定義される
class MyClass                     # クラス定義
  def instance_method             # インスタンスメソッド定義
  end
  class << self                   # MyClassの特異クラス定義
    def singleton_instance_method # 特異クラスのインスタンスメソッドを定義 = MyClassの特異メソッドを定義
    end
  end
end

a = MyClass.new                   # MyClassオブジェクトを生成
class << a                        # a(MyClassオブジェクト)の特異クラス定義
  def a_instance_method           # a のインスタンスメソッドを定義 = a の特異メソッドを定義
  end
end
メソッドの探索順
  • 下記のコードを見れば、オブジェクトとクラスで別の参照経路を辿っている。つまりこれらは別々のインスタンスであることがわかる。
    • 特異メソッドは他言語の静的メソッドのように振る舞うが、実際はそれぞれのメソッド探索経路に優先して実行されているだけ。
  • オブジェクトとクラス、どちらの場合も self の特異メソッドを最初に参照し、以後はメソッド探索経路を辿っているのがわかる。
    • self以外の特異メソッドは参照されない。
    • extendの挙動から、Rubyのクラスメソッドの探索経路はクラスの特異クラス継承経路を辿っている事がわかる。
def test
  p "Object#test"  # 継承経路の最後を定義
end

class Class
  def test
    p "Class#test"  # Classのインスタンスメソッド定義
    super
  end
end

module IncludeModule
  def test
    p "IncludeModule#test"  # IncludeModuleのインスタンスメソッド定義
    super
  end
end

module ExtendModule
  def test
    p "ExtendModule#test"  # ExtendModuleのインスタンスメソッド定義
    super
  end
end
  

class MyClass
  include IncludeModule
  extend ExtendModule
  def test
    p "MyClass#test"  # MyClassのインスタンスメソッド定義
    super
  end
  def self.test
    p "MyClass.test"  # MyClassの特異メソッド定義
    super
  end
end

a = MyClass.new
class << a
  def test
    p "a.test"  # aの特異メソッド定義
    super
  end
end

a.test
  #=> "a.test"              # aの特異メソッド = a の特異クラスのインスタンスメソッド
  #=> "MyClass#test"        # MyClassのインスタンスメソッド(以下はMyClassの継承経路順に探索)
  #=> "IncludeModule#test"  # IncludeModuleのインスタンスメソッド
  #=> "Object#test"         # 継承経路の最後
# オブジェクトの特異メソッドがインスタンスメソッド参照経路よりも優先されるのがわかる
# includeされたモジュールも含めてMyClass(a.class)の継承経路順にメソッドが実行されているのがわかる
p a.class.ancestors  #=> [MyClass, IncludeModule, Object, Kernel, BasicObject]

MyClass.test
  #=> "MyClass.test"       # MyClassの特異メソッド(クラスメソッド)
  #=> "ExtendModule#test"  # ExtendModuleのインスタンスメソッド(以下はMyClassの特異クラスの継承経路順に探索)
  #=> "Class#test"         # Classのインスタンスメソッド
  #=> "Object#test"        # 継承経路の最後
# MyClassの特異メソッド(クラスメソッド)がメソッド参照経路よりも優先されるのがわかる
# extendされたモジュールも含めてMyClass.singleton_classの継承経路順にメソッドが実行されているのがわかる
p MyClass.singleton_class.ancestors  #=> [ExtendModule, Class, Module, Object, Kernel, BasicObject]
参照: #include、extend
メソッドリスト、メソッド取得、respond_to?
  • メソッド取得系メソッドで取得できるメソッドの違い。
  • respond_to?はそのオブジェクトが実際にそのメソッドを呼べるかどうかを判定する。
  • 定義は表下。
MyClass::call_methodreultnotegetMethod
Object#methods(false)[:sg_pub_ins, :sg_pro_ins]singleton_methods(false)Object#method(name) -> Method
Object#methods(true)省略public_methods(true)Object#method(name) -> Method
Object#public_methods(false)[:sg_pub_ins, :allocate, :new, :superclass]class.public_instance_methods(false)
+ singleton_class.public_instance_methods(false)
Object#public_method(name) -> Method
Object#private_methods(false)[:sg_pri_ins, :inherited, :initialize, :initialize_copy]class.public_instance_methods(false)
+ singleton_class.private_instance_methods(false)
class.instance_method(name) -> UnboundMethod
singleton_class.instance_method(name) -> UnboundMethod
Object#singleton_methods(false)[:sg_pub_ins, :sg_pro_ins]singleton_class.public_instance_methods(false)
+ singleton_class.protected_instance_methods
Object#method(name) -> Method
class.public_instance_methods[:allocate, :new, :superclass]Class.public_instance_methodsObject#method(name) -> Method
class.protected_instance_methods[]Class.protected_instance_methodsObject#method(name) -> Method
class.private_instance_methods[:inherited, :initialize, :initialize_copy]Class.private_instance_methodsclass.instance_method(name) -> UnboundMethod
Module#instance_methods[:pub_ins, :pro_ins]public_instance_methods
+ protected_instance_methods
Module#instance_method(name) -> UnboundMethod
Module#public_instance_methods[:pub_ins]Module#instance_method(name) -> UnboundMethod
Module#protected_instance_methods[:pro_ins]Module#instance_method(name) -> UnboundMethod
Module#private_instance_methods[:pri_ins]Module#instance_method(name) -> UnboundMethod
singleton_class.public_instance_methods[:sg_pub_ins]Object#method(name) -> Method
singleton_class.protected_instance_methods[:sg_pro_ins]Object#method(name) -> Method
singleton_class.private_instance_methods[:sg_pri_ins]singleton_class.instance_method(name) -> UnboundMethod
class MyClass
  public
  def pub_ins; end
  protected
  def pro_ins; end
  private
  def pri_ins; end
  class << self
    public
    def sg_pub_ins; end
    protected
    def sg_pro_ins; end
    private
    def sg_pri_ins; end
  end
end
「クラス名は定数である」事の意味
  • Rubyではクラス名も定数である。つまりクラス名は値を持つ。
    • 定数なので名前空間のネストした定義においてレキシカルスコープの影響を受ける。
  • ObjectやMyClassといったクラス名定数の中身はClassオブジェクト(Classクラスのインスタンス)である。
    • ClassオブジェクトなのでClassやModuleのインスタンスメソッドを実行できる。
    • Classオブジェクトなので特異メソッドを定義すれば特異メソッドが最初に探索されるため、まるで静的メソッドのような振る舞いになる。
特異メソッドの覚え方(クラスメソッドとオーバーコールメソッド)
  • 特異メソッドはどこに定義されたかで主な役割が異なるが、実用的にはたった二種類の役割を覚えればよい。特異メソッドを理解するには、この二種類の使い方を覚えて使い分ける事が重要となる。
  • クラスに定義された特異メソッドは、クラスメソッドと呼ばれる。
    • クラス名.メソッド名(ローカルルール的にはクラス名::メソッド名)の形で呼ばれ、Javaでいうstaticメソッドのように振る舞う。
    • クラスメソッドはインスタンス化せずとも良い、もしくはインスタンス化とは関係の無い、クラス固有の処理を実装するのに向いている。
      • 例えばSymbol.all_symbolsやTime.nowのようなパターンがわかりやすい。
  • オブジェクトに定義された特異メソッドは、オーバーコールメソッドと呼ぶ(当Wikiによる提案)。
    • オブジェクト.メソッド名の形で呼ばれ、単一のインスタンス固有の処理を実装できる。
    • オーバーコールメソッドはインスタンスメソッドよりも優先して実行されるため、元の実装に影響を与えずにモンキーパッチを当てるという強力なメタプログラミングを実現できる。
  • 注意点として、特異メソッド(特異クラス)を定義したオブジェクトはMarshal.dumpできないという問題がある。RGSS3ではゲームをセーブする際にMarshal.dumpを利用しているので、これらの保存対象となるオブジェクトに特異メソッド(オーバーコールメソッド)を定義してしまうと、ゲームをセーブできなくなってしまうばかりか、対象としたセーブデータを削除する仕様となっている。そのためRGSS3でオーバーコールメソッドを実装する際には十分に留意しておく必要がある。
    • 解決策としては extend を使うのが良い。extend はモジュールのインスタンスメソッドをクラスメソッドとしてMix-inする機能だが、オブジェクトに対して extend した場合はオーバーコールメソッドとしてMix-inする機能となる。Ruby1.9.2のMarshalでは extend で実装された特異メソッドはdump時にエラーにならないため、ゲームのセーブ/ロードも問題なく行える。またメソッドをモジュールに定義して再利用できるため、同じような処理を行いたいオブジェクトでメソッドを共有できるという点でも優れる。
    • marshal_dump、marshal_loadメソッドをオーバーライドする方法もあるが、冗長になりやすい欠点がある。

オブジェクトの継承判定、クラスの継承判定

class O
end

class A < O
  class B < O
    class C
    end
  end
end

# オブジェクトの継承判定
a = A.new
b = A::B.new
c = A::B::C.new
p a.is_a?(O)  #=> true レシーバが引数のクラスを継承していればtrue
p b.is_a?(O)  #=> true
p c.is_a?(O)  #=> false
p a.kind_of?(O)  #=> true
p b.kind_of?(O)  #=> true
p c.kind_of?(O)  #=> false

# クラスの継承判定
p A       < O  #=> true 左辺が右辺の子(サブクラス)ならtrue
p A::B    < O  #=> true
p A::B::C < O  #=> false
p O > A        #=> true 左辺が右辺の親(スーパークラス)ならtrue
p O > A::B     #=> true
p O > A::B::C  #=> false
クラス継承の例外
  • Classクラスからはサブクラスを生成できない。つまりClassを継承したクラスは作成できない。
    • この事はなぜかRuby公式リファレンスにも記載されていない。
    • Moduleクラスはサブクラスを生成できるがあまり意味はない。
  • ClassはRubyでは特別なクラスとして扱われる。各種クラスオブジェクトのclassがClassになる。
    • singleton_classで取得可能な特異クラスもクラスオブジェクトのclassはClassである。
    • 言い換えれば、オブジェクトの型がクラスだとしたら、そのクラスのクラスオブジェクトの型がClassということ。
    • 公式リファレンスの記載と異なり、メタクラスだけでなく全てのクラスオブジェクトのclassがClassになる点に注意。

include、extend

  • モジュールを他のクラスやモジュールにMix-inして使う事ができる。
  • Module#include(mod) modを現在のクラスorモジュールに対してMix-inする。
    • modは現在のクラスとスーパークラスの継承関係の間に差し込まれる。つまりmodを継承したかのように振舞う。(KernelやEnumerableが良い例)
      • modのメソッドや定数、インスタンス変数は、現在のクラスorモジュールから参照できる。インスタンス変数のアクセサも有効。
    • modの特異メソッドは、現在のクラスorモジュールからは呼び出せない。つまりmodの特異クラスはincludeでは継承されない。
    • modで定義されているクラス変数(モジュール変数)は、現在のクラスorモジュールと値を共有する。
  • Module.included(cls_or_mod) cls_or_modに対して現在のモジュールがincludeされた時に呼び出されるメソッド。
    • includeに連動して共通処理を行いたい場合に有用。
    • このメソッドはModuleクラスのprivateメソッドとして定義されているが、呼ばれる際には現在のモジュールのクラスメソッドとして呼ばれる特性がある。そのためモジュール内でクラスメソッド(つまり特異メソッド)として定義しないと呼ばれないので注意。
  • Object#extend(mod) modを現在のオブジェクトの特異クラスとしてMix-inする。より正確には特異クラスに対して include する。
    • obj.extend(mod) の形で使用する。modはobj.singleton_class.ancestorsの先頭へと配置される。obj.singleton_class.ancestorsはobjの特異クラスである自分自身を除いた、include済みモジュールリストにobj.class.ancestorsをバインドしたリストを返す。つまりsingleton_class.ancestorsはobjの真の参照経路として働く(特異クラス自身はselfなのでancestorsよりも先に参照される点も含めて)。
    • 端的に言えばクラスにextendされたモジュールのメソッドはクラスメソッドとしてMix-inされる。
    • またオブジェクトにextendされたモジュールのメソッドはオブジェクトの特異メソッド(オーバーコールメソッド)としてMix-inされる。
    • この動作は前述の通り、オブジェクトの特異クラスに include するのとほぼ同じであるが extend された場合は Module.extended を呼び出す点が異なる。逆に言えばそれ以外に違いはない。
  • Module.extended(cls_or_mod) cls_or_modに対してに現在のモジュールがextendされた時に呼び出されるメソッド。
    • extendに連動して共通処理を行いたい場合に有用。
    • このメソッドはModuleクラスのprivateメソッドとして定義されているが、呼ばれる際には現在のモジュールのクラスメソッドとして呼ばれる特性がある。そのためモジュール内でクラスメソッド(つまり特異メソッド)として定義しないと呼ばれないので注意。
class SampleClass; end

a = SampleClass.new
p a.class.ancestors            #=> [SampleClass, Object, Kernel, BasicObject]
p a.singleton_class.ancestors  #=> [SampleClass, Object, Kernel, BasicObject] a.class.ancestorsがバインドされる

module SampleModule2; end
SampleClass.send(:include, SampleModule2)        # includeは自身の一つ上にMix-inする
p a.class.ancestors            #=> [SampleClass, SampleModule2, Object, Kernel, BasicObject]
p a.singleton_class.ancestors  #=> [SampleClass, SampleModule2, Object, Kernel, BasicObject]
# SampleClass(a.class)へのincludeがa.singleton_classにも影響している事から、ancestorsがバインドされていることがわかる

module SampleModule3; end
a.extend(SampleModule3)                          # extendはsingleton_classの先頭にMix-inする
p a.class.ancestors            #=> [SampleClass, SampleModule2, Object, Kernel, BasicObject]
p a.singleton_class.ancestors  #=> [SampleModule3, SampleClass, SampleModule2, Object, Kernel, BasicObject]

module SampleModule4; end
a.singleton_class.send(:include, SampleModule4)  # singleton_classへのincludeは先頭にMix-inする
p a.class.ancestors            #=> [SampleClass, SampleModule2, Object, Kernel, BasicObject]
p a.singleton_class.ancestors  #=> [SampleModule4, SampleModule3, SampleClass, SampleModule2, Object, Kernel, BasicObject]

# 以上の事から特異クラスの継承経路は、自身のincludeモジュールリストに元のクラスの継承経路をバインドしたものである事がわかる
# 重要なのは実際にメソッド探索経路として使われるのは特異クラスのancestorsの方であるという事実である
Ruby 1.9.3 リファレンスマニュアル > Module#include
Ruby 1.9.3 リファレンスマニュアル > Object#extend

include、extendとis_a?の関係
module Test_Module; end

class Include_Test
  include Test_Module
end

class Extend_Test
  extend Test_Module
end

a = Include_Test.new
b = Extend_Test.new
c = Object.new
c.extend Test_Module

p a.is_a?(Test_Module)  #=> true  includeはis_a関係が構築される(includeはclassに追加される)
p b.is_a?(Test_Module)  #=> false クラスへのextendはis_a関係は構築されない(b.classのsingleton_classに追加されるため反応しない)
p b.class.is_a?(Test_Module)  #=> true クラスへのextendでb.classがis_a関係を構築しているのがわかる
p c.is_a?(Test_Module)  #=> true  オブジェクトへのextendはis_a関係が構築される(c.singleton_classに追加されるため反応する)
# obj.singleton_class.ancestorsは特異クラス継承関係の後にobj.class.ancestorsが追加される
# 以上の事からobj.is_a?はobj.singleton_class.ancestors.include?と等価である事がわかる

include、extend時のメソッドリスト
module M
  def module_instance_method; end
  def self.module_singleton_method; end
end
o = Object.instance_methods
p M.instance_methods - o   #=> [:module_instance_method]
p M.singleton_methods      #=> [:module_singleton_method]

module Module2
  def include_test; end
end
M.send(:include, Module2)  # include
p M.instance_methods - o   #=> [:module_instance_method, :include_test]
p M.singleton_methods      #=> [:module_singleton_method]

module Module3
  def extend_test; end
end
M.extend(Module3)          # extend
p M.instance_methods - o   #=> [:module_instance_method, :include_test]
p M.singleton_methods      #=> [:module_singleton_method, :extend_test]

module Module4
  def singleton_include_test; end
end
M.singleton_class.send(:include, Module4)  # singleton_class.include
p M.instance_methods - o   #=> [:module_instance_method, :include_test]
p M.singleton_methods      #=> [:module_singleton_method, :singleton_include_test, :extend_test]

継承とincludeで継承されるもの/参照可能なもの

# 定義 参照/継承されないものは省略
module I
  INC_CONST = 0
  @@inc_class_var = 0
  @inc_ins_var = 0
  def inc_ins_method; end
  class << self
    @@inc_sig_class_var = 0
  end
end
module E
  def ext_ins_method; end
end
class O
  SUPER_CONST = 0
  @@super_class_var = 0
  @super_ins_var = 0
  def super_ins_method; end
  class << self
    @@super_sig_class_var = 0
    def super_sig_method; end
  end
end
class A
  class B < O
    include I
    extend E
    CONST = 0
    @@class_var = 0
    @ins_var = 0
    def ins_method
    end
    def self.sig_method
    end
    class << self
      @@sig_class_var = 0
    end
    class C
    end
  end
end

# 継承/参照の確認
o = Object.instance_methods
p A::B.instance_methods - o  #=> [:ins_method, :inc_ins_method, :super_ins_method]
p A::B.singleton_methods     #=> [:sig_method, :ext_ins_method, :super_sig_method]
p A::B.constants             #=> [:CONST, :C, :INC_CONST, :SUPER_CONST]
p A::B.class_variables       #=> [:@@class_var, :@@sig_class_var]
                             #   [:@@inc_class_var, :@@inc_sig_class_var, :@@super_class_var, :@@super_sig_class_var]も参照可能
p A::B.instance_variables    #=> [:@ins_var]
                             #   [:@inc_ins_var, :@super_ins_var]も参照可能

class A
  OUTER_CONST = 'A'
  class B
    def self.outer_const_get
      OUTER_CONST  # Aの名前空間内ならOUTER_CONSTを参照できる
    end
  end
end
p A::B.outer_const_get         #=> "A" 外部クラス定数のゲッターとして働く
p A::B::OUTER_CONST            #=> ERROR OUTER_CONSTはBの名前空間に属していないので参照できない
p A::B.class_eval{OUTER_CONST} #=> ERROR コンテキストは変化するが名前空間には入らないので参照できない

特異メソッドと特異クラスのインスタンスメソッドの違い
  • 特異メソッドは特異クラスのインスタンスメソッドから、継承したインスタンスメソッドを除いた物である。
    • つまり特異メソッドとしてアクセスできるのはあくまでも特異クラスに直接定義されたインスタンスメソッドのみである点を留意しておきたい。
p M.singleton_methods                        #=> [:module_singleton_method]
p M.singleton_class.instance_methods(false)  #=> [:module_singleton_method]

名前空間(定数)の参照経路

  • Rubyの定数参照経路はメソッドの探索経路と異なり、見つかった瞬間に探索をやめてしまう。そのためメソッドのようなsuperによる連結経路の可視化ができず、一つずつコメントアウトするという方法でしか経路を可視化できない。この特性が定数参照経路への理解を妨げる大きな要因になっている。
  • 下記コードの最後にある「見つからない場合の参照経路」は、実際に一つずつコメントアウトしながら確認した結果を載せている。参考になれば幸いである。
TEST = "::Object"
class Super0;          TEST = "::Super0"  + TEST; end
class Super1 < Super0; TEST = "::Super1"  + TEST; end
class Super2;          TEST = "::Super2"  + TEST; end
module Include;        TEST = "::Include" + TEST; end
  
class Outer < Super1
  TEST = "::Outer" + TEST
  class Base < Super2
    include Include
    TEST = "::Base" + TEST
    class Inner
      TEST = "::Inner" + TEST
    end
  end
end

p Outer::Base::TEST         #=> "::Base::Outer::Super1::Super0::Object" それぞれの定数宣言で上位参照の値を取り込んだ結果
p Outer::Base::Inner::TEST  #=> "::Inner::Base::Outer::Super1::Super0::Object"
# Baseで名前(定数)が見つからない場合の参照経路は、Base -> Outer -> Include -> Super2 -> Object
# Innerで名前(定数)が見つからない場合の参照経路は、Inner -> Base -> Outer -> Object
# Outerへの参照経路はレキシカルスコープで探索される点に注意(次項)
名前空間の定数参照経路がレキシカルスコープである例
  • Rubyの定数参照はレキシカルスコープの境界を可視化できる非常に珍しい好例である。
    • 珍しいという事は馴染みが無いという事なので、これも定数参照への理解を妨げる要因となっている。
TEST = "::Object"
class Super0;          TEST = "::Super0"  + TEST; end
class Super1 < Super0; TEST = "::Super1"  + TEST; end
class Super2;          TEST = "::Super2"  + TEST; end
module Include;        TEST = "::Include" + TEST; end
  
class Outer < Super1
  TEST = "::Outer" + TEST
end

class Outer::Base < Super2  # Outerのレキシカルスコープ外でBaseを定義した例
  include Include
  TEST = "::Base" + TEST
  class Inner
    TEST = "::Inner" + TEST
  end
end

p Outer::Base::TEST         #=> "::Base::Include::Object" レキシカルスコープ外のOuterは参照されない
p Outer::Base::Inner::TEST  #=> "::Inner::Base::Include::Object" InnerはBaseのレキシカルスコープ内なのがわかる

モジュール関数

# モジュール関数化せずに定義した場合
module MyModule
  def module_method;  end
end
class MyClass
  include MyModule
end

a = MyClass.new
a.module_method         # 呼び出し可能 モジュールのメソッドをスーパークラスのメソッドのように呼び出す
MyModule.module_method  # NoMethodError インスタンスメソッドはクラスメソッドとしては呼び出せない
p MyModule.instance_methods   # [:module_method] モジュールにインスタンスメソッドとして定義されている
p MyModule.singleton_methods  # [] 特異メソッド(クラスメソッド)としては定義されない
p MyClass.instance_methods    # [:module_method] モジュールのインスタンスメソッドがMix-inされている

# モジュール関数化した場合
module MyModule
  module_function :module_method
end
a.module_method         # PrivateMethodError モジュール関数はprivateメソッドに再定義される
MyModule.module_method  # 呼び出し可能 モジュール関数はクラスメソッドとして再定義される
p MyModule.instance_methods          # [] モジュールの(publicな)インスタンスメソッドではなくなる
p MyModule.private_instance_methods  # [:module_method] モジュールのprivateメソッドとして再定義される
p MyModule.singleton_methods         # [:module_method] モジュールの特異メソッドとして再定義される
p MyClass.instance_methods           # []
p MyClass.private_instance_methods   # [:module_method] モジュールのprivateメソッドがMix-inされている
Ruby 1.9.3 リファレンスマニュアル > Module#module_function

コンテキスト/クロージャの詳細

  • コンテキストは大きく分けてクラス/モジュール定義コンテキストとメソッド実行コンテキストがある。
    • class定義構文やmodule定義構文の中はクラス/モジュール定義コンテキストとなる。
    • インスタンスメソッド定義構文の中はメソッド実行コンテキストとなる。
    • 特異メソッド定義内はクラス定義コンテキストとなる。
  • コンテキストやクロージャにはいくつかの特例がある。
  • &引数を取らないメソッドにブロックを渡した場合、そのブロックのクロージャオブジェクトは生成されない。(*1)
    • クロージャオブジェクトは生成されないがクロージャとしては動作するので、ブロックにはコンテキストも保持されている。
    • オブジェクト化されていないブロックが存在する場合、procやlambdaによってオブジェクト化する事ができるが、非推奨となっている。
  • instance_eval(instance_exec)にブロックを渡した場合、特殊なクロージャで動作する。
    • 通常のクロージャはブロックが宣言されたコンテキストを保持する。
    • instance_eval(instance_exec)はレシーバオブジェクトのコンテキストとブロック宣言の外側のレキシカルスコープ(主にローカル変数)を合わせたクロージャを形成する。
      • このためブロックを宣言したコンテキストのインスタンス変数やメソッドにはアクセスできない。
    • より正確に言うと、クロージャが持つ情報(self+レキシカルスコープ+コード)のうち、selfだけがレシーバに差し替わる。
      • この事はクロージャオブジェクト(Procオブジェクト)をinstance_evalに渡すことで確認できる。
  • class_eval(module_eval)にブロックを渡した場合、instance_eval同様の特殊なクロージャで動作する。
    • レシーバモジュール(クラス)のコンテキストとブロック宣言の外側のレキシカルスコープを合わせたクロージャを形成する。
  • Kernel#evalの第二引数にBindingオブジェクトを渡すと、そのコンテキストでevalを実行できる。
    • Bindingオブジェクトは生成された時点でのコンテキスト情報のみを保持する。
    • 通常のオブジェクトのコンテキストはKernel#bindingメソッドでBindingオブジェクトとして取り出せる。
    • Bindingオブジェクト自身のコンテキストが変化するわけではない事に注意。
  • Procオブジェクトはクロージャオブジェクトとしてコンテキストを保持している。
    • このコンテキストはbindingメソッドでBindingオブジェクトとして取り出せる。
(*1)参照: まつもと直伝 プログラミングのオキテ 第5回(2)
コンテキスト、Bindingオブジェクト、クロージャ、ブロック、Procオブジェクトに含まれる情報の違い(表)
  • クロージャ(ブロック)はローカル変数だけでなくレキシカルスコープそのものを保持する。
    • 具体的に言うとレキシカルスコープが優先される定数への参照はローカル変数と同様、ブロックの外のレキシカルスコープに準ずる。これはselfのみを変化させるinstance_evalで確認できる。
  • おまけでinstance_evalやclass_evalによるコンテキストの変化、Methodオブジェクトに含まれる情報も追記。
名称含まれる情報備考
コンテキストself + レキシカルスコープコンテキストとはコードを実行する環境そのものである
selfこそがコンテキストの核であり、selfが変わればコンテキストも変化する
selfにはインスタンス変数へのスコープも含まれる
レキシカルスコープには定数への参照やローカル変数が含まれる
Bindingオブジェクトself + レキシカルスコープBindingオブジェクトはコンテキストをオブジェクト化したものである
BindingオブジェクトはKernel.evalの実行コンテキストとして利用できる
クロージャself + 外のレキシカルスコープ + 手続き外のレキシカルスコープとはブロック定義の外側のレキシカルスコープを指す
クロージャとはこのように手続きがコンテキストやスコープを持ち歩くという概念である
ブロックself + 外のレキシカルスコープ + 手続きブロック構文はクロージャという概念をコード化したものである
Procオブジェクトself + 外のレキシカルスコープ + 手続き
+ 引数の情報 + lambdaフラグ
Procオブジェクトはブロックをオブジェクト化した手続きオブジェクトである
RubyのクロージャオブジェクトはほぼProcオブジェクトとして実装されている
proc と lambda があり、引数の扱いやジャンプ構文の挙動が異なる
クロージャの情報に加えて引数の情報と lambda かどうかのフラグを保持する
保持しているコンテキストを bindingメソッドでBindingオブジェクトとして取り出せる
Fiberオブジェクトself + 外のレキシカルスコープ + 手続き
+ ブレークポイント
Fiberオブジェクトはブレークポイントを持つ特殊なクロージャオブジェクトである
callの代わりにFiber#resumeでパラメータを渡し、ブレークポイントから再開する
returnの代わりにFiber.yieldで戻り値を返し、ブレークポイントを記憶する
これによりまるでコマ送りするようにクロージャプロセスをステップ実行できる
instance_evalブロックレシーバオブジェクト
+ 外のレキシカルスコープ + 手続き
instance_evalはブロック内のselfがレシーバオブジェクトに丸ごと差し替わる
必然的にレシーバオブジェクトのメソッド実行コンテキストで実行される
class_evalブロック
(module_evalブロック)
レシーバクラス
+ 外のレキシカルスコープ + 手続き
class_evalはブロック内のselfがレシーバクラスに丸ごと差し替わる
必然的にレシーバクラスのクラス定義コンテキストで実行される
Class.newブロック
Struct.newブロック
新規生成された無名クラス
+ 外のレキシカルスコープ + 手続き
Class.newやStruct.newはブロック内のselfが生成された無名クラスに丸ごと差し替わる
必然的に無名クラスのクラス定義コンテキストで実行される
class_evalとの違いは対象が既存のクラスではなく新規の無名クラスである点にある
言い換えればnewメソッドで生成した無名クラスへのclass_evalと同義であると言える
Methodオブジェクトレシーバ + メソッド名
+ owner(名前空間) + 引数の情報
メソッド情報をレシーバごとオブジェクト化したもの Object#methodで取得
メソッド情報として所属クラス/モジュール(owner)の名前空間も保持する
言い換えればこれは定数へのレキシカルスコープを保持するという事である
Procオブジェクトと異なりコンテキストは保持しない(レシーバへの参照のみ)
保持しているレシーバオブジェクトを Method#receiverメソッドで取り出せる
UnboundMethodオブジェクトメソッド名 + owner(名前空間)
+ 引数の情報
メソッド情報をオブジェクト化したもの Module#instance_methodで取得
メソッド情報として所属クラス/モジュール(owner)の名前空間も保持する
実行時にbindメソッドにレシーバを渡しMethodオブジェクトを生成する必要がある
名前空間が入り乱れた複数のメソッドを扱うような特定のシチュエーションでは有用
複数のレシーバで同名のメソッドを共有する程度ならObject#sendの方が有用
クロージャと特殊ブロックのコンテキストの違い
class MyClass
  def test_closure
    a = :test1
    @proc = proc{ [a, self.class.name] } # Procオブジェクトを作成 test_methodのコンテキストが保存される
    a = :test2
  end
  def main
    test_closure
    a = :main
    p @proc.call                      #=> [:test2, "MyClass"] ローカル変数の値が保持したコンテキストの最終的な値である事が確認できる

    # instance_evalブロックのテスト
    ary = []
    p ary.instance_eval(&@proc)       #=> [:test2, "Array"] ローカル変数は変わらずselfがaryに差し替わっている事が確認できる
    p ary.instance_eval{ [a, self] }  #=> [:main, []] ブロック内は外のローカル変数を共有する

    # eval(binding)のテスト
    s = "[a, self.class.name]"
    p eval(s)                        #=> [:main, "MyClass"]
    p eval(s, @proc.binding)         #=> [:test2, "MyClass"] Bindingオブジェクトでコンテキストが変化した事が確認できる
    p @proc.binding.eval(s)          #=> [:test2, "MyClass"] evalの第二引数にBindingオブジェクトを渡すのと同じ
    p @proc.binding.instance_eval(s) #=> [:main, "Binding"] Bindingオブジェクト自身のコンテキストが変化するわけではない

    # class_evalブロックのテスト
    p MyClass.class_eval(&@proc)       #=> [:test2, "Class"]
    p MyClass.class_eval{ @proc }      #=> nil class_evalはインスタンス変数を共有しない
    p MyClass.class_eval{ [a, self] }  #=> [:main, MyClass] ブロック内は外のローカル変数を共有する

    # 無名クラス生成ブロックのテスト
    Class.new{ p @proc }      #=> nil Class.newのブロック内はclass_eval同様インスタンス変数を共有しない
    Class.new{ p [a, self] }  #=> [:main, #<Class:0x4567890>] Class.newのブロック内は外のローカル変数を共有する
  end
end
MyClass.new.main
Fiberの正体 コンテキストとレキシカルスコープ
  • RubyのFiberオブジェクトの正体は生成時のブロック(クロージャ)を抱えたブレークポイント管理オブジェクト。
    • あくまでも抱えたクロージャを呼び出すだけ。ブレークポイントがある以外はProcオブジェクトと似たようなもの。
    • call(param) する代わりに Fiber#resume(param) でパラメータを渡し、クロージャをブレークポイントから再開する。
    • return result の代わりに Fiber.yield(result) で戻り値を返し、同時にブレークポイントを記憶する。
  • Fiberオブジェクトはコンテキストを生成したりしているわけではない。
    • あくまでも抱えたクロージャの動作に依存する。クロージャはコンテキストを含んでいるので、異なるコンテキストで生成されれば異なるコンテキストを持つ。
    • 同じコンテキスト内で生成されたFiberオブジェクトはコンテキストも同じになる。これは新しいコンテキストを生成していない証明になる。クロージャがコンテキスト(self)とレキシカルスコープを保持する性質がそのままコード結果に反映されているのがわかる。
  • ロジカルな話をするとRGSS3(Ruby1.9.2p0)ではFiberオブジェクトは生成時に渡されたブロック(クロージャ)を専用のヒープに保管する。resume の際にはその専用ヒープへの切り替えが発生し、yield で元のコンテキストに戻すという、いわばスタックとヒープのコピペという力技でブレークポイントを実装している。このヒープ切り替えは通常のメソッド呼び出しなどとは比べるべくもなく遅い。Fiberがクッソ重いのはこのせい。RGSS3ではイベントの実行は全てFiberで行われているため、画面上のイベントの数が増えれば増えるほどFiberの数も増えて重くなる。実際に動いてるイベント(画面上に表示されているイベント)一つに付きFiberオブジェクトが一つ生成されるからだ。これらのイベントの中でコモンイベントを呼び出している場合、さらに追加のFiberが作られる。そのため実際のゲームでは画面上のイベント数+同時に動いているコモンイベント数の数だけFiberオブジェクトが生成される事になる。
    • この構造はいってみれば、今いる部屋(スタック)の机で英語を勉強しているときに、同じ部屋(self)や隣の部屋(異なるコンテキスト)に新しい机(ヒープ)と数学のテキスト(クロージャ)を用意し、一問解くごとに机ごと入れ替えて同時に勉強するようなもので、非効率極まりない。同じ机に二つのテキストを並べて勉強する方が遥かに効率的だ。この事は #並列処理の例 を参照すると良くわかる。
# Fiber例 
a = :lexical  # 外のレキシカルスコープのローカル変数
n = 0
@ins_var = 0  # selfコンテキストのインスタンス変数
f = Fiber.new do                # Fiberオブジェクトの生成
  loop do
    p [:fiber, a, @ins_var, n]  # ファイバー内の出力
    Fiber.yield(n)
    n += 1                      # レキシカル変数(ローカル変数)のインクリメント
  end
end

a = :lexical2  # 外のレキシカルスコープのローカル変数の書き換え
3.times do
 p [:self_, a, @ins_var, f.resume, n]  # ファイバーの外の出力
 @ins_var += 1                  # selfコンテキストのインスタンス変数のインクリメント
end

#=> [:fiber, :lexical2, 0, 0]     Fiberはコンテキストとレキシカルスコープを保持している
#=> [:self_, :lexical2, 0, 0, 0]  
#=> [:fiber, :lexical2, 1, 1]     コンテキストとレキシカルスコープのインクリメントが反映されている
#=> [:self_, :lexical2, 1, 1, 1]  
#=> [:fiber, :lexical2, 2, 2]     
#=> [:self_, :lexical2, 2, 2, 2]  
ブロック定義文とブロック定義節の違い
  • ブロック定義文は { } 波括弧で囲まれたブロック構文を指す。
  • ブロック定義節は do end で囲まれたブロック構文を指す。
  • これらは構文解析の優先順位が異なり、ブロック定義文は単項演算子よりも上、ブロック定義節は引数デリミタである ,(カンマ)よりも下になる。
    • 例えばブロックの評価結果を変数に代入する場合、定義文なら代入できるが定義節だと代入式が先に評価されてしまうため正常に動かない。
    • ブロック定義節は、クラス定義節などと同様に定義そのものを行う際に使用するのが良い。
    • ブロック定義文は、ブロックを渡したメソッドの戻り値を利用する際に使用するのが良い。
  • 一般的にはインラインで書く場合に定義文、マルチラインで書く場合に定義節などと書かれるが、その使い方は正しくないbuggyな書き方なので注意。

コンテキストが変化するタイミング

  • コンテキストは大きく分けて3つのタイミングで変化する。
  • トップレベルコンテキストでは、selfはmainというObjectクラスの特殊なトップレベルインスタンスとなる
    • トップレベルはmainオブジェクトの実行コンテキストであり、Objectクラスのクラス定義コンテキスト(private キーワード直下)でもある特殊なコンテキストである
  • クラス定義コンテキストでは、selfが定義中のクラスオブジェクトとなる。class定義文やmodule定義文の直下で切り替わる。
    • selfがクラスオブジェクトであるため、特異クラス定義である class << self が有効な構文となる。
    • 同様に特異メソッド定義である def self.my_method が成り立つのも、selfがクラスオブジェクトだからである。
    • class定義が class MyClass であるならば、前述の例はより正確には class << MyClass、def MyClass.my_method と定義される。このMyClassというクラスオブジェクトをselfが指し示しているため前述のselfでの定義が可能となるのである。
  • メソッド実行コンテキストでは、selfが実行中のオブジェクト(クラスのインスタンス)となる。def定義文の直下で切り替わる。
    • selfがクラスのインスタンスであるため、クラスのインスタンスメソッドやインスタンス変数にレシーバ無しでアクセスできる。
    • selfがクラスのインスタンスであるため、オブジェクト固有の情報にアクセスする際にselfを活用できる。例えば自分自身の情報を出力する際には p self が有効となる。
# トップレベルコンテキスト
p self  #=> main コンテキスト内の self は main(Objectクラスのトップレベルインスタンス)

class SampleClass
  # SampleClassのクラス定義コンテキスト
  p self     #=> SampleClass コンテキスト内の self は SampleClass
  # Classクラスのインスタンスメソッドやprivateメソッドを記述可能
  # 代表例)attr、extend、include、private、protected、public等
  # 定義構文(class、module、def、alias等)を記述可能

  class InnerClass        # クラス定義を行うと内部クラスとして定義する、自身の名前空間に所属させる
    # InnerClassのクラス定義コンテキスト(より正確にはSampleClass::InnerClassのクラス定義コンテキスト)
    p self  #=> SampleClass::InnerClass コンテキスト内の self は SampleClass::InnerClass
    # つまりInnerClassのフルネームはSampleClass::InnerClass
  end

  class << self  # self(SampleClass)の特異クラス定義コンテキスト
# class << SampleClass  形式での定義も可能(self = SampleClass なので当然と言えば当然)
    p self  #=> #<Class:SampleClass> コンテキスト内の self は SampleClass.singleton_class つまり SampleClass の特異クラス
    # Classクラスのインスタンスメソッドやprivateメソッドを記述可能
    # 代表例)attr、extend、include、private、protected、public等
    # 定義構文(class、module、def、alias等)を記述可能

    # 特異クラスの多重定義 特異クラスもただの無名クラスオブジェクトであるという証明
    # def method_name 〜 end で特異メソッドを定義可能(特異メソッドは特異クラスのインスタンスメソッド)
    # def self.method_name 〜 end で特異クラスの特異メソッドを定義可能(つまり特異クラスの特異クラスのインスタンスメソッド)
    class InnerClass  # 特異クラス内に内部クラスを作った場合
      p self  #=> #<Class:0xnnnn>::InnerClass  特異クラスオブジェクトの名前空間になる
    end
    class << InnerClass  # 特異クラス内の内部クラスに特異クラスを作った場合
      p self  #=> #<Class:#<Class:0xnnnn>::InnerClass>  特異クラスオブジェクトの内部クラスの特異クラスになる
    end
    # 特異クラスの多重定義は技術的には可能だがプログラム的には何の意味もない文字通りの穀潰し(メモリ喰い)なのでやらないように
  end

  def instance_method  # SampleClassのインスタンスメソッド定義
    # メソッド定義内はSampleClassのメソッド実行コンテキスト
    # コンテキスト内の selfはレシーバーとなる。つまり SampleClass.new で生成されたSampleClassオブジェクト
    # 定義構文(class、module、def、alias等)を記述した場合、メソッドが実行された時点で定義が実行される
    # 定義構文はself.class(SampleClass)のクラス定義コンテキストで実行されたのと同じ動作をする
    def dynamic_instance_method  # 動的なインスタンスメソッドの定義
      # 動的メソッド定義内はSampleClassのメソッド実行コンテキスト
      # コンテキスト内の selfはレシーバーとなる。定義構文内でもコンテキストはクラス定義コンテキストに記述したのと同じ
    end
  end

  def self.singleton_method  # self(SampleClass)の特異メソッドの定義
# def SampleClass.singleton_method  形式での定義も可能(self = SampleClass なので当然と言えば当然)
    # コンテキスト内の self はSampleClass(つまり特異メソッド定義内はコンテキストは変化しない)
    # 特異メソッドはいわゆるクラスメソッドとして機能するので当然といえば当然
  end

  p instance_methods(false)   #=> [instance_method]
  p singleton_methods(false)  #=> [singleton_method]
  new.instance_method         # instance_methodを実行
  p instance_methods(false)   #=> [instance_method, dynamic_instance_method] 動的メソッドが追加されている
end

module SampleModule
  # SampleModuleのモジュール定義コンテキスト
  p self     #=> SampleModule コンテキスト内の self は SampleModule
  # Moduleクラスのインスタンスメソッドやprivateメソッドを記述可能
  # 代表例)attr、extend、include、private、protected、public、module_function等
  # 定義構文(class、module、def、alias等)を記述可能
  class << self  # self(SampleModule)の特異クラス定義コンテキスト
    # モジュールであっても特異クラスはclassで定義する
    p self  #=> #<Class:SampleModule> コンテキスト内の self は SampleModule.singleton_class つまり SampleModule の特異クラス
  end

  def instance_method  # SampleModuleのインスタンスメソッド定義
    # メソッド定義内はSampleModuleのメソッド実行コンテキスト
    # コンテキスト内の selfはレシーバー、基本的にMix-inされたクラスのオブジェクト(モジュールのインスタンスメソッドはMix-inされなければ呼び出せない)
  end
  module_function(:instance_method)  # instance_methodをモジュール関数化する
  # module_functionは以下の処理を行う
  # private(:instance_method)  instance_methodをprivateメソッドに変更
  # def self.instance_method
  #   instance_methodの中身をコピーして特異メソッドとして定義
  # end
  alias :alias_instance_method  :instance_method  # エイリアスを設定してもモジュール関数化は引き継がれない
  module_function(:alias_instance_method)         # エイリアスも関数化したければ改めてmodule_functionを行う必要がある
end

class SampleClass::InnerClass
  # フルネームを指定することでトップレベルから直接SampleClass::InnerClassのクラス定義コンテキストに入れる
end
class << SampleClass
  # 同様に直接クラス名を指定することでトップレベルから直接SampleClassの特異クラス定義コンテキストに入れる
end

真偽の判定Tipsと論理演算子

  • false、nil、のみが偽、それ以外はすべて真となる。
    • この事はRubyにおいて全てのオブジェクトは真偽値としても扱える事を意味する。
    • ただし ==、eql?、equal? などで直接比較した場合、false と nil はイコールにはならないし、true と他のオブジェクトもイコールにはならない。
    • Rubyではリテラルも全てオブジェクトなので、false も FalseClassのインスタンス、nil も NilClassのインスタンスとして実装されている。
    • そのため他言語の null値と異なり、Rubyの nilオブジェクトには実体がある。
# 直接比較した場合
true && 1111     #=> 1111  演算子の例)オブジェクトはtrueとして扱われる
true == true     #=> true
true.eql? true   #=> true
true.equal? true #=> true
true == 1111     #=> false trueとオブジェクトはイコールにはならない
true.eql? 1111   #=> false
true.equal? 1111 #=> false

false || nil       #=> nil  演算子の例)nilはfalseとして扱われる
false == false     #=> true
false.eql? false   #=> true
false.equal? false #=> true
false == nil       #=> false falseとnilはイコールにはならない
false.eql? nil     #=> false
false.equal? nil   #=> false
  • Rubyでよく使われる if obj の式は obj が nil かどうかの判定に使われる事が多いが、obj が真偽値を取る場合は nil と false の区別を付けられない。
    • この場合、if obj.nil? を利用できる。nil?メソッドは NilClass を含めて全てのクラスで呼び出せるため obj が nil でもエラーする事は無い。
    • または if obj == false なども良い。
    • 通常 false と nil を判別する必要は無いが、例えばゲームのオプション設定でデフォルトが true の場合に問題が発生する。これを初期化する際に value ||= default_value としてしまうと、ユーザーが設定を false に変更していた場合に true に再設定されてしまう。特にRGSS3でF12リセットした場合にはインスタンスがそのままに初期化処理が呼ばれるため問題が起きやすい。このような場面ではきちんと initialize で初期化するか、nil判定などを使って初期化処理を書く必要がある。
  • 他言語のように 0 を false として判定したい場合、nonzero?メソッドを使う。value.nonzero? はゼロの場合に nil を返すため、判定式で false として扱われる。
    • nonzero? はメソッド名から bool を返すように見えるが実際には 0 以外の時 self を返す。そのため戻り値は 値 or nil となる。
    • マイナスの場合は false にならない点に注意。マイナスも false としたい場合は(value > 0)等で判定する必要がある。
否定演算子
  • Rubyの否定演算子には ! と not の二種類がある。これらは優先順位が異なる。つまり使用目的が異なる。
    • ! が優先順位が高く、not は低い。記号が高く英単語が低いと覚えれば楽。
  • !(単項否定演算子)は、右辺のオブジェクト単体を否定する際に使用する。これは非常に強く結びつくため単項否定と呼ばれる。
  • not(式否定演算子)は、右辺の式全体を否定する際に使用する。notの下にはフロー制御演算子や後置修飾子ぐらいしかないので演算子式を全てまとめて否定できる。
  • ! と not の優先順位は大きく離れている。特に ! は比較演算子やビット演算子、算術演算子よりも優先順位が上である事に注意。
    • 逆に式否定演算子は代入演算子よりも優先順位が下である点に注意。つまり a = not true などはパースエラーする。
  • 条件式と組み合わせる場合には注意が必要。乱用すると可読性を著しく下げる事になる。
    • 単項の否定条件式なら単項否定を使わずに unless を使えば済むが、複合条件を否定したい場合は unless よりも if not を使うよう推奨される事が多い。
    • ただし unless は否定条件式ではなく、条件式を満たす対象を除外する構文なので、それを知っていればむしろ可読性は向上する。
# 単項否定と式否定の優先順位の違い
p !true && false     #parsed p (!true) && false
p not true && false  #parsed p not (true && false)

# unlessを用いた否定式とif文の関係(下記は全て同じ動作をする)
unless a && b  # a && b を満たしたら、実行しない(シンプル)
if !(a && b)   # 次の(a && b)を満たす、ではないなら、実行する(迂遠)
if not a && b  # 上記と同じ
if (a && b).!  # (a && b)を満たす、ではないなら、実行する(こんなアホな書き方でも思考と文脈がリンクする分if notよりマシ)
unless a && b  # a && b を満たしたら、実行しない(シンプル、結局これが一番)
参照: 備忘録#unless、untilの混乱しない考え方
AND演算子とOR演算子
  • RubyのAND/OR演算子は && と and、|| と or それぞれ二種類ある。これらは優先順位が異なる。つまり使用目的が異なる。
    • &&、||が優先順位が高く、and、orは低い。記号が高く英単語が低いと覚えれば楽。
    • 論理演算子内の優先順位だけでなく、代入演算子との優先順位も異なる。記号>代入演算子>英単語の順に優先順位が高い。代入時は注意が必要。
  • && と ||(二項論理演算子)は、判定文などの条件式に使用する。&&(AND演算子)の方が ||(OR演算子)よりも優先度が高い。
  • and と or(フロー制御演算子)は、フローと値を制御する際に使用する。これらはフローを制御するため演算子同士の優先度はない。(次項で解説)
# 優先順位による違い
a = b && c    #parsed a = (b && c)  
a = b and c   #parsed (a = b) and c(フロー制御演算子を判定に使う間違った使用法)
a || b && c   #parsed a || (b && c)  
a or b and c  #parsed (a or b) and c(フロー制御演算子を判定に使う間違った使用法)

フロー制御演算子とフロー制御メソッド

  • Rubyの論理演算子には&&、||、and、orの4種類があり、それぞれ優先順位が異なる。
    • &&と||は&&が優先される。andとorは優先順位がかなり低く、and、orそれぞれに優先順位はない(つまり左から評価される)。
    • これらは「フロー制御にも用いる事ができる」と解説される場合があるが、その表現は正しくない。
  • and、orは非常に強力なフロー制御演算子である。
    • フロー制御である事を明確にするため、1行に1つまで行末に使い改行を伴う、をルール化するとコードの可読性とフローの可視性が向上する。
  • Rubyでは`値とnilのどちらか`を返すと言うメソッドが非常に多い。これはまさにフロー制御演算子を使うための仕様である。
    • このようなメソッドはフロー制御メソッドと呼ぶ。フロー制御演算子と組み合わせると非常に無駄のないコードが書ける。
  • 以下にサンプルコードを示す。
# 一般的な制御文を用いて戻り値を制御する場合
def test_method
  if a > 0 && b > 0  # functionを呼ぶ条件
    function(a, b)   # functionの戻り値がtest_methodの戻り値となる
  else
    false            # 条件を満たさなければtest_methodの戻り値としてfalseを返す
  end                # else節が無い場合、test_methodの戻り値はnilになる
  # 理解しやすいが冗長
end

# 論理演算子を用いて戻り値を制御する場合
def test_method
  a > 0 && b > 0 && function(a, b)  # 論理演算子がfalseになった時点でfalseを返し、そうでなければfunctionの戻り値がtest_methodの戻り値となる
  # 非常にコンパクトになっているが条件比較と関数呼び出しという二つのフローが一行に混在してしまい、可読性が低い
end

# フロー制御演算子を用いて戻り値を制御する場合
def test_method
  a > 0 && b > 0 and  # functionを呼ぶ条件が満たされれば次にフローを進め、満たされなければfalseを戻り値とする
    function(a, b)    # functionの戻り値がtest_methodの戻り値となる
  # 条件比較と関数呼び出しという二つのフローが視覚的に分離していて可読性が高く、制御文の冗長さもない
end

# フロー制御メソッド+フロー制御演算子の活用例
def find_employee(name)
  役員名簿.find{|m| m.name == name } or
  社員名簿.find{|m| m.name == name } or
  出向社員名簿.find{|m| m.name == name } or
  nil  # 存在しない
end
この記事はQiitaでより詳しく解説している
Qiita: https://qiita.com/midpolarnight/items/ba16bc2ea5a5...
フロー制御演算子による既存コードのリファクタリングのイディオム
# 元コード
class Game_Enemy
  def item_object(kind, data_id)
    return $data_items  [data_id] if kind == 1
    return $data_weapons[data_id] if kind == 2
    return $data_armors [data_id] if kind == 3
    return nil
  end
end

# フロー制御メソッドとフロー制御演算子を組み合わせた例
class Game_Enemy
  def item_object(kind, data_id)
    kind == 1 && $data_items  [data_id] or
    kind == 2 && $data_weapons[data_id] or
    kind == 3 && $data_armors [data_id] or
    nil
  end
end






Rubyリファレンスの補足

整数範囲

  • Rubyの整数はIntegerクラスだが、1.9では内部で更に小さい数値(Fixnum)と大きい数値(Bignum)にわけられる。
    • これらは自動的に変換され、プログラマが意識する必要は無い。上限は実行環境によって異なる。
  • RGSS3ではFixnumは符号付31ビット整数で-10_7374_1824〜10_7374_1823の範囲になる。これは64ビットOSを使っていても変わらない。
    • ちなみに数値を _(アンダーバー) で区切るのはRubyでサポートされている書き方。そのままリテラルとして扱えるので大きい数値を書く際は利用したい。
  • Fixnumを超える数値は自動的にBignumに変換される。Bignumは実質的に限界がない。例えば2の1024乗のような数値も問題なく扱える。
    • Bignumは数値に合わせて自動的にメモリが拡張される。21億なら48+4バイト、2^1024なら48+132バイトになる。(後述)
      • バイト数はBignum#sizeで取得できる。
    • BignumはFixnumよりも計算が遅いという欠点があるので必要がなければFixnumの範囲内で計算を収める方が効率的。とはいえそこまで気にするほどの差はない。
      • 100万回のインクリメント所要時間
        Fixnum(4bytes)0.073〜0.075秒
        Bignum(8bytes)0.162〜0.166秒
        Bignum(44bytes)0.360〜0.364秒
    • Bignumは数値が小さくなると自動的にメモリも縮小される。30ビット以下になった場合はFixnumに自動的に変換される。
  • Fixnumは実際にはオブジェクトへのポインタを含まない符号付31ビット整数として扱われるが、BignumはBignumオブジェクトへのポインタとして扱われる。
    • BignumオブジェクトはRubyの基本構造である40バイト構造体と4バイトx2個のフィールドで構成され、更に可変の32ビット整数配列のメモリを確保して数値を記録している。
    • つまり10億なら実際に4バイトしか使わないが、20億だと実際には48+4=52バイトのメモリを使用する。
    • 符号は構造体の方に記録されているため、配列は符号無し整数で記録される。
    • 可変32ビット整数配列は当然32ビットごとの値になるので、符号無し32ビット整数である42_9496_7295までは+4バイトで格納できる。42_9496_7296になると+8バイトに拡張される。

008は不正な値

  • Rubyでは0で始まる数字は8進数とみなされる。8進数に008は存在しないため Invalid octal digit エラーとなる。
    • コード上の体裁を整えるために数字を0詰めするクセのある人は注意されたし。
  • 例えばVXAceのデータベース上の表記に合わせようと $game_variables[010] などと書くと失敗する。010は8進数では8を意味する。
    • 008のようなエラーが出るバグよりも010のようなエラーが出ないバグの方が見つけづらい。数字の扱いには注意したい。
  • 0x010は16進数とみなされ16を表す。
  • 0b010は2進数とみなされ2を表す。
  • 0d010は明示的な10進数とみなされ10を表す。
  • 0o010は明示的な8進数とみなされ8を表す。
    • 他の言語では0o010だけが8進数とみなされ010は10進数という場合もあるので若干紛らわしいが、大抵の言語では010は8進数なので覚えておきたい。
    • ちなみにMVやMZはJavaScriptなため、010は8進数とみなされ8だが、008は10進数とみなされエラーせずに8として通ってしまうらしいので余計にややこしい。注意されたし。
  • 総評: 数字を0で始めてはいけない。

16進数変換、n進数変換

  • 10進数を16進文字列に変換するならsprintf("%X", 255)や255.to_s(16)を使う。to_sや%xなら"ff"、%Xなら"FF"を返す。
  • 16進文字列を10進数に変換するにはhexやto_i(16)を使う。"FF".hexなら255を返す。
  • to_s(n)、to_i(n)を使えば2〜36進数まで自在に変換できる。
    • Integer#to_s(n) 整数をn進数文字列に変換して返す。35.to_s(36)なら'z'を返す。
    • String#to_i(n) n進数文字列を整数に変換して返す。
      • to_iで数字に変換できる文字列はプリフィクスの有無を問わない。例えば"0xff"と"ff"はto_i(16)を渡せばどちらも255を返す。
      • プリフィクス付きの文字列にto_i(0)を渡すとプリフィクスを自動判別する。"0xf"、"0d15"、"0o17"、"017"、"0b1111"はto_i(0)を渡せば全て15を返す。
# 0で始まる文字列を数値変換した場合の注意点
"010".to_i     # 10 を返す 引数省略時は10として扱われる
"010".to_i(10) # 10 を返す 0始まりであっても10進数と見なす
"010".to_i(16) # 16 を返す 0始まりであっても16進数と見なす
"010".to_i(2)  # 2 を返す 0始まりであっても2進数と見なす
"010".to_i(8)  # 8 を返す 8進数として変換するには明示する必要がある
"010".to_i(0)  # 8 を返す 自動判別なら0をプリフィクスと解釈して8進数として返す

# プリフィクスの無い文字列の注意点
"1ff".to_i      # 1 を返す 10進数ではない文字が出現した時点で変換を終了する
"1ff".to_i(16)  # 511 を返す 明示してあるので16進数として解釈できる
"1ff".to_i(0)   # 1 を返す プリフィクスが無いため10進数として解釈され途中で変換が終了する
"1ff".to_i(36)  # 1851 を返す 36進数として変換する
"1_ff".to_i(36) # 1851 を返す リテラル同様 _ を無視する

# 数値変換の注意点
-1.to_s(16)     # "-1" を返す "ffffffff" になったりはしない

# 暗号化への応用
"string".to_i(36)   # 1743045676 を返す 36進数にする事で[0-9a-z]で構成された文字列を数値に暗号化できる 大文字小文字は無視される
1743045676.to_s(36) # 'string' を返す 復号時は小文字になる
1743045676.to_s(18) # '2f4842ag' を返す 復号時に進数を変えることで更に複雑な暗号化も可能
'2f4842ag'.to_i(18).to_s(36) # 'string' を返す 戻す時は逆順に変換するだけでいい

定数と多重代入に関するTips

  • クラス内で定義された定数はクラス内のどこからでも参照できる。
    • 定数は継承されるので、親子関係の参照経路を持つ。
    • 定数は外部クラスへのレキシカル参照経路を持つ。これは親子関係よりも優先される。
    • クラス内からの同名定数の参照経路は、自身の定数→外部クラスの定数を遡る→親クラスの定数を遡る→グローバル名前空間の定数の順。
      • includeされたモジュールの定数は親クラスを遡るルールで参照される。これはModule#ancestorsで確認できる。
      • 特異クラスの定数は参照されない。
  • クラス名は定数である。クラス名が持つ値はClassオブジェクト(つまりClassクラスのインスタンス)である。
  • 同名クラス名の参照経路は、内部クラス名→自身のクラス名→外部クラス名を遡る→親クラス名を遡る→グローバル名前空間のクラス名の順。
    • 自身と同名の内部クラスを定義した場合、自身よりも内部クラスを優先して参照する点に注意。
    • ロジカルな話をすると、Rubyの定数は自身に定義された定数を最優先で探索する。内部クラス名は自身に定義された定数なので優先して参照される。自身のクラス名は自身の外部クラスに定義された定数なので、参照経路としては内部クラス名よりも後になる。
    • 参照されるのは自身に直接定義された内部クラス名だけであり、内部クラスの内部クラスまでは参照しない。あくまでも自身に定義された定数だから内部クラス名が優先されているだけに過ぎない点に注意。
  • 多重代入は複数の変数に複数のデータを一括で格納できる機能。
    • 多重代入を利用する事で冗長なコードをすっきりとまとめるシンタックスシュガーとして機能する。
    • 多重代入は配列を引数とするメソッドやブロックパラメータから配列インデックスを排除して可読性を向上させる最も強力なイディオムである。
# 定数
CONST_VALUE = [1,2,3]  # 定数への代入
CONST_VALUE = [4,5,6]  # Rubyでは定数は再代入可能 可能だが再代入しないことが暗黙の了解となっている
CONST_VALUE[1] = 7     # Rubyでは定数が参照するオブジェクトの変更も可能
CONST_VALUE.freeze     # freezeする事で定数が参照するオブジェクトを変更不能にする事はできる
CONST_VALUE = 1        # freezeしても再代入は防げない freezeで防げるのはオブジェクトへの破壊的な操作のみ

# 多重代入 複数の変数に同時に値を代入できる
a,b,c = 1,2,3       # a = 1, b = 2, c = 3 と同じ
(a,b),c = [[1,2],3] # a = 1, b = 2, c = 3 と同じ この形式は配列を値に取るブロックパラメータなどで役に立つ
                    # メソッド引数やブロックパラメータは多重代入とほぼ同じルールが適用される
                    # この使い方は非常に強力な可読性向上のイディオムであり、上記と併せて最も多用される使用例でもある
a = [1,2,3,4,5]
a[1..3] = [7,8,9]   #=> [1,7,8,9,5] 配列の範囲参照への範囲代入も可能
a[2] = [2,3,4]      #=> [1,7,[2,3,4],9,5] 配列の要素に配列を代入しても範囲代入にはならない
a[2..2] = [4,3,2]   #=> [1,7,4,3,2,9,5] 範囲参照への範囲代入に過不足があると自動で調整される
b,c,d = a           # b = a[0], d = a[1], d = a[2] と同じ 配列展開も可能 メソッドの引数展開に良く利用される
b,c,*d = a          # b = a[0], d = a[1], d = a[2..-1] と同じ

# 多重代入の応用
a,b = b,a           # a と b を入れ替える 地味ながら非常に強力なイディオム
参照: Ruby 1.9.3 リファレンスマニュアル > 変数と定数#定数
参照: Ruby 1.9.3 リファレンスマニュアル > 演算子式
多重代入による既存コードのリファクタリングのイディオム
class Game_Interpreter
  # サンプル原文
  def command_311
    value = operate_value(@params[2], @params[3], @params[4])
    iterate_actor_var(@params[0], @params[1]) do |actor|
      next if actor.dead?
      actor.change_hp(value, @params[5])
      actor.perform_collapse_effect if actor.dead?
    end
    SceneManager.goto(Scene_Gameover) if $game_party.all_dead?
  end
  # 配列の範囲抽出と引数展開によるコード短縮
  def command_311
    value = operate_value(*@params[2..4])
    iterate_actor_var(*@params[0..1]) do |actor|
      next if actor.dead?
      actor.change_hp(value, @params[5])
      actor.perform_collapse_effect if actor.dead?
    end
    SceneManager.goto(Scene_Gameover) if $game_party.all_dead?
  end
  # 分かりやすい名前に多重代入して可読性向上
  def command_311
    opr, id, operation, operand_type, operand, enable_death = @params
    value = operate_value(operation, operand_type, operand)
    iterate_actor_var(opr, id) do |actor|
      next if actor.dead?
      actor.change_hp(value, enable_death)
      actor.perform_collapse_effect if actor.dead?
    end
    SceneManager.goto(Scene_Gameover) if $game_party.all_dead?
  end
end

p、puts、printの違い

  • ログ出力する方法はp、puts、printなどがある。これらは出力される情報が異なるので適切に使い分ける必要がある。
    • p(value)  puts value.inspect と同じ結果を出力する。
    • puts(value)  value + 改行を出力する。VXAceヘルプの記載と異なりnilは出力されない。
    • print(value)  valueを出力する。改行は出力されない。VXAceヘルプの記載と異なりnilは出力されない。
    • printf(format, value)  format文字列で整形したvalueを出力する。改行は出力されない。
      • print( sprintf( format, value ) ) と同じ。
  • 通常はpを使うのが一番間違いがない。
    • RGSS3では基本的にデバッグ用ログの出力ぐらいにしか使わないのでpで十分。
    • ただしputsは速度が速い。速度が必要な場合(ループ内のログ出力など)にはputsが有用。
# p, puts, printの違い
p "String"      #=> "String"\n
p 'String'      #=> "String"\n ''シングルクォートは ""ダブルクォートになる
puts "String"   #=> String\n putsの場合、"" や '' は付かない
print "String"  #=> String  printの場合も同様

p :String      #=> :String\n
puts :String   #=> String\n putsの場合、文字列の"String"と見分けがつかない
print :String  #=> String  printの場合も同様

p "0"      #=> "0"\n
puts "0"   #=> 0\n putsの場合、数字の0と見分けがつかない
print "0"  #=> 0  printの場合も同様

p 0      #=> 0\n
puts 0   #=> 0\n putsの場合、文字列の"0"と見分けがつかない
print 0  #=> 0  printの場合も同様

p nil      #=> nil\n
puts nil   #=> \n putsの場合、nilは出力されない
print nil  #=>   printの場合も同様

p      #=>  引数を省略した場合、何も出力しない
puts   #=> \n 引数を省略した場合、改行を出力する
print  #=>  引数を省略した場合、何も出力されない
参照: Ruby 1.9.3 リファレンスマニュアル > sprintf フォーマット

equal?、eql?、==、=== の違い

# Object#equal? は、object_id を比較する
# Object#object_id は各オブジェクトのインスタンスごとに一意の値を返す 文字列の場合、同じ文字列でも基本的にidは異なる
111.equal?(111)       # true 
"a".equal?("a")       # false 内容は同じでもobject_idは異なる
1.equal?(1.000)       # false クラスが異なるのでobject_idは異なる
([1,2]).equal?([1,2]) # false 内容は同じでもobject_idは異なる
({a:1}).equal?({a:1}) # false 内容は同じでもobject_idは異なる

# Object#eql? は、hash を比較する
# Object#hash は基本的に object_id と同じ値を返す 文字列の場合は内容ごとに一意の値を返す
111.eql?(111)       # true 
"a".eql?("a")       # true String#hashは内容ごとに一意の値を返す
1.eql?(1.000)       # false クラスが異なるのでhashは異なる
([1,2]).eql?([1,2]) # true Arrayのeql?は全ての要素をeql?で比較した結果を返す
({a:1}).eql?({a:1}) # true Hashのeql?はsizeを比較し、全てのキーをeql?で比較し、全ての値を==で比較した結果を返す

# Object#== は、内容を比較する
111 == 111         # true 
"a" == "a"         # true
1 == 1.000         # true クラスが違っても数値として同じならtrue
([1,2]) == ([1,2]) # true Arrayの==は全ての要素を==比較した結果を返す
({a:1}) == ({a:1}) # true Hashの==はHash#eql?と同じ

# Object#=== は、case式での振る舞いを考慮してオブジェクトごとに再定義することが推奨される
111 === 111         # true 通常は == と同じ結果を返す
"a" === "a"         # true
1 === 1.000         # true
([1,2]) === ([1,2]) # true
({a:1}) === ({a:1}) # true
# Module#=== v  # v.is_a?(Module) と同義
# Range#=== v   # Range#include?(v) と同義
# Regexp#=== v  # Regexp#=~ v の結果を bool で返す
# Proc#=== v    # lambda{|v| } の結果を返す case式のwhenにproc手続きを渡すための実装
  • Hashのキーの比較には eql? が使われる。そのため hash や eql? が適切に定義されていないオブジェクトはキーに向かない。
    • 逆にキーに向いているのはFixnum、Symbol。
    • HashのキーにStringが与えられた場合、その文字列のコピーをfreezeしたオブジェクトをキーとして保持する。そのためキー文字列を変更する事はできない。

Rangeクラスの範囲判定

  • Rangeクラスの範囲判定はcover?を使うのが一般的だが、VXAceヘルプには===しか書かれていない。他にinclude?やmember?も使用できる。
    • Rangeクラスの範囲判定を使うことで、例えばクエスト回数が5〜10の時だけセリフを変える、などの判定が容易になる。
    • 例えばRGSS3でスイッチに渡す添字が範囲内かどうかをチェックするには、(1...$data_system.switches.size).cover?(添字) とする。実際のコードでは添字の整数チェックもあった方がより堅牢。
  • Ruby2.6以降と異なり、RGSS3(Ruby1.9.2)ではcover?の引数にはRangeオブジェクトを取ることはできない。RGSS3でRange同士を比較する場合、始端と終端が含まれるかどうかで判別する必要がある。
# 例)
(1..20).cover?(20)   #=> true
(1...20).cover?(20)  #=> false  ...は終端を含まない

# include?などとの違い
('a'..'z').cover?('bug')    #=> true   <=>での比較結果を返す
('a'..'z').include?('bug')  #=> false  要素が含まれているかどうかを返す
('a'..'z').include?('def')  #=> false  Rangeは連続した文字列とはみなされない
('a'..'z').member?('def')   #=> false  1.9.2ではinclude?と同じ
('a'..'z') === 'def'        #=> false  1.9.2ではinclude?と同じ  === はcase文で使われる
'd' === ('a'..'z')          #=> false  1.9.2では === は右辺がRangeの場合は正しく判定できない
(1..20).cover?(1.5)    #=> true
(1..20).include?(1.5)  #=> true   1.9.2以降はtrueを返す。数値の場合はcover?と同じ
(1..20).member?(1.5)   #=> true   1.9.2ではinclude?と同じ
(1..20) === 1.5        #=> true   1.9.2ではinclude?と同じ  === はcase文で使われる
1.5 === (1..20)        #=> false  1.9.2では === は右辺がRangeの場合は正しく判定できない
(Time.new(2024,1,1)..Time.new(2024,10,30)).cover?(Time.new(2024,10,1))  #=> true  日付も判定可能
(Time.new(2024,1,1)..Time.new(2024,10,30)).cover?(Time.new(2024,11,1))  #=> false
# 単純な処理速度はinclude?よりcover?の方が速い

(1..20).cover?(2..5)    #=> false  1.9.2では範囲式は対応していない
(1..20).include?(2..5)  #=> false  1.9.2では範囲式は対応していない
(1..20).member?(2..5)   #=> false  1.9.2では範囲式は対応していない
(1..20) === (2..5)      #=> false  1.9.2では範囲式は対応していない
r1 = (1..20)
r2 = (2..5)
r1.min <= r2.min && r1.max >= r2.max # 冗長だがこういった方法で判別するしかない
#case文で範囲式を使う分には問題ないが、valueがRangeオブジェクトだと判定に失敗する
case value
when 1..5   # valueが1〜5の場合に実行される
when 6..10  # valueが6〜10の場合に実行される
else        # valueが1〜10ではない場合に実行される
end

Rubyの自由なcase文

  • Rubyのcase文は判定に === が用いられる。
    • === は様々なクラスで独自の判定に再定義されているため、非常に自由な文法でcase文を記述できる。
    • 判定は when式 === value の結果 になる。value は先に評価されてから when式 と比較される。
  • 'caseの式'(value)を省略すると when に条件式を書けるようになり更に自由度が上がる。
  • Procオブジェクトを渡すとProcオブジェクトを call した結果を返す。
    • ブロックを受け取るメソッドへProcオブジェクトを渡す場合と異なり、自動的に to_proc が呼ばれることは無い。
case value     # thenは省略可能 thenを省略する場合はwhen式と実行式を改行で分ける必要がある
when 1         then # valueが1の場合に実行される 他言語でも一般的なcase文の例
when 2, 3      then # valueが2か3の場合に実行される 複数の値を条件にする事も可能
when 4..6      then # valueが4〜6の場合に実行される 範囲式も使える
when *[7,8,9]  then # valueが7,8,9の場合に実行される Arrayは * で引数展開すれば使える
when Integer   then # valueがIntegerクラスかその継承クラスの場合に実行される 
when /red/     then # valueがredを含むシンボルか文字列の場合に実行される
when /#{:red}/ then # valueがredを含むシンボルか文字列の場合に実行される シンボルを正規表現で使う場合は式展開する
when /\d+/     then # valueが0-9を含むシンボルか文字列の場合に実行される
when /\w+/     then # valueがa-zA-Z0-9を含むシンボルか文字列の場合に実行される
end

# caseの式を省略するとwhenに条件式を書ける
case
when value == 1              then  # valueが1の場合に実行される
when value == 2 && switch    then  # valueが2でswitchがtrueの場合に実行される
when value.even?             then  # valueが偶数の場合に実行される
when [7,8,9].include?(value) then  # valueが配列に含まれる場合に実行される 引数展開するよりはスマート
end

# Procオブジェクトの場合
def odd?; lambda{|v| v.odd? }; end  # パラメータが奇数かどうかを返すprocオブジェクトを返すメソッド
odd = lambda{|v| v.odd? }           # パラメータが奇数かどうかを返すprocオブジェクト

case value
when odd?  then          # lambda{|v| v.odd? }.call(value) の結果を返す
when odd   then          # 同上
when :odd? then          # 自動的にto_procが呼ばれる事はないので単なるシンボルとの比較になる
when :odd?.to_proc then  # proc{|v| v.odd? }.call(value) の結果を返す
end                      # 複雑な式や特定のクラスに紐付いた判定を行う場合に可読性の向上に役立つ 速度は遅い

a = case value  # caseは最後に評価した値を返すため結果を代入する事もできる
when 1..3 then 1
when 4..6 then 2
when 7..9 then 3
end
# この特性により、判定メソッドで値を返す際に戻り値用変数やreturnなどを省略できる
def judge(value)
  case value        # result = case value などと書く必要がない
  when 1..3 then 1  # return 1 や result = 1 などと書く必要がない
  when 4..6 then 2
  when 7..9 then 3
  end               # もしelse節がなく条件を満たさない場合はnilを返す
end

ループの種類

# 一般的なループ
while i < max             # while文 右辺の式がtrueの間ループを続ける(falseになったら脱する)
  i = i.succ
end
until i < 0               # until文 右辺の式がtrueになったらループを脱する(falseの間続ける)
  i = i.pred
end

i = i.succ while i < max  # while修飾子 右辺の式がtrueの間、左辺の式を繰り返し実行する
i = i.pred until i < 0    # until修飾子 右辺の式がtrueになるまで、左辺の式を繰り返し実行する

loop                      # 無限ループ break文で脱出する必要がある
end

loop do                   # do-while代替の推奨構文
  i = i.succ
  break unless i < max
end

# イテレータによるループ
[1,2,3,4,5].each{|v|  }   # オブジェクトの要素を繰り返す 代表的なイテレータ
5.times{|i|  }            # レシーバの回数分処理を繰り返す (0...5).eachと同じ Rubyの一般的な固定数ループ
(1..5).step(step){|i|  }  # Rangeオブジェクトの範囲内を+stepしながら処理を繰り返す 引数にマイナスを指定可能
1.upto(5){|i|  }          # レシーバを基点として引数の数まで数を+1しながら処理を繰り返す (1..5).step(1)と同じ
5.downto(1){|i|  }        # レシーバを基点として引数の数まで数を-1しながら処理を繰り返す (1..5).step(-1)と同じ

# 非推奨構文
for i in obj [do]  # for文は非推奨 内部で obj.each を呼んでいる
end

begin
  i = i.succ
end while i < max  # begin end + while修飾子(do-while文)は非推奨 ループ処理を評価した後にwhile式を評価する
begin
  i = i.pred
end until i < 0    # begin end + until修飾子(do-until文)は非推奨 ループ処理を評価した後にuntil式を評価する
参照: #ループの速度比較

ソート例

  • 数値型はNumeric継承クラス、比較型はComparable実装クラス(Numeric、String、Symbol、Time)、その他はそれ以外のオブジェクト。
  • 安定性は同じデータがあった際の挙動。安定は入れ替わらないが、不安定は入れ替わる可能性がある。
    • Ruby1.9.2のsortはクイックソート(不安定ソート)なので、安定ソートするには工夫が必要。
ソートコード順序安定性数値型比較型その他備考
sort昇順不安定×Array、Hashにはより効率的な専用のsortがある
sort.reverse!降順不安定×reverseすれば簡単に逆順を得る事ができる
sort{|a,b| a <=> b }昇順不安定ブロックでの評価は遅い
sort{|a,b| b <=> a }降順不安定
sort_by{|a| a }昇順不安定sort_byは評価した値を元にソートする
オブジェクトの一要素を元にソートする場合などに有効
sort_by{|a| a }.reverse!降順不安定
sort_by{|a| -a }降順不安定××
sort_by(&:+@)昇順不安定××単項演算子のProc渡し、Numericのみ
sort_by(&:-@)降順不安定××
sort_by.with_index{|a,i| [a,i] }昇順安定indexを含む配列でソートする事で安定ソートになる
結果的に要素数と等しいArrayを生成するためメモリ効率は悪い
sort_by.with_index{|a,i| i.to_f / 1024 + a }
※割る数は配列要素数以上の2の乗数
昇順安定××indexをfloat化加算して安定ソートする、Integerのみ
状況は限定されるがindex配列式より高速省メモリ
sort_by{|a| [a.atk, a.def] }昇順不安定複数の要素で配列を作る事で複数条件ソートする
重複時に追加条件で並べ替えるため安定ソートに近い
要素数のArrayを生成するためメモリ効率は悪い
条件が4つ以上になると効率が落ちるので3つまで推奨
参照: #ソートの速度比較

イテレータの分類

  • Rubyのイテレータは大きく5つの種類に分けられる。
繰り返し
eachなど、コレクションに対して繰り返し処理を行うための基礎的なイテレータ。特にeachは全てのイテレータの基礎として動作する。
探索
find、indexなど、コレクションから要素を探したり、要素の状態を調べる事を目的としたイテレータ。
変換
injectなど、コレクションの要素を元に別のオブジェクトに変換する事を目的としたイテレータ。
生成
map、selectなど、コレクションの要素を元に新しいコレクションを生成する事を目的としたイテレータ。
破壊的
map!、select!など、コレクション自身を書き換える事を目的としたイテレータ。

代表的なイテレータまとめ

  • Rubyは非常に強力なイテレータ(特に内部イテレータ)を持つ言語なのでRGSS3でもそれらを活用できる。
  • 全てを網羅してはいないので詳細は公式リファレンス参照。
Enumerableで使えるイテレータ
# Enumerableモジュールをincludeするにはeachメソッドが実装されている必要がある
# Enumerable全般で、item は Hashなら[key,value]、Structならvalue が渡される
# Enumerable全般で、戻り値がArrayの場合、Hashなら[key,value]の配列(二次元配列)を返す

# 繰り返し
reverse_each {|item| ...} -> self            # 要素を逆順に評価する パラメータには各要素が渡される
each_with_index {|item, index| ...} -> self  # 要素を順番に評価する パラメータには各要素とインデックス番号が渡される
cycle(n=nil) {|item| ...} -> nil             # 要素を順番に評価する動作をn回繰り返す nilなら無限ループ パラメータには各要素が渡される
each_slice(n) {|list| ...} -> nil            # 要素をn個で区切って順番に評価する
[1,2,3,4,5,6].each_slice(2){|list|  }             # Array例)[1,2] [3,4] [5,6] が順に渡される
{a: 1, b: 2, c: 3, d: 4}.each_slice(2){|list|  }  # Hash例)[[:a,1],[:b,2]] [[:c,3],[:d,4]] が順に渡される

# 探索
find_index {|item| ...} -> Integer|nil                # 最初にtrueになった要素のインデックス番号を返す なければnilを返す
  # Hashの場合、ここで返されるインデックス番号はhash.to_a配列のインデックス番号に等しい
find(ifnone = nil) {|item| ...} -> item|nil|ifnone    # 最初にtrueになった要素を返す なければnilまたはifnone.callを返す
detect(ifnone = nil) {|item| ...} -> item|nil|ifnone  # 同上
max {|a,b| ...} -> item|nil      # 要素同士の<=>比較をブロックで行い最大の値を返す 要素が無ければnilを返す
min {|a,b| ...} -> item|nil      # 要素同士の<=>比較をブロックで行い最小の値を返す 要素が無ければnilを返す
max_by {|item| ...} -> item|nil  # ブロックの評価結果を<=>で比較して最大の値を返す 要素が無ければnilを返す
min_by {|item| ...} -> item|nil  # ブロックの評価結果を<=>で比較して最小の値を返す 要素が無ければnilを返す
all? {|item| ...} -> bool      # ブロックの評価結果が全て真ならtrueを返す(一つでも偽ならfalseを返す)
any? {|item| ...} -> bool      # ブロックの評価結果が全て偽ならfalseを返す(一つでも真ならtrueを返す)
none? {|item| ...} -> bool     # ブロックの評価結果が全て偽ならtrueを返す(一つでも真ならfalseを返す)
one? {|item| ...} -> bool      # ブロックの評価結果が真の要素が一つだけならtrueを返す
count {|item| ...} -> Integer  # ブロックを評価して真になった要素の数を返す

# 変換
group_by {|item| ...} -> Hash                   # ブロックの評価をキー、要素を値とするハッシュを返す
each_with_object(obj) {|item, obj| ...} -> obj  # 最初に渡したobjと共に要素を渡しobjを返す
inject(init) {|result, item| ...} -> result     # 初期値を元にブロックを評価した結果を反映して返す
reduce(init) {|result, item| ...} -> result     # 同上

# 新しい配列の生成(Hashの場合は二次元配列を返す)
sort {|a,b| ...} -> Array           # 要素同士の比較 <=> をブロックで行い昇順ソートした新しい配列を返す
sort_by {|item| ...} -> Array       # ブロックの評価結果を <=> で比較して昇順ソートした新しい配列を返す
grep(pattern) {|item| ...} -> Array # pattern === item が true の要素を集めた新しい配列を返す
collect {|item| ...} -> Array       # 各要素を評価した結果を集めた新しい配列を返す
map {|item| ...} -> Array           # 同上
find_all {|item| ...} -> Array      # 各要素を評価した値がtrueだった要素を集めた新しい配列を返す
select {|item| ...} -> Array        # 同上
reject {|item| ...} -> Array        # 各要素を評価した値がfalseだった要素を集めた新しい配列を返す
参照: Ruby 1.9.3 リファレンスマニュアル > module Enumerable
参照: #Enumerable実装クラス
Arrayで使えるイテレータ
# Enumerableのイテレータは全て使える
# 繰り返し
each {|item| ...} -> self          # 要素を順番に評価する パラメータには各要素が渡される
each_index {|index| ...} -> self   # 要素を順番に評価する パラメータにはインデックス番号が渡される
reverse_each {|item| ...} -> self  # 要素を逆順に評価する パラメータには各要素が渡される Array版

# 探索
find_index {|item| ...} -> Integer|nil  # 最初にtrueになった要素のインデックス番号を返す なければnilを返す Array版
index {|item| ...} -> Integer|nil       # 同上
rindex {|item| ...} -> Integer|nil      # 「最後」にtrueになった要素のインデックス番号を返す なければnilを返す
count {|item| ...} -> Integer           # ブロックを評価して真になった要素の数を返す Array版

# 新しい配列の生成(Hashの場合は二次元配列を返す)
collect {|item| ...} -> Array   # 各要素を評価した結果を集めた新しい配列を返す Array版
map {|item| ...} -> Array       # 同上 Array版
select {|item| ...} -> Array    # 各要素を評価した値がtrueだった要素を集めた新しい配列を返す Array版
reject {|item| ...} -> Array    # 各要素を評価した値がfalseだった要素を集めた新しい配列を返す Array版

# 破壊的メソッド
sort! {|a,b| ...} -> self         # 要素同士の <=> 比較をブロックで行い自身を昇順ソートする
sort_by! {|item| ...} -> self     # ブロックの評価結果を <=> で比較して自身を昇順ソートする
collect! {|item| ...} -> self     # 各要素を評価した結果で各要素を置き換える
map! {|item| ...} -> self         # 同上
delete_if {|item| ...} -> self    # 各要素を評価した結果がtrueの要素を自身から削除する(falseの要素だけを残す)
reject! {|item| ...} -> self|nil  # 上記と同じだが変更が無ければnilを返す
keep_if {|item| ...} -> self      # 各要素を評価した結果がfalseの要素を自身から削除する(trueの要素だけを残す)
select! {|item| ...} -> self|nil  # 上記と同じだが変更が無ければnilを返す
参照: Ruby 1.9.3 リファレンスマニュアル > class Array
Hashで使えるイテレータ
# Enumerableのイテレータは全て使える
# 繰り返し
each {|key, value| ...} -> self       # 要素を順番に評価する パラメータにはキーと値が渡される
each_pair {|key, value| ...} -> self  # 同上
each_key {|key| ...} -> self          # 要素を順番に評価する パラメータにはキーが渡される
each_value {|value| ...} -> self      # 要素を順番に評価する パラメータには値が渡される

# 新しいHashの生成
select {|key, value| ...} -> Hash       # 各要素を評価した値がtrueだった要素を集めた新しいHashを返す Hash版
reject {|key, value| ...} -> Hash       # 各要素を評価した値がfalseだった要素を集めた新しいHashを返す Hash版
merge(other) {|key, a, b| ...} -> Hash  # selfとotherを合成しキーが同じ値をブロックに渡し結果を値とした新しいHashを返す

# 破壊的メソッド
delete_if {|key, value| ...} -> self     # 各要素を評価した結果がtrueの要素を自身から削除する(falseの要素だけを残す)
reject! {|key, value| ...} -> self|nil   # 上記と同じだが変更が無ければnilを返す
keep_if {|key, value| ...} -> self       # 各要素を評価した結果がfalseの要素を自身から削除する(trueの要素だけを残す)
select! {|key, value| ...} -> self|nil   # 上記と同じだが変更が無ければnilを返す
merge!(other) {|key, a, b| ...} -> self  # selfにotherを合成しキーが同じ値をブロックに渡し結果で上書きする
update(other) {|key, a, b| ...} -> self  # 同上
参照: Ruby 1.9.3 リファレンスマニュアル > class Hash
Structで使えるイテレータ
# Enumerableのイテレータは全て使える
# 繰り返し
each {|value| ...} -> self               # 要素を順番に評価する パラメータには値が渡される
each_pair {|member, value| ...} -> self  # 要素を順番に評価する パラメータにはメンバ名と値が渡される

# Structのイテレータでメンバ名もセットで渡されるのは each_pair のみであり、他は全て値のみが渡される
# 恐らく構造体オブジェクトの中身は値のみの配列であり、メンバ名の情報は構造体クラスオブジェクトが持っているのだと思われる
# 基本的に構造体オブジェクトに対してイテレータを回す事はほぼない(構造体の配列に対するイテレータの方が圧倒的に多い)
参照: Ruby 1.9.3 リファレンスマニュアル > class Struct
参照: #拡張イテレータ例
Objectで使えるtap
# パイプ(tapはイテレータではなく単なるパイプとして働く)
tap {|x| ...} -> self  # selfをパラメータとして渡し、selfを返す チェインメソッドに"入り込む"ためのメソッド
参照: Ruby 1.9.3 リファレンスマニュアル > class Object#tap

代入、浅いコピー(shallow copy)、深いコピー(deep copy)

  • Rubyの代入は同一オブジェクトの参照のコピーなので、参照手段が増えるだけで中身は同じになる。
  • 複製メソッドとしては dup、clone があるが、いずれも shallow copy である。object_id は変化するが、そのオブジェクトが保持するオブジェクト(配列やハッシュの中身)までは複製されない。つまり同じ参照を維持する。
  • 中身のオブジェクトまで含めて複製(deep copy)したい場合には、公式リファレンスではMarshalを使ってエンコード→デコードする例が掲載されている。この方法ではMarshalできないオブジェクトについては複製できない事も提示されているが、その代案については一切触れられていない。
    • とは言えMarshalできないオブジェクトはIO関係クラスやスレッド、Proc関係あたりなので、通常の範囲内においては deep copy する必要がないという意味では問題はないと思われるが。
    • 特異メソッドを定義したオブジェクトもMarshalできない。ただ特異メソッドを定義したオブジェクトを deep copy したい状況というのが正直思いつかない。そもそも deep copy を必要とするオブジェクトであればわざわざ特異メソッドを定義する必要はなく通常のメソッドを定義すれば事足りるはず。
  • clone、dup が使えないクラスは、NilClass, TrueClass, FalseClass, Symbol, Fixnum
    • これらのインスタンスは常に一意であるため複製はできない。
# 特異メソッドとオブジェクトIDの例
a = Object.new
def a.tokui  # 特異メソッドの定義
  p "a.tokui #{object_id}"
end
a.tokui         #=> "a.tokui 86368630"

b = a
b.tokui         #=> "a.tokui 86368630"  object_id は同じ

c = a.clone
c.tokui         #=> "a.tokui 86368380"  object_id は異なるが特異メソッドは複製されている

d = a.dup
d.tokui         #=> NoMethodError  dup では特異メソッドは複製されない
p d.object_id   #=> 86368230  object_id は異なる

e = Marshal.load(Marshal.dump(a)) #=> TypeError  特異メソッドのあるオブジェクトはMarshalできない

o = Object.new
o.tokui         #=> NoMethodError  特異メソッドはObjectクラスに定義されたのではなく、a というオブジェクトに定義されている

#--------------------------------------------------------------
# shallow copy と deep copy の例
a = ["1", 2, 3]
b = a
c = a.clone
d = a.dup
m = Marshal.load(Marshal.dump(a))

b[0] << "b"; b[1] += 2
p [a, b]    #=> [["1b", 4, 3], ["1b", 4, 3]]      代入なので a も変更される
c[0] << "c"; c[1] += 3
p [a, c]    #=> [["1bc", 4, 3], ["1bc", 5, 3]]    clone は shallow copy なので配列内のオブジェクト(文字列)は複製されない
d[0] << "d"; d[1] += 4
p [a, d]    #=> [["1bcd", 4, 3], ["1bcd", 6, 3]]  dup も shallow copy 
m[0] << "m"; m[1] += 5
p [a, m]    #=> [["1bcd", 4, 3], ["1m", 7, 3]]    Marshal は deep copy なので文字列も複製される

#--------------------------------------------------------------
# freezeの複製
a = "sample"
a.freeze
a.upcase!  #=> RuntimeError  freezeしたオブジェクトは変更不可能
# freezeしたオブジェクトを元に戻す方法は無い点に注意

c = a.clone
c.upcase!  #=> RuntimeError clone は freeze状態や特異クラスも複製する

d = a.dup
d.upcase!  #=> "SAMPLE"     dup は freeze状態や特異クラスを複製しない






VXAce付属ヘルプ補足

Enumerable実装クラスリスト

  • Array, Dir, Enumerator, Hash, IO, File, Range, Struct
    • Enumerableは繰り返し処理を定義するMix-inモジュール。eachが実装されているクラスに組み込み可能。
    • この中でVXAceヘルプに記載があるのはArray, Hash, IO, File, Rangeのみ。
    • Enumeratorはヘルプには載ってないが使用可能。
    • Structもヘルプには載ってないが普通に使える。
参照: Ruby 1.9.3 リファレンスマニュアル > Enumerableモジュール

Comparable実装クラスリスト

  • Numeric, String, Symbol, Time
    • Comparableは大小比較に使われるMix-inモジュール。<=>メソッドが実装されているクラスに実装可能。
参照: Ruby 1.9.3 リファレンスマニュアル > Comparableモジュール

VXAce付属ヘルプに記載されていないが使用可能なクラス、メソッド、演算子

  • Rationalクラス、Complexクラス、Structクラス、Randomクラス(有理数、複素数、構造体、疑似乱数)
    • VXAce付属ヘルプからは省かれているが問題なく使用できる。
    • 例えば Rational(1,3)、Rational("1/3") はそれぞれ1/3(3分の1)を表す。Rational(1,3)*3 は 1。Rational(1,3)+2 は 7/3(2と3分の1) となる。
    • Random#rand は Random.rand や Kernel.rand と異なり引数にRangeオブジェクトを渡せるので使い勝手がいい。またオブジェクトなのでMarshalでシードを保持できる。
  • ObjectSpaceモジュール
    • VXAce付属ヘルプからは省かれているが問題なく使用できる。
  • Bindingクラス
    • VXAce付属ヘルプからは省かれているが問題なく使用できる。
    • Bindingクラスを返すKernel#binding、Proc#bindingも使用可能。
  • !~ 演算子
    • 正規表現リテラルと文字列のマッチング演算子 =~ の否定形。マッチしなかった場合にtrueを返し、マッチしたらfalseを返す。
  • BasicObjectクラス
    • Objectの親になるクラス。最低限の機能しか実装されていない。通常はObjectが最上位の親という認識で問題ない。ObjectやKernelのメソッドが不要なクラスを定義する場合に利用できる。
    • ! 否定メソッド。普通は否定演算子を使うべきだがコード記述途中に否定が必要と気づいた場合にカーソルを戻す手間を省ける程度のメリットはある。例えば !array.empty? を array.empty?.! のように書ける。日本語文脈的には「配列が.空.ではない」となるのでむしろ可読性は高いかもしれない? ただ他言語に慣れていると文法的に大分気持ち悪い。
    • instance_eval インスタンスをコンテキストとして実行される eval。Kernel#eval と異なりブロックも評価できる。Bindingオブジェクトは渡せない。
    • instance_exec 引数付きで実行できるブロック専用の instance_eval。文字列評価はできない点に注意。
  • Integerクラス(整数)
    • even? 整数が偶数の場合true
    • odd? 整数が奇数の場合true
    • [n] n番目(0が最下位)のビットを返す(0 or 1)。例えば 5[0]なら1を、5[1]なら0を、5[2]なら1を返す。
      • bool値を返すわけではない点に注意。例えば 5[1] は0だがRubyの0はtrueなので if 5[1] とすると常に真となる。
  • Enumerableモジュール
    • reverse_each 逆順にeachを行う
    • each_with_index each と each_index のミックス。ブロックに |item, index| が渡される。オブジェクトとインデックスの両方が必要な時に有用。
    • inject(symbol) ブロックの代わりに呼び出すメソッドをシンボルで指定する。ブロックで呼び出すよりも速度が速い。例えばinject(&:+)はinject(:+)と書ける。なお直接シンボルを渡せるのはinjectだけで他のEnumerableメソッドでは出来ないので注意。
  • Arrayクラス(配列)
    • << obj 配列の末尾にobjを破壊的に追加して self を返す。
    • sample(n=1) 配列からランダムな要素を返す。nには要素数を指定する。同じ要素は重複しない。ランダムアイテム入手などに便利。
      • 例えば8種類あるお菓子の中から3種類を取り出すような、重複を許さない複数要素の抜粋に特に効果を発揮する。
      • ツクールで言えばバトルメンバーから重複しないランダムな二人に効果を与えるような場合に有効。普通のRPGだとあまり見ない要素だが、例えばランダム二体に同士討ち付与みたいな特殊スキルを使うボスがいて、同士討ち状態のキャラはお互いを攻撃対象とするみたいなギミックも可能になる。ツクール標準の対象ランダム二体の場合、重複する可能性があるので難しい。
    • [範囲式] 配列の添え字には範囲式も使える。例えば [0,1,2,3,4,5][2..4] であれば [2,3,4] を返す。[2..-1] なら[2,3,4,5] を返す。
      • 範囲式を使った代入も可能。[0,1,2,3,4,5][2..4] = [7,8,9] なら配列の要素は [0,1,7,8,9,5] になる。この時、要素数が違う場合は自動的に長さが調整される。[0,1,2,3,4,5][2..4] = [7] の場合は [0,1,7,5]になり、[0,1,2,3,4,5][2..4] = [7,8,9,10] の場合は [0,1,7,8,9,10,5] になる。また [0,1,2,3,4,5][2..0] = [7,8,9] のように範囲式の右辺が左辺より小さい場合は左辺の直前への挿入となり [0,1,7,8,9,2,3,4,5] となる。
  • Hashクラス(連想配列)
    • default keyが存在しない場合に返す値を得る。
    • default=(value) keyが存在しない場合に返す値をvalueに変更する。
    • delete_if{|key, value| ... } ブロックを評価した結果が真になる要素を削除する。常にselfを返す。
    • fetch(key, default) keyに対応する値を返す。keyが存在しない場合はdefaultを返す。defaultが省略された場合はkeyが存在しない場合にエラーする。
      • default=は全体のデフォルト値を変更するが条件によって返す値を変更したい場合にはfetchを使った方が良い。
      • fetchにはブロックを渡すことも可能。ブロックを渡した場合はkeyが存在しない場合にブロックを評価した値を返す。
    • reject!{|key, value| ... } ブロックを評価した結果が真になる要素を削除する。削除する要素がなかった場合はnilを返す。そうでなければselfを返す。
  • Timeクラス(時刻)
    • month 月を返す。ヘルプに載ってるmonと同義。
    • yday 1月1日を1とした通算日を返す。1/31なら31、2/1なら32、12/31なら365(うるう年なら366)を返す。
    • sunday? monday? tuesday? wednesday? thursday? friday? saturday? それぞれ日月火水木金土の各曜日かどうかを返す。
  • Stringクラス(文字列)
    • self[integer] 文字列からinteger+1文字目にある文字を返す。"abcdefg"[1]は"b"を返す。
    • self[range] 文字列からrange範囲にある文字列を返す。"abcdefg"[3..5]なら"def"を返す。"abcdefg"[3..-1]は-1が終端を表し"defg"を返す。
    • self % args sprintfと同様にargsをフォーマットした新しい文字列を返す。sprintf self argsと同じ。
例)
p "name=%s id=%d Lv=%2d Exp=%7d" % [actor.name, actor.id, actor.level, actor.exp]
p sprintf("name=%s id=%d Lv=%2d Exp=%7d", actor.name, actor.id, actor.level, actor.exp)
# 上記2つは同じ結果を返す
# ただし % は引数が配列になる関係でオブジェクト生成が発生するためやや遅い 要素が単一の場合なら配列不要なので有用
  • Rangeクラス(範囲)
    • min 範囲の最小値を返す。beginやfirstとほぼ同等だが、両者と異なりブロックを評価することも可能。
    • max 範囲の最大値を返す。lastやendと異なり終端の有無で値が変わる。min同様ブロックを評価できる。
(1..6).begin  # 1 を返す begin、firstは始端を返す
(1..6).first  # 1 を返す 
(1...6).begin # 1 を返す
(1...6).first # 1 を返す
(1..6).end    # 6 を返す end、lastは終端を返す
(1..6).last   # 6 を返す
(1...6).end   # 6 を返す end、lastが返す値は範囲が終端を含むかどうかは関係ない
(1...6).last  # 6 を返す

(1..6).min    # 1 を返す minは最小値を返す
(1..6).max    # 6 を返す maxは最大値を返す
(1...6).min   # 1 を返す
(1...6).max   # 5 を返す 範囲が終端を含まない場合、maxは範囲内の最大値を返す
(1..6).min{ |a, b| b <=> a }  # 6 を返す
(1..6).max{ |a, b| b <=> a }  # 1 を返す
(1.0..6.0).max   # 6.0を返す
(1.0...6.0).max  # 終端を含まない場合、Integer以外はエラーする
(1.0..6.0).max{ |a, b| b <=> a }  # ブロックの戻り値がInteger以外はエラーする

VXAce付属ヘルプに記載はあるがあまり使われていない便利なメソッド

  • Arrayクラス
    • self[-n] nが負の値の場合、末尾からのインデックスとみなす。具体的には self[self.size-n] と同義になる。
    • insert(n, var) pushと異なり配列の途中に要素を追加する。n番目の直前に差し込まれ、元々あった要素は後ろにずれる。
      • nがマイナスなら後ろからn番目の直後に差し込まれる。-1なら末尾、-2なら末尾の直前(末尾から2番目の直後)。
      • 理由は知らないがタイトル画面やメニュー画面などのコマンドリストに独自コマンドを追加するような配布スクリプトは基本pushで書かれており、例えばタイトルコマンドならシャットダウンの下に独自コマンドが追加される物が多い。
      • insert(-2, var) とすれば最後の要素(タイトルならシャットダウン)の上(下から2番目の直後)に差し込まれる。

RGSS3での仕様違い

  • RGSS3では公式リファレンスと一部異なる動作が確認されている。ここではそれらを発見次第列挙していく。
    • 上記1.9.2と1.9.3リファレンスを元にしているがRGSS3は1.9.2p0なので単純にリファレンスとのrev違いの可能性もある。1.9.2リファレンスの元になっているrevが不明でかつ1.9時代のリファレンスでは更新履歴が確認できないためこの辺りの正確な状態は不明。
    • VXAce付属ヘルプに記載のあるRGSS3での仕様変更点についてはヘルプを参照のこと。
  • Structクラスのmembersメソッド
    • リファレンスでは特異メソッドのmembersがメンバのシンボルリストを返し、インスタンスメソッドのmembersがメンバ名文字列リストを返すとなっている。
    • RGSS3ではどちらもシンボルリストを返す。
a = Struct.new("Param", :hp, :mp, :atk, :def, :mat, :mdf, :spd, :luk)
b = Struct::Param.new(500,100, 10,12,14,16,18,20)
p a.method(:members)   #=> #<Method: Struct::Param.members> (特異メソッド)
p b.method(:members)   #=> #<Method: Struct::Param(Struct)#members> (インスタンスメソッド)
p a::method(:members)  #=> #<Method: Struct::Param.members>
p b::method(:members)  #=> #<Method: Struct::Param(Struct)#members>
p a.members            #=> [:hp, :mp, :atk, :def, :mat, :mdf, :spd, :luk]
p b.members            #=> [:hp, :mp, :atk, :def, :mat, :mdf, :spd, :luk]
  • 範囲式のオブジェクト
    • リファレンスには「範囲式はその両端が数値リテラルであれば、何度評価されても同じオブジェクトを返します。そうでなければ評価されるたびに新しい範囲オブジェクトを生成します。」とある。
    • RGSS3では両端がFixnumの場合、記述位置ごとに一意のオブジェクトを返す。
def fixnum_range
p [(  1..20 ).object_id, (  1..20 ).object_id]
end
def string_range
p [("a".."z").object_id, ("a".."z").object_id]
end

fixnum_range  #=> [35849780, 35849730] 同じ範囲でもidは別
fixnum_range  #=> [35849780, 35849730] 何度呼ばれてもidは同じ
string_range  #=> [35849640, 35849600] 同じ範囲でもidは別
string_range  #=> [35849720, 35849760] 呼ばれる度にIDが変化






Ruby応用

each中に親要素を破壊的に変更した場合の動作

  • Arrayのeachは評価中の親要素に変更があった場合、変更後の親要素を参照して動作する。
    • each中に新しい要素を追加すれば、追加した要素も評価される。
    • 削除時も参照インデックスはインクリメントされる。そのため現在評価中の要素自身を削除した場合、削除した直後の要素に関しては評価されない。
    • この動作は再帰を伴わないスタックの実装を容易にするが、普通に外部イテレータで回した方が可読性は高いと思う。
  • Hashのeachは評価中の親要素の変更はエラーになる。
a = [1,2,3,4,5]
a.each_with_index{ |v, i| a.delete_at(i) if i.odd? }
p a  #=> [1, 3, 4]

# i = 0, v = 1  =>  i.odd? = false
# i = 1, v = 2  =>  i.odd? = true  delete_at(1) = 2  => [1, 3, 4, 5]
# i = 2, v = 4  =>  i.odd? = false
# i = 3, v = 5  =>  i.odd? = true  delete_at(3) = 5  => [1, 3, 4]
#=> [1, 3, 4]

a = [1,2,3,4,5]
a.each_with_index{ |v, i| a.delete_at(i) }
p a  #=> [2, 4]

# i = 0, v = 1  =>  delete_at(0) = 1  => [2, 3, 4, 5]
# i = 1, v = 3  =>  delete_at(1) = 3  => [2, 4, 5]
# i = 2, v = 5  =>  delete_at(2) = 5  => [2, 4]
#=> [2, 4]

a = (1..10).to_a
a.each{|v| a.push(v + 10) if v < 15}
p a  #=> [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]

a = {1 => :a}
a.each{|k, v| a[k+1] = v.succ if k <= 5} # RuntimeErrorが出る Hashはeach中に要素の変更はできない

ブロック付きメソッドの実装

  • eachやmapのようなブロック付きメソッドを定義する方法。
  • ブロック付きメソッドは主に以下の4種に分けられる。
    • 繰り返し(each、timesなど)は主にイテレータと呼ばれる処理。集合(コレクション)に含まれる要素それぞれに対して処理を行いたい場合に使われる。
    • 列挙/変換(map、sort、injectなど)は集合(コレクション)の要素に対して処理を施した新しいコレクションを作ったり、または要素を集計して結果を算出したりする場合に使われる。新しいコレクションで自身を置き換える場合も含まれる。
    • 探索/抜粋(find、selectなど)は集合(コレクション)から特定の要素を探したり、特定の条件で要素を抜粋した新しいコレクションを作りたい場合に使われる。自身から特定の条件で要素を除外する場合も含まれる。
    • 初期化/後処理(File.openなど)は特定の処理の前後に決められた処理を行う必要がある場合に、その処理忘れを防ぐ目的で使われる。例えばFileはopenしたら必ずcloseしなければいけないが、File.openにブロックを渡すとブロック処理の後に自動的にcloseしてくれる。
  • ブロック付きメソッドを実装する主な目的は上記処理の独自実装にある。
    • 例えば reverse_each_with_index や reverse_find のようなメソッドの追加。
    • 例えば Array#sum や Array#to_h のような後期バージョンからのメソッドの逆輸入。
# sum の実装例でみるブロックの受け取り方
# 仕様 配列の合計を返す ブロックを渡すとブロックを評価した合計を返す
class Array
  # ブロックをyieldで受け取りinjectに渡す方法
  # yieldの引数が渡されたブロックのブロック引数として展開される
  def sum1(init = 0)                             # 初期値を定義
    return inject(init, :+) unless block_given?  # ブロックが無い場合はinjectで合計を返す
    inject(init){ |r,v| r += yield(v) }          # ブロックがある場合はブロックの評価結果を加算
  end
  # ブロックを引数(lambda)で受け取りinjectでProcオブジェクトをcallする方法
  # &block はProcオブジェクトなのでcallで実行できる callの引数が渡されたブロックのブロック引数として実行される
  def sum2(init = 0, &block)                     # 初期値を定義、ブロックはProcオブジェクトとして受け取る
    return inject(init, :+) unless block_given?  # ブロックが無い場合はinjectで合計を返す
    inject(init){ |r,v| r += block.call(v) }     # ブロックがある場合はブロック内でProcオブジェクトをcallする
  end
  # ブロックをyieldで受け取りループはwhile文で行う方法
  def sum3(init = 0)           # 初期値を定義
    sum = init
    i = 0
    e = size                   # while文にメソッドを置くとループの度に呼ばれるのでローカルに取得
    unless block_given?
      while i < e
        sum += self[i]         # ブロックが無い場合はwhileで合計を返す
        i = i.succ             # カウントアップは += よりも succ が速い
      end
    else
      while i < e
        sum += yield(self[i])  # ブロックがある場合はwhileで評価結果の合計を返す
        i = i.succ
      end
    end
    sum
  end
  # ブロックを引数(lambda)で受け取りループはwhile文で行う方法
  def sum4(init = 0, &block)   # 初期値を定義
    sum = init
    i = 0
    e = size    
    unless block_given?
      while i < e
        sum += self[i]         # ブロックが無い場合はwhileで合計を返す
        i = i.succ
      end
    else
      while i < e
        sum += block.call(self[i])  # ブロックがある場合はwhileで評価結果の合計を返す
        i = i.succ
      end
    end
    sum
  end
end

# 実行速度の違い(10000回ループ)
a = (1..50).to_a.shuffle
a.sum1           #=> 0.0320018s ブロックを渡さない場合はどれもほぼ同じ
a.sum2           #=> 0.0320019s
a.sum3           #=> 0.0310018s
a.sum4           #=> 0.0310018s
a.sum1{|v| v*2}  #=> 0.0721291s injectブロック内でyieldを実行するのでやや遅い
a.sum2{|v| v*2}  #=> 0.1277572s injectブロック内で更にProcオブジェクトをcallするので一番遅い
a.sum3{|v| v*2}  #=> 0.0498779s injectを使わずwhile文でループ 一番速いが冗長 Rubyらしさは無い
a.sum4{|v| v*2}  #=> 0.0845049s injectを使わずwhile文でループ Procオブジェクトをcallする分遅くなる
# 基本的にブロックが遅いので、ブロックをネストするほど遅くなる
# 速度だけを見れば while + yield の実装が一番速いが、実用性と記述の簡便さを考慮すると inject + yield がベストプラクティスと言える
  • Rubyではクロージャオブジェクトを極力作らないように設計されているため、ブロックを渡しただけではクロージャオブジェクトは生成されない。
    • このため yield でブロックを評価する場合はオブジェクトが生成されない分、わずかに速度が速い。
  • ブロックを引数に取る場合はProcオブジェクトとして渡されるため、即座にクロージャオブジェクト(Procオブジェクト)が生成される。
    • このオブジェクト生成がわずかなオーバーヘッドとなって速度に影響する。
参照: まつもと直伝 プログラミングのオキテ 第5回(2)
ブロック、yieldの特性
# 引数として渡されたブロックの特性
def test(&block)
  p block.class.name  #=> "Proc" ブロックはProcオブジェクト(クロージャオブジェクト)として渡される
  p block.lambda?     #=> true   ブロックはlambda
end

# ブロックと yield の特性
def test
  yield(5)       # ブロックを引数として取らない場合、クロージャオブジェクトは生成されない
end
test             #=> LocalJumpError  ブロックを渡さないと yield は undefined
test{|v| v}      #=> 5  ブロックを渡すとyieldの引数はブロックパラメータとして渡される
test{|v| v * 2}  #=> 10 yieldはブロックを評価した結果を返すメソッドのように振舞う
test(&:succ)     #=> 6  ブロックの代わりにProcオブジェクトやto_proc実装オブジェクトを渡せる この場合も&引数の定義は不要
# to_proc は組み込みクラスの場合 proc になる ブロックと異なり lambda ではないので引数の数が違っても動作する
# 上記はシンボルに & を付けて渡しているので :succ.to_proc が渡される
# Symbol#to_proc は proc{ |v| v.send(self) } と同義
# 当然だがこの過程でProcオブジェクト(クロージャオブジェクト)が生成されるので、yield であってもやや遅くなる

# ブロックの有無の判定方法
def test(&block)
  if block_given?    # ブロック引数は不要 ブロックが渡されていればtrue
  if defined? yield  # ブロック引数は不要 ブロックが渡されなければ yield は undefined なので判別可能 
  if block           # ブロック引数が必要 引数がオブジェクトかnilかで判別する
end
# 速度比較(100万回ループ)              ブロックなし ブロックあり
def test1;         true if block_given?;   end  #=> 0.1511336s    0.1548839s
def test2;         true if defined? yield; end  #=> 0.1431331s    0.1891359s
def test3(&block); true if block_given?;   end  #=> 0.1543839s    0.4825275s
def test4(&block); true if defined? yield; end  #=> 0.1451333s    0.5292804s
def test5(&block); true if block;          end  #=> 0.1365079s    0.5025287s
def test6(&block); true unless block.nil?; end  #=> 0.1631344s    0.4941533s
# 引数の有無と実際にブロックが渡されるかどうかで速度が大分変わる いずれにせよblock_given?が安定っぽい

# ブロックの受け取り方と速度(10万回ループ)
def test10;         yield(1,2);                  end  #=> 0.0163759s 引数なしyieldのみが一番速い
def test11;         Proc.new.call(1,2);          end  #=> 0.0612536s 非推奨文法 渡されたブロックをオブジェクト化する
def test12;         proc.call(1,2);              end  #=> 0.0570033s 非推奨文法
def test13;         lambda.call(1,2);            end  #=> 0.0977555s 非推奨文法
def test21(&block); yield(1,2);                  end  #=> 0.0517530s
def test22(&block); block[1,2];                  end  #=> 0.0520030s
def test23(&block); block.call(1,2);             end  #=> 0.0515030s
def test24(&block); block === [1,2];             end  #=> 0.0812546s 恐らく配列作成が必要なので遅い
def test25(&block); block.yield(1,2);            end  #=> 0.0581283s
def test26(&block); Proc.new(&block).call(1,2);  end  #=> 0.0652537s 非推奨文法 引数のProcオブジェクトをオブジェクト化するという無意味な構文
def test27(&block); proc(&block).call(1,2);      end  #=> 0.0666289s 非推奨文法
def test28(&block); lambda(&block).call(1,2);    end  #=> 0.0611285s 非推奨文法
参照: Ruby 1.9.3 リファレンスマニュアル > class Proc

Enumeratorでイテレータを組み合わせる/外部イテレータ

  • Enumeratorクラスはeach以外のイテレータを基準としてEnumerableモジュールのイテレータを実行できるラッパークラス。
    • 外部イテレータとしても機能する。
  • EnumeratorはObject#enum_for(to_enum)もしくはブロックなしの各種イテレータメソッドで生成できる。
  • Enumerableモジュールのイテレータメソッドはinclude先のeachメソッドを利用して各種イテレータを実行するため、eachの動作を変えてやればEnumerableのイテレータの動作も変化する。
    • ArrayとHashでEnumerableのイテレータメソッドのブロックパラメータが変化するのはこのため。
    • Enumeratorはこのeachの動作を簡単に他のイテレータに変更できる。
# Enumeratorを返す代表的なEnumerableイテレータ
reverse_each           # 要素を逆順にしたイテレータを返す
each_with_index        # |item, index| をパラメータとしたイテレータを返す
each_slice(n)          # 要素をn個で区切った配列をパラメータとしたイテレータを返す
each_with_object(obj)  # |item, obj| をパラメータとしたイテレータを返す

# Enumeratorを返す代表的なStringイテレータ
each_byte       # 文字列をbyte配列とみなしたイテレータを返す
each_char       # 文字列を文字の配列とみなしたイテレータを返す
each_codepoint  # 文字列をコードポイントの配列とみなしたイテレータを返す
each_line(sep)  # 文字列をsepで区切った行の配列とみなしたイテレータを返す

#Enumeratorクラスのイテレータ
with_index(offset) {|item, index| ...} -> object # index+offsetをパラメータに加えてイテレータを実行する
with_object(obj) {|item, obj| ...} -> object     # パラメータにobjを渡してイテレータを実行する
イテレータ組み合わせ例
  • EnumerableモジュールとEnumeratorを組み合わせる事で複雑なイテレータを簡単に記述できる。
b = [11,2,13,4,15,6]
p b.find{|v| v > 10 }                 #=> 11 最初に条件を満たした要素を返す
p b.reverse_each.find{|v| v > 10 }    #=> 15 逆順で最初に条件を満たした要素を返す
p b.map{|v| v }                       #=> [11,2,13,4,15,6] サンプル用にイテレータ順に要素を返すだけのコード
p b.each_slice(2).map{|v| v }         #=> [[11,2],[13,4],[15,6]] mapのパラメータがeach_slice(2)のものになる
p b.each_with_index.map{|v,i| [v,i] } #=> [[11,0],[2,1],[13,2],[4,3],[15,4],[6,5]] mapのパラメータがeach_with_indexのものになる
p b.map.with_index{|v,i| [v,i] }      #=> [[11,0],[2,1],[13,2],[4,3],[15,4],[6,5]] index付きでmapを実行する
p b.reverse_each.each_with_index.map{|v,i| [v,i] }  #=> [[6,0],[15,1],[4,2],[13,3],[2,4],[11,5]] 値は逆だがindexは逆順後のindexになる
p b.each_with_index.reverse_each.map{|v,i| [v,i] }  #=> [[6,5],[15,4],[4,3],[13,2],[2,1],[11,0]] 値もindexも逆順にしてイテレートできる

# 注意点
# selfを返すイテレータはEnumeratorで実行された場合、Enumeratorが保持するレシーバのイテレータとして動作したような結果を返す。
# ただしEnumerable#reverse_eachをブロック付きで実行した場合、なぜかreverse_eachのレシーバとなったEnumerator自身を返す。
p b.reverse_each.select{|v| v > 10}  #=> [15,13,11]
p b.select.each{|v| v > 10}          #=> [11,13,15] eachのブロックでselectのイテレータを評価した結果を返す
p b.select.each                      #=> #<Enumerator: [11,2,13,4,15,6]:select>(これはEnumerator#eachの特性なので正常)
p b.select                           #=> #<Enumerator: [11,2,13,4,15,6]:select>
p b.select.reverse_each              #=> #<Enumerator: #<Enumerator: [11,2,13,4,15,6]:select>:reverse_each>(これは正常)
p b.select.reverse_each{|v| v > 10}  #=> #<Enumerator: [11,2,13,4,15,6]:select>(謎) 本来は[15,13,11]を返すのが正常
並列処理の例
  • Enumeratorは外部イテレータとしても機能するが、Rubyの場合は内部イテレータが非常に優秀なので基本的には外部イテレータを使う必要は無い。
  • 一般的には複数のコレクションを並列処理するような場合には外部イテレータが役立つと解説される事が多い。
    • ただし内部イテレータを使って並列処理を実装するのも比較的簡単なので外部イテレータに優位性があるかと言うと微妙なところ。
    • Enumeratorの外部イテレータ機能はFiberで実装されているため、普通のイテレータに比べて大幅に遅い点も注意。
VAL_NAMES = $data_system.variables                         # 変数名配列
VALS_DATA = $game_variables.instance_variable_get(:@data)  # 変数配列
# 変数の値が存在する要素の[id, name, value]を表示する場合

# Enumeratorを外部イテレータとして使った実装例
names = VAL_NAMES.each.with_index  # 変数名配列のEnumerator.with_indexを得る
vals  = VALS_DATA.each             # 変数配列のEnumeratorを得る
loop do                            # 無限ループ
  n, i = names.next                # nextで要素を取り出す 要素がなくなるとloopを抜ける
  v = vals.next                    # nextで要素を取り出す 要素がなくなるとloopを抜ける
  p [i, n, v] unless v.nil?        # 値が存在したら[id, name, value]を出力
end

# while文による(Enumeratorを使わない)外部イテレータの実装例
vsize = VALS_DATA.size       # 変数配列のsizeを得る
i = -1
while (i = i.succ) < vsize   # while文でループ(idをインクリメント)
  n = VAL_NAMES[i]           # 名前を取り出す
  v = VALS_DATA[i]           # 変数の値を取り出す
  p [i, n, v] unless v.nil?  # 値が存在したら[id, name, value]を出力
end

# for文を使った外部イテレータの実装例(内部でeachを呼んでいる)
i = 0
for v in VALS_DATA           # for文で変数の値を取り出す
  n = VAL_NAMES[i]           # 名前を取り出す
  p [i, n, v] unless v.nil?  # 値が存在したら[id, name, value]を出力
  i = i.succ                 # idをインクリメント
end
  
# 内部イテレータを使った実装例
VALS_DATA.each_with_index do |v,i|  # 内部イテレータ(each_with_index)で変数の値とidを取り出す
  n = VAL_NAMES[i]                  # 名前を取り出す
  p [i, n, v] unless v.nil?         # 値が存在したら[id, name, value]を出力
end

# 内部イテレータ(zip)を使った実装例 二つの配列を合成した二次元配列を作るので配列が大きくなると効率が悪くなる
VALS_DATA.zip(VAL_NAMES).each_with_index{|(v,n),i| p [i, n, v] unless v.nil? }  # 1行で書ける

# 並列処理イテレータを新しく定義する例
class Array
  def parallel(ary)
    return to_enum(:parallel, ary) unless block_given?
    vsize = size
    i = -1
    yield(self[i], ary[i], i) while (i = i.succ) < vsize
    self
  end
end

# 並列処理イテレータを使った実装例
VALS_DATA.parallel(VAL_NAMES){|v,n,i| p [i, n, v] unless v.nil? }  # シンプルに書ける

# 並列処理イテレータのEnumeratorを外部イテレータとして使った実装例
data = VALS_DATA.parallel(VAL_NAMES)  # 変数配列と変数名配列からEnumeratorを得る
loop do                               # 無限ループ
  v, n, i = data.next                 # nextで要素を取り出す 要素がなくなるとloopを抜ける
  p [i, n, v] unless v.nil?           # 値が存在したら[id, name, value]を出力
end

# 速度比較  500要素配列1000回ループ8回平均(出力部分のみコメントアウト)
# Enumerator          #=> 4.792773750s 内部でFiberを使用しているので圧倒的に遅い
# Enumerator:parallel #=> 2.422638750s Enumeratorが一つで済むので並列Enumeratorよりも倍近く速いが内部イテレータの50倍遅い
# while               #=> 0.023626375s Fiberもブロックも使用していないので速いが冗長 
# for                 #=> 0.044502625s 内部でeachを使っているのでwhileよりは若干遅い
# each_with_index     #=> 0.051752875s ブロックを使っている分やや遅い
# zip                 #=> 0.082879750s 二次元配列を作る=要素数の配列とそれが入る配列を作るため要素が増えるほど遅い
# parallel            #=> 0.046502750s zip以上の簡潔さでeach_with_indexよりも速いので個人的にはベストプラクティス
# 並列処理イテレータは配列の数が3つ以上になると使えないのが欠点だが実用レベルでは十分 3つ以上ならforでもいいと思う
参照: #Fiberの正体

この記事はqiitaにも投稿している
qiita: https://qiita.com/midpolarnight/items/428efac52ae4...

配列に配列が含まれているかどうかを調べる

  • 配列Aに配列Bの要素が全て含まれているかどうかを調べるには all? メソッドにブロックを渡して判定する。
    • この判定は配列Bの要素が少なければ単純に 配列A.include? の羅列で問題ないが、配列Bが4以上になるとコードが冗長になり可読性や保守性が低下する。
    • (配列B - 配列A).empty? は記述は簡単だが実行速度は遅い。
  • 配列Aに配列Bの要素が一つでも含まれていれば良いなら、all? の代わりに any? を使う。
a = [1,2,3,4,5,6,7,8,9]
b = [2,3,4]
p b.all?{|v| a.include? v} # aにbが全て含まれていればtrue
p b.any?{|v| a.include? v} # aにbが一つでも含まれていればtrue

フリップフロップ構文

  • 判定文にはフリップフロップ式を書く事ができる。
    • フリップフロップ式は a が真になったら b が真になるまで真を返す式の事。
    • 利用法としては配列などの要素のうちの一部だけをeachで評価したい場合などに使える。
    • 書き方は (判定式)..(判定式) になる。範囲式と似ているがあくまでも判定文の機能なので判定文の外ではエラーする。
  • 以下の例文の場合、1〜10のうち、3〜6の場合に真となる。流れは以下を参照。
    1. 左辺( i == 3 )が真になるまでは偽を返す。
    2. 左辺( i == 3 )が真を返した後は右辺( i == 6 )が真を返すまで真を返す。
    3. 右辺( i == 6 )が真を返した後は偽を返す。
a = []
(1..10).each do |i|
  if (i == 3)..(i == 6)             # フリップフロップ式
    a << i
  end
end
# a = [3, 4, 5, 6] を返す

# 上記のフリップフロップ構文は以下のコードと同等の処理を行う
a = []
flag = false                        # フリップフロップ判定フラグ
(1..10).each do |i|
  flag = true if (i == 3)           # フリップフロップ式の左辺
  next unless flag                  # フリップフロップ判定
  a << i
  flag = false if flag && (i == 6)  # フリップフロップ式の右辺
end

# 判定式に含まれていないと不正な範囲式となりエラーする点に注意 この構文は判定式の文法であって範囲式としては成立しない
(1..10).select{ |i| (i == 3)..(i == 6) }                 #=> エラー
(1..10).select{ |i| false || (i == 3)..(i == 6) }        #=> エラー (右辺の結果を返すだけで判定式ではない)
(1..10).select{ |i| (i == 3)..(i == 6) ? true : false }  #=> [3, 4, 5, 6]

Procオブジェクトはlambdaを使う

  • ブロックを手続きオブジェクト化したProcオブジェクトは宣言方法によってprocとlambdaの二種類に分かれる。
    • 両者ともブロックを扱う点では同様だが、引数の扱い、手続きからの脱出、戻り値の点で異なる。
    • 実体は両方ともProcオブジェクトのインスタンスになる。違いはlambda?で判別する。
    • 詳しい違いは https://docs.ruby-lang.org/ja/1.9.3/class/Proc.htm... を参照。かなり詳しく載っている。
  • ざっくり言えばlambdaはメソッドのように振舞う。そのためprocに比べて堅牢で保守性が高い。
    • 扱い方も宣言方法もほぼ変わらないため、特に理由がなければlambdaで宣言しておくのがオススメ。
    • lambdaは引数の数が一致しなければ(メソッドのように)エラーするが、procは引数の数が違っても勝手にnilで埋めたり切り詰めたりして動作する。
    • メソッドの中にはブロックに対して暗黙のパラメータを渡すものがあり、その場合はlambdaだとエラーになる可能性がある。このようなメソッドでlambdaを使うには明示的に暗黙のパラメータを宣言してあげれば良い。
# ブロックをProcオブジェクト化する
tpup1 = Proc.new{ |v| v + 10 }  # Procオブジェクトの生成 tpup1.lambda? は false
tpup2 =     proc{ |v| v + 10 }  # proc式         tpup2.lambda? は false
tpup3 =   lambda{ |v| v + 10 }  # lambda式        tpup3.lambda? は true
tpup4 =        ->(v){ v + 10 }  # lambda式アロー演算子  tpup4.lambda? は true
array = [1, 3, 5, 7, 9]
array.map(&tpup1)  #=> [11, 13, 15, 17, 19]
array.map(&tpup2)  #=> 上記と同じ
array.map(&tpup3)  #=> 上記と同じ
array.map(&tpup4)  #=> 上記と同じ

# instance_eval はレシーバオブジェクトを暗黙でブロックに渡すため、パラメータなしlambdaだとエラーしてしまう
p =   proc{ tp += 10 }         # パラメータ無しでproc宣言
l = lambda{ tp += 10 }         # パラメータ無しでlambda宣言
$game_party.leader.instance_eval(&p)  # エラーしない
$game_party.leader.instance_eval(&l)  # wrong number of arguments (1 for 0)エラーが出る パラメータが1個あるのに定義数が0だよ 帰んな!って意味
l = lambda{|actor| tp += 10 }  # 暗黙のパラメータを定義したlambda宣言
$game_party.leader.instance_eval(&l)  # エラーしない
# proc と lambda のジャンプ構文の挙動の違い
def proc_return
  p =   proc{|n| if n == 2 then return :proc else n+5 end}
  a = ([1,2,3]).map(&p)
  print a
  return :method
end
def proc_next
  p =   proc{|n| if n == 2 then next :proc else n+5 end}
  a = ([1,2,3]).map(&p)
  print a
 return :method
end
def proc_break
  p =   proc{|n| if n == 2 then break :proc else n+5 end rescue :err}
  a = ([1,2,3]).map(&p)
  print a
  return :method
end
def lambda_return
  p = lambda{|n| if n == 2 then return :lamb else n+5 end}
  a = ([1,2,3]).map(&p)
  print a
  return :method
end
def lambda_next
  p = lambda{|n| if n == 2 then next :lamb else n+5 end}
  a = ([1,2,3]).map(&p)
  print a
  return :method
end
def lambda_break
  p = lambda{|n| if n == 2 then break :lamb else n+5 end}
  a = ([1,2,3]).map(&p)
  print a
  return :method
end

p proc_return   #=> :proc                 procのreturnはメソッドを抜ける
p proc_next     #=> [6, :proc, 8]:method  procのnextは手続きを抜ける
p proc_break    #=> [6, :err, 8]:method   procのbreakはエラーが出る
p lambda_return #=> [6, :lamb, 8]:method  lambdaのreturnは手続きを抜ける
p lambda_next   #=> [6, :lamb, 8]:method  lambdaのnextは手続きを抜ける
p lambda_break  #=> [6, :lamb, 8]:method  lambdaのbreakは手続きを抜ける
  • 完全に余談だがinstance_evalの暗黙のパラメータでレシーバオブジェクトを渡す仕様がイマイチ意味がわからない。instance_evalのブロック内は特殊なクロージャとなっており、レシーバオブジェクトのコンテキストとブロックの外側のレキシカルスコープでクロージャを形成する。そのためselfでレシーバオブジェクトにアクセスできるので、わざわざパラメータとして渡す意味はあるのだろうか?

&引数にメソッドを渡す

# &引数にはメソッドオブジェクトも渡せる
array.map(&method(:print))
# メソッドオブジェクトは以下のように展開される
# ブロックと引数の数が異なるメソッドを渡すとエラーになる
array.map{ |v| print v }

構造体の詳細

  • Rubyで構造体を扱う場合はStruct.newを利用して構造体クラスを生成し、構造体データごとに構造体クラスのインスタンスを生成して利用する。
  • Rubyには構造体と言うデータタイプは存在しない。
    • Structで生成されるのは構造体のように振る舞うクラスであり、Structはそういうサブクラスを生成するためのクラスジェネレーターでしかない。

構造体定義の基本
  • Struct.newにサブクラス名(文字列)、各メンバ名(シンボル)のリストを渡して構造体クラスを生成する。
  • 必要な構造体データごとに、構造体クラス.newにメンバの値を渡して各構造体オブジェクトを生成して利用する。
  • サブクラスの定義方法には色々な作法があるが、下記にそれらの定義方法ごとの動作の違いを列挙する。
# 例えば以下のような一般的な(公式リファレンス準拠の)Struct定義例の場合を考える
# 第一引数はサブクラス名、第二引数以降はメンバ名のシンボルリスト
# サブクラス名は通常のクラス定義同様、大文字で開始する必要がある点に注意
sample = Struct.new("SampleItem", :name, :icon, :price)
sample_items = [
  sample.new("Item1", 10, 500),
  sample.new("Item2", 52, 200),
  sample.new("Item3", 21, 100),
]

# 代入無しで記述すれば、単に「新しいサブクラスを自身の名前空間に定義」する構文となる
Struct.new("SampleItem", :name, :icon, :price)  # Struct::SampleItem が定義される
# つまりこの時点でクラス定義は完了しているので、それを代入したり再定義する必要はない

# 構造体のクラス構造は以下のようになる
Struct::SampleItem.ancestors  #=> [Struct::SampleItem, Struct, Enumerable, Object, Kernel, BasicObject]

# クラスが定義されているのだからそれを使って構造体オブジェクトを生成する
sample_items = [
  Struct::SampleItem["Item1", 10, 500],  # オブジェクト生成はnew以外にも[]でもできる
  Struct::SampleItem["Item2", 52, 200],
  Struct::SampleItem["Item3", 21, 100],
]

# クラスが定義されているのだからそれを開いてクラスを拡張する
class Struct::SampleItem
  def sample_method
  end
end

# メソッドは問題なく追加される
p Struct::SampleItem.instance_methods(false)  # [:name, :name=, :icon, :icon=, :price, :price=, :sample_method]
  • Struct.newはそれ単体で定義文。これを留意するだけで混乱は無くなる。
参照: Ruby 1.9.3 リファレンスマニュアル > class Struct.new

名前空間の省略や命名のイディオム
# 名前空間を省略したいならclass_evalを使う手もある
Struct.new("SampleItem", :name, :icon, :price)
sample_items = []
Struct::SampleItem.class_eval do 
  sample_items = [
    self["Item1", 10, 500],
    self["Item2", 52, 200],
    self["Item3", 21, 100],
  ]
end

# もしくは現在の名前空間に新しい定数を定義してクラス名のエイリアスとして使う
# 一般的な例文のようにローカル変数に代入するのではなく、定数(クラス名)に代入する
SampleItem = Struct.new("SampleItem", :name, :icon, :price)  # SampleItem = Struct::SampleItem と同じ
sample_items = [
  SampleItem["Item1", 10, 500],
  SampleItem["Item2", 52, 200],
  SampleItem["Item3", 21, 100],
]

# クラスを開く必要があるときはエイリアス名で開く
class SampleItem
  def sample_method
  end
end

# エイリアス定数の参照先は元のクラスなのでメソッドは元のクラスに問題なく追加される
p Struct::SampleItem.instance_methods(false)  # [name, name=, icon, icon=, price, price=, sample_method]

# クラス名のエイリアスを使うなら名前無し定義(無名のサブクラスの生成)でも問題ない
  # Struct.newでサブクラス名を省略すると無名のサブクラスを生成する
SampleItem = Struct.new(:name, :icon, :price)  
sample_items = [
  SampleItem["Item1", 10, 500],
  SampleItem["Item2", 52, 200],
  SampleItem["Item3", 21, 100],
]

# エイリアスの構造体のクラス構造は以下のようになる
SampleItem.ancestors  # [SampleItem, Struct, Enumerable, Object, Kernel, BasicObject]

# 定数に無名クラスを代入した場合、定数名がクラス名に設定される
a = Struct.new(:name, :icon, :price)
p a.name  #=> nil
SampleItem = a
p a.name  #=> "SampleItem" 名前が参照されたタイミングで名前が確定する

# 複数の定数に代入された場合の名前は不定
a = Struct.new(:name, :icon, :price)
p a.name  #=> nil
SampleItem1 = a
SampleItem2 = a
p a.name  #=> "SampleItem1" or "SampleItem2" どちらになるかはわからない
SampleItem = Struct.new(:name, :icon, :price)  # 宣言時に代入するのがベストプラクティス

# クラスを開く必要があるときはエイリアス名で開く
class SampleItem
  def sample_method
  end
end

# メソッドは問題なく追加される
p SampleItem.instance_methods(false)  # [:name, :name=, :icon, :icon=, :price, :price=, :sample_method]
  • 構造体クラスを生成するためのStruct.new構文は、無名クラスを作る際のClass.new構文と同等のものなのでイディオムを流用できる。
参照: 備忘録#無名クラスを作る

グローバルな定義
# 構造体をとあるクラスに紐付けるのではなく、他のクラスのようにグローバルな参照として定義したい場合
# トップレベルに定義文をおけばグローバルに定義できる
SampleItem = Struct.new(:name, :icon, :price)  

# 定義をクラス内に置くなら、グローバル名前空間にエイリアスを定義すればグローバルに参照できる
class MyClass
  ::SampleItem = Struct.new(:name, :icon, :price)  
end

# グローバル名前空間をフルネームで指定しても良い
class MyClass
  Object::SampleItem = Struct.new(:name, :icon, :price)
  # グローバルエイリアスを使うならクラスを開く際もグローバル名前空間の指定を忘れずに
  class ::SampleItem
    def sample_method
    end
  end
end
参照: Ruby 1.9.3 リファレンスマニュアル > class Struct

非推奨の構文
  • 一部のブログなどで紹介されているが使わないようにしたい。
# 下記のような構文は非推奨
class SubItem < Struct.new(:name, :icon, :price)
end

# 非推奨の理由
p SubItem.ancestors  # [SubItem, #<Struct:0x4456780>, Struct, Enumerable, Object, Kernel, BasicObject]
# 継承経路の間に無名クラスが挟まってしまっている 無名クラスはその名の通り、名前で参照することができない
# 一応、後から名付ける事は可能だが、それならそもそもこの構文を使う意味もない つまり非推奨
p NewName = SubItem.superclass
p SubItem.ancestors  # [SubItem, NewName, Struct, Enumerable, Object, Kernel, BasicObject]

# SubItemはあくまでも無名の構造体クラスを継承したサブクラス
p a = SubItem.new("sub", 1, 10)    #=> #<struct SubItem name="sub", icon=1, price=10>
p b = NewName.new("new", 2, 20)    #=> #<struct NewName name="new", icon=2, price=20>
p a.class.instance_methods(false)  #=> [] SubItemにはメンバ参照メソッドが定義されていない
p b.class.instance_methods(false)  #=> [:name, :name=, :icon, :icon=, :price, :price=]

# そもそもクラスの二重登録になるので非効率
# 新しいクラスを定義する際に下記構文を使うのと同じぐらい無駄
class MyClass < Class.new(Object)  # Class.newでObjectを継承した無名クラスを作り、それを継承したMyClassを定義
end

構造体クラスのデータ構造
  • Struct.newで生成される構造体クラスは内部にインスタンス変数を保持しているわけではない。
    • 内部のC構造体にメンバのデータを格納する配列を持ち、メンバ参照メソッドは内部データを参照するゲッター(セッター)として機能する。
SampleItem = Struct.new(:name, :icon, :price)  
a = SampleItem["alf", 5, 1000]
p a.instance_variables  #=> [] インスタンス変数は定義されていない

構造体クラスへのメソッド定義
# 宣言時にブロックを渡す 構造体クラスの生成と同時にメソッドを定義できる
SampleItem = Struct.new(:name, :icon, :price) do
  def initialize(name = "", icon = 0, price = 0)  # 初期値の設定などに向く
    super
  end
end
p SampleItem[].to_a  #=> ["", 0, 0] ※initialize未定義時は [nil, nil, nil] になる

# 宣言後にクラスを開く 構造体クラス生成後でもメソッドを追加できる
SampleItem = Struct.new(:name, :icon, :price)  
class SampleItem 
  def sell_price  # 機能の追加などに向く
    price / 2
  end
  alias sell  :sell_price  # aliasも勿論定義可能
end
a2 = SampleItem["Item1", 10, 500]
a2.sell_price  #=> 250
a2.sell        #=> 250

メンバの参照方法
SampleItem = Struct.new(:name, :icon, :price)
a = SampleItem["Item1", 10, 500]
# Classでアクセサを指定したインスタンス変数のようにメンバ名のメソッドで値を参照可能
p a.name   #=> "Item1"
p a.price  #=> 500

# Classでattr_accessor指定したのと同様に代入も可能
p a.price = 1000  #=> 1000

# Hashのようにシンボルや文字列を添え字にしても参照可能 そのため汎用性が非常に高い
p a[:name]   #=> "Item1"
p a['price'] #=> 500
m = "icon"
p a["#{m}"]  #=> 10 文字列が可能なので式展開も利用可能

# Arrayのように添え字でも参照可能 つまりJavaのLinkedHashMapのようにも扱える
p a[0]   #=> "Item1"
p a[2]   #=> 500
p a[-1]  #=> 500 Array同様、負の添え字も指定可能

# メンバと値のリストを、Hashのキーと値のリストのように取り出せる
p a.members  #=> [:name, :icon, :price]
p a.values   #=> ["Item1", 10, 500]

# 参照方法の自由度が高いため構造体配列のソートはシンプルに書ける
sample_items = [
  SampleItem["Item1", 10, 500],
  SampleItem["Item2", 52, 200],
  SampleItem["Item3", 21, 100],
  SampleItem["Item4", 38, 900],
]
# 構造体配列のソート(昇順)
sample_items.sort_by(&:price)             # priceで昇順ソートした新しい配列を返す
sample_items.sort_by{|item| item.price }  # 上記と同じ
# 構造体配列のソート(降順)
sample_items.sort_by(&:price).reverse!    # priceで降順ソートした新しい配列を返す
sample_items.sort_by{|item| -item.price } # 上記とほぼ同じ 実行速度もメモリ効率もほぼ同等

# 上記のようにメンバをClass、Array、Hashの良い所取りで参照できるのは構造体だけの強み
# RPGの能力値のように個別に名前で参照したり連続したデータをまとめて参照したりしたい場合に特に有用

メンバの高度な参照例
Param = Struct.new(:hp, :mp, :atk, :def, :mat, :mdf, :agi, :luk, :hit, :eva, :cri, :cev)
a = Param[500,100, 10,12,14,16,18,20, 1.0,0.5,0.2,0.1]
# 例えば上記のような構造体でatk〜lukまでを出力するコードは以下のように書ける

# 可読性を無視するなら範囲式でシンプルに書ける
(2..7).each{|i| p a[i] }  

# メンバ名のindexを取得して範囲式に変換する やや迂遠
(a.members.index(:atk)..a.members.index(:luk)).each{|i| p a[i] }

# each_pairでフリップフロップ構文を使う 固定範囲を抜き出すならベストプラクティス
a.each_pair{ |m, v| p v if (m == :atk)..(m == :luk) }

# whileを使う 余分なオブジェクトを生成しないが冗長だしRubyらしさがない
i = a.members.index(:atk) - 1 
max = a.members.index(:luk)
p a[i] while (i = i.succ) <= max

# 配列と異なり添え字に範囲式は使えない
a[2..7].each{|i| p i }  # エラーになる

# values_atなら範囲式が使える
a.values_at(2..7).each{|i| p i }

# values_atに参照するindexリストを渡す メンバ名が使えず項目が増えると冗長になるが唯一順不同にメンバを参照できる
a.values_at(2,4,6,3,5,7).each{|i| p i }

# 一度Hashに変換することでメンバ名でvalue_atできる
Hash[a.members.zip(a.values)].values_at(:atk, :mat, :def, :mdf).each{|i| p i }

フラグに使う引数の可読性向上のヒント

  • method_name(flag) の形式で flag によってメソッドの動作を制御する場合、呼び出し元からそのフラグの意味が判別しづらいという欠点がある。これは引数が増えるほどに顕著になる。
    • Ruby2.0以降であればキーワード引数などを利用して可読性を確保することも可能だが、あいにくとRGSS3ではキーワード引数を使えない。
  • デフォルト引数で省略時の値を false に設定し、シンボルを true 代わりに使うことで、呼び出し側である程度の可読性を確保する事ができる。
# フラグのデフォルト値は false にする。サンプルはGame_Partyクラスから抜粋
  def has_item?(item, include_equip = false)
    return true if item_number(item) > 0
    return include_equip ? members_equip_include?(item) : false
  end

# trueの場合の呼び出し例
has_item?($data_items[1], :include_equip) # 名前に意味のあるシンボルを渡す事で true とする

# falseの場合の呼び出し例
has_item?($data_items[1], :include_equip.!) # 否定メソッドで強制的に false を返す
has_item?($data_items[1])                   # または単純に初期値falseなので省略可能
  • 否定メソッドに関しては、Rubyでは !(否定演算子)は単なるメソッドとして実装されているため上記の記述が可能となる。
    • ! を文末に置くというプログラマが慣習的に感じる気持ち悪さを我慢すれば、コードの順序と思考の順序が同じ(include_equipがfalseなら)になるためむしろ可読性が向上する可能性すらある。
    • 初見では何をやっているのかわからないというデメリットも当然のように存在するが、Rubyへの理解が深まればいかにもRubyらしい書き方と言えなくはない。
  • 一応解説するとRubyでは、! や < などの一部のメソッドを演算子のように記述できる特例の構文解析で扱っているに過ぎない。
    • 「演算子をメソッドとして」扱えるのではなく、一部の演算子に該当する「メソッドを演算子として」扱えるのがRubyである。その真骨頂は実体がメソッドであるならば演算子すらも書き換える事ができるという強力なメタプログラミングにある。
    • 「そこ書き換えちゃダメだろ!」とプログラマがドン引きするほどのメタプログラミングを許す懐の広さ(無法地帯ともいう)を持つのがRubyなのである。
  • まあ往年のプログラマ的には気持ち悪い書き方なのは否定できないので、別解としてSymbolクラスにbooleanメソッドを定義する事で更に可読性を上げる方法も提示する。
    • こっちはまだマシ。
class Symbol
  def true # trueを返すtrueメソッドを定義
    true
  end
  def false # falseを返すfalseメソッドを定義
    false
  end
end

# trueの場合の呼び出し例
has_item?($data_items[1], :include_equip.true)

# falseの場合の呼び出し例
has_item?($data_items[1], :include_equip.false)






Ruby拡張(組み込みクラスにメソッド追加例)

Objectにitself、singleton_methodを組み込む

  • itself は self を返す。Ruby2.2からの逆輸入。
    • あまり使いどころは無いがたまにselfが欲しい場合には役に立つ。
  • singleton_method は特異メソッドのMethodオブジェクトを取得する。Ruby2.1からの逆輸入。
    • これも特に使いどころが不明。元々methodで普通に取得できる。
class Object
  def itself
    self
  end
  def singleton_method(name)
    if singleton_methods.include?(name)
      method(name)
    else
      raise NameError, "undefined singleton method `#{name}` for class `#{self}`"
    end
  end
end

true、falseをbool判定する

  • RubyにはBoolean型は存在しない。共通の方法で判別したい場合には以下の方法を使う。
    • 中身の無いモジュールを定義し、それぞれのクラスにincludeすれば共通のスーパークラスのように振舞わせる事ができる。
    • BasicObjectに bool?メソッドを定義し、TrueClass と FalseClass で true を返すように再定義する方法もある。
    • 判定時の実行速度としては後者の方が速い。
module Boolean  # 中身の無いBooleanモジュールを定義
end

class TrueClass
  include Boolean  # Booleanモジュールをインクルード
end

class FalseClass
  include Boolean  # Booleanモジュールをインクルード
end

Boolean === true   #=> true
Boolean === false  #=> true
Boolean === nil    #=> false
Boolean === 1      #=> false
# 全てのオブジェクトに bool? メソッドを定義する方法
class BasicObject
  def bool?; false; end
end

class TrueClass
  def bool?; true; end
end

class FalseClass
  def bool?; true; end
end

true.bool?  #=> true
false.bool? #=> true
nil.bool?   #=> false
1.bool?     #=> false

Comparableにclampメソッドを追加(Ruby2.4から逆輸入)

  • Ruby2.4から実装されたclampメソッドは引数で指定された範囲内に値を収める。RGSS3では使いどころが多いので実装しておきたい。
    • 例えばスキルの計算式で (a.atk*4 - b.def*2).clamp(1, 9999) とすると最低ダメージが1、最大ダメージが9999になる。ただしダメージ分散の処理がこの後に行われるため、この場合は分散度は0にしてやる必要がある。分散処理も併用したい場合は計算式に分散処理も含める必要がある。
    • ComparableをincludeしたクラスはNumeric、String、Symbol、Timeなので、それら全てで使えるようになる。
  • clamp(min, max) -> object
    • minとmaxの2つの引数を与えた場合、minを下回る場合にはmin、maxを上回る場合にはmaxを返す。
  • clamp(range) -> object
    • 引数に範囲式を与えると、range.minを下回る場合にはrange.min、range.maxを上回る場合にはrange.maxを返す。※2.4とは異なる仕様
module Comparable
  def clamp(min_or_range, max = nil)
    if max
      return min_or_range if self < min_or_range
      return max          if self > max
    else
      return min_or_range.min if self < min_or_range.min
      return min_or_range.max if self > min_or_range.max
    end
    self
  end
end
おまけ Integerに分散処理を定義する
# Floatでも使いたい場合はNumericに定義する
class Integer
  # self の ±v% の整数を返す。例として初期値をデータベースの分散度初期値(20)に合わせてあるが、自分の好みで調整した方が使いやすい。
  def var(v = 20)
    self + (self * (rand(v*2+1) - v) / 100.0).to_i
  end
end
# 分散処理と最大最小をまとめて指定した場合の計算式例
(a.atk*4 - b.def*2).var(5).clamp(1, 9999)  # ダメージを±5%、最小1、最大9999にする ダメージ分散度の設定は 0 にすること
(a.atk*4 - b.def*2).var.clamp(1, 9999)     # 引数を省略すれば±20%(宣言時に設定した初期値)になる
参照: Ruby 4.0 リファレンスマニュアル Comparable#clamp

Integerにビット判定を組み込む

  • Rubyは self[bit] でビットの値を取得できるが、そのままでは判定には使えない。
  • bit?(b) メソッドを追加してビット b が 1 なら true を返す。
class Integer
  def bit?(b)
    self[b] == 1
  end
end

Arrayにxor演算子を組み込む

  • Arrayには ^(XOR演算子)は実装されていない。需要は少ないと思うが必要になった時のために実装の一例を載せておく。
class Array
  def ^(array)
    (self - array) | (array - self)
  end
end

Arrayにexclude?、remain?を組み込む(蛇足)

  • include? の反対である exclude?、empty? の反対である remain?、どちらも実際問題として不要。
  • 条件分岐で否定演算子を減らす程度の役割はあるが、独自定義のメソッドは可読性低下の危険もあるので注意。
    • exclude? に関しては、include? がその性質上、否定演算子と相性が悪い(否定演算子の記述位置がメソッドから遠い)ため、そこにフォーカスするなら有用ではある。
    • 自作のクラスにこれらを組み込むのはあり。例えばRGSS3でメモ欄に特定のタグが含まれているかどうかをinclude?メソッドで実装するとして、この場合は逆に含まれていない判定も必要になる可能性が高いのでexclude?もセットで実装する、などは有効だろう。
class Array
  def exclude?(obj)  # メソッドの意味は「除外」つまり含めない=not include
    !include?(obj)
  end
  def remain?        # メソッドの意味は「残り」つまりまだ中身が有る=空ではない≒not empty
    !empty?
  end
end
# 使用例
return if user.inventory.exclude?(:tickets)   # チケットを持たないなら追い返す
return if !user.inventory.include?(:tickets)  # include?で書くと否定演算子が遠い

ary.shift while ary.remain?  # aryが残ってる間ループ
ary.shift until ary.empty?   # aryが空になったら終わる こっちの方がシンプルではある

Arrayにsum、averageを組み込む

  • sumはRuby2.4で追加された合計を返すメソッド。 averageは平均を返すメソッドでRubyには未実装。
    • どちらもRGSS3にあると便利なので掲載しておく。雑実装だがRGSS3での実用レベルではこれで十分。
  • Array#sum(init = 0) -> object
    • initを初期値として、Numeric配列を合計した結果を加算した値を返す。中身は Enumerable#inject(init, :+)。
  • Array#sum(init = 0) { |e| expr } -> object
    • initを初期値として、配列に対してブロックを評価した値の合計を返す。
  • Array#average -> object
    • 配列の平均値を返す。sumを利用して平均を出しているので浮動小数点数配列ではsumの計算精度の影響を受ける。
  • 注意点
    • 2.4で追加されたsumは「カハンの加算アルゴリズム」を使っているので浮動小数点数計算では誤差が少ない。詳しくは下記。
Kahan と ruby の sum
https://qiita.com/Nabetani/items/d64b39b6360c67a30...
たくさんの浮動小数点数を足す場合の誤差
https://qiita.com/Nabetani/items/59a8ec6160cd409c9...
参照: Ruby 4.0 リファレンスマニュアル Array#sum
class Array
  def sum(init = 0)
    return inject(init, :+) unless block_given?
    inject(init){ |r,v| r += yield(v) }
  end
  def average
    sum.fdiv(size)
  end
end

ArrayからSetを実装する

  • Ruby3.2で実装(正確にはsetライブラリから組み込みに昇格)されたSetをRGSS3でも擬似的に使うための代替手段。
    • RGSS3では拡張ライブラリをrequireする事ができないためsetライブラリも使えない。
    • 簡易的な実装だがRGSS3で似たような事をしたいだけなら十分に要件を満たす。
    • Game_BattlerBase#features_set のように |= を使えば似たような事はできるが、|= は毎回uniqが走るようなものなのでSetの方が圧倒的に速い。
  • Set#add(value) -> self
    • value が配列内に含まれていない場合に要素を追加する 含まれている場合は何もしない。
  • Set#add?(value) -> self|nil
    • value が配列内に含まれていない場合に要素を追加する 追加できたらselfを、できなければnilを返す。
  • Set#to_set -> Array
    • 重複を削除した配列を返す。
  • Set#include?(value) -> bool ※Arrayに定義済みをそのまま使える
    • valueが配列に含まれるならtrue、含まれなければfalseを返す。
  • Set#delete(value) -> Object|nil ※Arrayに定義済みをそのまま使える
    • set配列からvalueを削除する。削除できたらvalueを、できなければnilを返す。
class Set < Array
  def add(v)
    add?(v) || self
  end
  def add?(v)
    self << v unless include?(v)
  end
  def to_set
    uniq!
  end
end
HashからSetを実装する
  • Arrayとどっちで実装するかは好み。メモリ効率はArrayの方が上。非重複保障はHashの方が上。
  • 使い勝手やメソッドの意味はArrayと同じ。
    • Hash式の場合、hash[key] の形式でも存在判定可能(trueかnilを返す)。
class Set < Hash
  def add(v)
    add?(v) || self
  end
  def add?(v)
    (store(v, true); self) unless include?(v)
  end
  def to_a
    keys
  end
  def to_ary
    keys
  end
  def to_set
    self
  end
end

Rangeにsampleとrandを組み込む

class Range
  def sample(*argv)
    to_a.sample(*argv)
  end
  def rand
    Ramdom.new.rand(self)
  end
end

拡張イテレータ例

# Array用イテレータ
class Array
  # 要素内を逆順に検索し、最初にtrueになった要素を返す なければnilまたはifnone.callを返す
  # RGSS3ではイベントのページ探索(後方優先)などに使える
  def rfind(ifnone = nil)
    reverse_each do |i|
      return i if yield(i)
    end
    ifnone ? ifnone.call : nil
  end
  # 要素内を逆順に繰り返す ブロックには各要素とインデックス番号が渡される
  def each_with_rindex
    i = size
    reverse_each{|v| yield(v, i -= 1) }
  end
  # 配列の内容を逆順(降順)ソートする ブロックを与えるとその結果も逆になる
  def rsort
    block_given? ? sort{|a,b| yield(b,a) } : sort.reverse!
  end
  # ブロックの評価を元に逆順(降順)ソートする
  def rsort_by
    sort_by{|i| yield(i) }.reverse!
  end
  # 二つの配列を同時にイテレートする ブロックパラメータには |自身の要素, 引数配列の要素, index| が渡される
  # zipと異なり二次元配列を作らない ブロックが渡されない場合はEnumeratorを返す ブロックが渡されたらselfを返す
  def parallel(ary)
    return to_enum(:parallel, ary) unless block_given?
    vsize = size
    i = -1
    yield(self[i], ary[i], i) while (i = i.succ) < vsize
    self
  end
end
参照: Ruby 1.9.3 リファレンスマニュアル > class Array
参照: #代表的なイテレータまとめ

ハッシュのソート結果(配列)をハッシュ化

  • ハッシュのソートはキーと値をペアとした二次元配列を返す。それをハッシュに戻す方法。
  • 方法は色々あるが、Hashの特異メソッドでハッシュ化するのが早い。
    • Hash[ary] は、ary が 2*n の二次元配列の場合はそのまま変換できる。それ以外の配列の場合は変換できずに空のハッシュを返す。
    • Hash[*ary] とする事で、キーと値が順番に並んだ一次元配列の展開引数として変換できる。Array#flatten での一次元配列化と併用すれば多次元配列も変換できるが、要素数が偶数でなくてはならない。
# ソート例
h = {a: 10, g: 2, c: 35, d: 14, u: 5, z: 7}
h.sort{|(ak,av),(bk,bv)| bk <=> ak }  #=> [[:z,7], [:u,5], [:g,2], [:d,14], [:c,35], [:a,10]]
h.sort{|(ak,av),(bk,bv)| bv <=> av }  #=> [[:c,35], [:d,14], [:a,10], [:z,7], [:u,5], [:g,2]]
h.sort_by{|k,v| k }        #=> [[:a,10], [:c,35], [:d,14], [:g,2], [:u,5], [:z,7]]
h.sort_by{|k,v| v }        #=> [[:g,2], [:u,5], [:z,7], [:a,10], [:d,14], [:c,35]]
# ハッシュ化
Hash[h.sort_by{|k,v| k }]  #=> {:a=>10, :c=>35, :d=>14, :g=>2, :u=>5, :z=>7}
Hash[h.sort_by{|k,v| v }]  #=> {:g=>2, :u=>5, :z=>7, :a=>10, :d=>14, :c=>35}

# Arrayにハッシュ化メソッドの追加
class Array
  def to_h  # Ruby2.1からの逆輸入
    Hash[self]
  end
end

# ハッシュ化メソッド使用例
h.sort_by{|k,v| k }.to_h  #=> {:a=>10, :c=>35, :d=>14, :g=>2, :u=>5, :z=>7}
h.sort_by{|k,v| v }.to_h  #=> {:g=>2, :u=>5, :z=>7, :a=>10, :d=>14, :c=>35}

# 構造体をハッシュ化する例
Animal = Struct.new(:name, :rank, :hp, :atk, :spd) do
  def initialize(name="noname", rank=0, hp=nil, atk=nil, spd=nil)
    hp  = rand(rank) * 10 + 50 unless hp
    atk = rand(rank) * 5  + 20 unless atk
    spd = rand(rank) * 5  + 20 unless spd
    super(name, rank, hp, atk, spd)
  end
  def to_h  # ハッシュ化メソッドの定義
    Hash[members.zip(values)]
  end
end
  
dogs = [
  Animal["alf", 7, 80,50,30],
  Animal["ben", 3, 70],
  Animal["cry", 5],
  ]
    
dogs.each {|i| p i.to_h }
#=> {:name=>"alf", :rank=>7, :hp=>80, :atk=>50, :spd=>30}
#=> {:name=>"ben", :rank=>3, :hp=>70, :atk=>20, :spd=>20}
#=> {:name=>"cry", :rank=>5, :hp=>90, :atk=>30, :spd=>40}

Rangeの===でRangeクラスも判定可能にする

  • 1.9.2ではRange#===の引数にRangeを取ることができず、全てfalseになる。
  • Ruby2.6からの逆輸入。
class Range
  alias _orig_equal :===
  def ===(other)
    if other.is_a?(Range)
      self.min <= other.min && other.max <= self.max 
    else
      _orig_equal(other)
    end
  end
end

Structにindex、flip_flopを追加

# 汎用的なコードをStruct自体に定義する事でサブクラス全てで使えるようにする
class Struct
  # 渡された Symbol、String、Fixnum の members 内でのインデックス番号を返す
  def index(value)
    case value
    when Symbol
      members.index(value)
    when String
      members.index(value.to_sym)
    when Fixnum
      value
    else
      value.to_i
    end
  end
  # フリップフロップ構文をメソッド化 first, lastに渡せるのはSymbolのみ
  def flip_flop(first, last)
    self.each_pair{|m,v| yield(m, v) if (m == first)..(m == last) }
  end
  # 範囲を限定したeach_pairのように使う start, gorlに渡せるのはSymbol、String、Fixnum、もしくはto_i実装クラス
  # フリップフロップよりも多くの引数に対応できるが、その分オーバーヘッドがある
  def each_range(start, goal)
    first = start.is_a?(Symbol) ? start : members[index(start)]
    last  =  goal.is_a?(Symbol) ? goal  : members[index(goal)]
    self.each_pair{|m,v| yield(m, v) if (m == first)..(m == last) }
  end
end

# 使用例
Param = Struct.new(:hp, :mp, :atk, :def, :mat, :mdf, :agi, :luk, :hit, :eva, :cri, :cev)
a= Param[500,100, 10,12,14,16,18,20, 1.0,0.5,0.2,0.1]

a.flip_flop(:atk, :luk){|m, v| p "#{m} #{v}" }  # flip_flopでatk〜lukまでを出力する例
#=> atk 10
#=> def 12
#=> mat 14
#=> mdf 16
#=> agi 18
#=> luk 20 
a.each_range(:atk, :luk){|m, v| p [m,v] }  # each_rangeでatk〜lukまでを出力する例
#=> [:atk, 10]
#=> [:def, 12]
#=> [:mat, 14]
#=> [:mdf, 16]
#=> [:agi, 18]
#=> [:luk, 20]

# 速度比較 10万回所要時間平均
a.flip_flop(:hp, :mp)       #=> 0.2226100s 項目数に限らずほぼ一定
a.flip_flop(:atk, :cri)     #=> 0.2410000s 項目数に限らずほぼ一定
a.each_range(:hp, :mp)      #=> 0.2345000s 引数解析分オーバーヘッドが若干ある
a.each_range(:atk, :cri)    #=> 0.2529000s 引数解析分オーバーヘッドが若干ある
a.each_range('atk', 'cri')  #=> 0.4978000s 引数解析分オーバーヘッドが一番多い
a.each_range(2, 10)         #=> 0.3488000s 引数解析分オーバーヘッドがある
参照: Ruby 1.9.3 リファレンスマニュアル > class Struct

Classにsubclasses、Moduleにinnerclasses、outerclassを追加

  • Class.subclasses は自身のサブクラス一覧を返す。Ruby3.1から逆輸入。
    • 公式版と異なり、引数に false を与えることによって直接のサブクラスだけを含める機能も有する完全上位互換版。
  • Module.innerclasses は自身の内部クラス一覧を返す。引数が true(省略時)の場合、内部クラスの内部クラスも含める。false の場合、直下の内部クラスのみを含める。
  • Module.outerclass は自身が所属する一番直近の外部クラスを返す。
  • いずれの場合も無名クラスは対象外。判定方法が名前を名前空間フルネームと仮定して文字列解析しているので、名前が存在しないもしくは自由に名前をつけられる無名クラスは判別方法がない。
  • ついでに subclasses と innerclasses はブロック内でフロー制御演算子を使う例になっている。
# メソッドの定義
class Class
  def subclasses(inherited_too = true)
    ObjectSpace.each_object(Class).select do |o|
      o < self and
      inherited_too || o.superclass == self
    end
  end
end
class Module
  def innerclasses(inherited_too = true)
    ObjectSpace.each_object(Module).select do |o| 
      o.name and 
      o.name.index(self.name + "::") == 0 and 
      inherited_too || !o.name.rpartition(self.name + "::")[2].include?("::")
    end
  end
  def outerclass
    a = self.name.rpartition("::")
    a[0] == "" ? Object : Object.const_get(a[0])
  end
end

# メソッドのテスト
class O; end
class O2 < O; end
  
class A < O
  class B < O
    class C < O2; end
    class D < O2; end
  end
  class E
    Class.new  # 無名内部クラス
  end
end

p O.subclasses          #=> [A::B::D, A::B::C, A::B, A, O2]
p O.subclasses(false)   #=> [A::B, A, O2] 引数falseで直接のサブクラスのみを選出
p O2.subclasses         #=> [A::B::D, A::B::C]
p A.subclasses          #=> []
p A.innerclasses        #=> [A::E, A::B::D, A::B::C, A::B]
p A.innerclasses(false) #=> [A::E, A::B] 引数falseで直下の内部クラスのみを選出
p A.outerclass          #=> Object 外部クラスが無い場合はグローバル名前空間を返す
p A::B.subclasses       #=> []
p A::B.innerclasses     #=> [A::B::D, A::B::C]
p A::B.outerclass       #=> A
p A::E.innerclasses     #=> [] 無名クラスは対象外






Ruby高速化、省メモリ

リテラルのオブジェクト生成表

  • リテラルのうち、一部は評価されるたびにオブジェクトを生成する。このようなリテラルを多用するとメモリ効率が悪くなる。
    • 評価はコードが実行されるたびに行われる。
リテラルの例リテラルの種類オブジェクトの生成Classメモリ
123数値リテラル(Fixnum)数値ごとに一意 同じ数値なら常に同じオブジェクトを返すFixnum(*2)value
123_0000_0000数値リテラル(Bignum)評価されるたびに新しいオブジェクトを生成するBignumpointer
123.0数値リテラル(Float)評価されるたびに新しいオブジェクトを生成するFloatpointer
"abc"文字列リテラル評価されるたびに新しいオブジェクトを生成するStringpointer
/abc/正規表現リテラル評価ごとに一意 同じ正規表現なら常に同じオブジェクトを返す
ただし同じ正規表現でも記述位置が異なる場合には別のオブジェクトとして扱われる
Regexppointer
/#{v}/正規表現リテラル(式展開含む)評価されるたびに新しいオブジェクトを生成するRegexppointer
/#{v}/o正規表現リテラル(式展開 + oオプション)一度だけ評価され(*1)、同じオブジェクトを返すRegexppointer
[1, 2, 3]配列式評価されるたびに新しいオブジェクトを生成するArraypointer
{:a => 1, :b => 2}ハッシュ式評価されるたびに新しいオブジェクトを生成するHashpointer
1..20範囲式(両端がFixnum)評価ごとに一意 同じ範囲なら常に同じオブジェクトを返す
ただし同じ範囲でも記述位置が異なる場合には別のオブジェクトとして扱われる
Rangepointer
"a".."z"範囲式(Fixnum以外)評価されるたびに新しいオブジェクトを生成するRangepointer
:symbolシンボル名前ごとに一意 同じ名前なら常に同じオブジェクトを返す
ただし新しい名前はシンボルテーブルに追加され、GC対象にならない(1.9)
Symbol(*2)value
truetrue常に一意なTrueClassオブジェクトを返すTrueClass(*2)value
falsefalse常に一意なFalseClassオブジェクトを返すFalseClass(*2)value
nilnil常に一意なNilClassオブジェクトを返すNilClass(*2)value
(*1) 式展開の中身 v が変化する場合、正しくマッチングしない点に注意。評価は最初の一回しか行われないので常に最初のマッチの結果が返されることになる。
(*2) これら5種のクラスのオブジェクト(インスタンス)には特異クラスを定義できない。
# 例)正規表現リテラルの一意性
def test
  p [/abc/.object_id, /abc/.object_id]
end
test #=> [84904220, 84904140] 見た目は同じ正規表現でもIDは異なる
test #=> [84904220, 84904140] 何度呼ばれてもIDは変化しない

# 例)正規表現リテラル(式展開含む)の一意性
def test2(v)
  p [/#{v}/.object_id, /#{v}/ === "abcdef", /#{v}/o.object_id, /#{v}/o === "abcdef"]
end
test2("a") #=> [86360130, true, 86411130, true]
test2(:a)  #=> [86410810, true, 86411130, true] oオプションなしだとIDは変化する ありだとIDは変化しない
test2(:z)  #=> [86410440, false, 86411130, true] oオプションありだと結果も変化しないため正しくない
# 例)範囲リテラルの一意性
def fixnum_range
p [(  1..20 ).object_id, (  1..20 ).object_id]
end
def string_range
p [("a".."z").object_id, ("a".."z").object_id]
end

fixnum_range  #=> [35849780, 35849730] 同じ範囲でもidは別
fixnum_range  #=> [35849780, 35849730] 何度呼ばれてもidは同じ
string_range  #=> [35849640, 35849600] 同じ範囲でもidは別
string_range  #=> [35849720, 35849760] 呼ばれる度にIDが変化

定数文字列よりシンボルを使う

  • Rubyの基本テクニックなので必ず覚えたい。
    • シンボルの実体は一意性のある整数なのでメモリ効率や速度の面で非常に効率的。特に定数文字列やHashのキーワードなどに向いている。
  • シンボルは :name の形式で指定する。基本的に文字列 name に対応する。
  • SymbolクラスとStringクラスは共通のメソッドが多く、用途次第ではシームレスに扱う事も可能。
:name.id2name #=> "name"
:name.to_s    #=> "name"
'name'.to_sym #=> :name
'name'.intern #=> :name

# SymbolとStringをシームレスに扱う例 以下の二つは同じ結果を返す
p method("class").call
p method(:class).call

# SymbolクラスとStringクラスの共通のメソッド
# !~ <=> == === =~ [] slice capitalize casecmp downcase empty? encoding
# to_s inspect intern to_sym length size next succ swapcase upcase
:name.to_s     #=> "name"
"name".to_s    #=> "name"
:name.to_sym   #=> :name
"name".to_sym  #=> :name
:name.upcase   #=> :NAME
"name".upcase  #=> "NAME"

:abcde =~ /cd/      #=> 2 シンボル頭の : はマッチング対象にならない点に注意
"abcde" =~ /cd/     #=> 2
"abcde" =~ /#{:cd}/ #=> 2 正規表現にシンボルを使う場合は式展開を利用する
:abcde =~ /k/       #=> nil
"abcde" =~ /k/      #=> nil
/cd/ =~ :abcde      #=> 2 左右を入れ替えても結果は同じ
/cd/ =~ "abcde"     #=> 2
/#{:cd}/ =~ "abcde" #=> 2
/k/  =~ :abcde      #=> nil
/k/  =~ "abcde"     #=> nil

# SymbolクラスとStringクラスの同名で処理の異なるメソッド
# match
:abcde.match(/cd/)  #=> 2
"abcde".match(/cd/) #=> MatchDataオブジェクト Regexp.match(String)と同じ
/cd/.match("abcde") #=> MatchDataオブジェクト
:abcde.match(/k/)   #=> nil
"abcde".match(/k/)  #=> nil
/k/.match("abcde")  #=> nil

Symbol.all_symbolsに含まれる要素
  • Symbol.all_symbolsは実際にプログラム内で定義されているシンボルが全て含まれる。ここに含まれる情報は以下の通り。
    • 全ての定数名(クラス名、モジュール名も含む)
    • 全てのメソッド名(aliasメソッドや、アクセサによるメソッドも含む)
    • 全てのグローバル変数名
    • 全てのクラス変数名
    • 全てのインスタンス変数名(クラスインスタンス変数含む)
    • 全てのローカル変数名(引数名やブロックパラメータも含む)
    • 一部の擬似変数名や予約語(selfやbeginなど)
    • Ruby内部のC実装で使用される特殊シンボル
    • 全てのシンボルリテラル(Structのメンバ名なども含む)
    • 動的に参照されるシンボル名(動的にシンボルに変換された文字列なども含む)
  • RGSS3環境だと軽く2000は超える。
    • RGSS3環境ではシンボル情報は使われなくなってもメモリに残り続け、GC対象とならない。そのため特に動的なシンボル生成(文字列からの変換など)は
# プログラム内で使われている全シンボルリストをファイルに書き出すコード
open('symbol.log', 'w'){|f| f.puts Symbol.all_symbols.sort }

文字列連結には << を使う

  • +で連結すると新しいオブジェクトが作られる。<<で連結すると左辺を拡張してメモリをそのまま利用する。
    • 当然 + に比べて << の方が処理速度、メモリ効率共に効率的。
    • <<は破壊的なので、左辺文字列を再利用する必要がある場合には使えないので注意。

新しいオブジェクトを返すチェインメソッドは2回目以降を破壊的にする

  • 例えば数字の配列に対して、nilを排除して、奇数だけを集めて、降順で並べ替える、というような処理を行う場合、チェインメソッドで書く事が多い。
    • 全ての処理で新しい配列が作られるため処理速度やメモリ効率の面で無駄が多い。
    • Arrayクラスには同様の処理を行う破壊的メソッドも多く用意されているので、2つ目以降の処理を破壊的に変更する事で元の配列に影響を与えずに処理速度やメモリ効率の向上を期待できる。
    • 注意点として一部の破壊的メソッド(特に要素数が変化する場合)はフロー制御メソッドとして実装されている。これらはselfが変化しない場合には nil を返す設計になっている。このようなメソッドは無理にチェインせず、縦に並べて書けばいい。重要なのはチェインする事ではなくオブジェクト生成数を抑える事にある。もしくは破壊的イテレータなどは常にselfを返すメソッドが同時に用意されているので、そちらを使えばいい。
# サンプル用に1〜100+nilのランダム配列の作成
a = (1..100).to_a.concat([nil] * 5).shuffle!

# 普通に書いた場合
a.compact.select{ |v| v.odd? }.sort_by{ |v| -v }

# 2つ目以降を破壊的にする(select!の代わりに常にselfを返すkeep_ifを使う)
a.compact.keep_if{ |v| v.odd? }.sort_by!{ |v| -v }

# sort_byは内部で新しい配列を作成するためメモリを優先するならsortの方が良い
# 速度的にはsort_byの方が早い場合が多いのでケースバイケース
a.compact.keep_if{ |v| v.odd? }.sort!{ |a, b| b <=> a }

# ただし上記のような単純な降順ソートならこっちの方がメモリ消費も少なくて圧倒的に速い
a.compact.keep_if{ |v| v.odd? }.sort!.reverse!

# フロー制御メソッドは無理にチェインせずに普通に縦に並べる
a.sort.compact!
a.uniq!
# どうしてもチェインしたい場合は以下のようにもできるがスマートではない
(a.sort.compact! || a).uniq!

消費メモリ関連

  • Rubyは全てがオブジェクトなのでリテラルやnilもオブジェクトとなっている。
    • Rubyではオブジェクトは基本構造体で管理されているため、最低でも40バイトのメモリを使う。
    • 例外としてFixnum、Symbol、TrueClass、FalseClass、NilClassは基本構造体を使わないため消費メモリは32ビット or 64ビットになる。
      • RGSS3では64ビットマシンでも32ビットで扱われる。そのため小さな整数も符号付31ビット整数になる。
      • シンボルは若干扱いが異なる。
  • 詳細は下記URL メモリを意識したRubyプログラミング(翻訳) を参照。

多重代入の速度

  • 多重代入と個別代入では多重代入の方が早い。
    • 多重代入は変数の数にかかわらず代入一回分の実行コストしかかかっていない。
    • 多重代入一回と個別代入一回では個別代入一回の方が早いが、ほとんど差はない。
    • とはいえ代入の実行コストはi7で1/100万秒にも満たないので、個別に複数書いても実感できるほどの差はない。

ループの速度比較

# 各10万回ループ10回平均
max = 10_0000
r = (1..max)
a = (1..max).to_a

i = i.succ while i < max   # 0.0200s 単文の場合はwhile修飾子が一番速い
i = i.succ until i >= max  # 0.0200s until修飾子も同様に速い
i = i.pred while i > 0     # 0.0400s succよりもpredの方が大分遅い
i = i.pred until i <= 0    # 0.0400s 
i += 1 while i < max       # 0.0200s succと+=はほぼ同じ(誤差レベルではsuccが速い事が多い)
i -= 1 until i <= 0        # 0.0200s predよりも-=の方が明らかに速い(+=と同じ)
while i < max              # 0.0200s while修飾子 と while文 はほぼ変わらない
  i = i.succ
end
until i <= 0               # 0.0200s until修飾子 と until文 はほぼ変わらない
  i -= 1
end
a.each {|i| }              # 0.0600s Array#each(と言うかブロック)は while よりも3倍以上遅い
r.each {|i| }              # 0.0600s Range#eachもArray#eachと同じ
max.times {|i| }           # 0.0600s timesもeachと同じ(差は誤差レベル)
1.upto(max) {|i| }         # 0.0600s uptoもeachと同じ
max.downto(1) {|i| }       # 0.0600s downtoもeachと同じ
for i in a; end            # 0.0700s forは内部でeachを呼ぶのでその分やや遅い
a.each_with_index {|i| }   # 0.0900s Enumerable#each_with_indexはeachの1.5倍遅い(Enumerableが遅いイメージ)
a.reverse_each {|i| }      # 0.0600s Array#reverse_eachはeach並みに速い
r.reverse_each {|i| }      # 0.0900s Enumerable#reverse_eachはArrayの1.5倍遅い
参照: #ループの種類

ソートの速度比較

  • ソートは条件によって使い分ける必要があるため単純比較はできないが、参考程度に同じ条件での速度比較を置いておく。
    • 条件:1〜1000までのランダム配列をソート
    • 見ての通り、単純なソートなら sort と sort.reverse が別次元で速い。
    • $game_party.members.sort_by(&:atk) のような場合は sort_by もしくは sort_by.reverse が速い。
# 数値のソート 100回ループ * 10回平均
a = (1..500).to_a.shuffle.concat((1..500).to_a.shuffle)
# 組み込みイテレータ(不安定ソート)
a.sort                                              # 0.0080s 単純な昇順ソート クソ速い
a.sort.reverse                                      # 0.0080s reverseで降順ソート クソ速い
a.sort{|a,b| a <=> b }                              # 0.1000s ブロック渡し昇順ソート ブロックなしに比べて13倍ぐらい遅い
a.sort{|a,b| b <=> a }                              # 0.1000s ブロック渡し降順ソート 
a.sort_by(&:+@)                                     # 0.0550s 単項演算子で昇順ソート sortの7倍ぐらい遅い
a.sort_by(&:-@)                                     # 0.0550s 単項演算子で降順ソート 
a.sort_by{|a| a }                                   # 0.0550s ブロック付き昇順sort_by ブロック付きsortよりは倍ぐらい速い
a.sort_by{|a| -a }                                  # 0.0570s ブロック付き降順sort_by 
a.sort_by{|a| a}.reverse                            # 0.0550s ブロック付き昇順をreverse ブロック付き降順sort_byとほぼ同じ
# Eumeratorで安定ソート
a.each_with_index.sort_by{|a,i| [a,i] }             # 0.3170s index配列で安定ソート sort_byの6倍遅くメモリもかなり食う
a.sort_by.with_index{|a,i| [a,i] }                  # 0.3120s index配列で安定ソート with_indexの方がやや速い
a.each_with_index.sort_by{|a,i| i.to_f / 1000 + a } # 0.0980s float加算で安定ソート 比較的早いがInteger配列にしか使えない
a.sort_by.with_index{|a,i| i.to_f / 1000 + a }      # 0.0900s float加算で安定ソート with_indexの方がやや速い
# 安定ソートアルゴリズムを実装(コードは割愛)
a.strand_sort_by{|v|  v }                           # 0.5000s ストランドソートで昇順ソート 安定ソートで一番速くメモリ効率も悪くない
a.strand_sort_by{|v| -v }                           # 0.5000s ストランドソートで降順ソート 悪く無いがindex配列の方が1.5倍以上速い
# 文字列のソート 100回ループ * 10回平均
t = ('a'..'sf').to_a  # 500要素
a = t.dup.shuffle.concat(t.dup.shuffle)
# 組み込みイテレータ(不安定ソート)
a.sort                                              # 0.0210s 単純な昇順ソート 数値ソートよりは大分遅くなるが十分速い
a.sort.reverse                                      # 0.0220s reverseで降順ソート
a.sort{|a,b| a <=> b }                              # 0.1060s ブロック渡し昇順ソート ブロックなしに比べて5倍ぐらい遅い
a.sort{|a,b| b <=> a }                              # 0.1090s ブロック渡し降順ソート 数値と比べてもさほど変わらない
a.sort_by{|a| a }                                   # 0.0660s ブロック付き昇順sort_by ブロック付きsortよりは1.5倍ぐらい速い
a.sort_by{|a| a }.reverse                           # 0.0660s ブロック付き昇順をreverse 数値に比べるとやや遅い
# Eumeratorで安定ソート
a.each_with_index.sort_by{|a,i| [a,i] }             # 0.3470s index配列で安定ソート sort_byの5倍遅くメモリもかなり食う
a.sort_by.with_index{|a,i| [a,i] }                  # 0.3350s index配列で安定ソート 数値に比べるとやや遅い
# 安定ソートアルゴリズムを実装(コードは割愛)
a.strand_sort_by{|v| v }                            # 0.8320s ストランドソートで昇順ソート 数値よりも効率が大分落ちる
sortのブロック付きが遅い理由
ブロック無しは内部でcで書かれたクイックソートで処理されるが、ブロックはRubyのコードとして評価されるため遅い。
sortブロック付きよりもsort_by(ブロック必須)が速い理由
sortは判定のたびに式を評価するが、sort_byは評価した式を元に内部でクイックソートされる。評価が一度なので速い。
sort_byのブロック渡しで a よりも -a が遅い理由
Rubyでは -(単項演算子)はメソッド -@ で実装されているため、メソッドを呼び出す分、若干遅い。
index配列がメモリを食う理由
ソートする要素の数だけArrayオブジェクトを生成する。

Array操作の速度比較から見る運用例

  • 下記の例から以下の運用方法が適する事がわかる。
  • スタック(Stack List)
    • 操作:push(あるいは<<)で末尾に追加し、popで末尾から取り出す。
    • 用途:最後に追加したものを最初に処理する後入れ先出し(Last In First Out)の使い方。Undo(巻き戻し)機能や、コールスタック(呼び出し履歴)など。
  • キュー(Queue)
    • 操作:push(あるいは<<)で末尾に追加し、shiftで先頭から取り出す。
    • 用途:追加された順番に処理する先入れ先出し(First In First Out)の使い方。キャッシュとして働くタスク処理や、予約実行など。
  • 入力制限デック(Input-Restricted Deque)
    • 操作:push(あるいは<<)で末尾に追加し、popで末尾からあるいはshiftで先頭から取り出す。
    • 用途:固定長スタックのような使い方。例えば過去10件の閲覧履歴や、回数制限UnDoなど。
  • 出力制限デック(Output-Restricted Deque)
    • 操作:push(あるいは<<)で末尾に、あるいはunshiftで先頭に追加し、shiftで先頭から取り出す。
    • 用途:優先順位を横入り可能なキューとしての使い方。Rubyではunshiftが遅いので優先順位の違う2つキューを作った方が良い。
# 1000要素100回ループ * 10回平均
a = (1..500).to_a.shuffle.concat( (1..500).to_a.shuffle)
m = a.size

# b = a.dup; のオーバーヘッドはほぼゼロ
b = a.dup; b.shift         until b.empty?    #=> 0.0060s 先頭から取り出し 速い
b = a.dup; b.pop           until b.empty?    #=> 0.0060s 末尾から取り出し 速い
b = a.dup; b.delete_at(-1) until b.empty?    #=> 0.0065s 末尾を削除 速い
b = a.dup; b.delete_at(0)  until b.empty?    #=> 0.0108s 先頭を削除 やや遅い

# b = []; のオーバーヘッドはほぼゼロ
b = [];    b << 0          while b.size < m  #=> 0.0033s 末尾に追加 クソ速い
b = [];    b.push(0)       while b.size < m  #=> 0.0052s 末尾に追加 十分速い
b = [];    b.insert(-1,0)  while b.size < m  #=> 0.0122s 末尾に追加 pushの倍以上遅い
b = [];    b.unshift(0)    while b.size < m  #=> 0.0225s 先頭に追加 要素が増えるごとに遅くなる
b = [];    b.insert(0,0)   while b.size < m  #=> 0.0292s 先頭に追加 unshiftよりも更に遅い

フリーエリア

管理人/副管理人のみ編集できます

広告募集中