303

私はブロックとyieldそれらがRubyでどのように機能するかを理解しようとしています。

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

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

4

10 に答える 10

416

はい、最初は少し不可解です。

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

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

例:

、属性とメソッドPersonを持つクラスを取ります。メソッドが呼び出されると、属性がブロックに渡されます。namedo_with_namename

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

    def do_with_name   # expects a block
        yield( @name ) # invoke the block and pass the `@name` attribute
    end
end

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

person = Person.new("Oscar")

# Invoking the method passing a block to print the value
person.do_with_name do |value|
    puts "Got: #{value}"
end

印刷します:

Got: Oscar

ブロックがパラメータとしてと呼ばれる変数を受け取ることに注意してくださいvalue。コードが呼び出さyieldれると、引数として値が渡されます@name

yield( @name )

同じメソッドを別のブロックで呼び出すことができます。

たとえば、名前を逆にするには:

reversed_name = ""

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

puts reversed_name

=> "racsO"

その他のより興味深い実例:

配列内の要素をフィルター処理します。

 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?

オプションでない場合は、それを呼び出すだけです。

irbこれらの例は、 (Interactive Ruby Shell)を使用してコンピューターで試すことができます。

コピー/貼り付け可能な形式のすべての例を次に示します。

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

    def do_with_name   # expects a block
        yield( @name ) # invoke the block and pass the `@name` attribute
    end
end


person = Person.new("Oscar")

# Invoking the method passing a block to print the value
person.do_with_name do |value|
    puts "Got: #{value}"
end


reversed_name = ""

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

puts reversed_name



# Filter elements in an array:    
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]  

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



# Sort by name length:     
days.sort do |x,y|
   x.size <=> y.size
end
于 2010-06-18T02:51:42.720 に答える
32

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 =)
于 2010-06-18T01:49:45.403 に答える
24

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

私がリンクしている投稿はruby1.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がないので、上記のエラーが発生している可能性があります。

于 2010-06-18T01:49:52.467 に答える
13

この記事はとても役に立ちました。特に、次の例:

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

したがって、レールにいるときは、次のように記述します。

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

これにより、(内部)パラメーターを持つブロックrespond_toを生成する関数が実行されます。次に、この内部変数で関数を呼び出します。これにより、コマンドを実行するためのコードブロックが生成されます。要求されたファイル形式の場合にのみ生成されることに注意してください。(技術:これらの関数は、ソースからわかるように実際には使用されませんが、機能は基本的に同じです。説明については、この質問を参照してください。)これは、関数が初期化を実行し、呼び出し元のコードから入力を取得する方法を提供します。その後、必要に応じて処理を続行します。doformat.htmlrender.htmlblock.callyield

言い換えれば、無名関数を引数として取り、それをjavascriptで呼び出す関数に似ています。

于 2014-05-14T17:19:15.180 に答える
12

すでに素晴らしい答えに、なぜあなたがそのように物事を行うのかを付け加えたかったのです。

あなたがどの言語から来ているのか分かりませんが、それが静的な言語であると仮定すると、この種のものは見覚えがあるでしょう。これは、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. 必ずクリーンアップしてください

これはあなたがルビーでそれをする方法です

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で同じようなことができないわけではありません。実際、人々は何十年もの間それを行ってきました。それは戦略パターンと呼ばれます。違いは、ブロックがないと、ファイルの例のような単純なものの場合、作成する必要のあるクラスとメソッドの量が原因で戦略が過剰になることです。ブロックを使用すると、それを行うのは非常にシンプルでエレガントな方法であるため、コードをそのように構造化しないことは意味がありません。

ブロックが使用される方法はこれだけではありませんが、他のブロック(railsのform_for apiで確認できるBuilderパターンなど)は十分に類似しているため、これに頭を巻くと何が起こっているかが明らかになります。ブロックが表示された場合、通常は、メソッド呼び出しが実行したいことであり、ブロックが実行方法を記述していると想定するのが安全です。

于 2010-06-18T02:16:12.667 に答える
10

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

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

基本構文

ブロックは、{}またはdo..endで囲まれたコードの一部です。慣例により、中括弧構文は1行のブロックに使用する必要があり、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.

イールドステートメントに到達すると、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つのメソッドに複数のブロックを渡すことはできません。各メソッドは1つのブロックのみを受け取ることができます。

詳細については、http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.htmlをご覧ください。

于 2016-05-01T01:50:29.443 に答える
5

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

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

puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
于 2015-02-04T01:26:31.020 に答える
4

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

于 2013-12-02T01:02:11.280 に答える
2

ここで歩留まりについて言いたいことが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.timesブロックに到達すると、このブロックは3回呼び出されます。それがyieldを呼び出すたび。その歩留まりは、各メソッドを呼び出したメソッドに関連付けられたブロックにリンクされています。歩留まりが呼び出されるたびに、クライアントコードの各メソッドのブロックに制御が戻ることに注意することが重要です。ブロックの実行が終了すると、3.timesブロックに戻ります。そしてこれは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" 

したがって、外部イテレータを使用して種類を呼び出すたびに、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クラスのように、一部のクラスには各メソッドがありません。

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

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

于 2018-10-29T01:33:21.897 に答える
0

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

Def Up(anarg)
  yield(anarg)
end

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

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

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

于 2016-09-21T15:34:27.687 に答える