web-dev-qa-db-ja.com

Rubyのブロックとイールド

ブロックとyieldと、Rubyでの動作を理解しようとしています。

yieldはどのように使用されますか?私が見たRailsアプリケーションの多くは、yieldを奇妙な方法で使用しています。

誰かが私に説明したり、それらを理解するためにどこに行くべきかを教えてもらえますか?

257
Matt Elhotiby

はい、最初は少し困惑しています。

Rubyでは、メソッドはコードの任意のセグメントを実行するためにコードブロックを受け取る場合があります。

メソッドがブロックを予期する場合、yield関数を呼び出してブロックを呼び出します。

これは、たとえばリストを反復処理したり、カスタムアルゴリズムを提供したりする場合に非常に便利です。

次の例をご覧ください。

名前で初期化されたPersonクラスを定義し、呼び出されたときに、受信したブロックにname属性を渡すだけのdo_with_nameメソッドを提供します。

class Person 
    def initialize( name ) 
         @name = name
    end

    def do_with_name 
        yield( @name ) 
    end
end

これにより、そのメソッドを呼び出して任意のコードブロックを渡すことができます。

たとえば、名前を出力するには次のようにします。

person = Person.new("Oscar")

#invoking the method passing a block
person.do_with_name do |name|
    puts "Hey, his name is #{name}"
end

印刷します:

Hey, his name is Oscar

ブロックは、パラメーターとしてnameという変数を受け取ります(N.B.この変数は好きなように呼び出すことができますが、nameと呼ぶのは理にかなっています)。コードがyieldを呼び出すと、このパラメーターに@nameの値が入力されます。

yield( @name )

別のアクションを実行する別のブロックを提供できます。たとえば、名前を逆にします。

#variable to hold the name reversed
reversed_name = ""

#invoke the method passing a different block
person.do_with_name do |name| 
    reversed_name = name.reverse
end

puts reversed_name

=> "racsO"

まったく同じメソッド(do_with_name)を使用しました-これは単なる異なるブロックです。

この例は簡単です。より興味深い使用法は、配列内のすべての要素をフィルタリングすることです。

 days = ["monday", "tuesday", "wednesday", "thursday", "friday"]  

 # select those which start with 't' 
 days.select do | item |
     item.match /^t/
 end

=> ["tuesday", "thursday"]

または、たとえば文字列サイズに基づいて、カスタムソートアルゴリズムを提供することもできます。

 days.sort do |x,y|
    x.size <=> y.size
 end

=> ["monday", "friday", "tuesday", "thursday", "wednesday"]

これがあなたがそれをよりよく理解するのに役立つことを願っています。

ところで、ブロックがオプションの場合は、次のように呼び出す必要があります。

yield(value) if block_given?

がオプションでない場合は、単に呼び出します。

363
OscarRyz

Rubyでは、メソッドは、通常の引数に加えてブロックが提供されるような方法で呼び出されたかどうかを確認できます。通常、これはblock_given?メソッドを使用して行われますが、最終引数名の前にアンパサンド(&)を付けることにより、明示的なProcとしてブロックを参照することもできます。

メソッドがブロックで呼び出された場合、メソッドは必要に応じていくつかの引数を使用して、ブロックをyield制御できます(ブロックを呼び出します)。次の例を示すこのメソッド例を検討してください。

def foo(x)
  puts "OK: called as foo(#{x.inspect})"
  yield("A gift from foo!") if block_given?
end

foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How Nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How Nice =)

または、特別なブロック引数構文を使用します。

def bar(x, &block)
  puts "OK: called as bar(#{x.inspect})"
  block.call("A gift from bar!") if block
end

bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How Nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How Nice =)
22
maerics

誰かがここで本当に詳細な答えを提供する可能性は十分にありますが、私は常に この投稿 Robert Sosinskiからブロック、プロシージャ、ラムダ間の微妙さの素晴らしい説明であると見つけました。

リンクしている投稿がRuby 1.8に固有のものであると信じていることを付け加えます。 Ruby 1.9では、ブロック変数がブロックに対してローカルであるなど、いくつかのことが変更されました。 1.8では、次のようなものが得られます。

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"

一方、1.9は以下を提供します。

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"

このマシンには1.9がないので、上記にエラーがあるかもしれません。

22
theIV

既に素晴らしい答えにあなたがそのように物事をする理由を少し付け加えたかったのです。

あなたがどの言語から来ているのか分かりませんが、それが静的言語であると仮定すると、この種のことはおなじみになります。これは、Javaでファイルを読み取る方法です。

public class FileInput {

