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

Rubyのクロージャ:ブロック、Procs、Lambdas

Ruby Magicでは、毎日使用するものの背後にある魔法に飛び込んで、それらがどのように機能するかを理解するのが大好きです。このエディションでは、ブロック、プロシージャ、ラムダの違いについて説明します。

ファーストクラス関数を使用するプログラミング言語では、関数を変数に格納し、引数として他の関数に渡すことができます。関数は、他の関数を戻り値として使用することもできます。

クロージャは、環境を備えたファーストクラスの関数です。環境は、クロージャーが作成されたときに存在していた変数へのマッピングです。クロージャーは、別のスコープで定義されている場合でも、これらの変数へのアクセスを保持します。

Rubyにはファーストクラスの関数はありませんが、ブロック、プロシージャ、ラムダの形式のクロージャがあります。ブロックは、コードのブロックをメソッドに渡すために使用され、procsとlambdaは、コードのブロックを変数に格納できるようにします。

ブロック

Rubyでは、ブロック 後で実行するために作成できるコードのスニペットです。ブロックは、do内でそれらを生成するメソッドに渡されます およびend キーワード。多くの例の1つは、#eachです。 列挙可能なオブジェクトをループするメソッド。

[1,2,3].each do |n|
  puts "#{n}!"
end
 
[1,2,3].each { |n| puts "#{n}!" } # the one-line equivalent.

この例では、ブロックがArray#eachに渡されます メソッド。配列内の各アイテムのブロックを実行し、コンソールに出力します。

def each
  i = 0
  while i < size
    yield at(i)
    i += 1
  end
end

このArray#eachの簡略化された例では 、while ループ、yield 配列内のすべてのアイテムに対して渡されたブロックを実行するために呼び出されます。ブロックは暗黙的にメソッドに渡されるため、このメソッドには引数がないことに注意してください。

暗黙のブロックとyield キーワード

Rubyでは、メソッドは暗黙的および明示的にブロックを取ることができます。暗黙的なブロック受け渡しは、yieldを呼び出すことで機能します メソッド内のキーワード。 yield キーワードは特別です。渡されたブロックを見つけて呼び出すため、メソッドが受け入れる引数のリストにブロックを追加する必要はありません。

Rubyでは暗黙的なブロック受け渡しが許可されているため、ブロックを使用してすべてのメソッドを呼び出すことができます。 yieldを呼び出さない場合 、ブロックは無視されます。

irb> "foo bar baz".split { p "block!" }
=> ["foo", "bar", "baz"]

呼び出されたメソッドがする場合 譲歩すると、渡されたブロックが検出され、yieldに渡された引数とともに呼び出されます。 キーワード。

def each
  return to_enum(:each) unless block_given?
 
  i = 0
  while i < size
    yield at(i)
    i += 1
  end
end

この例では、Enumeratorのインスタンスを返します。 ブロックが与えられない限り。

yield およびblock_given? キーワードは、現在のスコープでブロックを検索します。これにより、ブロックを暗黙的に渡すことができますが、変数に格納されていないため、コードがブロックに直接アクセスすることはできません。

ブロックを明示的に渡す

アンパサンドパラメータ(通常は&blockと呼ばれます)を使用して引数としてブロックを追加することにより、メソッドでブロックを明示的に受け入れることができます。 )。ブロックが明示的になっているため、#callを使用できます yieldに依存するのではなく、結果のオブジェクトに直接メソッドを適用します 。

&block 引数は適切な引数ではないため、ブロック以外でこのメソッドを呼び出すと、ArgumentErrorが生成されます。 。

def each_explicit(&block)
  return to_enum(:each) unless block
 
  i = 0
  while i < size
    block.call at(i)
    i += 1
  end
end

ブロックがこのように渡されて変数に格納されると、自動的に procに変換されます。 。

手順

「proc」はProcのインスタンスです クラス。実行するコードブロックを保持し、変数に格納できます。 procを作成するには、Proc.newを呼び出します。 ブロックを渡します。

proc = Proc.new { |n| puts "#{n}!" }

procは変数に格納できるため、通常の引数と同じようにメソッドに渡すこともできます。その場合、procは明示的に渡されるため、アンパサンドは使用しません。

def run_proc_with_random_number(proc)
  proc.call(random)
end
 
proc = Proc.new { |n| puts "#{n}!" }
run_proc_with_random_number(proc)

プロシージャを作成してメソッドに渡す代わりに、前に見たRubyのアンパサンドパラメータ構文を使用して、代わりにブロックを使用できます。

def run_proc_with_random_number(&proc)
  proc.call(random)
end
 
run_proc_with_random_number { |n| puts "#{n}!" }

メソッドの引数に追加されたアンパサンドに注意してください。これにより、渡されたブロックがprocオブジェクトに変換され、メソッドスコープの変数に格納されます。

ヒント :状況によっては、メソッドにprocを含めると便利ですが、ブロックをprocに変換すると、パフォーマンスが低下します。可能な限り、代わりに暗黙のブロックを使用してください。

