私はブロックを理解しようとしています yield
、それらがRubyでどのように機能するかます。
使い方はyield
?私が見てきたRailsアプリケーションの多くは、yield
奇妙な方法で使用されています。
誰かが私に説明したり、それらを理解するためにどこへ行くべきかを私に示したりできますか?
私はブロックを理解しようとしています yield
、それらがRubyでどのように機能するかます。
使い方はyield
?私が見てきたRailsアプリケーションの多くは、yield
奇妙な方法で使用されています。
誰かが私に説明したり、それらを理解するためにどこへ行くべきかを私に示したりできますか?
回答:
はい、最初は少し不可解です。
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 = ""
"Oscar"
(答えはあまり明確ではありません)
person.do_with_name {|string| yield string, something_else }
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からのこの投稿は、ブロック、プロシージャ、ラムダの間の微妙な点の優れた説明であることが常にわかりました。
私がリンクしている投稿は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がないので、上記でエラーが発生する可能性があります。
私は、なぜあなたがすでに素晴らしい答えにそのように物事を行うのかということをちょっと付け加えたかったのです。
どの言語から来たのかはわかりませんが、それが静的言語であると仮定すると、この種のことはなじみ深く見えるでしょう。これは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で確認できます)など)はよく似ているので、これに頭を回すと、何が起こっているのか明らかです。ブロックが表示された場合、通常はメソッド呼び出しが実行したいものであり、ブロックが実行方法を記述していると想定しても安全です。
File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end
。Javaの人たちをもっと強く笑います。
IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }
(プラスなしメモリの問題)
私が見つかりました。この記事は非常に有用であること。特に、次の例:
#!/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で呼び出す関数に似ています。
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をご覧ください。
ここで、歩留まりについて、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で呼び出された一部のオブジェクトの場合、列挙メソッドがどうなるかを明示する必要があります。
Yieldは、名前のないブロックとして使用して、メソッドで値を返すことができます。次のコードを検討してください。
Def Up(anarg)
yield(anarg)
end
1つの引数が割り当てられたメソッド「Up」を作成できます。これで、関連するブロックを呼び出して実行するyieldにこの引数を割り当てることができます。パラメータリストの後にブロックを割り当てることができます。
Up("Here is a string"){|x| x.reverse!; puts(x)}
Upメソッドがyieldを引数付きで呼び出すと、要求を処理するためにブロック変数に渡されます。