  public static void main(String[] args) {

    File file = new File("C:\\MyFile.txt");
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    DataInputStream dis = null;

    try {
      fis = new FileInputStream(file);

      // Here BufferedInputStream is added for fast reading.
      bis = new BufferedInputStream(fis);
      dis = new DataInputStream(bis);

      // dis.available() returns 0 if the file does not have more lines.
      while (dis.available() != 0) {

      // this statement reads the line from the file and print it to
        // the console.
        System.out.println(dis.readLine());
      }

      // dispose all the resources after using them.
      fis.close();
      bis.close();
      dis.close();

    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

ストリームチェーン全体を無視して、アイデアはこれです

  1. クリーンアップが必要なリソースを初期化する
  2. リソースを使用する
  3. 必ず整理してください

これがRubyでのやり方です

File.open("readfile.rb", "r") do |infile|
    while (line = infile.gets)
        puts "#{counter}: #{line}"
        counter = counter + 1
    end
end

大きく異なります。これを分解する

  1. fileクラスにリソースの初期化方法を伝えます
  2. ファイルクラスに何をするかを伝える
  3. まだ入力中のJava人を笑います;-)

ここでは、ステップ1と2を処理する代わりに、基本的に別のクラスに委任します。ご覧のとおり、これにより、記述する必要のあるコードの量が劇的に減り、読みやすくなり、メモリリークやファイルロックがクリアされない可能性が低くなります。

今では、Javaで似たようなことをできないわけではありません。実際、人々は何十年もそれをやっています。 Strategy パターンと呼ばれます。違いは、ブロックがないと、ファイルの例のような単純なものの場合、記述する必要があるクラスとメソッドの量が多いため、戦略が過剰になります。ブロックを使用すると、非常にシンプルでエレガントな方法であるため、コードをそのように構成しないことは意味がありません。

これはブロックが使用される唯一の方法ではありませんが、他のもの(ビルダーパターンなど、Railsのform_for apiで見ることができます)は十分に似ているので、これに頭を包むと何が起こっているかが明らかになります。ブロックが表示されている場合、通常はメソッド呼び出しがやりたいことであり、ブロックがそのやりかたを記述していると想定するのが安全です。

13
Matt Briggs

この記事 が非常に役立つことがわかりました。特に、次の例:

#!/usr/bin/Ruby

def test
  yield 5
  puts "You are in the method test"
  yield 100
end

test {|i| puts "You are in the block #{i}"}

test do |i|
    puts "You are in the block #{i}"
end

次の出力が得られます。

You are in the block 5
You are in the method test
You are in the block 100
You are in the block 5
You are in the method test
You are in the block 100

したがって、本質的にyield Rubyが呼び出されるたびに、doブロック内または{}内でコードが実行されます。パラメーターがyieldに提供される場合、これはdoブロックのパラメーターとして提供されます。

私にとって、これはdoブロックが何をしているのかを本当に理解したのは初めてでした。基本的に、関数が内部データ構造へのアクセスを提供する方法であり、反復または関数の構成のためです。

したがって、Railsに次のように記述します。

respond_to do |format|
  format.html { render template: "my/view", layout: 'my_layout' }
end

これはrespond_to関数を実行し、(内部)doパラメーターを持つformatブロックを生成します。次に、この内部変数に対して.html関数を呼び出します。これにより、renderコマンドを実行するコードブロックが生成されます。 .htmlは、要求されたファイル形式の場合にのみ生成されることに注意してください。 (技術: source からわかるように、これらの関数は実際にはyieldではなくblock.callを使用しますが、機能は基本的に同じです。議論については この質問 を参照してください。)関数が何らかの初期化を実行し、呼び出しコードから入力を取得し、必要に応じて処理を続行する方法。

または、別の言い方をすれば、匿名関数を引数として取り、それをjavascriptで呼び出す関数に似ています。

12
zelanix

Rubyでは、ブロックは基本的に任意のメソッドに渡して実行できるコードの塊です。ブロックは常にメソッドとともに使用され、通常はデータを(引数として)フィードします。

ブロックは、Ruby gems(Railsを含む)およびよく書かれたRubyコードで広く使用されています。オブジェクトではないため、変数に割り当てることはできません。

基本的な構文

ブロックは、{}またはdo..endで囲まれたコードです。慣例により、中括弧構文は単一行ブロックに使用し、do..end構文は複数行ブロックに使用する必要があります。

{ # This is a single line block }

do
  # This is a multi-line block
end 

どのメソッドも、ブロックを暗黙の引数として受け取ることができます。ブロックは、メソッド内のyieldステートメントによって実行されます。基本的な構文は次のとおりです。

def meditate
  print "Today we will practice zazen"
  yield # This indicates the method is expecting a block
end 

# We are passing a block as an argument to the meditate method
meditate { print " for 40 minutes." }

Output:
Today we will practice zazen for 40 minutes.

Yieldステートメントに達すると、meditateメソッドはブロックに制御を渡し、ブロック内のコードが実行され、制御がメソッドに返されます。メソッドはyieldステートメントの直後に実行を再開します。

メソッドにyieldステートメントが含まれる場合、呼び出し時にブロックを受け取ることを期待しています。ブロックが提供されていない場合、yieldステートメントに到達すると例外がスローされます。ブロックをオプションにし、例外の発生を回避できます。

def meditate
  puts "Today we will practice zazen."
  yield if block_given? 
end meditate

Output:
Today we will practice zazen. 

メソッドに複数のブロックを渡すことはできません。各メソッドは1つのブロックのみを受信できます。

詳細は http://www.zenruby.info/2016/04/introduction-to-blocks-in-Ruby.html

7
user3847220

私は時々、このような「利回り」を使用します。

def add_to_http
   "http://#{yield}"
end

puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
5
Samet Sazak

簡単に言うと、作成するメソッドでブロックを取得して呼び出すことができます。 yieldキーワードは、具体的には、ブロック内の「スタッフ」が実行されるスポットです。

4
ntarpey

ここで、収量について2つのポイントを作成します。まず、ここでの多くの答えは、yieldを使用するメソッドにブロックを渡すさまざまな方法について説明していますが、制御フローについても説明しましょう。これは、複数の時間をブロックに渡すことができるため、特に重要です。例を見てみましょう:

class Fruit
  attr_accessor :kinds

  def initialize 
    @kinds = %w(orange Apple pear banana)
  end

  def each 
    puts 'inside each'
    3.times { yield (@kinds.tap {|kinds| puts "selecting from #{kinds}"} ).sample }
  end  
end

f = Fruit.new
f.each do |kind|
  puts 'inside block'
end    

=> inside each
=> selecting from ["orange", "Apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "Apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "Apple", "pear", "banana"]
=> inside block

各メソッドが呼び出されると、行ごとに実行されます。ここで、3.xブロックに到達すると、このブロックが3回呼び出されます。 yieldを呼び出すたびに。その収量は、各メソッドを呼び出したメソッドに関連付けられたブロックにリンクされています。 yieldが呼び出されるたびに、クライアントコード内の各メソッドのブロックに制御が戻ることに注意することが重要です。ブロックの実行が終了すると、3xブロックに戻ります。そして、これは3回起こります。したがって、yieldは明示的に3回別々に呼び出されるため、クライアントコードのそのブロックは3回別々に呼び出されます。

2番目のポイントは、enum_forとyieldについてです。 enum_forはEnumeratorクラスをインスタンス化し、このEnumeratorオブジェクトもyieldに応答します。

class Fruit
  def initialize
    @kinds = %w(orange Apple)
  end

  def kinds
    yield @kinds.shift
    yield @kinds.shift
  end
end

f = Fruit.new
enum = f.to_enum(:kinds)
enum.next
 => "orange" 
enum.next
 => "Apple" 

したがって、外部イテレーターでkindを呼び出すたびに、yieldが1回だけ呼び出されることに注意してください。次回呼び出したときに、次のyieldを呼び出します。

Enum_forに関して興味深い情報があります。オンラインのドキュメントには、次のことが記載されています。

enum_for(method = :each, *args) → enum
Creates a new Enumerator which will enumerate by calling method on obj, passing args if any.

str = "xyz"
enum = str.enum_for(:each_byte)
enum.each { |b| puts b }    
# => 120
# => 121
# => 122

Enum_forの引数としてシンボルを指定しない場合、Rubyは列挙子をレシーバの各メソッドにフックします。 Stringクラスなど、一部のクラスにはeachメソッドがありません。

str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String

したがって、enum_forで呼び出されるオブジェクトの場合、列挙メソッドが何であるかを明示する必要があります。

1
Donato

Yieldは、メソッドで値を返す名前のないブロックとして使用できます。次のコードを検討してください。

Def Up(anarg)
  yield(anarg)
end

1つの引数が割り当てられたメソッド「Up」を作成できます。これで、関連するブロックを呼び出して実行するyieldにこの引数を割り当てることができます。パラメーターリストの後にブロックを割り当てることができます。

Up("Here is a string"){|x| x.reverse!; puts(x)}

Upメソッドが引数を指定してyieldを呼び出すと、ブロック変数に渡されてリクエストが処理されます。

0
gkstr1