Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

【Ruby入門】ブロック・Proc・Lambdaの違いを徹底解説!yieldやクロージャの仕組みまで完全ガイド

Rubyにおけるブロック(blocks)、Proc、Lambda。

これらは一体何なのでしょうか?

どのように動作するのか?

そして、それぞれにどんな違いがあるのでしょうか?

この記事を読めば、それらすべてがわかります。さらに実践的な知識も一緒に身につけましょう!

目次

  • 1. Rubyのブロックを理解する
  • 2. yieldキーワードの使い方
  • 3. 暗黙的ブロックと明示的ブロック
  • 4. ブロックが渡されたかどうかを確認する方法
  • 5. Lambdaとは何か?
  • 6. LambdaとProcの違い
  • 7. クロージャ(Closure)
  • 8. Bindingクラス
  • 9. まとめ

Rubyのブロックを理解する

Rubyのブロックとは、メソッドに渡すことができる小さな「匿名関数」のようなものです。

ブロックは do / end で囲むか、波括弧 {} の間に記述します。複数の引数を持つことも可能です。

引数名は2本の縦棒 | の間に定義します。

each を使ったことがあるなら、あなたはすでにブロックを使っています!

以下がその例です

# 形式1:1行のブロックに推奨される書き方
[1, 2, 3].each { |num| puts num }
                 ^^^^^ ^^^^^^^^
                 ブロック   ブロック
               引数        本体
# 形式2:複数行のブロックに推奨される書き方
[1, 2, 3].each do |num|
  puts num
end

Rubyのブロックが便利なのは、ちょっとした処理(コード)を保存しておいて、後で使えるからです。

たとえば、ファイルへのデータ書き込み、要素同士の比較、エラーメッセージの出力など、さまざまな用途に活用できます。

yieldキーワードの使い方

Rubyにおける yield とは何を意味するのでしょうか?

yieldRubyのキーワードで、使用された時点でブロックを呼び出します。

つまり、メソッドがブロックを「利用する」ための仕組みなのです!

yield キーワードを使うと、ブロック内のコードが実行され、その処理を行います。

通常のRubyメソッドを呼び出すのと同じイメージです。

以下がその例です

def print_once
  yield
end

print_once { puts "Block is being run" }

このコードは print_once に渡されたブロックを実行します。その結果、「Block is being run」という文字列が画面に出力されます。

ご存知でしたか…?

yield は複数回使用できるのです。

yield を呼び出すたびにブロックが実行されます。これは同じメソッドを繰り返し呼び出すようなものです。

def print_twice
  yield
  yield
end

print_twice { puts "Hello" }

# "Hello"
# "Hello"

そしてメソッドと同じように…

yield には任意の数の引数を渡すことができます。

def one_two_three
  yield 1
  yield 2
  yield 3
end

one_two_three { |number| puts number * 10 }
# 10, 20, 30

ここで渡した引数は、ブロック側の引数になります。

この例では number がそれに該当します。

暗黙的ブロックと明示的ブロック

ブロックには「明示的(explicit)」と「暗黙的(implicit)」の2種類があります。

明示的とは、メソッドの引数リストの中でブロックに名前を付けることを指します。

明示的なブロックは、別のメソッドに渡したり、変数に保存して後で使ったりすることができます。

以下がその例です

def explicit_block(&block)
  block.call # yieldと同じ意味
end

explicit_block { puts "Explicit block called" }

&block という引数に注目してください…

これがブロックに名前を付ける方法です!

ブロックが渡されたかどうかを確認する方法

ブロックなしで yield を呼び出そうとすると、no block given (yield) というエラーが発生します。

block_given? メソッドを使えば、ブロックが渡されているかどうかを事前にチェックできます。

def do_something_with_block
  return "No block given" unless block_given?
  yield
end

こうすることで、誰かがブロックなしでメソッドを呼び出しても、エラーを防ぐことができます。

Lambdaとは何か?

Lambda(ラムダ)は、特別な構文を使ってブロックとその引数を定義する方法です。

定義したLambdaは変数に保存して、後で利用できます。

RubyのLambdaを定義する構文は次のようになります:

say_something = -> { puts "This is a lambda" }

代替構文として、-> の代わりに lambda メソッドを使うこともできます。

Lambdaを定義しても、中のコードは実行されません。メソッドを定義しても実行されないのと同じで、実際に実行するには call メソッドが必要です。

say_something = -> { puts "This is a lambda" }
say_something.call

# "This is a lambda"

lambdacall する方法は他にもあります。存在は知っておくと良いですが、可読性の観点から call を使うことをおすすめします。

一覧はこちら

my_lambda = -> { puts "Lambda called" }

my_lambda.call
my_lambda.()
my_lambda[]
my_lambda.===

Lambdaは引数を受け取ることもできます。以下がその例です:

times_two = ->(x) { x * 2 }
times_two.call(10)
# 20

lambda に誤った数の引数を渡すと、通常のメソッドと同様に例外が発生します。

LambdaとProcの違い

Procは非常に似た概念です…

違いの一つは、作成方法にあります。

my_proc = Proc.new { |x| puts x }

実は、専用の Lambda クラスは存在しません。lambda は特殊な Proc オブジェクトにすぎません。Proc のインスタンスメソッドを見てみると、lambda? というメソッドがあることに気づくでしょう。

さて

ProcはLambdaとは異なる挙動を示します。特に引数の扱いにおいて顕著です:

t = Proc.new { |x,y| puts "I don't care about arguments!" }
t.call
# "I don't care about arguments!"

proclambda のもう一つの違いは、return 文に対する反応です。

