yield
Ruby'de blokları ve nasıl çalıştıklarını anlamaya çalışıyorum .
Nasıl yield
kullanılır? Rails uygulamalarının birçoğunu yield
garip bir şekilde kullandım.
Birisi bana açıklayabilir ya da onları nerede anlayacağımı gösterebilir mi?
yield
Ruby'de blokları ve nasıl çalıştıklarını anlamaya çalışıyorum .
Nasıl yield
kullanılır? Rails uygulamalarının birçoğunu yield
garip bir şekilde kullandım.
Birisi bana açıklayabilir ya da onları nerede anlayacağımı gösterebilir mi?
Yanıtlar:
Evet, başlangıçta biraz kafa karıştırıcı.
Ruby'de yöntemler, kodun rastgele bölümlerini gerçekleştirmek için bir kod bloğu alabilir.
Bir yöntem bir blok beklediğinde, yield
işlevi çağırarak bloğu çağırır .
Bu, örneğin bir liste üzerinde yineleme yapmak veya özel bir algoritma sağlamak için çok kullanışlıdır.
Aşağıdaki örneği alın:
Person
Bir adla başlatılan bir sınıf tanımlayacağım ve do_with_name
çağrıldığında, name
alınan bloğa sadece özniteliği geçirecek bir yöntem sağlayacağım .
class Person
def initialize( name )
@name = name
end
def do_with_name
yield( @name )
end
end
Bu, bu yöntemi çağırmamıza ve rastgele bir kod bloğu geçirmemize olanak tanır.
Örneğin, adı yazdırmak için:
person = Person.new("Oscar")
#invoking the method passing a block
person.do_with_name do |name|
puts "Hey, his name is #{name}"
end
Yazdırır:
Hey, his name is Oscar
Dikkat edin, blok, parametre olarak adlı bir değişkeni alır name
(NB, bu değişkeni istediğiniz herhangi bir şey olarak adlandırabilirsiniz, ancak çağırmak mantıklıdır name
). Kod çağrıldığında yield
, bu parametreyi @name
.
yield( @name )
Farklı bir eylem gerçekleştirmek için başka bir blok sağlayabiliriz. Örneğin, adı tersine çevirin:
#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"
Tam olarak aynı yöntemi ( do_with_name
) kullandık - bu sadece farklı bir blok.
Bu örnek önemsizdir. Daha ilginç kullanımlar, bir dizideki tüm öğeleri filtrelemektir:
days = ["monday", "tuesday", "wednesday", "thursday", "friday"]
# select those which start with 't'
days.select do | item |
item.match /^t/
end
=> ["tuesday", "thursday"]
Veya, örneğin dize boyutuna göre özel bir sıralama algoritması da sağlayabiliriz:
days.sort do |x,y|
x.size <=> y.size
end
=> ["monday", "friday", "tuesday", "thursday", "wednesday"]
Umarım bu daha iyi anlamanıza yardımcı olur.
BTW, blok isteğe bağlıysa, şöyle çağırmalısınız:
yield(value) if block_given?
İsteğe bağlı değilse, çağırmanız yeterlidir.
DÜZENLE
@hmak bu örnekler için bir repl.it oluşturdu: https://repl.it/@makstaks/blocksandyieldsrubyexample
racsO
olursa the_name = ""
"Oscar"
(yanıtta çok açık değildir)
person.do_with_name {|string| yield string, something_else }
Ruby'de yöntemler, normal argümanlara ek olarak bir blok sağlanacak şekilde çağrılıp çağrılmadığını kontrol edebilir. Genellikle bu block_given?
yöntem kullanılarak yapılır, ancak &
son bağımsız değişken adından önce bir ve işareti ( ) ön ekini ekleyerek bloğa açık bir Proc olarak da başvurabilirsiniz .
Bir yöntem bir blokla çağrılırsa, yöntem yield
, gerekirse bazı bağımsız değişkenlerle bloğa kontrol edebilir (bloğu çağırır). Aşağıdakileri gösteren bu örnek yöntemi düşünün:
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 =)
Veya, özel blok bağımsız değişkeni sözdizimini kullanarak:
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 =)
Birisinin burada gerçekten ayrıntılı bir cevap vermesi oldukça olasıdır, ancak Robert Sosinski'nin bu gönderisini her zaman bloklar, proclar ve lambdalar arasındaki inceliklerin büyük bir açıklaması olarak buldum .
Bağladığım gönderinin ruby 1.8'e özgü olduğuna inandığımı eklemeliyim. Blok 1.9'da blok değişkenleri gibi yerel olan bazı şeyler değişmiştir. 1.8'de aşağıdakine benzer bir şey elde edersiniz:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"
Oysa 1.9 size şunları verir:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"
Bu makinede 1.9'um yok, bu yüzden yukarıda bir hata olabilir.
Zaten harika cevaplara neden böyle şeyler yapacağınızı eklemek istedim.
Hangi dilden geldiğiniz hakkında bir fikriniz yok, ancak bunun statik bir dil olduğunu varsayarsak, bu tür şeyler tanıdık gelecektir. Java'da böyle bir dosyayı okursunuz
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();
}
}
}
Akarsu zincirleme şey tüm göz ardı, fikir bu
Bu yakutta nasıl yaparsın
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
Çılgınca farklı. Bunu yıkmak
Burada, birinci ve ikinci adımı ele almak yerine, temelde bunu başka bir sınıfa devredersiniz. Gördüğünüz gibi, bu, yazmanız gereken kod miktarını önemli ölçüde azaltır, bu da daha kolay okunmasını sağlar ve bellek sızıntıları veya dosya kilitlerinin temizlenmeme olasılığını azaltır.
Şimdi, java'da benzer bir şey yapamayacağınız gibi değil, aslında, insanlar bunu yıllardır yapıyorlar. Buna Strateji modeli denir . Fark, bloklar olmadan, dosya örneği gibi basit bir şey için, yazmanız gereken sınıfların ve yöntemlerin miktarı nedeniyle stratejinin aşırıya kaçmasıdır. Bloklarla, bunu yapmanın o kadar basit ve zarif bir yoludur ki, kodunuzu bu şekilde yapılandırmak bir anlam ifade etmez.
Blokların tek yolu bu değildir, ancak diğerleri (raylarda api için form_for'da görebileceğiniz Oluşturucu deseni gibi), başınızı bu etrafa sardığınızda ne olması gerektiği kadar benzerdir. Blokları gördüğünüzde, yöntem çağrısının yapmak istediğiniz şey olduğunu varsaymak genellikle güvenlidir ve blok, bunu nasıl yapmak istediğinizi açıklamaktadır.
File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end
ve Java adamlarına daha da gülmek.
IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }
(artı hafıza sorunu yok)
Bu makalenin çok faydalı olduğunu gördüm . Özellikle, aşağıdaki örnek:
#!/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
bu da aşağıdaki çıktıyı vermelidir:
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
Yani esasen ruby'ye her çağrı yapıldığında yield
, kod do
blokta veya içeride çalıştırılır {}
. Eğer bir parametre sağlanırsa, yield
bu do
bloğa parametre olarak sağlanacaktır .
Benim için, ilk defa do
blokların ne yaptığını anladım . Temel olarak, işlevin dahili veri yapılarına erişim vermesinin bir yolu, yineleme veya işlevin yapılandırılması için.
Yani raylarda aşağıdakileri yazıyorsunuz:
respond_to do |format|
format.html { render template: "my/view", layout: 'my_layout' }
end
Bu respond_to
, do
bloğu (dahili) format
parametresiyle veren işlevi çalıştıracaktır . Daha sonra .html
bu iç değişken üzerindeki fonksiyonu çağırırsınız, bu da render
komutu çalıştırmak için kod bloğunu verir . .html
Bunun yalnızca istenen dosya biçimi olması durumunda elde edileceğini unutmayın . (Teknik olarak: bu işlevler aslında kullanmak block.call
değil yield
sen gördüğünüz gibi kaynağın ancak işlevsellik temelde aynı olduğunu görmek bu soruyu . Bir tartışma için) Bu, bazı başlatma sonra çağıran koddan girdi almak gerçekleştirmek için fonksiyon için bir yol sağlar ve daha sonra gerekirse işleme devam edin.
Ya da başka bir deyişle, isimsiz bir işlevi argüman olarak alan ve sonra javascript olarak çağıran bir işleve benzer.
Ruby'de, bir blok temel olarak herhangi bir yönteme iletilebilen ve herhangi bir yöntemle çalıştırılabilen bir kod parçasıdır. Bloklar her zaman genellikle onlara veri besleyen yöntemlerle kullanılır (bağımsız değişken olarak).
Bloklar Ruby taşlarında (Raylar dahil) ve iyi yazılmış Ruby kodunda yaygın olarak kullanılır. Nesne değildirler, bu nedenle değişkenlere atanamazlar.
Blok, {} veya do..end ile çevrelenen bir kod parçasıdır. Geleneksel olarak, küme ayracı sözdizimi tek satırlı bloklar için ve do..end sözdizimi çok satırlı bloklar için kullanılmalıdır.
{ # This is a single line block }
do
# This is a multi-line block
end
Herhangi bir yöntem örtük argüman olarak bir blok alabilir. Bir blok, bir yöntem içindeki verim ifadesi tarafından yürütülür. Temel sözdizimi:
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.
Verim ifadesine ulaşıldığında, meditasyon yöntemi bloğa kontrol verir, blok içindeki kod yürütülür ve kontrol, verim ifadesinden hemen sonra yürütmeyi sürdüren yönteme döndürülür.
Bir yöntem bir getiri ifadesi içerdiğinde, çağrı sırasında bir blok almayı bekler. Bir blok sağlanmazsa, getiri ifadesine ulaşıldığında bir istisna atılır. Bloğu isteğe bağlı yapabilir ve bir istisnanın ortaya çıkmasını önleyebiliriz:
def meditate
puts "Today we will practice zazen."
yield if block_given?
end meditate
Output:
Today we will practice zazen.
Bir yönteme birden fazla blok geçirmek mümkün değildir. Her yöntem yalnızca bir blok alabilir.
Daha fazla bilgi için: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html
Bazen böyle "verim" kullanın:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Logger
Kullanıcının gerek duymadığı durumlarda bazı görevlerin yerine getirilmemesi gibi birçok neden vardır.
Burada verim hakkında iki noktaya değinmek istiyorum. Birincisi, burada birçok cevap verimi kullanan bir yönteme bir blok geçirmenin farklı yolları hakkında konuşurken, kontrol akışı hakkında da konuşalım. Bu özellikle önemlidir, çünkü bir bloğa ÇOK kez verebilirsiniz. Bir örneğe bakalım:
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
Her yöntem çağrıldığında satır satır yürütülür. Şimdi 3.times bloğuna ulaştığımızda, bu blok 3 kez çağrılacaktır. Her seferinde verimi çağırır. Bu verim, her yöntemi çağıran yöntemle ilişkili bloğa bağlanır. Her verim çağrıldığında, istemin kodundaki her yöntemin bloğuna kontrolü döndürdüğünü fark etmek önemlidir. Blok yürütme işlemi tamamlandığında, 3.times bloğuna geri döner. Ve bu 3 kez olur. Böylece verim kodu 3 ayrı kez çağrılır, çünkü verim açıkça 3 ayrı kez çağrılır.
İkinci noktam enum_for ve verim hakkında. enum_for, Enumerator sınıfını başlatır ve bu Enumerator nesnesi de verime yanıt verir.
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"
Harici yineleyici ile her tür çağrışımızda, yalnızca bir kez verimi çağıracağına dikkat edin. Bir dahaki sefere dediğimizde, bir sonraki verimi çağırır vb.
Enum_for ile ilgili ilginç bir haber var. Çevrimiçi belgeler aşağıdakileri belirtir:
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 öğesine bağımsız değişken olarak bir sembol belirtmezseniz, ruby numaralandırıcıyı alıcının her yöntemine bağlar. String sınıfları gibi bazı sınıfların her yöntemi yoktur.
str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String
Bu nedenle, enum_for ile çağrılan bazı nesneler için numaralandırma yönteminizin ne olacağı konusunda açık olmalısınız.
Verim , yöntemde bir değer döndürmek için isimsiz blok olarak kullanılabilir. Aşağıdaki kodu göz önünde bulundurun:
Def Up(anarg)
yield(anarg)
end
Bir bağımsız değişken atanmış bir "Yukarı" yöntemi oluşturabilirsiniz. Artık bu bağımsız değişkeni ilişkili bir bloğu çağıracak ve yürütecek olan verim için atayabilirsiniz. Bloğu parametre listesinden sonra atayabilirsiniz.
Up("Here is a string"){|x| x.reverse!; puts(x)}
Up yöntemi bir argüman ile verimi çağırdığında, isteği işlemek için block değişkenine iletilir.