最終更新:
tamamohaika 2026年08月09日(日) 15:04:40履歴
- Rubyの本質を理解する
- Rubyリファレンスの補足
- VXAce付属ヘルプ補足
- Ruby応用
- Ruby拡張(組み込みクラスにメソッド追加例)
- Objectにitself、singleton_methodを組み込む
- true、falseをbool判定する
- Comparableにclampメソッドを追加(Ruby2.4から逆輸入)
- Integerにビット判定を組み込む
- Arrayにxor演算子を組み込む
- Arrayにexclude?、remain?を組み込む(蛇足)
- Arrayにsum、averageを組み込む
- ArrayからSetを実装する
- Rangeにsampleとrandを組み込む
- 拡張イテレータ例
- ハッシュのソート結果(配列)をハッシュ化
- Rangeの===でRangeクラスも判定可能にする
- Structにindex、flip_flopを追加
- Classにsubclasses、Moduleにinnerclasses、outerclassを追加
- 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では便宜上名前空間クラスと呼称するものとする。
- Mix-inモジュールは、他のクラスに組み込む(Mix-inする)事で機能を追加する使い方である。Mix-inに使うメソッドによって挙動が異なる。
- 内部クラス(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 最新版 リファレンスマニュアル > Ruby用語集
参照: #コンテキスト/クロージャの詳細
- Rubyは基本的にレキシカルスコープ(静的スコープ)を採用しており、動的スコープ(ダイナミックスコープ)は採用していない。
- レキシカルスコープにはいくつかの種類があり、帰属するオブジェクトによって振る舞いが変化する。
- Rubyのprivate、protectedはスコープ(可視性)ではなく、呼び出し制限(call limit)であるためここには載せていない。
| 項目 | スコープ | 帰属 | 備考 |
|---|---|---|---|
| グローバル変数 | グローバルスコープ またはスコープなし | プログラム全体 | どこからでもアクセス可能 |
| 擬似変数(self) | コンテキストで変化 | コンテキスト | 現在のコンテキストの主体をあらわす疑似変数 コンテキスト内のどこからでもアクセス可能 |
| クラス変数 | クラススコープ | クラス/モジュール | クラスごとの変数 サブクラスと値を共有する |
| クラスインスタンス変数 | クラススコープ | クラス/モジュール | クラスごとの変数 サブクラスごとに値を持つ |
| インスタンス変数 | オブジェクトスコープ | インスタンス | インスタンスごとに値を持つ |
| ローカル変数 | レキシカル(厳密) | コンテキスト | 使い捨ての変数 メソッドの引数やブロックパラメータにも使われる ローカル変数は最も厳密な(狭い)レキシカルスコープを持つ |
| 定数 | 名前空間 + クラス | クラス/モジュール | 定数はレキシカル名前空間とクラス継承経路を併せたスコープを持つ(*1) |
| クラス名 | 名前空間 + クラス | クラス/モジュール | Rubyのクラス名は定数 |
| メソッド | メソッドスコープ | クラス/モジュール | 特異メソッドとクラス継承経路を併せた参照経路を持つ |
| クラスメソッド | メソッドスコープ | クラス/モジュール | クラスの特異メソッドとして定義される |
| ブロック | ブロックスコープ | selfコンテキスト | ブロックは外側のローカル変数を保持するクロージャとして働く |
- まず最初に覚えておきたいのは、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_methods | a.singleton_class.instance_methods | 特異メソッドは特異クラスに直接定義されたメソッド |
| 特異メソッドの取得 | a.singleton_methods | 割愛 | |
| クラス定義 | class MyClass | class << MyClass | 特異クラスはクラスオブジェクトに対して定義される |
| オブジェクトへのクラス定義 | 不可 | class << a | 特異クラスはオブジェクトに対して定義される |
| メソッド定義 | def method_name | def 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?はそのオブジェクトが実際にそのメソッドを呼べるかどうかを判定する。
- 定義は表下。
| MyClass::call_method | reult | note | getMethod |
|---|---|---|---|
| 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_methods | Object#method(name) -> Method |
| class.protected_instance_methods | [] | Class.protected_instance_methods | Object#method(name) -> Method |
| class.private_instance_methods | [:inherited, :initialize, :initialize_copy] | Class.private_instance_methods | class.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になる点に注意。
- モジュールを他のクラスやモジュールにMix-inして使う事ができる。
- Module#include(mod) modを現在のクラスorモジュールに対してMix-inする。
- modは現在のクラスとスーパークラスの継承関係の間に差し込まれる。つまりmodを継承したかのように振舞う。(KernelやEnumerableが良い例)
- modのメソッドや定数、インスタンス変数は、現在のクラスorモジュールから参照できる。インスタンス変数のアクセサも有効。
- modの特異メソッドは、現在のクラスorモジュールからは呼び出せない。つまりmodの特異クラスはincludeでは継承されない。
- modで定義されているクラス変数(モジュール変数)は、現在のクラスorモジュールと値を共有する。
- modは現在のクラスとスーパークラスの継承関係の間に差し込まれる。つまりmodを継承したかのように振舞う。(KernelやEnumerableが良い例)
- 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
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?と等価である事がわかる
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]
# 定義 参照/継承されないものは省略
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オブジェクトとして取り出せる。
- クロージャ(ブロック)はローカル変数だけでなくレキシカルスコープそのものを保持する。
- 具体的に言うとレキシカルスコープが優先される定数への参照はローカル変数と同様、ブロックの外のレキシカルスコープに準ずる。これは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
- 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
- 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の混乱しない考え方
- 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の整数は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秒
- 100万回のインクリメント所要時間
- Bignumは数値が小さくなると自動的にメモリも縮小される。30ビット以下になった場合はFixnumに自動的に変換される。
- Bignumは数値に合わせて自動的にメモリが拡張される。21億なら48+4バイト、2^1024なら48+132バイトになる。(後述)
- 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バイトに拡張される。
- 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で始めてはいけない。
- 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' を返す 戻す時は逆順に変換するだけでいい
- クラス内で定義された定数はクラス内のどこからでも参照できる。
- 定数は継承されるので、親子関係の参照経路を持つ。
- 定数は外部クラスへのレキシカル参照経路を持つ。これは親子関係よりも優先される。
- クラス内からの同名定数の参照経路は、自身の定数→外部クラスの定数を遡る→親クラスの定数を遡る→グローバル名前空間の定数の順。
- 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(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 フォーマット
# 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クラスの範囲判定は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文は判定に === が用いられる。
- === は様々なクラスで独自の判定に再定義されているため、非常に自由な文法で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!など、コレクション自身を書き換える事を目的としたイテレータ。
# 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実装クラス
# 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
# 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
# Enumerableのイテレータは全て使える
# 繰り返し
each {|value| ...} -> self # 要素を順番に評価する パラメータには値が渡される
each_pair {|member, value| ...} -> self # 要素を順番に評価する パラメータにはメンバ名と値が渡される
# Structのイテレータでメンバ名もセットで渡されるのは each_pair のみであり、他は全て値のみが渡される
# 恐らく構造体オブジェクトの中身は値のみの配列であり、メンバ名の情報は構造体クラスオブジェクトが持っているのだと思われる
# 基本的に構造体オブジェクトに対してイテレータを回す事はほぼない(構造体の配列に対するイテレータの方が圧倒的に多い)
参照: Ruby 1.9.3 リファレンスマニュアル > class Struct参照: #拡張イテレータ例
# パイプ(tapはイテレータではなく単なるパイプとして働く)
tap {|x| ...} -> self # selfをパラメータとして渡し、selfを返す チェインメソッドに"入り込む"ためのメソッド
参照: Ruby 1.9.3 リファレンスマニュアル > class Object#tap- 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状態や特異クラスを複製しない
- Array, Dir, Enumerator, Hash, IO, File, Range, Struct
- Enumerableは繰り返し処理を定義するMix-inモジュール。eachが実装されているクラスに組み込み可能。
- この中でVXAceヘルプに記載があるのはArray, Hash, IO, File, Rangeのみ。
- Enumeratorはヘルプには載ってないが使用可能。
- Structもヘルプには載ってないが普通に使える。
- Numeric, String, Symbol, Time
- Comparableは大小比較に使われるMix-inモジュール。<=>メソッドが実装されているクラスに実装可能。
- 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以外はエラーする
- Arrayクラス
- self[-n] nが負の値の場合、末尾からのインデックスとみなす。具体的には self[self.size-n] と同義になる。
- insert(n, var) pushと異なり配列の途中に要素を追加する。n番目の直前に差し込まれ、元々あった要素は後ろにずれる。
- nがマイナスなら後ろからn番目の直後に差し込まれる。-1なら末尾、-2なら末尾の直前(末尾から2番目の直後)。
- 理由は知らないがタイトル画面やメニュー画面などのコマンドリストに独自コマンドを追加するような配布スクリプトは基本pushで書かれており、例えばタイトルコマンドならシャットダウンの下に独自コマンドが追加される物が多い。
- insert(-2, var) とすれば最後の要素(タイトルならシャットダウン)の上(下から2番目の直後)に差し込まれる。
- 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が変化
- 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オブジェクト)が生成される。
- このオブジェクト生成がわずかなオーバーヘッドとなって速度に影響する。
# 引数として渡されたブロックの特性
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クラスは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の場合に真となる。流れは以下を参照。
- 左辺( i == 3 )が真になるまでは偽を返す。
- 左辺( i == 3 )が真を返した後は右辺( i == 6 )が真を返すまで真を返す。
- 右辺( 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オブジェクトは宣言方法によって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はそれ単体で定義文。これを留意するだけで混乱は無くなる。
# 名前空間を省略したいなら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)
- 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
- 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
- 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
# 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- Rubyは self[bit] でビットの値を取得できるが、そのままでは判定には使えない。
- bit?(b) メソッドを追加してビット b が 1 なら true を返す。
class Integer
def bit?(b)
self[b] == 1
end
end
- Arrayには ^(XOR演算子)は実装されていない。需要は少ないと思うが必要になった時のために実装の一例を載せておく。
class Array
def ^(array)
(self - array) | (array - self)
end
end
- 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が空になったら終わる こっちの方がシンプルではある
- 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...
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
- 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
- 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
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}
- 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自体に定義する事でサブクラス全てで使えるようにする
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 は自身のサブクラス一覧を返す。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 #=> [] 無名クラスは対象外
- リテラルのうち、一部は評価されるたびにオブジェクトを生成する。このようなリテラルを多用するとメモリ効率が悪くなる。
- 評価はコードが実行されるたびに行われる。
| リテラルの例 | リテラルの種類 | オブジェクトの生成 | Class | メモリ |
|---|---|---|---|---|
| 123 | 数値リテラル(Fixnum) | 数値ごとに一意 同じ数値なら常に同じオブジェクトを返す | Fixnum(*2) | value |
| 123_0000_0000 | 数値リテラル(Bignum) | 評価されるたびに新しいオブジェクトを生成する | Bignum | pointer |
| 123.0 | 数値リテラル(Float) | 評価されるたびに新しいオブジェクトを生成する | Float | pointer |
| "abc" | 文字列リテラル | 評価されるたびに新しいオブジェクトを生成する | String | pointer |
| /abc/ | 正規表現リテラル | 評価ごとに一意 同じ正規表現なら常に同じオブジェクトを返す ただし同じ正規表現でも記述位置が異なる場合には別のオブジェクトとして扱われる | Regexp | pointer |
| /#{v}/ | 正規表現リテラル(式展開含む) | 評価されるたびに新しいオブジェクトを生成する | Regexp | pointer |
| /#{v}/o | 正規表現リテラル(式展開 + oオプション) | 一度だけ評価され(*1)、同じオブジェクトを返す | Regexp | pointer |
| [1, 2, 3] | 配列式 | 評価されるたびに新しいオブジェクトを生成する | Array | pointer |
| {:a => 1, :b => 2} | ハッシュ式 | 評価されるたびに新しいオブジェクトを生成する | Hash | pointer |
| 1..20 | 範囲式(両端がFixnum) | 評価ごとに一意 同じ範囲なら常に同じオブジェクトを返す ただし同じ範囲でも記述位置が異なる場合には別のオブジェクトとして扱われる | Range | pointer |
| "a".."z" | 範囲式(Fixnum以外) | 評価されるたびに新しいオブジェクトを生成する | Range | pointer |
| :symbol | シンボル | 名前ごとに一意 同じ名前なら常に同じオブジェクトを返す ただし新しい名前はシンボルテーブルに追加され、GC対象にならない(1.9) | Symbol(*2) | value |
| true | true | 常に一意なTrueClassオブジェクトを返す | TrueClass(*2) | value |
| false | false | 常に一意なFalseClassオブジェクトを返す | FalseClass(*2) | value |
| nil | nil | 常に一意なNilClassオブジェクトを返す | NilClass(*2) | value |
(*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は実際にプログラム内で定義されているシンボルが全て含まれる。ここに含まれる情報は以下の通り。
- 全ての定数名(クラス名、モジュール名も含む)
- 全てのメソッド名(aliasメソッドや、アクセサによるメソッドも含む)
- 全てのグローバル変数名
- 全てのクラス変数名
- 全てのインスタンス変数名(クラスインスタンス変数含む)
- 全てのローカル変数名(引数名やブロックパラメータも含む)
- 一部の擬似変数名や予約語(selfやbeginなど)
- Ruby内部のC実装で使用される特殊シンボル
- 全てのシンボルリテラル(Structのメンバ名なども含む)
- 動的に参照されるシンボル名(動的にシンボルに変換された文字列なども含む)
- RGSS3環境だと軽く2000は超える。
- RGSS3環境ではシンボル情報は使われなくなってもメモリに残り続け、GC対象とならない。そのため特に動的なシンボル生成(文字列からの変換など)は
# プログラム内で使われている全シンボルリストをファイルに書き出すコード
open('symbol.log', 'w'){|f| f.puts Symbol.all_symbols.sort }
- +で連結すると新しいオブジェクトが作られる。<<で連結すると左辺を拡張してメモリをそのまま利用する。
- 当然 + に比べて << の方が処理速度、メモリ効率共に効率的。
- <<は破壊的なので、左辺文字列を再利用する必要がある場合には使えないので注意。
- 例えば数字の配列に対して、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オブジェクトを生成する。
- 下記の例から以下の運用方法が適する事がわかる。
- スタック(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よりも更に遅い