lambda は通常のメソッドのように普通に return します。

しかし proc は、現在のコンテキストから return しようとします。

どういうことか説明しましょう

次のコードを実行すると、procLocalJumpError 例外を発生させるのがわかります。

理由は、トップレベルのコンテキストからは return できないためです。

試してみましょう

# 正常に動作する
my_lambda = -> { return 1 }
puts "Lambda result: #{my_lambda.call}"

# 例外が発生する
my_proc = Proc.new { return 1 }
puts "Proc result: #{my_proc.call}"

もし proc がメソッドの中にあれば、return を呼び出すことはそのメソッドからのリターンと同等になります。

それを示したのが次の例です。

def call_proc
  puts "Before proc"
  my_proc = Proc.new { return 2 }
  my_proc.call
  puts "After proc"
end

p call_proc
# "Before proc" は表示されるが "After proc" は表示されない

proclambda の違いをまとめると、次のようになります:

  • Lambdaは -> {} で、Procは Proc.new {} で定義する。
  • Procは現在のメソッドからreturnするのに対し、LambdaはLambda自身からreturnする。
  • Procは引数の数が正しくなくても気にしないが、Lambdaは例外を発生させる。

このリストを見ると、lambda の方が proc よりも通常のメソッドに近い振る舞いをしていることがわかります。

クロージャ(Closure)

RubyのProcとLambdaには、もう一つ特別な性質があります。Procを作成すると、その時点の実行スコープが一緒に保存されるのです。

この概念は「クロージャ」と呼ばれることがあり、proc が定義されたコンテキストのローカル変数やメソッドなどの値を持ち運ぶことを意味します。

ただし、実際の値ではなく参照を保持しています。そのため、Procの作成後に変数が変更された場合でも、Procは常に最新の値を参照します。

例を見てみましょう

def call_proc(my_proc)
  count = 500
  my_proc.call
end

count   = 1
my_proc = Proc.new { puts count }

p call_proc(my_proc) # 何が出力されるでしょうか?

この例では、ローカル変数 count1 に設定されています。

また、my_proc という名前のProcと、引数として渡された任意のProcやLambdaを(call メソッド経由で)実行する call_proc メソッドがあります。

このプログラムは何を出力すると思いますか?

500 が最も論理的な結論のように思えますが、「クロージャ」の効果により、実際には 1 が出力されます。

これは、Procが count の値を「Procが定義された場所」、つまりメソッド定義の外側から参照しているために起こる現象です。

Bindingクラス

RubyのProcやLambdaは、このスコープ情報をどこに保存しているのでしょうか?

Binding クラスについてご紹介しましょう…

binding メソッドで Binding オブジェクトを作成すると、コード上のその地点への「アンカー(錨)」を作ることになります。

その時点で定義されていたすべての変数、メソッド、クラスは、まったく別のスコープにいても、このオブジェクトを通じて後からアクセスできます。

def return_binding
  foo = 100
  binding
end

# bindingのおかげで、fooは定義されたメソッドの
# 外側にいるにもかかわらずアクセスできる。
puts return_binding.class
puts return_binding.eval('foo')

# fooを直接出力しようとするとエラーになる。
# 理由は、fooがメソッドの外では一度も定義されていないから。
puts foo

言い換えれば、binding オブジェクトのコンテキスト下で何かを実行することは、そのコードが binding が定義された場所と同じ場所にある場合と同じことです(先ほどの「アンカー」の比喩を思い出してください)。

binding オブジェクトを直接使う必要はほとんどありませんが、こういう仕組みが存在することは知っておいて損はないでしょう 🙂

まとめ

この記事では、ブロックの仕組み、RubyのProcとLambdaの違い、そしてブロックを作成する際に発生する「クロージャ」の効果について学びました。

取り上げなかったトピックの一つとして、curry メソッドがあります。

このメソッドを使うと、必要な引数の一部またはすべてを事前に渡すことができます。

引数を一部だけ渡すと、それらの引数が「事前ロード」された新しいProcが返され、すべての引数が揃った時点でProcが実行されます。

この記事がお役に立てれば幸いです!

ぜひ下のフォームから購読登録をして、友人にもシェアしてください 🙂


  1. PCでWhatsApp Webを使う方法:設定手順から便利な活用術まで徹底解説

    WhatsApp Webを使えば、パソコンのブラウザから手軽にWhatsAppのメッセージを読んだり返信したりできます。スマートフォンを取り出すことなく、PC画面だけでチャットを完結できるのが大きな魅力です。この記事では、WhatsApp Webの基本的な仕組みから初期設定の手順、できること・できないこと、さらには便利な活用テクニックまで、初心者にもわかりやすく徹底的に解説します。 WhatsApp Webの利用に必要なもの 準備自体はとてもシンプルで、ほとんどの方はすでに必要なものを揃えているはずです。念のため、以下のリストをご確認ください。 背面カメラが使えるAndroidスマートフォ

  2. MacでAndroidメッセージを使いこなす完全ガイド

    AndroidのメッセージをMacで使う?一見不可能に思えますよね。両ブランドは熾烈な競争関係にあるため、「AndroidとMacは相性が悪い」と考える人がほとんどです。しかし、Googleはユーザーにとってより便利で柔軟なコミュニケーション環境を実現するため、Android Messagesを開発しました。 このメッセージングプラットフォームは、Windows PCとMacの両方で利用できます。そのため、Macユーザーでも難しく考える必要はありません。Android Messagesは複数のデバイス間で互換性があるため、さまざまなブランドのガジェットを持つユーザーにとって理想的な選択肢となっ