26
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rubyの勉強。Part1 yield、Procとlambdaの関係について。

Last updated at Posted at 2014-02-24

自作のRubyのコードを利用して勉強します。

タイトルの通りPart続きですのでコードを少しずつ修正、改良して
Rubyの動作を見ていくつもりです。
この記事の対象者は初級プログラマやRuby初心者程度です。
一緒に理解していければ嬉しいです。

まずはコードを見てみましょう。
https://gist.github.com/hiroshiro/8962833/120929e2f6c6277847531bbfe1ea51431084f883
動作
URLにあるページ上のjpg画像をファイルに保存します。

getHttpJpg.rb
##
# 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_fileyieldに変えるだけです簡単ですね。
これで依存はなくなりました。
しかし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とは全然違います。

試しにyieldreturnにしてみるとわかります。その動作だと
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.newprocと同様

##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の大きさを計算するプログラムとかで活躍するかもしれません。
それは機会があればやってみたいです。

ということで今回はここまで
次回はクラス、モジュール、例外を勉強します。

26
25
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?