忍者ブログ

ぢみへんプログラミング日誌

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

Rubyでメソッドの定義してあるファイルを知る方法

Ruby-talk ML で便利な技を教えてもらったので忘れる前に残しておく。

参考書を読んだり、ソースコードを読んでいると、コード内で呼び出されている既存メソッドが元々どこのクラス/モジュールに属しているのか知りたいときがある。そのメソッドの詳細を求めてライブラリドキュメントを読む目的であることが多い。

自クラスの先祖(スーパークラスおよびinclude済みのモジュール)は、以下のように求めることができる。

#インクルード済モジュール名の配列を得る
modules = self.class.included_modules

#全スーパークラス及びinclude済モジュール名の配列を得る
ancestors = self.ancestors

#全祖先の集合からモジュール名の集合を引くとクラス名だけの集合になる
super_classes = ancestors - modules

eval( "#{super_classes}.class.public_instance_methods") #publicメソッド名配列取得
eval( "#{super_classes}.class.public_instance_methods") #protectedメソッド名配列取得
eval( "#{super_classes}.class.public_instance_methods") #privateメソッド名配列取得


これで理屈上、全ての親クラス(スーパークラス)とinclude済みモジュールのメソッド名を把握できると思ったのだが、少し甘かった。

どういうことかというと。

クラスの場合はRubyは単一継承しか認めないためこの方法で問題はない。しかしモジュールの場合、モジュール中に別のモジュールがincludeされてしまうと、その別モジュールに関して上記方法では追跡できないという問題が起きてしまう。

この問題についてRuby-talk ML で質問したところ、知りたいメソッド名が分かっているならもっと簡単な方法があるとの教えを受けた。以下がそれである。

source_file_name, line = *(method( :method_name).source_location)


selfの継承するクラスもしくはモジュールのメソッド名を引数に、Objectクラスのインスタンスメソッドである'method'を呼び出すと、Methodクラスのインスタンスが手に入る。そのインスタンスメソッド'source_location'を使うとメソッドの定義してあるファイル名と、定義箇所の行数で構成した配列を入手できるのだ。これは便利! ありがとうDavis!

注:ruby で定義されていない(つまりネイティブ である)場合は nil であることに注意。
例えば method(:p).source_location とやっても、pはネイティブ(=Ruby本体に組み込まれている)のでソースファイルは存在しない。
PR

Ruby on Rails Windows版インストール記録

0.pik環境削除
pik環境とRailsの環境を併用できるのか確信がないため、一旦pikをアンインストールする。
アンインストールはコントロールパネルの「プログラムのアンインストール」メニューから実行できる。


