私はブロックとyield
それらがRubyでどのように機能するかを理解しようとしています。
どのようにyield
使用されますか?私が調べたRailsアプリケーションの多くはyield
、奇妙な方法で使用されています。
誰かが私に説明したり、それらを理解するためにどこに行くべきかを教えてもらえますか?
はい、最初は少し不可解です。
Rubyでは、メソッドはコードの任意のセグメントを実行するためにコードブロックを受け取ることができます。
メソッドがブロックを予期している場合、関数を呼び出すことでブロックを呼び出すことができyield
ます。
例:
、属性とメソッドPerson
を持つクラスを取ります。メソッドが呼び出されると、属性がブロックに渡されます。name
do_with_name
name
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
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 =)
誰かがここで本当に詳細な答えを提供する可能性は十分にありますが、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がないので、上記のエラーが発生している可能性があります。
この記事はとても役に立ちました。特に、次の例:
#!/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
を生成する関数が実行されます。次に、この内部変数で関数を呼び出します。これにより、コマンドを実行するためのコードブロックが生成されます。要求されたファイル形式の場合にのみ生成されることに注意してください。(技術:これらの関数は、ソースからわかるように実際には使用されませんが、機能は基本的に同じです。説明については、この質問を参照してください。)これは、関数が初期化を実行し、呼び出し元のコードから入力を取得する方法を提供します。その後、必要に応じて処理を続行します。do
format
.html
render
.html
block.call
yield
言い換えれば、無名関数を引数として取り、それをjavascriptで呼び出す関数に似ています。
すでに素晴らしい答えに、なぜあなたがそのように物事を行うのかを付け加えたかったのです。
あなたがどの言語から来ているのか分かりませんが、それが静的な言語であると仮定すると、この種のものは見覚えがあるでしょう。これは、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();
}
}
}
ストリームチェーン全体を無視すると、アイデアはこれです
これはあなたがルビーでそれをする方法です
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
大きく異なります。これを分解する
ここでは、ステップ1と2を処理する代わりに、基本的にそれを別のクラスに委任します。ご覧のとおり、これにより、作成する必要のあるコードの量が大幅に削減され、読みやすくなり、メモリリークやファイルロックがクリアされない可能性が低くなります。
さて、Javaで同じようなことができないわけではありません。実際、人々は何十年もの間それを行ってきました。それは戦略パターンと呼ばれます。違いは、ブロックがないと、ファイルの例のような単純なものの場合、作成する必要のあるクラスとメソッドの量が原因で戦略が過剰になることです。ブロックを使用すると、それを行うのは非常にシンプルでエレガントな方法であるため、コードをそのように構造化しないことは意味がありません。
ブロックが使用される方法はこれだけではありませんが、他のブロック(railsのform_for apiで確認できるBuilderパターンなど)は十分に類似しているため、これに頭を巻くと何が起こっているかが明らかになります。ブロックが表示された場合、通常は、メソッド呼び出しが実行したいことであり、ブロックが実行方法を記述していると想定するのが安全です。
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をご覧ください。
私は時々このような「利回り」を使用します:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
簡単に言うと、生成するメソッドでブロックを取得して呼び出すことができます。具体的には、yieldキーワードは、ブロック内の「stuff」が実行される場所です。
ここで歩留まりについて言いたいことが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で呼び出される一部のオブジェクトの場合、列挙メソッドがどうなるかを明示的に指定する必要があります。
Yieldは、メソッドで値を返すための名前のないブロックとして使用できます。次のコードを検討してください。
Def Up(anarg)
yield(anarg)
end
1つの引数が割り当てられたメソッド「Up」を作成できます。これで、この引数をyieldに割り当てて、関連するブロックを呼び出して実行することができます。パラメータリストの後にブロックを割り当てることができます。
Up("Here is a string"){|x| x.reverse!; puts(x)}
Upメソッドがyieldを呼び出すと、引数を指定してブロック変数に渡され、リクエストが処理されます。