Bloklar ve verimler Ruby


275

yieldRuby'de blokları ve nasıl çalıştıklarını anlamaya çalışıyorum .

Nasıl yieldkullanılır? Rails uygulamalarının birçoğunu yieldgarip bir şekilde kullandım.

Birisi bana açıklayabilir ya da onları nerede anlayacağımı gösterebilir mi?


2
Ruby'nin bilgisayar bilimi ile ilgili verim özelliğinin cevabı ile ilgilenebilirsiniz . Sizinkinden biraz farklı bir soru olmasına rağmen, konuya biraz ışık tutabilir.
Ken Bloom

Yanıtlar:


393

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, yieldiş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:

PersonBir adla başlatılan bir sınıf tanımlayacağım ve do_with_nameçağrıldığında, namealı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


o yazdırır nasıl racsOolursa the_name = ""
Paritosh Piplewar

2
Maalesef, ad ile başlatılan bir örnek değişkendir "Oscar" (yanıtta çok açık değildir)
OscarRyz

Buna benzer kod ne olacak? person.do_with_name {|string| yield string, something_else }
f.ardelian

7
Javascripty terimleriyle, belirli bir yönteme geri çağrı iletmenin ve çağırmanın standartlaştırılmış bir yoludur. Açıklama için teşekkürler!
yitznewton

Daha genel bir şekilde - bir blok, Strateji modeli için yakut "geliştirilmiş" bir sözdizimi şekeri. çünkü tipik kullanım, diğer işlemler bağlamında bir şeyler yapmak için bir kod sağlamaktır. Ancak yakut geliştirmeleri, bağlamı geçmek için blok kullanarak DSL'ler yazmak gibi harika şeylere bir yol açar
Roman Bulgakov

25

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

Bir bloğu tetiklemenin farklı yollarını bilmek güzel.
LPing

22

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.


Bu makalede harika bir açıklama, tüm kendi başıma olduğunu anlamak için aylar sürdü =)
maerics

Katılıyorum. Açıklayanların yarısını okuyana kadar bildiğimi sanmıyorum.
theIV

Güncellenen bağlantı da 404'tür. İşte Wayback Machine bağlantısı .
klenwell

@klenwell haber verdiğiniz için teşekkürler, bağlantıyı tekrar güncelledim.
theIV

13

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

  1. Temizlenmesi gereken kaynağı başlat
  2. kaynak kullan
  3. temizlediğinden emin ol

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

  1. File sınıfına kaynağın nasıl başlatılacağını söyleyin
  2. dosya sınıfına onunla ne yapacağını söyle
  3. hala yazıyor java adamlara gülmek ;-)

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.


5
Bunu biraz basitleştirelim: File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" endve Java adamlarına daha da gülmek.
Michael Hampton

1
@MichaelHampton, birkaç gigabayt uzunluğunda bir dosyayı okuduktan sonra gülün.
akostadinov

@akostadinov Hayır ... bu beni ağlatmak istiyor!
Michael Hampton

3
@MichaelHampton Ya da, daha da iyisi: IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }(artı hafıza sorunu yok)
Fon Monica's Lawsuit

12

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 doblokta veya içeride çalıştırılır {}. Eğer bir parametre sağlanırsa, yieldbu dobloğa parametre olarak sağlanacaktır .

Benim için, ilk defa doblokları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, dobloğu (dahili) formatparametresiyle veren işlevi çalıştıracaktır . Daha sonra .htmlbu iç değişken üzerindeki fonksiyonu çağırırsınız, bu da renderkomutu çalıştırmak için kod bloğunu verir . .htmlBunun yalnızca istenen dosya biçimi olması durumunda elde edileceğini unutmayın . (Teknik olarak: bu işlevler aslında kullanmak block.calldeğil yieldsen 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.


8

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.

Temel Sözdizimi

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


Bu, blok ve verimin ne olduğunu ve bunları nasıl kullanacağımı gerçekten anlamamı sağlayan (sadece) cevaptır.
Eric Wang

5

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"}

Tamam ama neden ? LoggerKullanıcının gerek duymadığı durumlarda bazı görevlerin yerine getirilmemesi gibi birçok neden vardır.
Ulysse BN

4

Basitçe ifade etmek gerekirse, oluşturduğunuz yöntemin blok alıp çağırmasına izin verin. Özellikle verim anahtar sözcüğü, bloktaki 'öğelerin' gerçekleştirileceği noktadır.


1

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.


0

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.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.