Rubyのブロックとイールド


275

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

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

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


2
コンピュータサイエンスに関連したRubyのイールド機能への回答に興味があるかもしれません。これはあなたの質問とはやや異なる質問ですが、その問題にいくつかの光を当てるかもしれません。
ケンブルーム

回答:


393

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

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

メソッドがブロックを期待するとき、それはyield関数を呼び出すことによってそれを呼び出します。

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

次の例を見てください。

Person名前で初期化されたクラスを定義し、do_with_name呼び出されたとき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(この変数は好きなように呼び出すことができますが、呼び出すことは意味があります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?

オプションでない場合は、それを呼び出してください。

編集

@hmakがこれらの例のrepl.itを作成しました:https ://repl.it/@makstaks/blocksandyieldsrubyexample


それはどのように印刷さracsO場合 the_name = ""
Paritosh Piplewar

2
名前は次のように初期化されたインスタンス変数です"Oscar" (答えはあまり明確ではありません)
OscarRyz

このようなコードはどうですか?person.do_with_name {|string| yield string, something_else }
f.ardelian

7
つまり、JavaScriptの用語では、これは、コールバックを特定のメソッドに渡し、それを呼び出す標準化された方法です。説明ありがとう!
yitznewton 2013

より一般的な方法で-ブロックは、戦略パターンのルビ「拡張」構文シュガーです。典型的な使用法は、他の操作のコンテキストで何かを行うコードを提供することです。しかし、ルビーの機能強化は、コンテキストを渡すためにブロックを使用してDSLを書くなどのクールな方法への道を開きます
Roman Bulgakov

25

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 =)

ブロックをトリガーするさまざまな方法を知っておくと便利です。
LPは2015年

22

ここで誰かが本当に詳細な答えを提供する可能性は十分ありますが、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がないので、上記でエラーが発生する可能性があります。


その記事の素晴らしい説明、自分ですべてを理解するのに何ヶ月もかかった=)
maerics

同意する。説明する内容の半分は、それを読むまでわかりませんでした。
theIV

更新されたリンクも404になりました。これがWayback Machineリンクです。
klenwell 16

@klenwellお知らせありがとうございます。リンクを再度更新しました。
theIV 2016

13

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

どの言語から来たのかはわかりませんが、それが静的言語であると仮定すると、この種のことはなじみ深く見えるでしょう。これは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で確認できます)など)はよく似ているので、これに頭を回すと、何が起こっているのか明らかです。ブロックが表示された場合、通常はメソッド呼び出しが実行したいものであり、ブロックが実行方法を記述していると想定しても安全です。


5
それを少し単純化してみましょうFile.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end。Javaの人たちをもっと強く笑います。
マイケルハンプトン14

1
@MichaelHampton、数ギガバイトの長さのファイルを読んだら笑ってください。
akostadinov 2015

@akostadinovいいえ...それで私は泣きたくなります!
マイケルハンプトン

3
いっそ@MichaelHamptonまたは、: IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }(プラスなしメモリの問題)
基金モニカの訴訟

12

私が見つかりました。この記事は非常に有用であること。特に、次の例:

#!/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

したがって、基本的にyieldruby への呼び出しが行われるたびに、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で呼び出す関数に似ています。


8

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

ブロックは、Ruby gem(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をご覧ください。


これは、ブロックとイールドとは何か、そしてそれらの使用方法を本当に理解させる(唯一の)答えです。
Eric Wang

5

私は時々このように "yield"を使います:

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

puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}

いいけどなんで ?Loggerユーザーが必要としない場合にタスクを実行する必要がないなど、多くの理由があります。あなたはあなたのことを説明すべきです...
ユリスBN

4

簡単に言うと、生成するメソッドはブロックを受け取って呼び出すことができます。特にyieldキーワードは、ブロック内の「スタッフ」が実行される場所です。


1

ここで、歩留まりについて、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

eachメソッドが呼び出されると、1行ずつ実行されます。3.timesブロックに到達すると、このブロックは3回呼び出されます。毎回yieldを呼び出します。そのyieldは、eachメソッドを呼び出したメソッドに関連付けられたブロックにリンクされています。yieldが呼び出されるたびに、制御がクライアントコードのeachメソッドのブロックに戻ることに注意することが重要です。ブロックの実行が完了すると、3回のブロックに戻ります。そして、これは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クラスのように、いくつかのクラスにはeachメソッドがありません。

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

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


0

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

Def Up(anarg)
  yield(anarg)
end

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

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

Upメソッドがyieldを引数付きで呼び出すと、要求を処理するためにブロック変数に渡されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.