LoginSignup
0
1

More than 5 years have passed since last update.

ruby: &とprocオブジェクト学習メモ

Posted at

Rubyで "&" を使うと幸せになれるらしいよ (*´Д`)ノ

という記事を読んでいても力不足のためかいまいちピンと来なかったので、自分なりに理解できるように挙動を書いていこうと思います。以下のコードは全て元記事から引用したものであって僕のオリジナルではありません。

ex.rb
#1
3.times do
  puts 'hogehoge'
end

proc = Proc.new { puts 'hogehoge' }

#2
3.times do
  proc.call
end

#3
3.times(&proc)

この辺の挙動は一目瞭然です。procオブジェクト内で定義した変数や機能などをあとで使いたいときに&procで呼び出せる。つまりコードの節約になるってこと、だと思います。

def sample_mb(&hoge)
  hoge.call "hoge", "fuga"
end

sample_mb {|a, b| p [a, b]} # ["hoge", "fuga"]

続いてこちら。予めsample_mbという関数を定義した後arg = {|a, b| p [a, b]}でその関数を呼んでいます。「ん?でもここで&hogeっていつどこで定義されてるの?」となってしまったので調べてみることに。

    202: def sample_mb(&hoge)
 => 203:   binding.pry
    204:   hoge.call "hoge", "huga"
    205: end

[1] pry(#<Module>)> hoge
=> #<Proc:0x007fff49471238@/Users/hironorisama/Projects/github/gakkimania/lib/tasks/migrate.rake:207>
[2] pry(#<Module>)> hoge.call "hoge"
["hoge", nil]
=> ["hoge", nil]
[3] pry(#<Module>)> hoge.call "hoge", "hogehoge", "hogehogehoge"
["hoge", "hogehoge"]
=> ["hoge", "hogehoge"]

sample_mbを呼んだ瞬間に[a,b]として配列が用意されるのかと思っていたらhoge自体はprocオブジェクトとして生成されるだけであってhoge.callで箱に中身が入って初めて配列となって姿を表した、という実感。中身の数が宣言した数より少ない場合はnilに多い場合はシカト。つまりエラーを出さないという面では使い方によっては良いことなのかもしれません。ちなみにこれはprocをインスタンスとして使うlambdaでは起きない挙動です。

proc   # returns '#<Proc:0x007f96b1032d30@(irb):75>'
lam    # returns '<Proc:0x007f96b1b41938@(irb):76 (lambda)>'
test.rb
lam = lambda { |x| puts x }    # creates a lambda that takes 1 argument
lam.call(2)                    # prints out 2
lam.call                       # ArgumentError: wrong number of arguments (0 for 1)
lam.call(1,2,3)                # ArgumentError: wrong number of arguments (3 for 1)

# In contrast, procs don’t care if they are passed the wrong number of arguments.

proc = Proc.new { |x| puts x } # creates a proc that takes 1 argument
proc.call(2)                   # prints out 2
proc.call                      # returns nil
proc.call(1,2,3)               # prints out 1 and forgets about the extra arguments

procオブジェクトについて

procオブジェクトはこちらこの辺の記事より簡単な定義を確認しました。

proc = Proc.new { puts "Hello world" }
proc.class # returns 'Proc'
proc.call "hogehoge"

ActiveRecordetcと同じくproc内にも独自に定義されたメソッドがありcallもその中の一つ。つまり配列に対してarray.callをすると

[8] pry(#<Module>)> def try_call_on_array()
[8] pry(#<Module>)*   array = []  
[8] pry(#<Module>)*   array.call "hoge"
[8] pry(#<Module>)*   array
[8] pry(#<Module>)* end  
=> :try_call_on_array
[9] pry(#<Module>)> try_call_on_array()
NoMethodError: undefined method `call' for []:Array

「そんなメソッド定義されてないよ!」と怒られてしまう。

他にも

[21] pry(#<Module>)> def proc_test
[21] pry(#<Module>)*   proc = Proc.new{ return }  
[21] pry(#<Module>)*   puts "fuga"  
[21] pry(#<Module>)*   proc.call
[21] pry(#<Module>)* end  
=> :proc_test
[22] pry(#<Module>)> proc_test
fuga
=> nil

[18] pry(#<Module>)> def proc_test
[18] pry(#<Module>)*   proc = Proc.new{ return }
[18] pry(#<Module>)*   puts "hello world"
[18] pry(#<Module>)*   lam.call
[18] pry(#<Module>)* end  
=> :proc_test
[19] pry(#<Module>)> lambda_test
hoge
=> nil

は同じ挙動を示すのに対し

[12] pry(#<Module>)> def lambda_test
[12] pry(#<Module>)*   lam = lambda { return }  
[12] pry(#<Module>)*   lam.call  
[12] pry(#<Module>)*   puts "Hello world"  
[12] pry(#<Module>)* end  
=> :lambda_test
[13] pry(#<Module>)> lambda_test
Hello world
=> nil
[14] pry(#<Module>)> def proc_test
[14] pry(#<Module>)*   proc = Proc.new { return }  
[14] pry(#<Module>)*   proc.call  
[14] pry(#<Module>)*   puts "Hello world"  
[14] pry(#<Module>)* end  
=> :proc_test
[15] pry(#<Module>)> 
[16] pry(#<Module>)> proc_test
=> nil

procreturn処理を行なってすぐcallを行った後にbreakしてしまうのに対し、lambdaはその後も最後の行まで処理を行ってくれます。lambdaprocの違いについてはこの記事も参考になります。

(時系列で書いているので)話がだいぶそれましたが、プラクティカルな面では以下のように使えると元記事がおっしゃっていました。

test.rb
require 'rubygems'
require 'active_support'

class Sample
  attr_accessor :id

  def initialize
    @id = Time.now.to_i
  end
end

data1 = Sample.new
data2 = Sample.new
data3 = Sample.new
@data = [data1, data2, data3]

p @data.map {|data| data.id} # [1264564410, 1264564410, 1264564410]
p @data.map(&:id)            # [1264564410, 1264564410, 1264564410]

投稿者曰く「["aaa", "bbbb", "cc"].map(&:length)["aaa", "bbbb", "cc"].map {|word| word.length}」は同じことを意味していると。明らかにスッキリしています。

0
1
0

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
0
1