LoginSignup
21
18

More than 1 year has passed since last update.

【未経験の登竜門!】RubyのブロックとyieldとcallとProcとラムダ式を超わかりやすく解説してみた(Rails6.0でもよく出てくるよ)

Last updated at Posted at 2021-06-07

はじめに

未経験エンジニアやエンジニア1年生のあなたは、「ブロック・yield・call・Proc・ラムダ式」を人に説明することができますか?
この辺りは避けて通ってきた人も多い知識だと思うので、不安な人はこの機会に全部まとめて学んじゃいましょう!!!
では超わかりやすい解説スタートです!

そもそもブロックとは

【記法】
do ... end, { } のどちらか。この記号と記号に囲まれた中身を含めてブロックという。

【ブロックの定義】

メソッド呼び出しの際に引数と一緒に渡すことのできる処理のかたまり | たのしいRuby P199

メソッド呼び出しの際というのがポイント。

てことで例えば、

exmaple
def greeting
  puts 'おはよー!'
end

# メソッドの呼び出し
greeting do
# 特に意味はないけど、メソッド呼び出し時に
# こうやってdo ... end で囲めばブロックを渡したことになる。
end

この場合の出力は、もちろん 'おはよー!' です。

ブロックの活用法

さっきのgreetingメソッドの例は全く意味のない使い方ですね。
まあ、ブロックはメソッドに渡す事ができるって理解できたらOKです!

さあ、ここからはブロックの有効的な活用法をご紹介しますが、みなさんがdo ... end を見て最初に思い浮かぶのは、eachじゃないでしょうか?
(私だけだったらすみません...)

未経験の方は、

「ブロックってメソッドの後につけるんだよね。そもそもeachってメソッドなの?」

こんな疑問の声が聞こえた気がしたので、一応説明しておくと、eachもメソッドです!

・「メソッドはクラスに定義されているもの」
・「(インスタンス)メソッドはオブジェクトに対して使用する」
っていう基本ルールはOKですかね?

実はeachメソッドは、Enumerable クラスに定義されているメソッドなのです!

Enumerable は、日本語で「列挙可能な」的な意味です。データが列挙されているデータ型をイメージしてみてください。
ご名答!
配列・ハッシュ・集合などが全てEnumerableクラスに属しています。(すぐ思い浮かんだ未経験の方はすごい!!!)

話がそれましたが、だからeachメソッドは、

eachメソッド
[1, 2, 3].each

って感じで配列の後ろに付けられるんですね!

メソッドの後ろにブロックをつけることができるので、

each do ... end
[1, 2, 3].each do |ele|
end

ってできるわけですね。
ここで、eachとブロックの役割について解説しておきます。

each ... 配列の要素を一つずつブロックの中に渡す。
ブロック ... 要素を受け取った後の処理内容。

ブロックの概要は伝わりましたでしょうか!

yield

次はyieldです!

yieldの役割は、「メソッドの呼び出しをブロック付きで行った際、メソッドの中のyieldでブロックの処理内容を実行」することです。

こちらの理解はなかなか難しいと思うので、またgreetingを使ったコードで説明します!
下のコードを見てください!

greetingメソッド
def greeting
  puts 'おはよー!'
  yield # greeting メソッドをブロック付きで呼び出した時にブロックの処理内容がここに入ります。
end

# greetingメソッドをブロック付きで呼び出す
greeting do
  puts 'こんばんはー!!'
end
出力
'おはよー!'
'こんばんはー!'

yieldの位置にブロック内の処理であるputs 'こんばんはー!!'が入ることで、出力に示したような結果になりました。

※ メソッド内にyieldを書いている時に、ブロックなしでメソッドを呼び出すとエラーが発生します。お気をつけください。

greetingメソッド
def greeting
  puts 'おはよー!'
  yield # greeting メソッドをブロック付きで呼び出した時にブロックの処理内容がここに入ります。
end

# greetingメソッドをブロックなしで呼び出す
greeting
出力
LocalJumpError: no block given (yield)

こういった場合は、block_given?メソッドを使います。このメソッドはブロックが渡されているときにtrueを返します。

greetingメソッド
def greeting
  puts 'おはよー!'
  if block_given? # ブロックが渡された場合にyieldを実行する
    yield
  end
end

# greetingメソッドをブロックなしで呼び出す
greeting
出力
'おはよー!'

ブロックを引数で渡す。Callでブロックを実行する。

ブロックを引数に渡したい場合は、以下の記法を使います。

ブロックを引数で渡す
def method(&ブロック)
  # ブロックを実行する
  ブロック.call
end

ブロックを渡してブロックの処理内容をcallで実行するってことですね!
これも例を見せておきます。

example.rb
def greeting(&b)
  puts 'おはよー!'
  text = b.call('こんばんはー') # ブロックの処理内容を実行
  puts text
end

# ブロックを引数に渡して実行
greeting do |text|
  puts "#{text}!!"
end
出力
おはよー!
こんばんはー!!

callでブロックを呼び出す際は引数も取ることができます。

yieldとcallの違いは何?

yieldもcallもブロックを呼び出すという点では同じです。
では何が違うのかというと、「callは引数から明示的にブロックを渡す事ができる」ということですね!

はて?って人はもう一度yieldとcallの説明を見直していただければ納得できると思います。