1. Rubyインストール+gemパッケージとrakeコマンドのアップデート
RubyInstaller for Windowsのサイト(http://rubyinstaller.org)からインストーラーをダウンロード後、インストール実行でOK。

Rubyのバージョンは2012/10/11時点のものだが、http://www.railsinstaller.org/ で確認してみて、大きく変わらなければ問題なさそう(推測)

その後、以下のコマンドでgemパッケージとrakeコマンドを最新にする。

> gem update --system
> gem update rake



2.Webkitインストールと一部のgemの事前インストール
jsonに関連するgemモジュールの構築を事前にwebkitで行っておく必要があるらしいので、まずwebkitをインストールする。
http://rubyinstaller.org/downloads/ にアクセスし、「Development Kit」と書かれた項目からEXEファイルをダウンロードする。(2012/10/11時点ではファイル名が「DevKit-tdm-32-4.5.2-20111229-1559-sfx.exe」)

適当に c:\RubyWebkit というディレクトリを作成し、そこに解凍したwebkitの内容物を配置した後、以下のコマンドを続ける。

c:\RubyWebkit>ruby dk.rb init
[INFO] found RubyInstaller v1.9.3 at C:/Ruby193

Initialization complete! Please review and modify the auto-generated
'config.yml' file to ensure it contains the root directories to all
of the installed Rubies you want enhanced by the DevKit.

c:\RubyWebkit>ruby dk.rb install
[INFO] Updating convenience notice gem override for 'C:/Ruby193'
[INFO] Installing 'C:/Ruby193/lib/ruby/site_ruby/devkit.rb'

c:\RubyWebkit>devkitvars

c:\RubyWebkit>gem install json --no-ri --no-rdoc
....
....
Successfully installed json-X.X.X (Xは数字)



3.SQLite3のインストール
http://www.sqlite.org/download.html より「Precompiled Binaries for Windows」と書かれた項目からdllとコマンドラインシェルを入手する。2012/10/11時点での該当ファイルは以下の通り。

  • sqlite-shell-win32-x86-3071401.zip

  • sqlite-dll-win32-x86-3071401.zip


それぞれの圧縮ファイルを解凍すると、実行形式ファイル(exe)とdllファイル(defファイル付属)が得られるので、それをそのままインストール済みRuby環境(c:\Ruby193\bin)に配置する。このとき配置した場所に環境設定でPATHが通っている必要がある。

以下の通りバージョン確認をして動作すればOK。

>sqlite3 -version
3.7.14.1 2012-10-04 19:37:12 091570e46d04e84b67228e0bdbcd6e1fb60c6bdb


この後、RubyからSQLiteにアクセスするためのドライバもインストールする。

>gem install sqlite3



4.Railsインストール
「gem install rails」とコマンドを打ち込めばOK。railsのバージョン指定やRdoc等の文書インストールの有無も選択するオプションも存在する模様。
railsのインストール場所は「gem which rails」で確認可能。

なお、「gem install rails」の実行中に利用可能なgemパッケージが古い、もしくは合っていないと英語でエラーが出てくる場合があるが、その際はバージョン番号付でインストールすべきパッケージの名称を表示してくれるので、それに従って「gem install xxxx(バージョン含めたパッケージの名前)」とすること。


5.動作確認
「rails -v」と入力して「Rails 3.2.8」のように表示されていればインストールはOK。


課題

pik環境とRailsの併用

複数のruby環境と複数のRails環境が組み合わさることが容易に予測されるため、pikとの併用方法および特定のRailsだけを使う方法を探し出しておくことが必要。

Sqlite以外のDBを使う方法

Sqlite以外のDBを使うことが仕事では多いので当然そのあたりのことをDBのインストールも含めてまとめておく必要がありそう。



参考URL
http://www.ruby.or.jp/ja/tech/install/ruby/install_win.html
http://www.ruby.or.jp/ja/tech/install/web_application/install.html
http://www.oiax.jp/rails/zakkan/rails_3_1_installation_on_windows.html
参考書籍
Ruby on Rails 3 アプリケーションプログラミング 山田祥寛

rubyのprivateとprotectedの違いは分かりにくい

C++やJava、あるいはPHPといった言語から見ると、Rubyのprivate/protectedの考え方は少し違うので分かりにくい。

まずRubyの場合、スーパークラス(基底クラス)privateメソッドをサブクラス(派生クラス)のメソッド内から呼び出すことができる。この時点でC++といった言語とは違う。C++では基底クラスのprivateメンバ/メンバ関数を派生クラス側で呼び出すことはできない。(はず)
このためこの特徴からだけではRubyのprivate/protectedメソッドのアクセス制限の違いが判断できない。

次に同じクラスのインスタンスを引数に取るメソッド内で、selfとは異なるインスタンスではあるが、同じクラスから生成されたオブジェクトのメソッド呼び出しについてはどうか。
結論から言うと、ここの部分でRubyのprivate/protectedの違いがある。他インスタンスのprivateメソッドはインスタンス外部から呼び出すことができないのに対し、他インスタンスのprotectedメソッドは呼び出しが可能となる。

以下はこの動作を確認するコード。

class A
private
def pm
puts "invoked method pm(private in class A)"
end

protected
def prom
puts "invoked method prom(protected in class A)"
end

def use_pm1
puts "invoked method use_pm1(protected in class A)"
end

public
def pubm
puts "invoked method pubm(public in class A)"
end

def use_pm2
pm
puts "invoked method use_pm2(public in class A)"
end

def use_prom
self.prom
self.use_pm1
puts "invoked method use_prom(public in class A)"
end
end


class B < A
def use_pm_b
pm
puts "invoked method use_pm_b(public in class B)"
end

def use_prom_b
prom
use_pm1
puts "invoked method use_prom_b(public in class B)"
end

def show_other(other)

# other.pm #can't invoke other object's private method
other.prom
other.pubm
puts "invoked method show_other(public in class B)"
end
end
#--------------------------------------------

a = A.new

#a.pm # => NoMethod Error
#a.prom # => NoMethod Error
#a.use_pm1 # => NoMethod Error
a.pubm
a.use_pm2
a.use_prom

b = B.new
b.use_pm_b
b.use_prom_b

b2 = B.new
b2.show_other(b)


【実行結果】
invoked method pubm(public in class A)
invoked method pm(private in class A)
invoked method use_pm2(public in class A)
invoked method prom(protected in class A)
invoked method use_pm1(protected in class A)
invoked method use_prom(public in class A)
invoked method pm(private in class A)
invoked method use_pm_b(public in class B)
invoked method prom(protected in class A)
invoked method use_pm1(protected in class A)
invoked method use_prom_b(public in class B)
invoked method prom(protected in class A)
invoked method pubm(public in class A)
invoked method show_other(public in class B)

紛らわしいRubyの変数スコープ

Ruby1.8.7ではコードブロックとラムダの引数と変数の扱いが紛らわしい。
ブロック(もしくはラムダ)の引数、あるいはブロック内(またはラムダ内)の変数が、そのブロック(もしくはラムダ)外の変数と同名である場合は、外にある変数が使用される。

以下実例。

C:\Users\admin>irb
irb(main):001:0> a = 1
=> 1
irb(main):002:0> def fnc1
irb(main):003:1> a = 2
irb(main):004:1> end
=> nil
irb(main):005:0> fnc1
=> 2
irb(main):006:0> a
=> 1
irb(main):007:0> def fnc2(a) a = 3 end
=> nil
irb(main):008:0> fnc2(4)
=> 3
irb(main):009:0> a
=> 1
irb(main):010:0> p1 = lambda{|x| a += 1}
=> #
irb(main):011:0> p1.call(5)
=> 2
irb(main):012:0> a
=> 2
irb(main):013:0> p2 = lambda{|a| a+= 2}
=> #
irb(main):014:0> p2.call(5)
=> 7
irb(main):015:0> a
=> 7
irb(main):016:0> (1..10).each{|i| a += i}
=> 1..10
irb(main):017:0> a
=> 62
irb(main):018:0> (1..10).each{|a| a += 1}
=> 1..10
irb(main):019:0> a
=> 11


見ての通り、メソッドの引数やメソッド内の変数に関しては、メソッド外に同名の変数があっても全く互いに影響を及ぼしあわない。
ところがブロックやラムダの場合、外部変数のスコープがブロック(ラムダ)内のスコープに浸食してしまう。特に驚くのは引数の名前を外部変数と同じにした場合で、このときも引数が独自のローカル変数ではなく、外部変数の参照を意味するので、C/C++やPHPあたりに慣れた頭にはびっくりである。

引数名の扱いに関しては1.9系で改善された。

irb(main):001:0> a = 1
=> 1
irb(main):002:0> p1 = lambda{|x| a += 1}
=> #
irb(main):003:0> p1.call(5)
=> 2
irb(main):004:0> a
=> 2
irb(main):005:0> p2 = lambda{|a| a += 2}
=> #
irb(main):006:0> p2.call(5)
=> 7
irb(main):007:0> a
=> 2
irb(main):008:0> (1..10).each{|i| a += i}
=> 1..10
irb(main):009:0> a
=> 57
irb(main):010:0> (1..10).each{|a| a += 1}
=> 1..10
irb(main):011:0> a
=> 57


見ての通り、引数に外部変数と同名の変数を割り当てた場合は、その変数はブロック(もしくはラムダ)内で完全にローカル変数としてのスコープを持っていて、外部とは遮断されていることが分かる。

はじめてのrubyプログラム

オライリーの「はじめてのruby」と「プログラミング言語 ruby」を読みながら、とりあえずrubyで初めてのプログラムを書いてみた。

Arrayクラスを拡張して、二分探索メソッド(binary_search)を追加。

まぁ……そんな大した実装ではないんだけどね 笑




# MyArray.rb
#
class Array

private
def _bsearch(val, begin_idx, end_idx)

center_idx = begin_idx + ((end_idx + 1) - begin_idx) / 2

element_val = self[center_idx]

ret_index = if val == element_val then
center_idx
elsif begin_idx == end_idx then
nil
elsif val < element_val then
_bsearch(val, begin_idx, center_idx - 1)
else
_bsearch(val, center_idx + 1, end_idx)
end
ret_index
end

public
def binary_search(val)
_bsearch(val, 0, self.length - 1)
end
end

# test

#↓の配列は 20.times.map{|i| srand(i); rand(100)} をirb上で実行して入手
arr = [44, 37, 40, 24, 46, 99, 10, 47, 67, 92, 9, 25, 75, 82, 88, 72, 41, 15, 42, 93]
arr.sort!
p arr

index = arr.binary_search( 67 )
puts "index of '67' is #{index}"


実行結果:

C:\Users\admin\rb_workspace>ruby MyArray.rb
[9, 10, 15, 24, 25, 37, 40, 41, 42, 44, 46, 47, 67, 72, 75, 82, 88, 92, 93, 99]
index of '67' is 12

この2分探索メソッド、普通にArray#eachメソッド(先頭からシーケンシャルに配列アクセスして要素を調べる方法)でデータを探すのと比べた場合、少量の要素の配列では差が出ない。
そこでランダム数値の要素を100万個まで増やしてみたら、ほぼ平均して二分探索の方が速いことが分かった。

これは上記のクラス定義をそのままにしてテストコードを以下のように変えて試してみると良くわかる。

arr = 1000000.times.map{|i| srand(i); rand(9999999)}
arr << 1000000
arr.compact!
arr.sort!
puts("size of array is #{arr.length}")
puts(Time.now.to_i)
index = arr.binary_search( 1000000 )
puts "index of '1000000' is #{index}"
puts(Time.now.to_i)

puts(Time.now.to_i)
arr.each_with_index do |v, i|
if v == (1000000) then
puts "index of '1000000' is #{i}"
puts(Time.now.to_i)
break;
end
end


上記コードの「1000000」となっている部分は検索対象の配列要素の値で、これが大きい値ほど、検索する配列の後方に配置される仕掛けになっている。
それでこの値を「2000000」としたり、「5000000」、「9999999」としてみると、筆者の環境では要素の値が「1000000」、配列上のインデックスが99933のとき、両者の速度はほぼ一致した。従って配列上のインデックスがそれよりも前の場合、両者の速度はほとんど遜色ないと思われる。

逆に値が「2000000」、先頭からのインデックスが200046の要素を検索したときには既に1秒近くの差が開き始めており、その差は配列の後半にある要素を検索すればするほど大きくなっていった。つまり、約7割から8割の要素の検索において二分探索アルゴリズムの方が高速に動作したと結論づけられる。

この例はアルゴリズムの理論の確認には役にたつかもしれないが、極端に大きな配列でないとこの効果を確かめることはできないので、実用上ほとんどの場合は無視しても構わないだろう。大体はArray#eachで事足りると推測できそうだ。


それにしても組込データ型ですら実行時に拡張できるとは恐れ入った。(恐らく発想自体はLispあたりから受け継いだのではないかと思うのだけれども定かではない)。これはC/C++にはない発想だし、Javaにもないんじゃないの?
  

カレンダー

04 2024/05 06
S M T W T F S
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

フリーエリア

最新CM

バーコード

ブログ内検索

Copyright ©  -- ぢみへんプログラミング日誌 --  All Rights Reserved

Design by CriCri / Material by petit sozai emi / powered by NINJA TOOLS / 忍者ブログ / [PR]