#to_proc

シンボル、ハッシュ、メソッドは、#to_procを使用してprocに変換できます。 メソッド。これのよく見られる使用法は、シンボルから作成されたprocをメソッドに渡すことです。

[1,2,3].map(&:to_s)
[1,2,3].map {|i| i.to_s }
[1,2,3].map {|i| i.send(:to_s) }

この例は、#to_sを呼び出す3つの同等の方法を示しています 配列の各要素に。最初のシンボルでは、接頭辞としてアンパサンドが付いたシンボルが渡され、#to_procを呼び出すことで自動的にprocに変換されます。 方法。最後の2つは、そのprocがどのように見えるかを示しています。

class Symbol
  def to_proc
    Proc.new { |i| i.send(self) }
  end
end

これは単純化された例ですが、Symbol#to_procの実装 内部で何が起こっているかを示しています。このメソッドは、1つの引数を取り、selfを送信するprocを返します。 それに。 self以降 はこのコンテキストのシンボルであり、Integer#to_sを呼び出します。 メソッド。

ラムダ

ラムダは本質的に、いくつかの際立った要素を備えたprocです。これらは、2つの点で「通常の」メソッドに似ています。呼び出されたときに渡される引数の数を強制することと、「通常の」戻り値を使用することです。

引数がないラムダを呼び出す場合、または引数を予期しないラムダに引数を渡すと、RubyはArgumentErrorを発生させます。 。

irb> lambda (a) { a }.call
ArgumentError: wrong number of arguments (given 0, expected 1)
        from (irb):8:in `block in irb_binding'
        from (irb):8
        from /Users/jeff/.asdf/installs/ruby/2.3.0/bin/irb:11:in `<main>'

また、ラムダはreturnキーワードをメソッドと同じように扱います。 procを呼び出すと、プログラムはproc内のコードブロックに制御を渡します。したがって、procが戻ると、現在のスコープが戻ります。 procが関数内で呼び出され、returnを呼び出す場合 、関数もすぐに戻ります。

def return_from_proc
  a = Proc.new { return 10 }.call
  puts "This will never be printed."
end

この関数はprocに制御を与えるので、それが戻ると、関数は戻ります。この例で関数を呼び出すと、出力が出力されて10が返されることはありません。

def return_from_lambda
  a = lambda { return 10 }.call
  puts "The lambda returned #{a}, and this will be printed."
end

ラムダを使用する場合、 印刷されます。 returnを呼び出す ラムダでは、returnを呼び出すように動作します メソッド内で、a 変数には10が入力されます 線がコンソールに印刷されます。

ブロック、プロシージャ、ラムダ

ブロック、proc、ラムダの両方について説明したので、ズームアウトして比較を要約しましょう。

  • ブロックは、コードのビットを関数に渡すためにRubyで広く使用されています。 yieldを使用する キーワードを使用すると、ブロックをprocに変換せずに暗黙的に渡すことができます。
  • アンパサンドのプレフィックスが付いたパラメーターを使用する場合、ブロックをメソッドに渡すと、メソッドのコンテキストでprocが生成されます。 Procはブロックのように動作しますが、変数に格納できます。
  • ラムダはメソッドのように動作するプロシージャです。つまり、アリティを強制し、親スコープではなくメソッドとして返されます。

これで、Rubyのクロージャについての調査は終わりです。字句スコープやバインディングなどのクロージャーについて学ぶことはまだたくさんありますが、今後のエピソードのためにそれを保持します。それまでの間、@ AppSignalで、Ruby Magicの今後の記事、クロージャー、またはその他の方法で読みたいことをお知らせください。


  1. Rubyでのラムダの使用

    ブロックはRubyの非常に重要な部分であり、ブロックなしで言語を想像するのは難しいです。しかし、ラムダ?ラムダが好きなのは誰ですか?あなたはそれを使わずに何年も行くことができます。まるで過ぎ去った時代の遺物のようです。 ...しかし、それは完全に真実ではありません。ラムダを少し調べてみると、ラムダにはいくつかの興味深いトリックがあります。 この記事では、ラムダの使用法の基本から始めて、さらに興味深い高度な使用法に移ります。したがって、ラムダを毎日使用していて、それらについてすべて知っている場合は、下にスクロールするだけです。 Lambdasについて覚えておくべき主なことは、それらが関数の

  2. LoggerとLogrageを使用してRubyにログインする

    Rubyでのログの操作 ロギングは、アプリケーションが通常対処する主要なタスクの1つです。ログは、たとえば、必要なときに使用されます アプリ内で何が起こっているかを確認します それらを監視する、または 特定のデータの指標を収集します。 新しいプログラミング言語を学ぶとき、情報を記録するための最初の明白な選択は、ネイティブメカニズムです。通常、それは簡単で、文書化されており、コミュニティ全体に広く行き渡っています。 ログデータは、使用している会社、ビジネス、アプリケーションの種類によって大きく異なります。したがって、あなたとあなたのチームが選択したロギングソリューションがその全体的な使