yieldではメソッドの定義の際、引数としてブロックを受け取りません。メソッド呼び出し時にブロックを渡していれば、yieldでブロック内の処理内容を実行します。

対してcallは、メソッドの定義の際、引数として「&ブロック」という表記でブロックを受け取ります。ブロックを渡している場合は、ブロック.callでブロック内の処理内容を実行します。

どちらも「ブロック内の処理内容を実行する」ということを理解しておいてください!

Proc

Rubyには、Procクラスというクラスが存在します。
Procクラスは、今までさんざん説明してきたブロックをオブジェクト化するためのクラスです。
Procクラスのオブジェクトを作成する時に、引数でブロックを渡します。

Procオブジェクト作成
greeting = Proc.new { 'おはよー!' }

このままgreetingを呼び出してもブロックオブジェクトを保存しているメモリアドレスを返すだけでブロック内の処理内容は実行されません。
(メモリアドレスの話も今度詳しく解説する予定なので、気になる方は僕のTwitterで最新情報をチェックしてみてください!)

ブロックの処理内容を実行するメソッド覚えてます?
そうです。callメソッドです!
なので、下みたいにすれば実行できます。

Procオブジェクト(ブロック)を実行
greeting = Proc.new { 'おはよー!' }
greeting.call #=> 'おはよー!'

もちろん引数も渡せます!

引数ありのProcオブジェクトを実行
greeting = Proc.new {|text1, text2| text1 + text2 }
greeting.call('おはよー!', 'からのこんばんはー!') #=> 'おはよー!からのこんばんはー!'

まとめると、Procオブジェクトはブロックそのもの。a = Proc.new {} した時のaはProcオブジェクトの保存先のメモリアドレスが入っているだけなので、実行する際は、a.callのように呼び出す。

Procオブジェクトを引数に渡す

ブロックを引数に渡す方法を思い出しましょう。

ブロックを引数に渡す
def greeting(&b)
  puts 'おはよー!'
  text = b.call('こんばんはー') # ブロックの処理内容を実行
  puts text
end

# ブロックを引数に渡して実行
greeting do |text|
  puts "#{text}!!"
end
出力
おはよー!
こんばんはー!!

ここで重要なポイントは、引数に渡しているものはブロックそのものであるということ。ブロックそのものを渡す際は、&(アンパサンド)が必須です。でなければ、Rubyはブロックを普通の引数として認識してしまいます。また、ブロックは一つのメソッドに一つまでしか渡せません

これらを解決するのがProcオブジェクト!
ブロックの代わりにProcオブジェクトを渡すことを考えてみましょう。
オブジェクトを渡すのであれば普通の引数で渡せますね!てことで&が必要なくなります。
また、一つのメソッドに一つまでの制限もなくなります。

では、実際にProcオブジェクトを渡した場合のコードを見てみましょう。

ブロックの代わりにProcオブジェクトを渡す
def greeting(b)
  puts 'おはよー!'
  text = b.call('こんばんはー!', 'せい!') # ブロックの処理内容を実行
  puts text
end

# ブロックを引数に渡して実行
greeting_proc = Proc.new {|text1, text2| puts text1 + text2 }

greeting(greeting_proc)
出力
おはよー!
こんばんはー!せい!

どうですか?Procオブジェクトがなんとなく理解できましたか?
まだクリアになっていない方はこの章を何度か読み直してみてください!

ラムダ式

最後の章です!
後少し頑張ってください!

ラムダ式はコンピュータサイエンスの基礎であり、JavaScriptなどでも登場する呼び出し可能オブジェクトです。
ラムダ式に関して説明しようと思うとそれだけで記事が書けてしまうので、ここではProcとの絡みにのみ着目してお話します。

ラムダ式とは、以下のような表記のことです!

ラムダ式
->(a, b){ a + b }
or
lambda {|a, b| a + b }

そしてこの表記で何ができるかというと、Procと同じくブロックのオブジェクトが作れます。
ここでProcの表記と比較してみます。
意味は一緒です!

Procとラムダの比較
block_obj = Proc.new {|a, b| a + b }

block_obj = ->(a, b){ a + b }

二つの挙動に細かい違いはありますが、まず使えるようになることが目的であればまずはこれでオッケーです。
では最後に例のごとくgreetingメソッドを使ってラムダ式を実践してみましょう!

greeting.rb
def greeting(b)
  puts 'おはよー!'
  text = b.call('こんばんはー')
  puts text
end

block_obj = ->(text){ puts "#{text}!!" }
greeting(block_obj)
出力
'おはよー!'
'こんばんはー!!'

まとめ

ブロックとyieldとcallとProcとラムダ式について学んできました。
この辺りは未経験にとっての登竜門で、避けて通ってきた未経験の方やエンジニア1年生の方も多いのではないでしょうか。
Railsなどのフレームワークを学ぶことはもちろん大事ですが、それと同じかそれ以上にコンピュータサイエンスの基礎や、言語の理解を深めることは大事だと思っています!
この記事が、皆様がそういった基礎概念に目を向けるきっかけになれば幸いです。

Twitterで「未経験の方・エンジニア1年目の方に向けて、エンジニア1年目の学びを発信」してますので、興味がある方は是非のぞいてみてください!
では長らくお付き合いいただきありがとうございました!
また、次の記事でお会いしましょう〜!👋

-END-

21
18
1

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
21
18