自作のRubyのコードを利用して勉強します。
タイトルの通りPart続きですのでコードを少しずつ修正、改良して
Rubyの動作を見ていくつもりです。
この記事の対象者は初級プログラマやRuby初心者程度です。
一緒に理解していければ嬉しいです。
まずはコードを見てみましょう。
https://gist.github.com/hiroshiro/8962833/120929e2f6c6277847531bbfe1ea51431084f883
動作
URLにあるページ上のjpg画像をファイルに保存します。
##
# getHttpJpg.rb
#
# 使い方
#
# 例
# ruby getHttpJpg.rb http://himasoku.com/archives/51824048.html
# JPGフォルダにjpgがダウンロードされる。
#
##
##
# hpricotを使う。
# gem install hpricot
##
require 'hpricot'
require 'open-uri'
##
# 引数をurlにする。
##
$url = ARGV[0]
doc = Hpricot( open($url).read)
##
# 空の保存ファイルを作る。
##
Dir.mkdir("./JPG") unless Dir.exist?("./JPG")
##
# カレントディレクトリをJPGファイルにする。
##
Dir.chdir("./JPG")
##
# ファイルをセーブする。
##
def save_file(url)
filename = File.basename(url)
open(filename, 'wb') do |file|
open(url) do |data|
file.write(data.read)
end
end
end
##
# jpgファイルがあればファイルをセーブ
##
def extract_jpg(doc)
(doc/:img).each do |link|
save_file(link[:src].to_s) if link[:src] =~ /.jpg/
end
end
extract_jpg(doc)
経験を積んでいるプログラマの方は
コードの書き方にとてもこだわりがあると思います。
私はRubyのコードの書き方についてまだ良くわかっていません。
コメントの文章一つ見ても良い文章には見えません。
なのでまた別のPartで直しながら勉強していきます。
ここではこの2つのメソッドに注目します。
##
# ファイルをセーブする。
##
def save_file(url)
filename = File.basename(url)
open(filename, 'wb') do |file|
open(url) do |data|
file.write(data.read)
end
end
end
##
# jpgファイルがあればファイルをセーブ
##
def extract_jpg(doc)
(doc/:img).each do |link|
save_file(link[:src].to_s) if link[:src] =~ /.jpg/
end
end
この2つのメソッドは動作には何も問題ありません。
しかし、extract_jpg
メソッドの方がsave_file
メソッドに依存しています。
当前ですが、もしsave_file
メソッドがなければextract_jpg
メソッドは使えません。
そもそもextract_jpgというメソッド名は間違っています。
保存しているのでメソッド名にsaveを入れるべきです。(メソッド名についてはまた別のPartで)
ここでタイトルの通りyield
を使ってextract_jpg
からURLを渡すコードに
変えてみましょう。
##
# jpgファイルがあれば, URLを渡す。
##
def extract_jpg(doc)
(doc/:img).each do |link|
yield link[:src].to_s if link[:src] =~ /.jpg/
end
end
save_file
をyield
に変えるだけです簡単ですね。
これで依存はなくなりました。
しかしURLを渡すだけなので、渡して保存するメソッドを作らなくてはいけません。
##
# URLにあるjpgをセーブする
##
def save_url_files(doc)
extract_jpg(doc) {|x| save_file(x)}
end
save_url_files(doc)
これで動作します。コードはこちら
https://gist.github.com/hiroshiro/8962833/b9847db29664a820b0fb3be2ab14f0847c875ff9
##yieldの動作
yield
はブロックを作り
method do
yield
#ここにある要素を(順に)取り出せる様になる。ブロックを作る。
end
そのメソッド{ブロック}から要素を取り出しができる
method
{|要素| → 取り出す}
といったイメージでしょうか。ここではextract_jpg
メソッドから
要素を変数xにして順にURLを取り出しています。
##yieldとreturn
yieldはreturnとは全然違います。
試しにyield
をreturn
にしてみるとわかります。その動作だと
extract_jpg
メソッドのlink
のイテレーターから最初のURLだけを返します。
そしてメソッドを終了してしまいます。
returnの場合
method do
return
#ここにある要素を一つ返します。メソッドもすぐに終了。
end
当然ながら、ブロックを作りません。
変数 ⇚
method
変数に返す。
returnを使う場合ではメソッド内で文字列の配列などの塊をそのまま帰す方法になります。
##
# jpgファイルの, URLの配列を返す。
##
def extract_jpg(doc)
urls = []
(doc/:img).each do |link|
urls = link[:src].to_s if link[:src] =~ /.jpg/
end
urls #returnの記述は省略できる。
end
extract_jpg.each do |url|
save_file(url)
end
私は恥ずかしながら、yield
の存在を知らなかった時にこういう方法を取っていました。
##ブロック引数&とProcオブジェクトの関係。
ブロック引数&を手前に付けることで明示的にブロックを作ることができます。
##
# jpgファイルがあれば, URLを渡す。
##
def extract_jpg(doc,&block)
(doc/:img).each do |link|
block.call link[:src].to_s if link[:src] =~ /.jpg/
end
end
上記では&block引数を作り、blockが要素を呼び出せる様にしている。
&引数は最後の引数(一番右側)しなければならない。
これは先ほどのyieldと同じ動作をしています。
method do &ブロック
ブロック.call
##ここにある要素を(順に)取り出せる様になる。yieldと同様。
end
このblock
は実はProcオブジェクトです。
つまり、&
を付けない場合は下記と同じ動作です。
def extract_jpg(doc,block = Proc.new)
&b
はb = Proc.new
と同様。
それか直接call
もできる。これも同じ動作。
Proc.new.call link[:src].to_s if link[:src] =~ /.jpg/
#Proc.newはグローバル変数procなので実はnewする必要はない。
proc.call link[:src].to_s if link[:src] =~ /.jpg/
ruby1.9以上はグローバル変数procを使っても良い。Proc.new
はproc
と同様
##procとlambdaの関係
procとlambdaは似ていますが違います。
rubyの文法から見ると使い方が違うという感じでしょうか。
簡単にいうとprocはオブジェクトをnew
から作るclassObjectです。
http://www.ruby-doc.org/core-2.1.0/Proc.html
例
block = Proc.new {|x| puts x}
block.call("hoge")
=>hoge
対してlambdaはオブジェクトを作るメソッドのようなものです。
実際、lambdaはkernelモジュールのメソッドです。
http://www.ruby-doc.org/core-2.1.0/Kernel.html#method-i-lambda
例
block = lambda{|x| puts x}
#ruby1.8の場合
block = ->(x){ puts x}
#ruby1.9以上
block.call("hoge")
=>hoge
ではlambdaとprocのオブジェクトは同じなのでしょうか。
pryを使ってlambda?メソッドをテストしてみます。2.1.0はRubyのバージョンです
2.1.0 (main)> lambda {}.lambda?
=> true
2.1.0 (main)> proc {}.lambda?
=> false
上記を見ると2つは違うオブジェクトじゃないかと思ってしまいますね。
lambdaを実行するKernelのCソースを見ます。
下記ではlambdaはprocオブジェクトを作っているように見える。やはり同じprocオブジェクトなのか。
VALUE
rb_block_lambda(void)
{
return proc_new(rb_cProc, TRUE);
}
ではlambda?はprocとlamdbaをどうやって判定しているのでしょうか。
procを実行するKernelのCソースも見てみましょう。
VALUE
rb_block_proc(void)
{
return proc_new(rb_cProc, FALSE);
}
proc_new
の右の引数がFalse
ですね。
下記のProc.new
のソースもFalse
です。
static VALUE
rb_proc_s_new(int argc, VALUE *argv, VALUE klass)
{
VALUE block = proc_new(klass, FALSE);
rb_obj_call_init(block, argc, argv);
return block;
}
ついでにlambda?
メソッドのソースも見ておきます。
VALUE
rb_proc_lambda_p(VALUE procval)
{
rb_proc_t *proc;
GetProcPtr(procval, proc);
return proc->is_lambda ? Qtrue : Qfalse;
}
つまり
Rubyコード | C コード | |
---|---|---|
lambda | proc_new | TRUE |
proc | proc_new | FALSE |
Proc.new | proc_new | FALSE |
lambdaを実行するのみTRUEを渡しています。
##lambdaを使ってみる。
lambdaを使う場合はどういう時に使う時が最善なのでしょうか。
直感的には矢印をさして式を書き変換するというイメージでしょうか。
実際にコードを書いてみましょう。
https://gist.github.com/hiroshiro/8962833/9ace1980002cf5a72e37921633bb1479b6633032
##
# jpgがあればURLをセーブする。
##
def save_extract_jpg(doc)
(doc/:img).each do |link|
->(url){ save_file(url) }.call(link[:src].to_s) if link[:src] =~ /.jpg/
end
end
save_extract_jpg(doc)
save_extract_jpg
のイテレーターの内部でlambda->
を使って保存するようにしました。
しかしこれだとlambdaを使う意味がないような気がします。
lambdaは関数プログラミングを代表するメソッドなので
この場面で使うとしたら、jpgの大きさを計算するプログラムとかで活躍するかもしれません。
それは機会があればやってみたいです。
ということで今回はここまで
次回はクラス、モジュール、例外を勉強します。